@visorcraft/idlehands 0.9.1

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.
Files changed (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/agent.js +2604 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/anton/controller.js +341 -0
  6. package/dist/anton/controller.js.map +1 -0
  7. package/dist/anton/lock.js +110 -0
  8. package/dist/anton/lock.js.map +1 -0
  9. package/dist/anton/parser.js +303 -0
  10. package/dist/anton/parser.js.map +1 -0
  11. package/dist/anton/prompt.js +203 -0
  12. package/dist/anton/prompt.js.map +1 -0
  13. package/dist/anton/reporter.js +119 -0
  14. package/dist/anton/reporter.js.map +1 -0
  15. package/dist/anton/session.js +51 -0
  16. package/dist/anton/session.js.map +1 -0
  17. package/dist/anton/types.js +7 -0
  18. package/dist/anton/types.js.map +1 -0
  19. package/dist/anton/verifier.js +263 -0
  20. package/dist/anton/verifier.js.map +1 -0
  21. package/dist/bench/compare.js +239 -0
  22. package/dist/bench/compare.js.map +1 -0
  23. package/dist/bench/debug_hooks.js +17 -0
  24. package/dist/bench/debug_hooks.js.map +1 -0
  25. package/dist/bench/json_extract.js +22 -0
  26. package/dist/bench/json_extract.js.map +1 -0
  27. package/dist/bench/openclaw.js +86 -0
  28. package/dist/bench/openclaw.js.map +1 -0
  29. package/dist/bench/report.js +116 -0
  30. package/dist/bench/report.js.map +1 -0
  31. package/dist/bench/runner.js +312 -0
  32. package/dist/bench/runner.js.map +1 -0
  33. package/dist/bench/types.js +2 -0
  34. package/dist/bench/types.js.map +1 -0
  35. package/dist/bot/commands.js +444 -0
  36. package/dist/bot/commands.js.map +1 -0
  37. package/dist/bot/confirm-discord.js +133 -0
  38. package/dist/bot/confirm-discord.js.map +1 -0
  39. package/dist/bot/confirm-telegram.js +290 -0
  40. package/dist/bot/confirm-telegram.js.map +1 -0
  41. package/dist/bot/discord.js +826 -0
  42. package/dist/bot/discord.js.map +1 -0
  43. package/dist/bot/format.js +210 -0
  44. package/dist/bot/format.js.map +1 -0
  45. package/dist/bot/session-manager.js +270 -0
  46. package/dist/bot/session-manager.js.map +1 -0
  47. package/dist/bot/telegram.js +678 -0
  48. package/dist/bot/telegram.js.map +1 -0
  49. package/dist/cli/agent-turn.js +45 -0
  50. package/dist/cli/agent-turn.js.map +1 -0
  51. package/dist/cli/args.js +236 -0
  52. package/dist/cli/args.js.map +1 -0
  53. package/dist/cli/bot.js +252 -0
  54. package/dist/cli/bot.js.map +1 -0
  55. package/dist/cli/build-repl-context.js +365 -0
  56. package/dist/cli/build-repl-context.js.map +1 -0
  57. package/dist/cli/command-registry.js +20 -0
  58. package/dist/cli/command-registry.js.map +1 -0
  59. package/dist/cli/commands/anton.js +271 -0
  60. package/dist/cli/commands/anton.js.map +1 -0
  61. package/dist/cli/commands/editing.js +328 -0
  62. package/dist/cli/commands/editing.js.map +1 -0
  63. package/dist/cli/commands/model.js +274 -0
  64. package/dist/cli/commands/model.js.map +1 -0
  65. package/dist/cli/commands/project.js +255 -0
  66. package/dist/cli/commands/project.js.map +1 -0
  67. package/dist/cli/commands/runtime.js +63 -0
  68. package/dist/cli/commands/runtime.js.map +1 -0
  69. package/dist/cli/commands/session.js +281 -0
  70. package/dist/cli/commands/session.js.map +1 -0
  71. package/dist/cli/commands/tools.js +126 -0
  72. package/dist/cli/commands/tools.js.map +1 -0
  73. package/dist/cli/commands/trifecta.js +221 -0
  74. package/dist/cli/commands/trifecta.js.map +1 -0
  75. package/dist/cli/commands/tui.js +17 -0
  76. package/dist/cli/commands/tui.js.map +1 -0
  77. package/dist/cli/init.js +222 -0
  78. package/dist/cli/init.js.map +1 -0
  79. package/dist/cli/input.js +360 -0
  80. package/dist/cli/input.js.map +1 -0
  81. package/dist/cli/oneshot.js +254 -0
  82. package/dist/cli/oneshot.js.map +1 -0
  83. package/dist/cli/repl-context.js +2 -0
  84. package/dist/cli/repl-context.js.map +1 -0
  85. package/dist/cli/runtime-cmds.js +811 -0
  86. package/dist/cli/runtime-cmds.js.map +1 -0
  87. package/dist/cli/service.js +145 -0
  88. package/dist/cli/service.js.map +1 -0
  89. package/dist/cli/session-state.js +130 -0
  90. package/dist/cli/session-state.js.map +1 -0
  91. package/dist/cli/setup.js +815 -0
  92. package/dist/cli/setup.js.map +1 -0
  93. package/dist/cli/shell.js +79 -0
  94. package/dist/cli/shell.js.map +1 -0
  95. package/dist/cli/status.js +392 -0
  96. package/dist/cli/status.js.map +1 -0
  97. package/dist/cli/watch.js +33 -0
  98. package/dist/cli/watch.js.map +1 -0
  99. package/dist/client.js +676 -0
  100. package/dist/client.js.map +1 -0
  101. package/dist/commands.js +194 -0
  102. package/dist/commands.js.map +1 -0
  103. package/dist/config.js +507 -0
  104. package/dist/config.js.map +1 -0
  105. package/dist/confirm/auto.js +13 -0
  106. package/dist/confirm/auto.js.map +1 -0
  107. package/dist/confirm/headless.js +41 -0
  108. package/dist/confirm/headless.js.map +1 -0
  109. package/dist/confirm/terminal.js +90 -0
  110. package/dist/confirm/terminal.js.map +1 -0
  111. package/dist/context.js +49 -0
  112. package/dist/context.js.map +1 -0
  113. package/dist/git.js +136 -0
  114. package/dist/git.js.map +1 -0
  115. package/dist/harnesses.js +171 -0
  116. package/dist/harnesses.js.map +1 -0
  117. package/dist/history.js +139 -0
  118. package/dist/history.js.map +1 -0
  119. package/dist/index.js +700 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/indexer.js +374 -0
  122. package/dist/indexer.js.map +1 -0
  123. package/dist/jsonrpc.js +76 -0
  124. package/dist/jsonrpc.js.map +1 -0
  125. package/dist/lens.js +525 -0
  126. package/dist/lens.js.map +1 -0
  127. package/dist/lsp.js +605 -0
  128. package/dist/lsp.js.map +1 -0
  129. package/dist/markdown.js +275 -0
  130. package/dist/markdown.js.map +1 -0
  131. package/dist/mcp.js +554 -0
  132. package/dist/mcp.js.map +1 -0
  133. package/dist/recovery.js +178 -0
  134. package/dist/recovery.js.map +1 -0
  135. package/dist/replay.js +132 -0
  136. package/dist/replay.js.map +1 -0
  137. package/dist/replay_cli.js +24 -0
  138. package/dist/replay_cli.js.map +1 -0
  139. package/dist/runtime/executor.js +418 -0
  140. package/dist/runtime/executor.js.map +1 -0
  141. package/dist/runtime/planner.js +197 -0
  142. package/dist/runtime/planner.js.map +1 -0
  143. package/dist/runtime/store.js +289 -0
  144. package/dist/runtime/store.js.map +1 -0
  145. package/dist/runtime/types.js +2 -0
  146. package/dist/runtime/types.js.map +1 -0
  147. package/dist/safety.js +446 -0
  148. package/dist/safety.js.map +1 -0
  149. package/dist/spinner.js +224 -0
  150. package/dist/spinner.js.map +1 -0
  151. package/dist/sys/context.js +124 -0
  152. package/dist/sys/context.js.map +1 -0
  153. package/dist/sys/snapshot.sh +97 -0
  154. package/dist/term.js +61 -0
  155. package/dist/term.js.map +1 -0
  156. package/dist/themes.js +135 -0
  157. package/dist/themes.js.map +1 -0
  158. package/dist/tools.js +1114 -0
  159. package/dist/tools.js.map +1 -0
  160. package/dist/tui/branch-picker.js +65 -0
  161. package/dist/tui/branch-picker.js.map +1 -0
  162. package/dist/tui/command-handler.js +108 -0
  163. package/dist/tui/command-handler.js.map +1 -0
  164. package/dist/tui/confirm.js +90 -0
  165. package/dist/tui/confirm.js.map +1 -0
  166. package/dist/tui/controller.js +463 -0
  167. package/dist/tui/controller.js.map +1 -0
  168. package/dist/tui/event-bridge.js +44 -0
  169. package/dist/tui/event-bridge.js.map +1 -0
  170. package/dist/tui/events.js +2 -0
  171. package/dist/tui/events.js.map +1 -0
  172. package/dist/tui/keymap.js +144 -0
  173. package/dist/tui/keymap.js.map +1 -0
  174. package/dist/tui/layout.js +11 -0
  175. package/dist/tui/layout.js.map +1 -0
  176. package/dist/tui/render.js +186 -0
  177. package/dist/tui/render.js.map +1 -0
  178. package/dist/tui/screen.js +48 -0
  179. package/dist/tui/screen.js.map +1 -0
  180. package/dist/tui/state.js +167 -0
  181. package/dist/tui/state.js.map +1 -0
  182. package/dist/tui/theme.js +70 -0
  183. package/dist/tui/theme.js.map +1 -0
  184. package/dist/tui/types.js +2 -0
  185. package/dist/tui/types.js.map +1 -0
  186. package/dist/types.js +2 -0
  187. package/dist/types.js.map +1 -0
  188. package/dist/upgrade.js +412 -0
  189. package/dist/upgrade.js.map +1 -0
  190. package/dist/utils.js +87 -0
  191. package/dist/utils.js.map +1 -0
  192. package/dist/vault.js +520 -0
  193. package/dist/vault.js.map +1 -0
  194. package/dist/vim.js +160 -0
  195. package/dist/vim.js.map +1 -0
  196. package/package.json +67 -0
  197. package/src/sys/snapshot.sh +97 -0
package/dist/index.js ADDED
@@ -0,0 +1,700 @@
1
+ #!/usr/bin/env node
2
+ import readline from 'node:readline/promises';
3
+ import * as rlBase from 'node:readline';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import fsSync from 'node:fs';
8
+ import fs from 'node:fs/promises';
9
+ import { loadConfig, defaultConfigPath, applyRuntimeEndpoint } from './config.js';
10
+ import { runSetup, guidedRuntimeOnboarding } from './cli/setup.js';
11
+ import { projectDir } from './utils.js';
12
+ import { createSession } from './agent.js';
13
+ import { unifiedDiffFromBuffers } from './replay_cli.js';
14
+ import { makeStyler, resolveColorMode, colorizeUnifiedDiff, warn as warnFmt, err as errFmt } from './term.js';
15
+ import { resolveTheme } from './themes.js';
16
+ import { createVimState, handleVimKeypress } from './vim.js';
17
+ import { loadCustomCommands, expandArgs } from './commands.js';
18
+ import { performUpgrade, performRollback, dailyUpdateCheck, detectInstallSource } from './upgrade.js';
19
+ import { setLockdown, setSafetyLogging, loadSafetyConfig } from './safety.js';
20
+ import { loadGitStartupSummary } from './git.js';
21
+ // ── Extracted CLI modules ────────────────────────────────────────────
22
+ import { parseArgs, asNum, asBool, friendlyError, loadMcpServerConfigFile, printHelp, } from './cli/args.js';
23
+ import { readUserInput, readStdinIfPiped, reverseSearchHistory, isPathCompletionContext, expandAtFileRefs, expandPromptImages, } from './cli/input.js';
24
+ import { lastSessionPath, namedSessionPath, projectSessionPath, loadPromptTemplates, loadHistory, appendHistoryLine, rotateHistoryIfNeeded, } from './cli/session-state.js';
25
+ import { runDirectShellCommand } from './cli/shell.js';
26
+ import { replayCaptureFile, formatStatusLine, renderStartupBanner, formatCount, endpointLooksLocal, } from './cli/status.js';
27
+ import { generateInitContext, formatInitSummary, } from './cli/init.js';
28
+ import { buildReplContext } from './cli/build-repl-context.js';
29
+ import { runAgentTurnWithSpinner } from './cli/agent-turn.js';
30
+ import { printBotHelp, runBotSubcommand } from './cli/bot.js';
31
+ import { runHostsSubcommand, runBackendsSubcommand, runModelsSubcommand, runSelectSubcommand, runHealthSubcommand } from './cli/runtime-cmds.js';
32
+ import { runOneShot } from './cli/oneshot.js';
33
+ import { registerAll, findCommand, allCommandNames } from './cli/command-registry.js';
34
+ import { sessionCommands } from './cli/commands/session.js';
35
+ import { runtimeCommands } from './cli/commands/runtime.js';
36
+ import { modelCommands } from './cli/commands/model.js';
37
+ import { editingCommands } from './cli/commands/editing.js';
38
+ import { projectCommands } from './cli/commands/project.js';
39
+ import { trifectaCommands } from './cli/commands/trifecta.js';
40
+ import { toolCommands } from './cli/commands/tools.js';
41
+ import { antonCommands } from './cli/commands/anton.js';
42
+ import { runTui } from './cli/commands/tui.js';
43
+ async function main() {
44
+ const args = parseArgs(process.argv.slice(2));
45
+ const { version } = JSON.parse(await fs.readFile(new URL("../package.json", import.meta.url), "utf8"));
46
+ if (args.version || args.v) {
47
+ console.log(version);
48
+ process.exit(0);
49
+ }
50
+ if (args._[0] === 'upgrade' || args.upgrade) {
51
+ const configPath = typeof args.config === 'string' ? args.config : defaultConfigPath();
52
+ let source = 'github';
53
+ try {
54
+ const raw = await fs.readFile(configPath, 'utf8');
55
+ const cfg = JSON.parse(raw);
56
+ if (cfg.install_source === 'npm' || cfg.install_source === 'github') {
57
+ source = cfg.install_source;
58
+ }
59
+ else {
60
+ source = detectInstallSource();
61
+ }
62
+ }
63
+ catch {
64
+ source = detectInstallSource();
65
+ }
66
+ await performUpgrade(version, source);
67
+ process.exit(0);
68
+ }
69
+ if (args._[0] === 'rollback' || args.rollback) {
70
+ await performRollback();
71
+ process.exit(0);
72
+ }
73
+ if (args.help || args.h) {
74
+ printHelp();
75
+ process.exit(0);
76
+ }
77
+ const configPath = typeof args.config === 'string' ? args.config : defaultConfigPath();
78
+ if (args._[0] === 'setup') {
79
+ const setupResult = await runSetup(configPath);
80
+ if (setupResult !== 'run')
81
+ process.exit(0);
82
+ // 'run' → fall through to normal startup below
83
+ }
84
+ // First-run detection: no config file + no CLI endpoint = offer setup
85
+ if (process.stdin.isTTY && process.stdout.isTTY) {
86
+ let hasConfig = false;
87
+ try {
88
+ await fs.access(configPath);
89
+ hasConfig = true;
90
+ }
91
+ catch { }
92
+ const hasEndpointArg = typeof args.endpoint === 'string';
93
+ const isSubcommand = args._[0] === 'bot' || args._[0] === 'hosts' || args._[0] === 'backends'
94
+ || args._[0] === 'models' || args._[0] === 'select' || args._[0] === 'health'
95
+ || args._[0] === 'upgrade' || args._[0] === 'rollback' || args._[0] === 'init'
96
+ || args._[0] === 'service';
97
+ if (!hasConfig && !hasEndpointArg && !isSubcommand) {
98
+ console.log('\n No config file found. Running interactive setup...');
99
+ console.log(' (To skip, pass --endpoint <url>)\n');
100
+ const setupResult = await runSetup(configPath);
101
+ if (setupResult !== 'run')
102
+ process.exit(0);
103
+ // 'run' → fall through to normal startup below
104
+ }
105
+ }
106
+ const cliCfg = {
107
+ endpoint: typeof args.endpoint === 'string' ? args.endpoint : undefined,
108
+ model: typeof args.model === 'string' ? args.model : undefined,
109
+ dir: typeof args.dir === 'string' ? path.resolve(args.dir) : undefined,
110
+ max_tokens: asNum(args['max-tokens'] ?? args.max_tokens),
111
+ temperature: asNum(args.temperature),
112
+ top_p: asNum(args['top-p'] ?? args.top_p),
113
+ timeout: asNum(args.timeout),
114
+ response_timeout: asNum(args['response-timeout'] ?? args.response_timeout),
115
+ max_iterations: asNum(args['max-iterations'] ?? args.max_iterations),
116
+ context_window: asNum(args['context-window'] ?? args.context_window),
117
+ approval_mode: typeof args['approval-mode'] === 'string'
118
+ ? args['approval-mode']
119
+ : asBool(args['non-interactive'] ?? args.non_interactive) ? 'reject'
120
+ : asBool(args.plan) ? 'plan' : undefined,
121
+ verbose: asBool(args.verbose),
122
+ quiet: asBool(args.quiet),
123
+ dry_run: asBool(args['dry-run'] ?? args.dry_run),
124
+ output_format: typeof args['output-format'] === 'string' ? args['output-format'] : undefined,
125
+ fail_on_error: asBool(args['fail-on-error'] ?? args.fail_on_error),
126
+ diff_only: asBool(args['diff-only'] ?? args.diff_only),
127
+ no_confirm: asBool(args['no-confirm'] ?? args.no_confirm ?? args.yolo),
128
+ mode: asBool(args.sys) ? 'sys' : undefined,
129
+ sys_eager: asBool(args['sys-eager'] ?? args.sys_eager) || undefined,
130
+ i_know_what_im_doing: asBool(args['i-know-what-im-doing'] ?? args.i_know_what_im_doing),
131
+ theme: typeof args.theme === 'string' ? args.theme : undefined,
132
+ vim_mode: asBool(args.vim ?? args['vim-mode'] ?? args.vim_mode),
133
+ harness: typeof args.harness === 'string' ? args.harness : undefined,
134
+ context_file: typeof args['context-file'] === 'string' ? args['context-file'] : undefined,
135
+ no_context: asBool(args['no-context'] ?? args.no_context),
136
+ context_max_tokens: asNum(args['context-max-tokens'] ?? args.context_max_tokens),
137
+ compact_at: asNum(args['compact-at'] ?? args.compact_at),
138
+ step_mode: asBool(args.step),
139
+ auto_detect_model_change: asBool(args['auto-detect-model-change'] ?? args.auto_detect_model_change),
140
+ show_server_metrics: asBool(args['show-server-metrics']) !== undefined
141
+ ? asBool(args['show-server-metrics'])
142
+ : (asBool(args['no-server-metrics']) === undefined ? undefined : !asBool(args['no-server-metrics'])),
143
+ slow_tg_tps_threshold: asNum(args['slow-tg-tps-threshold'] ?? args.slow_tg_tps_threshold),
144
+ mcp_tool_budget: asNum(args['mcp-tool-budget'] ?? args.mcp_tool_budget),
145
+ mcp_call_timeout_sec: asNum(args['mcp-call-timeout-sec'] ?? args.mcp_call_timeout_sec),
146
+ color: typeof args.color === 'string' ? args.color : undefined,
147
+ auto_update_check: asBool(args['no-update-check']) === undefined ? undefined : !asBool(args['no-update-check']),
148
+ offline: asBool(args.offline),
149
+ trifecta: {
150
+ enabled: asBool(args['no-trifecta']) === undefined ? undefined : !asBool(args['no-trifecta']),
151
+ vault: {
152
+ enabled: asBool(args['no-vault']) === undefined ? undefined : !asBool(args['no-vault']),
153
+ mode: (() => {
154
+ const raw = typeof args['vault-mode'] === 'string' ? args['vault-mode'].toLowerCase() : undefined;
155
+ if (!raw)
156
+ return undefined;
157
+ if (raw === 'active' || raw === 'passive' || raw === 'off')
158
+ return raw;
159
+ console.error(`Invalid --vault-mode: ${raw} (expected active|passive|off)`);
160
+ return undefined;
161
+ })()
162
+ },
163
+ lens: {
164
+ enabled: asBool(args['no-lens']) === undefined ? undefined : !asBool(args['no-lens'])
165
+ },
166
+ replay: {
167
+ enabled: asBool(args['no-replay']) === undefined ? undefined : !asBool(args['no-replay'])
168
+ }
169
+ }
170
+ };
171
+ const { config } = await loadConfig({ configPath, cli: cliCfg });
172
+ // If a runtime is active, use its endpoint (unless --endpoint was passed explicitly)
173
+ const cliEndpoint = typeof args.endpoint === 'string' ? args.endpoint : undefined;
174
+ const runtimeOverrode = await applyRuntimeEndpoint(config, cliEndpoint);
175
+ if (runtimeOverrode && config.verbose) {
176
+ console.error(`[runtime] Using endpoint from active runtime: ${config.endpoint}`);
177
+ }
178
+ if (typeof args.mcp === 'string' && args.mcp.trim()) {
179
+ const extraServers = await loadMcpServerConfigFile(args.mcp.trim());
180
+ const existing = Array.isArray(config.mcp?.servers) ? config.mcp.servers : [];
181
+ config.mcp = config.mcp ?? { servers: [] };
182
+ config.mcp.servers = [...existing, ...extraServers];
183
+ console.log(`[mcp] loaded ${extraServers.length} ad-hoc server(s) from ${path.resolve(args.mcp.trim())}`);
184
+ }
185
+ if (typeof args.replay === 'string' && args.replay.trim()) {
186
+ const replayPath = path.resolve(args.replay);
187
+ await replayCaptureFile(replayPath, config);
188
+ process.exit(0);
189
+ }
190
+ // --- Safety initialization (Phase 9) ---
191
+ await loadSafetyConfig();
192
+ if (asBool(args.lockdown)) {
193
+ setLockdown(true);
194
+ }
195
+ if (config.verbose) {
196
+ setSafetyLogging(true);
197
+ }
198
+ // --- Service subcommand ---
199
+ if (args._[0] === 'service') {
200
+ const { runServiceSubcommand } = await import('./cli/service.js');
201
+ await runServiceSubcommand(args);
202
+ return;
203
+ }
204
+ // --- Bot subcommand ---
205
+ if (args._[0] === 'bot') {
206
+ if (args.help) {
207
+ printBotHelp();
208
+ return;
209
+ }
210
+ await runBotSubcommand({ botTarget: args._[1] || 'telegram', config, configPath, cliCfg, all: Boolean(args.all) });
211
+ return;
212
+ }
213
+ // Runtime subcommands — catch Ctrl+C cleanly
214
+ for (const [name, handler] of [
215
+ ['hosts', runHostsSubcommand],
216
+ ['backends', runBackendsSubcommand],
217
+ ['models', runModelsSubcommand],
218
+ ['select', runSelectSubcommand],
219
+ ['health', runHealthSubcommand],
220
+ ]) {
221
+ if (args._[0] === name) {
222
+ try {
223
+ await handler(args, config);
224
+ }
225
+ catch (e) {
226
+ if (e?.code === 'ABORT_ERR' || e?.name === 'AbortError') {
227
+ process.stdout.write('\n');
228
+ }
229
+ else
230
+ throw e;
231
+ }
232
+ return;
233
+ }
234
+ }
235
+ // TUI is the default for interactive TTY sessions.
236
+ // --no-tui forces classic CLI. Non-TTY environments skip TUI automatically.
237
+ const forceNoTui = asBool(args['no-tui'] ?? args.no_tui) === true;
238
+ const hasTTY = !!(process.stdin.isTTY && process.stdout.isTTY);
239
+ if (hasTTY && !forceNoTui) {
240
+ const launched = await runTui(config, args);
241
+ if (launched)
242
+ return; // TUI ran successfully
243
+ // Fall through to classic CLI if TUI validation failed
244
+ }
245
+ // Terminal styling + theme
246
+ const colorMode = (cliCfg.color ?? 'auto');
247
+ const { enabled } = resolveColorMode(colorMode);
248
+ const themeFns = await resolveTheme(config.theme ?? 'default');
249
+ let S = makeStyler(enabled, themeFns);
250
+ const positionalInstruction = args._.filter((a) => a !== 'bot' && a !== 'setup' && a !== 'init').join(' ').trim();
251
+ const promptFlag = typeof args.prompt === 'string'
252
+ ? args.prompt
253
+ : (typeof args.p === 'string' ? args.p : '');
254
+ const pipedInput = await readStdinIfPiped();
255
+ let instruction = (promptFlag || positionalInstruction || '').trim();
256
+ if (!instruction && pipedInput) {
257
+ instruction = pipedInput;
258
+ }
259
+ else if (instruction && pipedInput) {
260
+ instruction = `${pipedInput}\n\n${instruction}`.trim();
261
+ }
262
+ const oneShotFlag = asBool(args['one-shot']);
263
+ const isOneShot = Boolean(instruction) || Boolean(oneShotFlag) || Boolean(promptFlag);
264
+ if (args._[0] === 'init' || asBool(args.init)) {
265
+ const cwd = projectDir(config);
266
+ const summary = await generateInitContext(cwd);
267
+ const rendered = formatInitSummary(summary);
268
+ const outPath = path.join(cwd, '.idlehands.md');
269
+ console.log(rendered);
270
+ let shouldWrite = false;
271
+ if (process.stdin.isTTY) {
272
+ if (fsSync.existsSync(outPath)) {
273
+ const existing = await fs.readFile(outPath, 'utf8').catch(() => '');
274
+ const diff = await unifiedDiffFromBuffers(Buffer.from(existing), Buffer.from(rendered));
275
+ if (diff.trim()) {
276
+ console.log(colorizeUnifiedDiff(diff.trim(), S));
277
+ }
278
+ }
279
+ const rlInit = readline.createInterface({ input, output });
280
+ const ans = (await rlInit.question(`Generated .idlehands.md (~${summary.tokenEstimate} tokens). Write? [Y/n] `)).trim().toLowerCase();
281
+ rlInit.close();
282
+ shouldWrite = !ans || ans === 'y' || ans === 'yes';
283
+ }
284
+ else {
285
+ console.error('Refusing to write .idlehands.md non-interactively. Re-run with a TTY to confirm.');
286
+ process.exit(2);
287
+ }
288
+ if (shouldWrite) {
289
+ await fs.writeFile(outPath, rendered, 'utf8');
290
+ console.log(`Wrote ${outPath}`);
291
+ process.exit(0);
292
+ }
293
+ console.log('Cancelled.');
294
+ process.exit(0);
295
+ }
296
+ if (isOneShot) {
297
+ if (!instruction) {
298
+ console.error('Usage: idlehands --one-shot "your instruction" (or -p/--prompt, or piped stdin)');
299
+ process.exit(2);
300
+ }
301
+ await runOneShot({ instruction, config, S });
302
+ }
303
+ // REPL (persistent session for KV cache reuse)
304
+ await rotateHistoryIfNeeded();
305
+ const priorHistory = await loadHistory();
306
+ let lastPersistedHistory = priorHistory[priorHistory.length - 1] ?? '';
307
+ const promptTemplates = await loadPromptTemplates();
308
+ // ── Register all slash commands ──
309
+ registerAll([
310
+ ...sessionCommands,
311
+ ...runtimeCommands,
312
+ ...modelCommands,
313
+ ...editingCommands,
314
+ ...projectCommands,
315
+ ...trifectaCommands,
316
+ ...toolCommands,
317
+ ...antonCommands,
318
+ ]);
319
+ let customCommands = await loadCustomCommands(projectDir(config));
320
+ const getSlashCommands = () => [
321
+ ...allCommandNames(),
322
+ ...Object.keys(promptTemplates),
323
+ ...ctx.customCommands.keys(),
324
+ ];
325
+ const completer = (line) => {
326
+ const trimmed = line.trim();
327
+ if (trimmed.startsWith('/')) {
328
+ const slashCommands = getSlashCommands();
329
+ const hits = slashCommands.filter(c => c.startsWith(trimmed));
330
+ return [hits.length ? hits : slashCommands, trimmed];
331
+ }
332
+ const m = /(.*\s)?([^\s]*)$/.exec(line);
333
+ const token = m?.[2] ?? '';
334
+ const isAtRef = token.startsWith('@');
335
+ const tokenPath = isAtRef ? token.slice(1) : token;
336
+ const allowBarePath = isPathCompletionContext(line);
337
+ if (tokenPath && (isAtRef || allowBarePath || tokenPath.includes('/') || tokenPath.startsWith('.') || tokenPath.startsWith('~'))) {
338
+ const base = tokenPath.startsWith('~')
339
+ ? path.resolve(os.homedir(), tokenPath.slice(1))
340
+ : path.resolve(projectDir(config), tokenPath || '.');
341
+ const dir = tokenPath && tokenPath.includes('/') ? path.dirname(base) : path.resolve(projectDir(config));
342
+ const prefix = tokenPath && tokenPath.includes('/') ? path.basename(base) : tokenPath;
343
+ try {
344
+ const entries = fsSync.readdirSync(dir, { withFileTypes: true });
345
+ const hits = entries
346
+ .filter((e) => String(e.name).startsWith(prefix))
347
+ .map((e) => {
348
+ const rel = path.relative(projectDir(config), path.join(dir, e.name));
349
+ const completed = rel + (e.isDirectory() ? '/' : '');
350
+ return isAtRef ? `@${completed}` : completed;
351
+ });
352
+ return [hits, token];
353
+ }
354
+ catch {
355
+ return [[], token];
356
+ }
357
+ }
358
+ return [[], line];
359
+ };
360
+ const rl = readline.createInterface({ input, output, historySize: 10_000, completer });
361
+ rl.history = [...priorHistory].reverse();
362
+ const vimState = createVimState();
363
+ if (config.vim_mode)
364
+ vimState.mode = 'normal';
365
+ if (process.stdin.isTTY) {
366
+ const approvalModes = ['plan', 'reject', 'default', 'auto-edit', 'yolo'];
367
+ rlBase.emitKeypressEvents(input, rl);
368
+ input.on('keypress', (_ch, key) => {
369
+ if (config.vim_mode) {
370
+ const consumed = handleVimKeypress(vimState, rl, _ch, key);
371
+ if (consumed) {
372
+ const vimTag = vimState.mode === 'normal' ? S.dim('[N] ') : S.dim('[I] ');
373
+ const runModeTag = config.mode === 'sys' ? S.dim('[sys] ') : '';
374
+ const approvalTag = config.approval_mode !== 'auto-edit' ? S.dim(`[${config.approval_mode}] `) : '';
375
+ rl.setPrompt(vimTag + runModeTag + approvalTag + S.bold(S.cyan('> ')));
376
+ rl.prompt(true);
377
+ return;
378
+ }
379
+ }
380
+ if (key && key.name === 'tab' && key.shift) {
381
+ const idx = approvalModes.indexOf(config.approval_mode);
382
+ config.approval_mode = approvalModes[(idx + 1) % approvalModes.length];
383
+ process.stderr.write(`\r\x1b[K${S.dim(`[approval: ${config.approval_mode}]`)}\n`);
384
+ const runModeTag = config.mode === 'sys' ? S.dim('[sys] ') : '';
385
+ const approvalTag = config.approval_mode !== 'auto-edit' ? S.dim(`[${config.approval_mode}] `) : '';
386
+ rl.setPrompt(runModeTag + approvalTag + S.bold(S.cyan('> ')));
387
+ rl.prompt(true);
388
+ return;
389
+ }
390
+ if (key?.ctrl && key?.name === 'e') {
391
+ const seed = String(rl.line ?? '').trim();
392
+ rl.write(null, { ctrl: true, name: 'u' });
393
+ rl.write(`/edit ${seed}`);
394
+ rl.write(null, { name: 'return' });
395
+ return;
396
+ }
397
+ if (key?.ctrl && key?.name === 'r') {
398
+ void reverseSearchHistory(rl, S);
399
+ return;
400
+ }
401
+ });
402
+ }
403
+ const confirm = async (prompt) => {
404
+ const ans = (await rl.question(prompt)).trim().toLowerCase();
405
+ return ans === 'y' || ans === 'yes';
406
+ };
407
+ const { TerminalConfirmProvider } = await import('./confirm/terminal.js');
408
+ const { HeadlessConfirmProvider } = await import('./confirm/headless.js');
409
+ const { AutoApproveProvider } = await import('./confirm/auto.js');
410
+ const confirmProvider = config.approval_mode === 'yolo'
411
+ ? new AutoApproveProvider()
412
+ : config.approval_mode === 'reject'
413
+ ? new HeadlessConfirmProvider('reject')
414
+ : process.stdin.isTTY
415
+ ? new TerminalConfirmProvider(rl)
416
+ : new HeadlessConfirmProvider(config.approval_mode);
417
+ let session;
418
+ try {
419
+ session = await createSession({ config, confirm, confirmProvider });
420
+ }
421
+ catch (e) {
422
+ const msg = e?.message ?? '';
423
+ if (msg.includes('No models found') && process.stdin.isTTY) {
424
+ const ok = await guidedRuntimeOnboarding();
425
+ if (ok) {
426
+ // Runtime started — re-apply endpoint and retry
427
+ await applyRuntimeEndpoint(config);
428
+ session = await createSession({ config, confirm, confirmProvider });
429
+ }
430
+ else {
431
+ throw e;
432
+ }
433
+ }
434
+ else {
435
+ throw e;
436
+ }
437
+ }
438
+ const sessionName = typeof args.session === 'string' && args.session.trim() ? args.session.trim() : '';
439
+ const resumeArg = args.resume;
440
+ const continueFlag = !!asBool(args['continue']);
441
+ const resumeNamed = typeof resumeArg === 'string' && resumeArg.trim() ? resumeArg.trim() : '';
442
+ const resumeFile = continueFlag
443
+ ? projectSessionPath(projectDir(config))
444
+ : (resumeNamed
445
+ ? namedSessionPath(resumeNamed)
446
+ : (sessionName ? namedSessionPath(sessionName) : lastSessionPath()));
447
+ const shouldFresh = !!asBool(args.fresh);
448
+ const shouldResume = continueFlag || !!asBool(resumeArg) || !!resumeNamed;
449
+ if (!shouldFresh) {
450
+ const resumeRaw = await fs.readFile(resumeFile, 'utf8').catch(() => '');
451
+ if (resumeRaw.trim()) {
452
+ try {
453
+ const parsed = JSON.parse(resumeRaw);
454
+ const msgs = Array.isArray(parsed?.messages) ? parsed.messages : [];
455
+ if (msgs.length >= 2) {
456
+ let ok = shouldResume;
457
+ if (!shouldResume && process.stdin.isTTY) {
458
+ const modelLabel = parsed?.model ? `model: ${parsed.model}, ` : '';
459
+ const turns = Math.max(0, msgs.filter((m) => m.role !== 'system').length);
460
+ const savedAt = parsed?.savedAt ? new Date(parsed.savedAt).toLocaleString() : 'unknown time';
461
+ const ans = (await rl.question(`Resume previous session? (${modelLabel}${turns} turns, saved ${savedAt}) [Y/n] `)).trim().toLowerCase();
462
+ ok = !ans || ans === 'y' || ans === 'yes';
463
+ }
464
+ if (ok) {
465
+ session.restore(msgs);
466
+ if (parsed?.model) {
467
+ try {
468
+ session.setModel(String(parsed.model));
469
+ }
470
+ catch { }
471
+ }
472
+ console.log(S.dim(`[resume] restored session from ${resumeFile}`));
473
+ }
474
+ }
475
+ }
476
+ catch {
477
+ // ignore corrupt session file
478
+ }
479
+ }
480
+ }
481
+ // ── Build ReplContext ──
482
+ const ctx = buildReplContext({
483
+ session, config, rl, S, version,
484
+ vimState, customCommands, enabled, confirm,
485
+ sessionName, resumeFile,
486
+ warnFmt, errFmt,
487
+ });
488
+ // ── Signal handlers ──
489
+ let sigintCount = 0;
490
+ process.on('SIGINT', () => {
491
+ if (ctx.activeShellProc) {
492
+ try {
493
+ ctx.activeShellProc.kill('SIGKILL');
494
+ }
495
+ catch { }
496
+ process.stdout.write('\n');
497
+ ctx.activeShellProc = null;
498
+ return;
499
+ }
500
+ if (ctx.watchActive) {
501
+ process.stdout.write('\n');
502
+ ctx.stopWatchMode(true);
503
+ return;
504
+ }
505
+ sigintCount++;
506
+ if (sigintCount >= 2) {
507
+ void ctx.shutdown(130);
508
+ return;
509
+ }
510
+ session.cancel();
511
+ setTimeout(() => { sigintCount = 0; }, 1500).unref();
512
+ });
513
+ process.on('SIGTERM', () => { void ctx.shutdown(143); });
514
+ // ── Startup ──
515
+ const startupGitSummary = await loadGitStartupSummary(projectDir(config)).catch(() => '');
516
+ renderStartupBanner(session, config, S, {
517
+ firstRun: priorHistory.length === 0,
518
+ lockdown: !!asBool(args.lockdown),
519
+ gitSummary: startupGitSummary || undefined,
520
+ });
521
+ if (config.show_server_metrics !== false) {
522
+ const startupHealth = await ctx.readServerHealth(true);
523
+ if (ctx.healthUnsupported) {
524
+ console.log(S.dim('[server] /health unavailable on this endpoint; /server stats disabled.'));
525
+ }
526
+ else if (startupHealth?.ok) {
527
+ const startupBits = [
528
+ `status: ${startupHealth.statusText || 'ok'}`,
529
+ startupHealth.model ? `model: ${startupHealth.model}` : undefined,
530
+ startupHealth.contextSize ? `ctx: ${formatCount(startupHealth.contextSize)}` : undefined,
531
+ startupHealth.slotCount !== undefined ? `slots: ${formatCount(startupHealth.slotCount)}` : undefined,
532
+ ].filter(Boolean);
533
+ if (startupBits.length)
534
+ console.log(S.dim(`[server] ${startupBits.join(' | ')}`));
535
+ }
536
+ }
537
+ try {
538
+ const skipInternet = !!config.offline || endpointLooksLocal(config.endpoint);
539
+ const shouldCheck = config.auto_update_check !== false && !skipInternet;
540
+ if (shouldCheck) {
541
+ const src = config.install_source || detectInstallSource();
542
+ void dailyUpdateCheck(version, src, { timeoutMs: 3000, offline: !!config.offline })
543
+ .then((updateInfo) => {
544
+ if (updateInfo)
545
+ console.log(S.yellow(`\n Update available: ${updateInfo.current} → ${updateInfo.latest} (run: idlehands upgrade)\n`));
546
+ }).catch(() => { });
547
+ }
548
+ }
549
+ catch { }
550
+ // ── REPL loop ──
551
+ while (true) {
552
+ let raw;
553
+ const runModeTag = config.mode === 'sys' ? S.dim('[sys] ') : '';
554
+ const approvalTag = config.approval_mode !== 'auto-edit' ? S.dim(`[${config.approval_mode}] `) : '';
555
+ const prompt = ctx.shellMode
556
+ ? S.bold(S.yellow('$ '))
557
+ : runModeTag + approvalTag + S.bold(S.cyan('> '));
558
+ try {
559
+ if (ctx.statusBarEnabled)
560
+ ctx.statusBar.render(ctx.lastStatusLine);
561
+ raw = await readUserInput(rl, prompt);
562
+ }
563
+ catch {
564
+ break;
565
+ }
566
+ let line = raw.trim();
567
+ if (!line)
568
+ continue;
569
+ if (line !== lastPersistedHistory) {
570
+ await appendHistoryLine(line);
571
+ lastPersistedHistory = line;
572
+ }
573
+ // Shell mode toggle
574
+ if (line === '!') {
575
+ ctx.shellMode = !ctx.shellMode;
576
+ console.log(ctx.shellMode ? S.dim('[shell] direct shell mode ON (use ! or /exit-shell to leave)') : S.dim('[shell] direct shell mode OFF'));
577
+ continue;
578
+ }
579
+ if (line === '/exit-shell') {
580
+ ctx.shellMode = false;
581
+ console.log(S.dim('[shell] direct shell mode OFF'));
582
+ continue;
583
+ }
584
+ // ── Anton guard ── warn on free-form prompts and shell while Anton is running
585
+ if (ctx.antonActive && !line.startsWith('/anton')) {
586
+ const isShell = (ctx.shellMode && !line.startsWith('/')) || /^!{1,2}\s*\S/.test(line);
587
+ const isAgentTurn = !line.startsWith('/');
588
+ if (isShell || isAgentTurn) {
589
+ console.log('⚠️ Anton is running. File changes may conflict. Use /anton stop first, or proceed at your own risk.');
590
+ }
591
+ }
592
+ // Direct shell execution
593
+ const shouldRunShell = (ctx.shellMode && !line.startsWith('/')) || /^!{1,2}\s*\S/.test(line);
594
+ if (shouldRunShell) {
595
+ const injectOutput = !ctx.shellMode && line.startsWith('!!');
596
+ const command = ctx.shellMode ? line : line.slice(injectOutput ? 2 : 1).trim();
597
+ if (!command) {
598
+ console.log('Usage: !<command> (or !!<command> to also inject output into context)');
599
+ continue;
600
+ }
601
+ const timeoutSec = config.mode === 'sys' ? 60 : 30;
602
+ console.log(S.dim(`[shell] $ ${command}`));
603
+ const result = await runDirectShellCommand({
604
+ command, cwd: projectDir(config), timeoutSec,
605
+ onStart: (proc) => { ctx.activeShellProc = proc; },
606
+ onStop: () => { ctx.activeShellProc = null; },
607
+ });
608
+ if (result.timedOut)
609
+ console.log(warnFmt(`[shell] killed after ${timeoutSec}s timeout`, S));
610
+ if (!result.timedOut && result.rc !== 0)
611
+ console.log(warnFmt(`[shell] exited with rc=${result.rc}`, S));
612
+ if (injectOutput) {
613
+ const merged = `${result.out}${result.err}`.slice(-4000);
614
+ session.messages.push({ role: 'user', content: `[Shell output]\n$ ${command}\n${merged}` });
615
+ console.log(S.dim('[shell] output injected into conversation context.'));
616
+ }
617
+ continue;
618
+ }
619
+ // Slash command dispatch via registry
620
+ const [slashHeadRaw, ...slashRest] = line.split(/\s+/);
621
+ const slashHead = slashHeadRaw.toLowerCase();
622
+ const registeredCmd = findCommand(line);
623
+ if (registeredCmd) {
624
+ const cmdArgs = line.replace(/^\S+\s*/, '').trim();
625
+ try {
626
+ const handled = await registeredCmd.execute(ctx, cmdArgs, line);
627
+ S = ctx.S; // sync in case command changed styler (e.g. /theme)
628
+ if (handled)
629
+ continue;
630
+ }
631
+ catch (e) {
632
+ console.error(errFmt(`${registeredCmd.name}: ${e?.message ?? String(e)}`, S));
633
+ continue;
634
+ }
635
+ }
636
+ // Template expansion (fall through to agent turn)
637
+ if (line.startsWith('/') && Object.prototype.hasOwnProperty.call(promptTemplates, slashHead)) {
638
+ const template = promptTemplates[slashHead];
639
+ ctx.pendingTemplate = template;
640
+ console.log(S.dim(`[template] queued from ${slashHead}. Your next prompt will be prefixed.`));
641
+ continue;
642
+ }
643
+ // Custom command expansion (modifies line, falls through to agent turn)
644
+ if (line.startsWith('/') && ctx.customCommands.has(slashHead)) {
645
+ const cmd = ctx.customCommands.get(slashHead);
646
+ const expanded = expandArgs(cmd.template, slashRest).trim();
647
+ if (!expanded) {
648
+ console.log(warnFmt(`[command] ${slashHead} expanded to empty prompt.`, S));
649
+ continue;
650
+ }
651
+ line = expanded;
652
+ console.log(S.dim(`[command] ${slashHead} → prompt injected (${cmd.source})`));
653
+ }
654
+ // ── Agent turn ──
655
+ try {
656
+ const expandedRes = await expandAtFileRefs(line, projectDir(config), config.context_max_tokens ?? 8192);
657
+ for (const w of expandedRes.warnings)
658
+ console.log(S.dim(w));
659
+ const promptText = ctx.pendingTemplate ? `${ctx.pendingTemplate}\n\n${expandedRes.text}` : expandedRes.text;
660
+ ctx.pendingTemplate = null;
661
+ const imageExpanded = await expandPromptImages(promptText, projectDir(config), session.supportsVision);
662
+ for (const w of imageExpanded.warnings)
663
+ console.log(S.dim(w));
664
+ ctx.lastRunnableInput = imageExpanded.content;
665
+ const res = await runAgentTurnWithSpinner(ctx, imageExpanded.content);
666
+ await ctx.maybeOfferAutoCommit(line);
667
+ if (config.verbose) {
668
+ const { renderMarkdown } = await import('./markdown.js');
669
+ console.log(renderMarkdown(res.text, { color: S.enabled, verbose: true }));
670
+ }
671
+ ctx.lastStatusLine = formatStatusLine(session, config, S);
672
+ console.log(ctx.lastStatusLine);
673
+ if (ctx.statusBarEnabled)
674
+ ctx.statusBar.render(ctx.lastStatusLine);
675
+ }
676
+ catch (e) {
677
+ process.stdout.write('\n');
678
+ console.error(errFmt(friendlyError(e), S));
679
+ }
680
+ }
681
+ ctx.stopWatchMode(false);
682
+ rl.close();
683
+ }
684
+ main().catch((e) => {
685
+ if (e?.code === 'ABORT_ERR' || e?.name === 'AbortError') {
686
+ process.stdout.write('\n');
687
+ process.exit(0);
688
+ }
689
+ const msg = e?.message ?? String(e);
690
+ if (msg.includes('No models found')) {
691
+ console.error(`\n ${msg}`);
692
+ console.error(`\n To set up runtime orchestration, run: idlehands setup`);
693
+ console.error(` Or configure manually: idlehands hosts add → idlehands backends add → idlehands models add\n`);
694
+ }
695
+ else {
696
+ console.error(msg);
697
+ }
698
+ process.exit(1);
699
+ });
700
+ //# sourceMappingURL=index.js.map