samagotchi 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +198 -1
- data/README.md +56 -4
- data/bin/chi +118 -50
- data/docs/cli.md +184 -9
- data/docs/configuration.md +333 -47
- data/docs/desktop.md +45 -4
- data/docs/guardrails.md +11 -0
- data/docs/hooks.md +208 -5
- data/docs/plugins.md +68 -2
- data/docs/releasing.md +23 -13
- data/docs/sessions.md +45 -17
- data/lib/samagotchi/answer_display.rb +95 -0
- data/lib/samagotchi/archive_store.rb +90 -0
- data/lib/samagotchi/bootstrap/config_writer.rb +342 -0
- data/lib/samagotchi/bootstrap/probe.rb +262 -0
- data/lib/samagotchi/bootstrap_command.rb +347 -0
- data/lib/samagotchi/bridge/pending_card.rb +89 -0
- data/lib/samagotchi/bridge/turn_accumulator.rb +15 -3
- data/lib/samagotchi/bridge.rb +13 -1
- data/lib/samagotchi/bridge_client.rb +6 -2
- data/lib/samagotchi/bundles/check-in/manifest.yml +10 -0
- data/lib/samagotchi/bundles/check-in/plugin.rb +244 -0
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +531 -0
- data/lib/samagotchi/bundles/source-links/manifest.yml +14 -0
- data/lib/samagotchi/bundles/source-links/source_links.md +5 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +10 -6
- data/lib/samagotchi/bundles/system/delegated.md +6 -7
- data/lib/samagotchi/bundles/system/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/self_map.md +8 -2
- data/lib/samagotchi/client.rb +81 -19
- data/lib/samagotchi/commands/registry.rb +8 -0
- data/lib/samagotchi/config.rb +252 -48
- data/lib/samagotchi/desktop/macos/App.swift +12 -8
- data/lib/samagotchi/desktop/macos/ChiRunner.swift +17 -9
- data/lib/samagotchi/desktop/macos/Images.swift +113 -0
- data/lib/samagotchi/desktop/macos/Info.plist.erb +6 -0
- data/lib/samagotchi/desktop/macos/Panel.swift +180 -25
- data/lib/samagotchi/desktop/macos.rb +59 -8
- data/lib/samagotchi/desktop_command.rb +6 -3
- data/lib/samagotchi/edit_preview.rb +82 -0
- data/lib/samagotchi/empty_answer_retry.rb +43 -0
- data/lib/samagotchi/engine.rb +434 -140
- data/lib/samagotchi/gem_update.rb +89 -0
- data/lib/samagotchi/guardrails/approval.rb +35 -4
- data/lib/samagotchi/guardrails/load_failures.rb +9 -3
- data/lib/samagotchi/guardrails/scratch_writes.rb +40 -0
- data/lib/samagotchi/guardrails.rb +1 -0
- data/lib/samagotchi/hooks/registry.rb +24 -5
- data/lib/samagotchi/host_registry.rb +9 -12
- data/lib/samagotchi/idle_client.rb +24 -15
- data/lib/samagotchi/idle_recap.rb +5 -1
- data/lib/samagotchi/idle_reminders.rb +2 -2
- data/lib/samagotchi/image_store.rb +10 -6
- data/lib/samagotchi/kernel_loop.rb +73 -94
- data/lib/samagotchi/live_versions.rb +59 -0
- data/lib/samagotchi/llm/api_key.rb +41 -0
- data/lib/samagotchi/llm/chat_loop.rb +132 -29
- data/lib/samagotchi/llm/errors.rb +41 -9
- data/lib/samagotchi/llm/http.rb +57 -17
- data/lib/samagotchi/llm/openai_chat.rb +17 -30
- data/lib/samagotchi/log_subscriber.rb +18 -3
- data/lib/samagotchi/memory_bundle/installer.rb +65 -63
- data/lib/samagotchi/memory_bundle/provenance.rb +51 -12
- data/lib/samagotchi/memory_bundle/shipped_update.rb +157 -0
- data/lib/samagotchi/memory_bundle/status.rb +4 -1
- data/lib/samagotchi/memory_bundle/system_bundle.rb +81 -53
- data/lib/samagotchi/model_profile.rb +24 -1
- data/lib/samagotchi/plugin/context.rb +22 -1
- data/lib/samagotchi/plugin/sessions.rb +3 -1
- data/lib/samagotchi/prompt.rb +4 -2
- data/lib/samagotchi/reminder_store.rb +1 -9
- data/lib/samagotchi/reply_wait.rb +126 -0
- data/lib/samagotchi/sampling_settings.rb +58 -0
- data/lib/samagotchi/self_report.rb +18 -3
- data/lib/samagotchi/send_command.rb +252 -11
- data/lib/samagotchi/session.rb +52 -11
- data/lib/samagotchi/session_archive_command.rb +107 -0
- data/lib/samagotchi/session_commands.rb +46 -7
- data/lib/samagotchi/session_manager.rb +115 -25
- data/lib/samagotchi/session_metrics.rb +222 -106
- data/lib/samagotchi/steer.rb +72 -0
- data/lib/samagotchi/terminal_ui/attached_loop.rb +57 -28
- data/lib/samagotchi/terminal_ui/event_renderer.rb +21 -11
- data/lib/samagotchi/terminal_ui/formatting.rb +40 -8
- data/lib/samagotchi/terminal_ui/input_support.rb +7 -19
- data/lib/samagotchi/terminal_ui/question_prompt.rb +35 -0
- data/lib/samagotchi/terminal_ui.rb +134 -247
- data/lib/samagotchi/text_diff.rb +181 -0
- data/lib/samagotchi/thinking.rb +115 -0
- data/lib/samagotchi/tool_activity.rb +3 -1
- data/lib/samagotchi/tool_runner.rb +34 -1
- data/lib/samagotchi/tools/ask_user_question.rb +41 -33
- data/lib/samagotchi/tools/builtins.rb +15 -4
- data/lib/samagotchi/tools/delegate_wait.rb +26 -69
- data/lib/samagotchi/tools/edit.rb +23 -9
- data/lib/samagotchi/tools/execute.rb +52 -14
- data/lib/samagotchi/tools/task_runtime.rb +19 -0
- data/lib/samagotchi/tools/task_wait.rb +27 -3
- data/lib/samagotchi/tools/write.rb +4 -0
- data/lib/samagotchi/turn_flow.rb +12 -2
- data/lib/samagotchi/turn_note.rb +60 -6
- data/lib/samagotchi/update_command.rb +308 -0
- data/lib/samagotchi/update_hint.rb +59 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +7 -9
- data/lib/samagotchi/web/app.rb +91 -7
- data/lib/samagotchi/web/message_parts.rb +8 -3
- data/lib/samagotchi/web/public/activity.js +13 -1
- data/lib/samagotchi/web/public/annotate_presets.js +26 -0
- data/lib/samagotchi/web/public/annotations.js +13 -0
- data/lib/samagotchi/web/public/app.js +472 -111
- data/lib/samagotchi/web/public/card.js +5 -3
- data/lib/samagotchi/web/public/chat_view.js +13 -1
- data/lib/samagotchi/web/public/copy.js +20 -4
- data/lib/samagotchi/web/public/ctx.js +15 -0
- data/lib/samagotchi/web/public/data.js +23 -6
- data/lib/samagotchi/web/public/diff_view.js +58 -0
- data/lib/samagotchi/web/public/format.js +9 -0
- data/lib/samagotchi/web/public/index.html +60 -3
- data/lib/samagotchi/web/public/notify.js +175 -0
- data/lib/samagotchi/web/public/question_card.js +5 -2
- data/lib/samagotchi/web/public/sessions_list.js +7 -0
- data/lib/samagotchi/web/public/timing.js +39 -14
- data/lib/samagotchi/web/public/turn_events.js +75 -5
- data/lib/samagotchi/web/public/turn_view.js +49 -8
- data/lib/samagotchi/web/server.rb +8 -4
- data/lib/samagotchi/web/session_hub.rb +2 -1
- data/lib/samagotchi/web/session_summary.rb +24 -1
- data/lib/samagotchi/worker.rb +16 -4
- metadata +31 -1
data/bin/chi
CHANGED
|
@@ -38,16 +38,18 @@ if ARGV[0] == "sessions"
|
|
|
38
38
|
sessions_args = ARGV[1..] || []
|
|
39
39
|
sub = sessions_args[0]
|
|
40
40
|
if sub.nil? || %w[-h --help help].include?(sub)
|
|
41
|
-
puts "Usage: chi sessions <list|stop|delete|prune|clean> [options]"
|
|
41
|
+
puts "Usage: chi sessions <list|stop|archive|unarchive|delete|prune|clean> [options]"
|
|
42
42
|
puts " list [--sort updated_at|created_at] [--order desc|asc] [--limit N]"
|
|
43
|
-
puts " [--live] [--cwd PATH] [--format text|json|tsv]"
|
|
43
|
+
puts " [--live] [--cwd PATH] [--format text|json|tsv] [--archived]"
|
|
44
44
|
puts " --live: sessions a worker runs now (the ones chi note reaches), 10 unless --limit"
|
|
45
45
|
puts " --cwd PATH: sessions in PATH or below; json/tsv (id<TAB>description) are for scripts"
|
|
46
46
|
puts " [--scope=all]: every project's sessions; by default only this git project's (all outside a repo)"
|
|
47
|
+
puts " --archived: archived sessions too (marked [archived]; json: archived: true)"
|
|
47
48
|
puts " stop ID... # stop each session's worker (IDs or unique prefixes); chi --resume ID then starts a fresh one"
|
|
49
|
+
puts " archive ID... # hide sessions (and their delegates) from every list and keep them for good; unarchive ID... brings them back"
|
|
48
50
|
puts " delete [--force] ID... # delete sessions for good (IDs or unique prefixes); --force stops a live worker first"
|
|
49
51
|
puts " prune [--dry-run] [--days N] [--keep N] [--keep-status running,...] [--test-only]"
|
|
50
|
-
puts " clean [--dry-run] [--days N] # test sessions (SAMAGOTCHI_ENV=test, CI): all of them, or those older than N days"
|
|
52
|
+
puts " clean [--dry-run] [--days N] # test sessions (SAMAGOTCHI_ENV=test, CI) and leftover chi scratch ones: all of them, or those older than N days"
|
|
51
53
|
puts "Defaults: days=14 keep=500 keep_status=running (env overrides: SAMAGOTCHI_SESSION_RETENTION_DAYS etc.)"
|
|
52
54
|
exit 0
|
|
53
55
|
end
|
|
@@ -60,6 +62,7 @@ if ARGV[0] == "sessions"
|
|
|
60
62
|
order = nil
|
|
61
63
|
limit = nil
|
|
62
64
|
live = sessions_args.include?("--live")
|
|
65
|
+
include_archived = sessions_args.include?("--archived")
|
|
63
66
|
cwd = nil
|
|
64
67
|
format = nil
|
|
65
68
|
scope = nil
|
|
@@ -89,6 +92,9 @@ if ARGV[0] == "sessions"
|
|
|
89
92
|
when "delete"
|
|
90
93
|
require "samagotchi/session_delete_command"
|
|
91
94
|
exit Samagotchi::SessionDeleteCommand.new(sessions_args[1..]).run
|
|
95
|
+
when "archive", "unarchive"
|
|
96
|
+
require "samagotchi/session_archive_command"
|
|
97
|
+
exit Samagotchi::SessionArchiveCommand.new(sub, sessions_args[1..]).run
|
|
92
98
|
when "list"
|
|
93
99
|
unless format.nil? || %w[text json tsv].include?(format)
|
|
94
100
|
warn "Unknown format #{format.inspect}: use --format text|json|tsv"
|
|
@@ -107,7 +113,8 @@ if ARGV[0] == "sessions"
|
|
|
107
113
|
# and test runs stay out.
|
|
108
114
|
if live || cwd || (format && format != "text")
|
|
109
115
|
summaries = Samagotchi::SessionManager.session_summaries(
|
|
110
|
-
live: live, cwd: cwd, limit: limit || (live ? 10 : nil), include_tests: false, project_root: project
|
|
116
|
+
live: live, cwd: cwd, limit: limit || (live ? 10 : nil), include_tests: false, project_root: project,
|
|
117
|
+
include_archived: include_archived
|
|
111
118
|
)
|
|
112
119
|
case format
|
|
113
120
|
when "json"
|
|
@@ -115,7 +122,11 @@ if ARGV[0] == "sessions"
|
|
|
115
122
|
# or nil; recap: the saved recap's first sentence, or nil; project:
|
|
116
123
|
# its git project's root, or nil
|
|
117
124
|
# parent_id: the session that delegated it (the delegate tool), or nil
|
|
118
|
-
|
|
125
|
+
# archived: hidden from the lists (only with --archived can it be true)
|
|
126
|
+
# scratch: a `chi scratch` session (deleted when its REPL ends, a
|
|
127
|
+
# leftover one at the next sweep)
|
|
128
|
+
# ctx_pct: how full the context was after the last turn, or nil
|
|
129
|
+
keys = %i[id short_id desc cwd project updated_at live busy owner recap parent_id archived scratch ctx_pct]
|
|
119
130
|
puts JSON.generate(summaries.map { |summary| summary.slice(*keys) })
|
|
120
131
|
when "tsv"
|
|
121
132
|
summaries.each { |summary| puts "#{summary[:id]}\t#{summary[:desc]}" }
|
|
@@ -131,7 +142,10 @@ if ARGV[0] == "sessions"
|
|
|
131
142
|
end
|
|
132
143
|
# A delegated session points at its parent.
|
|
133
144
|
child = summary[:parent_short_id] ? " ↳ #{summary[:parent_short_id]}" : ""
|
|
134
|
-
|
|
145
|
+
flag = summary[:scratch] ? " [scratch]" : ""
|
|
146
|
+
flag += " [archived]" if summary[:archived]
|
|
147
|
+
ctx = Samagotchi::SessionMetrics.context_label(summary[:ctx_pct])
|
|
148
|
+
puts "#{summary[:id]} #{state.ljust(8)} #{ctx.ljust(8)} #{summary[:updated_at]} #{desc}#{flag}#{child}"
|
|
135
149
|
end
|
|
136
150
|
if project
|
|
137
151
|
puts "#{summaries.empty? ? "" : "\n"}#{scope_note.call(summaries.size)}"
|
|
@@ -143,7 +157,8 @@ if ARGV[0] == "sessions"
|
|
|
143
157
|
end
|
|
144
158
|
sort ||= "updated_at"
|
|
145
159
|
order ||= "desc"
|
|
146
|
-
sessions = Samagotchi::SessionManager.list_sessions(sort: sort, order: order, limit: limit, project_root: project
|
|
160
|
+
sessions = Samagotchi::SessionManager.list_sessions(sort: sort, order: order, limit: limit, project_root: project,
|
|
161
|
+
include_archived: include_archived)
|
|
147
162
|
# The saved recap's first sentence, else the last prompt, cut as before.
|
|
148
163
|
state_dir = Samagotchi::Session.default_state_dir
|
|
149
164
|
list_text = lambda do |s|
|
|
@@ -152,9 +167,14 @@ if ARGV[0] == "sessions"
|
|
|
152
167
|
end
|
|
153
168
|
# A delegated session points at its parent: ↳ <parent's short id>.
|
|
154
169
|
row = lambda do |s|
|
|
155
|
-
flag = s.test_run ? " [test]" : ""
|
|
170
|
+
flag = s.scratch ? " [scratch]" : (s.test_run ? " [test]" : "")
|
|
171
|
+
flag += " [archived]" if s.archived
|
|
156
172
|
child = s.parent_id ? " ↳ #{s.parent_id[0, 8]}" : ""
|
|
157
|
-
"
|
|
173
|
+
# How full the context was after the last turn: "ctx 12%", blank when unknown.
|
|
174
|
+
ctx = Samagotchi::SessionMetrics.context_label(
|
|
175
|
+
Samagotchi::SessionMetrics.saved_context_pct(Samagotchi::Session.session_dir(s.id, state_dir: state_dir))
|
|
176
|
+
)
|
|
177
|
+
"#{s.id} #{s.status.ljust(8)} #{ctx.ljust(8)} #{s.updated_at} #{list_text.call(s)}#{flag}#{child}"
|
|
158
178
|
end
|
|
159
179
|
if project
|
|
160
180
|
sessions.each { |s| puts row.call(s) }
|
|
@@ -214,7 +234,7 @@ if ARGV[0] == "sessions"
|
|
|
214
234
|
end
|
|
215
235
|
exit 0
|
|
216
236
|
else
|
|
217
|
-
warn "Unknown sessions subcommand: #{sub}. Use: list, stop, delete, prune, clean"
|
|
237
|
+
warn "Unknown sessions subcommand: #{sub}. Use: list, stop, archive, unarchive, delete, prune, clean"
|
|
218
238
|
exit 1
|
|
219
239
|
end
|
|
220
240
|
end
|
|
@@ -231,6 +251,18 @@ if ARGV[0] == "send"
|
|
|
231
251
|
exit Samagotchi::SendCommand.new(ARGV[1..] || []).run
|
|
232
252
|
end
|
|
233
253
|
|
|
254
|
+
# `chi bootstrap`: find a model server and write config.yml, before OptionParser (--model, --name are its own)
|
|
255
|
+
if ARGV[0] == "bootstrap"
|
|
256
|
+
require "samagotchi/bootstrap_command"
|
|
257
|
+
exit Samagotchi::BootstrapCommand.new(ARGV[1..] || []).run
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# `chi update`: bring an installed chi up to date, before OptionParser (its own flags)
|
|
261
|
+
if ARGV[0] == "update"
|
|
262
|
+
require "samagotchi/update_command"
|
|
263
|
+
exit Samagotchi::UpdateCommand.new(ARGV[1..] || []).run
|
|
264
|
+
end
|
|
265
|
+
|
|
234
266
|
# `chi desktop`: the native "Send to chi" helper, before OptionParser (its own flags)
|
|
235
267
|
if ARGV[0] == "desktop"
|
|
236
268
|
require "samagotchi/desktop_command"
|
|
@@ -346,7 +378,7 @@ if ARGV[0] == "bundle"
|
|
|
346
378
|
lines << ""
|
|
347
379
|
lines << "Your task: resolve each conflict by editing the file in place using the memory_write tool."
|
|
348
380
|
lines << "Use memory_read to inspect current content if needed. The correct scope for memory_write is the bundle's scope."
|
|
349
|
-
lines << "When all conflicts are resolved, type /exit to finish. If you
|
|
381
|
+
lines << "When all conflicts are resolved, type /exit to finish. If you stop early, the files stay as they are (the rest of the upgrade is already applied)."
|
|
350
382
|
lines.join("\n")
|
|
351
383
|
end
|
|
352
384
|
|
|
@@ -503,7 +535,7 @@ if ARGV[0] == "bundle"
|
|
|
503
535
|
ans = begin; $stdin.gets; rescue => _e; nil; end
|
|
504
536
|
launch = ans && ans.strip.downcase.start_with?("y")
|
|
505
537
|
else
|
|
506
|
-
puts "Non-interactive terminal
|
|
538
|
+
puts "Non-interactive terminal: kept your edits in the file(s) above; the rest is upgraded. Re-run with --force to take the bundle's version, or --agent in a TTY to merge."
|
|
507
539
|
exit 2
|
|
508
540
|
end
|
|
509
541
|
if launch
|
|
@@ -511,37 +543,15 @@ if ARGV[0] == "bundle"
|
|
|
511
543
|
puts "Launching interactive session for conflict resolution… (/exit when done)"
|
|
512
544
|
require "samagotchi/terminal_ui"
|
|
513
545
|
Samagotchi::TerminalUI.new(prompt: prompt).run
|
|
514
|
-
#
|
|
515
|
-
#
|
|
516
|
-
#
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
# Re-collect provenance files from target dir for this bundle
|
|
520
|
-
prov_files = {}
|
|
521
|
-
target_scope = scope || provenance.read[:scope]&.to_s || "system"
|
|
522
|
-
target_dir = Samagotchi::MemoryBundle::Installer.new(source: expanded_source, name: bundle_name, scope: target_scope).send(:resolve_target_dir, target_scope)
|
|
523
|
-
# Actually reuse provenance write with current target files for bundle keys
|
|
524
|
-
bundle_files = installer.conflicts.keys + installer.results.select { |_, r| %w[installed updated].include?(r[:status]) }.keys
|
|
525
|
-
# Fallback: collect all md that exist and were in installer results
|
|
526
|
-
all_keys = installer.results.keys
|
|
527
|
-
all_keys.each do |k|
|
|
528
|
-
p = File.join(target_dir, k)
|
|
529
|
-
prov_files[k] = p if File.exist?(p)
|
|
530
|
-
end
|
|
531
|
-
if prov_files.any?
|
|
532
|
-
Samagotchi::MemoryBundle::Provenance.new(name: bundle_name).write(
|
|
533
|
-
files: prov_files,
|
|
534
|
-
scope: target_scope,
|
|
535
|
-
version: manifest.version,
|
|
536
|
-
source_path: expanded_source
|
|
537
|
-
)
|
|
538
|
-
puts "Provenance updated after interactive resolution."
|
|
539
|
-
end
|
|
540
|
-
end
|
|
546
|
+
# The installer already recorded the upgrade (conflicted files
|
|
547
|
+
# kept their old base); the resolved files now start from the
|
|
548
|
+
# bundle's version.
|
|
549
|
+
Samagotchi::MemoryBundle::Provenance.new(name: bundle_name).resolve_conflicts(installer.conflicts)
|
|
550
|
+
puts "Provenance updated after interactive resolution."
|
|
541
551
|
puts "Upgrade resolved interactively."
|
|
542
552
|
exit 0
|
|
543
553
|
else
|
|
544
|
-
puts "
|
|
554
|
+
puts "Kept your edits in the file(s) above; the rest is upgraded. chi bundle diff #{bundle_name} FILE shows the base; re-run with --force to take the bundle's version."
|
|
545
555
|
exit 2
|
|
546
556
|
end
|
|
547
557
|
end
|
|
@@ -611,6 +621,7 @@ if ARGV[0] == "bundle"
|
|
|
611
621
|
puts "Target: #{st[:target_dir]}"
|
|
612
622
|
st[:files].each do |k, info|
|
|
613
623
|
mods = []
|
|
624
|
+
mods << "conflict (kept your edits over v#{st[:provenance][:version]}: chi bundle diff #{name} #{k})" if info[:conflict]
|
|
614
625
|
mods << "modified" if info[:modified]
|
|
615
626
|
mods << "missing" if info[:missing]
|
|
616
627
|
mods << "no-index" unless info[:index_present]
|
|
@@ -780,6 +791,7 @@ if ARGV[0] == "bundle"
|
|
|
780
791
|
line += " (shipped v#{b.upgrade.version}: chi bundle upgrade #{b.upgrade.source})" if b.upgrade
|
|
781
792
|
puts line
|
|
782
793
|
end
|
|
794
|
+
puts " (or all at once: chi update)" if installed.any?(&:upgrade)
|
|
783
795
|
end
|
|
784
796
|
unless available.empty?
|
|
785
797
|
puts ""
|
|
@@ -881,7 +893,13 @@ if ARGV[0] == "bundle"
|
|
|
881
893
|
exit 0
|
|
882
894
|
end
|
|
883
895
|
|
|
884
|
-
|
|
896
|
+
# `chi scratch`: the run options below, for a session that leaves nothing
|
|
897
|
+
# behind (TerminalUI scratch:). The word goes before OptionParser sees it.
|
|
898
|
+
scratch = ARGV[0] == "scratch"
|
|
899
|
+
ARGV.shift if scratch
|
|
900
|
+
|
|
901
|
+
# no_interrupt / no_default_input start from config.yml or the env; the flags below turn them on.
|
|
902
|
+
options = { verbose: false, no_interrupt: Samagotchi::Config.get("no_interrupt"), non_interactive: false, no_default_input: Samagotchi::Config.get("no_default_input"), web: false, web_port: nil, web_open: false, web_markdown: nil, web_turn_view: nil }
|
|
885
903
|
cli_overrides = {}
|
|
886
904
|
|
|
887
905
|
if ARGV.any? { |arg| arg == "--backend" || arg.start_with?("--backend=") }
|
|
@@ -898,7 +916,7 @@ ARGV.each do |arg|
|
|
|
898
916
|
base = arg.split("=", 2).first
|
|
899
917
|
# If dashed version is a known flag (registry or hardcoded), reject underscore variant
|
|
900
918
|
dashed = base.tr("_", "-")
|
|
901
|
-
known = %w[--prompt --resume --attach --shared --no-shared --model --memory --mute --no-interrupt --non-interactive --verbose --no-default-input --port --open] + Samagotchi::Config.cli_entries.map(&:cli_flag)
|
|
919
|
+
known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --memory --mute --no-interrupt --non-interactive --verbose --no-default-input --port --open] + Samagotchi::Config.cli_entries.map(&:cli_flag)
|
|
902
920
|
if known.include?(dashed) || known.include?(base)
|
|
903
921
|
warn "Unknown option: #{arg} (did you mean #{dashed}?)"
|
|
904
922
|
exit 1
|
|
@@ -908,13 +926,16 @@ end
|
|
|
908
926
|
parser = OptionParser.new do |opts|
|
|
909
927
|
opts.banner = <<~BANNER
|
|
910
928
|
Usage: chi [options]
|
|
929
|
+
chi bootstrap [HOST[:PORT]|URL] first setup: find the model server, write config.yml
|
|
930
|
+
chi scratch [options] a one-time session in this terminal: nothing is kept
|
|
911
931
|
chi web [--port PORT] [--open] [--scope=all] the web UI
|
|
912
|
-
chi sessions <list|stop|delete|prune|clean>
|
|
932
|
+
chi sessions <list|stop|archive|unarchive|delete|prune|clean>
|
|
913
933
|
chi note [--source NAME] [-m TEXT] ID...|--all background context for sessions
|
|
914
|
-
chi send [-m TEXT] ID
|
|
934
|
+
chi send [-m TEXT] ID...|--new a message into sessions, as if typed there
|
|
915
935
|
chi bundle <install|upgrade|uninstall|status|diff|list|build>
|
|
916
936
|
chi desktop <install|upgrade|uninstall|status> macOS "Send to chi" helper
|
|
917
937
|
chi self version, paths, model and bundles
|
|
938
|
+
chi update [--dry-run] update this chi, its bundles and the desktop helper
|
|
918
939
|
(each subcommand takes --help)
|
|
919
940
|
|
|
920
941
|
BANNER
|
|
@@ -925,6 +946,8 @@ parser = OptionParser.new do |opts|
|
|
|
925
946
|
opts.on("--model NAME", "Model selector (runtime only, overrides --resume) [alias for --default-model]") { |m| options[:model] = m; cli_overrides["default.model"] = m }
|
|
926
947
|
opts.on("--profile NAME", Samagotchi::ModelProfile::NAMES,
|
|
927
948
|
"Prompt profile for every model in this run (#{Samagotchi::ModelProfile::NAMES.join('|')}) [alias for --model-profile]") { |p| cli_overrides["model.profile"] = p }
|
|
949
|
+
opts.on("--thinking LEVEL", Samagotchi::Thinking::LEVELS.map(&:to_s), "How much the model thinks this run: off|low|medium|high|default (outranks config.yml; " \
|
|
950
|
+
"sessions already running keep theirs) [env SAMAGOTCHI_THINKING_LEVEL]") { |l| cli_overrides["thinking.level"] = l }
|
|
928
951
|
opts.on("--memory NAME", "Preload a memory entry into the system prompt (repeatable; a comma list too)") { |m| (options[:memories] ||= []) << m }
|
|
929
952
|
opts.on("--mute NAME", "Hide a memory from this session: not in the prompt, refused by memory_read (repeatable; a comma list too)") { |m| (options[:muted] ||= []) << m }
|
|
930
953
|
opts.on("--no-interrupt", "Raise the tool call limit to 1000 iterations (useful for long tasks)") { options[:no_interrupt] = true; cli_overrides["no_interrupt"] = true }
|
|
@@ -938,7 +961,8 @@ parser = OptionParser.new do |opts|
|
|
|
938
961
|
"every session outside a repo) or all") { |v| options[:scope] = v }
|
|
939
962
|
# Accepted values of string entries that the registry doesn't type as enums.
|
|
940
963
|
value_hints = { "thinking.ui" => "spinner|off", "status.line" => "on|off", "status.width_mode" => "terminal_cap|fixed",
|
|
941
|
-
"web.host" => "127.0.0.1|::1|localhost", "recap.sentences" => "N|N-M"
|
|
964
|
+
"web.host" => "127.0.0.1|::1|localhost", "recap.sentences" => "N|N-M",
|
|
965
|
+
"web.annotate_presets" => "text|text" }
|
|
942
966
|
# Universal config flags (implicit convention: ENV SAMAGOTCHI_* ↔ YAML dotted ↔ CLI --kebab)
|
|
943
967
|
# Generated from Samagotchi::Config registry — Option A: snake leaf in YAML, kebab in CLI.
|
|
944
968
|
Samagotchi::Config.cli_entries.each do |entry|
|
|
@@ -981,12 +1005,22 @@ end
|
|
|
981
1005
|
# The subcommands with their own parsers ran above; what parse! leaves is
|
|
982
1006
|
# `web` or nothing. Anything else is a typo, not a prompt: refuse it rather
|
|
983
1007
|
# than start a session.
|
|
984
|
-
|
|
1008
|
+
web = ARGV.first == "web" && !scratch
|
|
1009
|
+
stray = web ? ARGV[1] : ARGV.first
|
|
985
1010
|
if stray
|
|
986
|
-
warn "Error: #{
|
|
1011
|
+
warn "Error: #{web || scratch ? "unexpected argument" : "unknown command"} #{stray} (see chi --help)"
|
|
987
1012
|
exit 1
|
|
988
1013
|
end
|
|
989
1014
|
|
|
1015
|
+
# A scratch session is new, runs here and goes when it ends.
|
|
1016
|
+
if scratch
|
|
1017
|
+
flag = { resume: "--resume", attach: "--attach", shared: "--shared" }.find { |key, _flag| options[key] }&.last
|
|
1018
|
+
if flag
|
|
1019
|
+
warn "Error: chi scratch starts a new session in this terminal and keeps nothing; it can't take #{flag}"
|
|
1020
|
+
exit 1
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
|
|
990
1024
|
# Apply CLI precedence: CLI > ENV > file
|
|
991
1025
|
unless cli_overrides.empty?
|
|
992
1026
|
Samagotchi::Config.reload!(cli_overrides: cli_overrides)
|
|
@@ -1029,7 +1063,18 @@ when "web"
|
|
|
1029
1063
|
end
|
|
1030
1064
|
end
|
|
1031
1065
|
|
|
1066
|
+
# The first interactive start of a new installed chi says what `chi update` would update.
|
|
1067
|
+
chi_update_hint = lambda do
|
|
1068
|
+
require "samagotchi/installed_gem"
|
|
1069
|
+
require "samagotchi/update_hint"
|
|
1070
|
+
if Samagotchi::UpdateHint.wanted?(prompt: options[:prompt], non_interactive: options[:non_interactive],
|
|
1071
|
+
tty: $stdin.tty? && $stdout.tty?, installed: Samagotchi::InstalledGem.spec)
|
|
1072
|
+
Samagotchi::UpdateHint.show
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1032
1076
|
if options[:web]
|
|
1077
|
+
chi_update_hint.call
|
|
1033
1078
|
require "samagotchi/web/server"
|
|
1034
1079
|
port = options[:web_port] || Samagotchi::Config.get("web.port") || 4567
|
|
1035
1080
|
# legacy env fallback
|
|
@@ -1039,7 +1084,8 @@ if options[:web]
|
|
|
1039
1084
|
turn_view = options[:web_turn_view]
|
|
1040
1085
|
turn_view = Samagotchi::Config.get("web.turn_view") if turn_view.nil?
|
|
1041
1086
|
exit Samagotchi::Web::Server.launch(port: port, scope: options[:scope] || "project", open_browser: options[:web_open],
|
|
1042
|
-
markdown: markdown, turn_view: turn_view
|
|
1087
|
+
markdown: markdown, turn_view: turn_view,
|
|
1088
|
+
annotate_presets: Samagotchi::Config.get("web.annotate_presets"))
|
|
1043
1089
|
end
|
|
1044
1090
|
|
|
1045
1091
|
# --resume and --attach take a unique id prefix too, like git.
|
|
@@ -1050,6 +1096,22 @@ rescue Samagotchi::Session::AmbiguousId => e
|
|
|
1050
1096
|
exit 1
|
|
1051
1097
|
end
|
|
1052
1098
|
|
|
1099
|
+
# A `chi scratch` session a killed REPL left behind would turn into a kept
|
|
1100
|
+
# one if opened again; the next sweep deletes it.
|
|
1101
|
+
%i[resume attach].each do |key|
|
|
1102
|
+
next unless options[key]
|
|
1103
|
+
|
|
1104
|
+
leftover = begin
|
|
1105
|
+
Samagotchi::Session.load(options[key]).scratch
|
|
1106
|
+
rescue ArgumentError
|
|
1107
|
+
false # no such session: the launch says so
|
|
1108
|
+
end
|
|
1109
|
+
next unless leftover
|
|
1110
|
+
|
|
1111
|
+
warn "Error: that's a leftover scratch session; it is deleted at the next sweep (chi sessions clean)"
|
|
1112
|
+
exit 1
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1053
1115
|
# --memory and --mute names are checked here, before the launch: a worker's
|
|
1054
1116
|
# stderr goes nowhere, so its engine's warnings would not be seen. Warnings
|
|
1055
1117
|
# only; the session starts either way.
|
|
@@ -1081,8 +1143,13 @@ if options[:memories] || options[:muted]
|
|
|
1081
1143
|
end
|
|
1082
1144
|
end
|
|
1083
1145
|
|
|
1146
|
+
chi_update_hint.call
|
|
1084
1147
|
require "samagotchi/launch_mode"
|
|
1085
|
-
launch, launch_note =
|
|
1148
|
+
launch, launch_note = if scratch
|
|
1149
|
+
[:repl, nil]
|
|
1150
|
+
else
|
|
1151
|
+
Samagotchi::LaunchMode.resolve(options, shared_config: Samagotchi::Config.get("session.shared"))
|
|
1152
|
+
end
|
|
1086
1153
|
warn launch_note if launch_note
|
|
1087
1154
|
|
|
1088
1155
|
if launch == :attached
|
|
@@ -1124,7 +1191,8 @@ begin
|
|
|
1124
1191
|
memories: options[:memories] || [],
|
|
1125
1192
|
muted_memories: options[:muted] || [],
|
|
1126
1193
|
non_interactive: options[:non_interactive],
|
|
1127
|
-
model_name: options[:model]
|
|
1194
|
+
model_name: options[:model],
|
|
1195
|
+
scratch: scratch
|
|
1128
1196
|
).run
|
|
1129
1197
|
rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::TerminalUI::SessionNotFound, Samagotchi::ModelProfile::MissingModel => e
|
|
1130
1198
|
warn "Error: #{e.message}"
|