Class: Kreator::InteractiveCLI

Inherits:
Object
  • Object
show all
Defined in:
lib/kreator/interactive_cli.rb

Defined Under Namespace

Classes: ChatModel, Command, Config, MatchedCommand, TranscriptEntry

Constant Summary collapse

PROMPT_MARKER =
"You"
DEFAULT_MODELS =
{
  "openai" => %w[gpt-5.2 gpt-5.2-pro gpt-5.2-codex gpt-5-mini gpt-5-nano gpt-4.1 gpt-4.1-mini],
  "anthropic" => %w[
    claude-sonnet-4-20250514
    claude-opus-4-1-20250805
    claude-opus-4-20250514
    claude-3-7-sonnet-20250219
    claude-3-5-haiku-20241022
  ],
  "openrouter" => %w[
    openrouter/auto
    openai/gpt-5.2
    anthropic/claude-sonnet-4
    google/gemini-2.5-pro
  ]
}.freeze
DEFAULT_CONTEXT_WINDOWS =
{
  "openai" => [
    [/gpt-5(?:\.|-|\z)/, 400_000],
    [/4\.1|4o/, 128_000]
  ],
  "anthropic" => [
    [/.*/, 200_000]
  ],
  "openrouter" => [
    [/gpt-5(?:\.|-|\z)/, 400_000],
    [/4\.1|4o/, 128_000],
    [/claude/, 200_000]
  ]
}.freeze
COMMAND_AUTOCOMPLETE =
[
  ["/exit", "exit the CLI"],
  ["/help", "show commands"],
  ["/new", "start a new session"],
  ["/resume", "resume recent session"],
  ["/model", "show or change model"],
  ["/session", "show or resume session"],
  ["/label", "label current session"],
  ["/search", "search sessions"],
  ["/export", "export current session"],
  ["/cleanup", "delete empty sessions"],
  ["/branches", "list session branches"],
  ["/fork", "fork session history"],
  ["/prompts", "list prompt templates"],
  ["/prompt", "run a prompt template"],
  ["/skills", "list skills"],
  ["/plugins", "list plugins"],
  ["/plugin validate", "validate a plugin"],
  ["/compact", "compact session context"]
].freeze
COMMANDS =
[
  Command.new(pattern: %r{\A(?::q|/(?:exit|quit))\z}, handler: :exit_command),
  Command.new(pattern: %r{\A/help\z}, handler: :help_command),
  Command.new(pattern: %r{\A/new\z}, handler: :new_session_command),
  Command.new(pattern: %r{\A/resume\z}, handler: :resume_recent_command),
  Command.new(pattern: %r{\A/model\s+(.+)\z}, handler: :select_model_command),
  Command.new(pattern: %r{\A/model\z}, handler: :current_model_command),
  Command.new(pattern: %r{\A/session\s+(.+)\z}, handler: :resume_session_command),
  Command.new(pattern: %r{\A/session\z}, handler: :current_session_command),
  Command.new(pattern: %r{\A/label\s+(.+)\z}, handler: :label_session_command),
  Command.new(pattern: %r{\A/search\s+(.+)\z}, handler: :search_sessions_command),
  Command.new(pattern: %r{\A/export(?:\s+(\S+))?\z}, handler: :export_session_command),
  Command.new(pattern: %r{\A/cleanup\z}, handler: :cleanup_empty_sessions_command),
  Command.new(pattern: %r{\A/branches\z}, handler: :branches_command),
  Command.new(pattern: %r{\A/fork\s+(\d+)\z}, handler: :fork_session_command),
  Command.new(pattern: %r{\A/prompts\z}, handler: :prompts_command),
  Command.new(pattern: %r{\A/skills\z}, handler: :skills_command),
  Command.new(pattern: %r{\A/(?:plugins|plugin list)\z}, handler: :plugins_command),
  Command.new(pattern: %r{\A/plugin\s+validate\s+(.+)\z}, handler: :validate_plugin_command),
  Command.new(pattern: %r{\A/prompt\s+(\S+)(?:\s+(.+))?\z}m, handler: :prompt_template_command),
  Command.new(pattern: %r{\A/compact\z}, handler: :compact_command)
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(config = nil) ⇒ InteractiveCLI

Returns a new instance of InteractiveCLI.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/kreator/interactive_cli.rb', line 97

def initialize(config = nil, **)
  config ||= Config.new(**)
  @provider_builder = config.provider_builder
  @provider_name = config.provider_name
  @model = config.model
  @tools = config.tools
  @context = config.context
  @session_manager = config.session_manager
  @session = config.session
  @stdin = config.stdin
  @stdout = config.stdout
  @stderr = config.stderr
  @resources = config.resources || Resources.new
  @compact_threshold = config.compact_threshold
  @last_usage = nil
  @context_window = inferred_context_window
end

Instance Method Details

#available_autocomplete_itemsObject



162
163
164
165
166
167
168
169
170
171
# File 'lib/kreator/interactive_cli.rb', line 162

def available_autocomplete_items
  command_items = COMMAND_AUTOCOMPLETE.map do |value, description|
    { value: value, label: value, description: description, kind: "command" }
  end
  skill_items = resources.skills.map do |skill|
    { value: "$#{skill.name}", label: "skill: #{skill.name}", description: "load skill context", kind: "skill" }
  end

  command_items + skill_items
end

#available_modelsObject



142
143
144
# File 'lib/kreator/interactive_cli.rb', line 142

def available_models
  (DEFAULT_MODELS.fetch(provider_name, []) | [model]).compact
end

#available_pluginsObject



158
159
160
# File 'lib/kreator/interactive_cli.rb', line 158

def available_plugins
  resources.plugins
end

#available_promptsObject



150
151
152
# File 'lib/kreator/interactive_cli.rb', line 150

def available_prompts
  resources.prompt_templates
end

#available_sessionsObject



146
147
148
# File 'lib/kreator/interactive_cli.rb', line 146

def available_sessions
  session_manager.list(cwd: Dir.pwd)
end

#available_skillsObject



154
155
156
# File 'lib/kreator/interactive_cli.rb', line 154

def available_skills
  resources.skills
end

#context_meterObject



203
204
205
206
207
208
209
210
211
212
# File 'lib/kreator/interactive_cli.rb', line 203

def context_meter
  window = @context_window
  used = @last_usage&.fetch("total_tokens", nil)

  {
    window: window,
    used: used,
    available: window && used ? [window - used, 0].max : nil
  }
end

#fork_session(entry_index) ⇒ Object



214
215
216
217
218
219
# File 'lib/kreator/interactive_cli.rb', line 214

def fork_session(entry_index)
  return system_line("Session persistence disabled") unless session

  @session = session_manager.fork(path: session.path, entry_index: entry_index)
  system_line("Forked session #{@session.id} from entry #{entry_index}")
end

#resume_session(path) ⇒ Object



198
199
200
201
# File 'lib/kreator/interactive_cli.rb', line 198

def resume_session(path)
  @session = @session_manager.open(path: path)
  system_line("Resumed session #{@session.id}")
end

#runObject



115
116
117
118
119
120
121
122
123
124
# File 'lib/kreator/interactive_cli.rb', line 115

def run
  return run_line_mode unless tty?

  load_charm!
  Bubbletea.run(ChatModel.new(runtime: self))
  0
rescue LoadError => e
  @stderr.puts "kreator: interactive mode requires Charm Ruby gems: #{e.message}"
  1
end

#select_model(model_name) ⇒ Object



190
191
192
193
194
195
196
# File 'lib/kreator/interactive_cli.rb', line 190

def select_model(model_name)
  @model = model_name
  @session&.append_model_change(provider: @provider_name, model: @model)
  @last_usage = nil
  @context_window = inferred_context_window
  system_line("Model set to #{@model}")
end

#submit(prompt) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/kreator/interactive_cli.rb', line 126

def submit(prompt)
  prompt = prompt.to_s.strip
  return [] if prompt.empty?

  command = matched_command(prompt)
  return send(command.handler, command.match) if command

  run_agent(prompt)
rescue StandardError => e
  [system_line("#{e.class}: #{e.message}")]
end

#transcriptObject



138
139
140
# File 'lib/kreator/interactive_cli.rb', line 138

def transcript
  messages.map { |message| format_message(message) }
end

#welcome_panelObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/kreator/interactive_cli.rb', line 173

def welcome_panel
  rows = [
    ">_ Kreator (v#{Kreator::VERSION})",
    "",
    "model:     #{model}   /model to change",
    "directory: #{display_directory}"
  ]
  content_width = [44, rows.map(&:length).max].max
  horizontal = "" * (content_width + 2)

  [
    "#{horizontal}",
    *rows.map { |row| "#{row.ljust(content_width)}" },
    "#{horizontal}"
  ].join("\n")
end