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.5 gpt-5.4 gpt-5.4-mini gpt-5.3-codex gpt-5.3-codex-spark gpt-5.2],
  "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
    moonshotai/kimi-k2.6
    qwen/qwen3.6-plus
    openai/gpt-5.5
    openai/gpt-5.4
    openai/gpt-5.3-codex
    z-ai/glm-5
    moonshotai/kimi-k2.5
    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" => [
    [/kimi-k2\.[56]/, 262_144],
    [/qwen3\.6-plus/, 1_000_000],
    [/glm-5/, 80_000],
    [/gpt-5(?:\.|-|\z)/, 400_000],
    [/4\.1|4o/, 128_000],
    [/claude/, 200_000]
  ]
}.freeze
COMMAND_AUTOCOMPLETE =
[
  ["/exit", "exit the CLI"],
  ["/help", "show commands"],
  ["/clear", "clear context and start a new session"],
  ["/new", "clear context and start a new session"],
  ["/resume", "resume recent session"],
  ["/login", "authenticate OpenAI ChatGPT/Codex"],
  ["/logout", "remove Kreator OpenAI OAuth credentials"],
  ["/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/(?:clear|new)\z}, handler: :clear_session_command),
  Command.new(pattern: %r{\A/resume\z}, handler: :resume_recent_command),
  Command.new(pattern: %r{\A/login\z}, handler: :login_command),
  Command.new(pattern: %r{\A/logout\z}, handler: :logout_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.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/kreator/interactive_cli.rb', line 112

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
  @auth_manager = config.auth_manager || Providers::OpenAIAuth
  @last_usage = nil
  @context_window = inferred_context_window
end

Instance Method Details

#available_autocomplete_itemsObject



180
181
182
183
184
185
186
187
188
189
# File 'lib/kreator/interactive_cli.rb', line 180

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



160
161
162
# File 'lib/kreator/interactive_cli.rb', line 160

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

#available_pluginsObject



176
177
178
# File 'lib/kreator/interactive_cli.rb', line 176

def available_plugins
  resources.plugins
end

#available_promptsObject



168
169
170
# File 'lib/kreator/interactive_cli.rb', line 168

def available_prompts
  resources.prompt_templates
end

#available_sessionsObject



164
165
166
# File 'lib/kreator/interactive_cli.rb', line 164

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

#available_skillsObject



172
173
174
# File 'lib/kreator/interactive_cli.rb', line 172

def available_skills
  resources.skills
end

#consume_context_clearedObject



233
234
235
236
237
# File 'lib/kreator/interactive_cli.rb', line 233

def consume_context_cleared
  cleared = @context_cleared
  @context_cleared = false
  cleared
end

#consume_model_changedObject



239
240
241
242
243
# File 'lib/kreator/interactive_cli.rb', line 239

def consume_model_changed
  changed = @model_changed
  @model_changed = false
  changed
end

#context_meterObject



222
223
224
225
226
227
228
229
230
231
# File 'lib/kreator/interactive_cli.rb', line 222

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



245
246
247
248
249
250
# File 'lib/kreator/interactive_cli.rb', line 245

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



217
218
219
220
# File 'lib/kreator/interactive_cli.rb', line 217

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

#runObject



131
132
133
134
135
136
137
138
139
140
# File 'lib/kreator/interactive_cli.rb', line 131

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



208
209
210
211
212
213
214
215
# File 'lib/kreator/interactive_cli.rb', line 208

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
  @model_changed = true
  system_line("Model set to #{@model}")
end

#submit(prompt, signal: nil, on_entry: nil) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/kreator/interactive_cli.rb', line 142

def submit(prompt, signal: nil, on_entry: nil)
  prompt = prompt.to_s.strip
  return [] if prompt.empty?

  command = matched_command(prompt)
  return dispatch_command(command, signal: signal, on_entry: on_entry) if command

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

#transcriptObject



156
157
158
# File 'lib/kreator/interactive_cli.rb', line 156

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

#welcome_panelObject



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/kreator/interactive_cli.rb', line 191

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