@robota-sdk/agent-cli 3.0.0-beta.2 → 3.0.0-beta.20

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.
package/dist/node/bin.cjs CHANGED
@@ -24,40 +24,514 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
- var import_node_util = require("util");
28
- var import_node_fs2 = require("fs");
29
- var import_node_path2 = require("path");
27
+ var import_node_fs3 = require("fs");
28
+ var import_node_path3 = require("path");
30
29
  var import_node_url = require("url");
31
- var readline = __toESM(require("readline"), 1);
32
30
  var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
31
+ var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
33
32
 
34
- // src/permissions/permission-prompt.ts
35
- var import_chalk = __toESM(require("chalk"), 1);
36
- var PERMISSION_OPTIONS = ["Allow", "Deny"];
37
- var ALLOW_INDEX = 0;
38
- function formatArgs(toolArgs) {
39
- const entries = Object.entries(toolArgs);
40
- if (entries.length === 0) {
41
- return "(no arguments)";
33
+ // src/utils/cli-args.ts
34
+ var import_node_util = require("util");
35
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
36
+ function parsePermissionMode(raw) {
37
+ if (raw === void 0) return void 0;
38
+ if (!VALID_MODES.includes(raw)) {
39
+ process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
40
+ `);
41
+ process.exit(1);
42
+ }
43
+ return raw;
44
+ }
45
+ function parseMaxTurns(raw) {
46
+ if (raw === void 0) return void 0;
47
+ const n = parseInt(raw, 10);
48
+ if (isNaN(n) || n <= 0) {
49
+ process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
50
+ `);
51
+ process.exit(1);
52
+ }
53
+ return n;
54
+ }
55
+ function parseCliArgs() {
56
+ const { values, positionals } = (0, import_node_util.parseArgs)({
57
+ allowPositionals: true,
58
+ options: {
59
+ p: { type: "boolean", short: "p", default: false },
60
+ c: { type: "boolean", short: "c", default: false },
61
+ r: { type: "string", short: "r" },
62
+ model: { type: "string" },
63
+ "permission-mode": { type: "string" },
64
+ "max-turns": { type: "string" },
65
+ version: { type: "boolean", default: false },
66
+ reset: { type: "boolean", default: false }
67
+ }
68
+ });
69
+ return {
70
+ positional: positionals,
71
+ printMode: values["p"] ?? false,
72
+ continueMode: values["c"] ?? false,
73
+ resumeId: values["r"],
74
+ model: values["model"],
75
+ permissionMode: parsePermissionMode(values["permission-mode"]),
76
+ maxTurns: parseMaxTurns(values["max-turns"]),
77
+ version: values["version"] ?? false,
78
+ reset: values["reset"] ?? false
79
+ };
80
+ }
81
+
82
+ // src/utils/settings-io.ts
83
+ var import_node_fs = require("fs");
84
+ var import_node_path = require("path");
85
+ function getUserSettingsPath() {
86
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
87
+ return (0, import_node_path.join)(home, ".robota", "settings.json");
88
+ }
89
+ function readSettings(path) {
90
+ if (!(0, import_node_fs.existsSync)(path)) return {};
91
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
92
+ }
93
+ function writeSettings(path, settings) {
94
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
95
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
96
+ }
97
+ function updateModelInSettings(settingsPath, modelId) {
98
+ const settings = readSettings(settingsPath);
99
+ const provider = settings.provider ?? {};
100
+ provider.model = modelId;
101
+ settings.provider = provider;
102
+ writeSettings(settingsPath, settings);
103
+ }
104
+ function deleteSettings(path) {
105
+ if ((0, import_node_fs.existsSync)(path)) {
106
+ (0, import_node_fs.unlinkSync)(path);
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+
112
+ // src/print-terminal.ts
113
+ var readline = __toESM(require("readline"), 1);
114
+ var PrintTerminal = class {
115
+ write(text) {
116
+ process.stdout.write(text);
117
+ }
118
+ writeLine(text) {
119
+ process.stdout.write(text + "\n");
120
+ }
121
+ writeMarkdown(md) {
122
+ process.stdout.write(md);
123
+ }
124
+ writeError(text) {
125
+ process.stderr.write(text + "\n");
126
+ }
127
+ prompt(question) {
128
+ return new Promise((resolve) => {
129
+ const rl = readline.createInterface({
130
+ input: process.stdin,
131
+ output: process.stdout,
132
+ terminal: false,
133
+ historySize: 0
134
+ });
135
+ rl.question(question, (answer) => {
136
+ rl.close();
137
+ resolve(answer);
138
+ });
139
+ });
140
+ }
141
+ async select(options, initialIndex = 0) {
142
+ for (let i = 0; i < options.length; i++) {
143
+ const marker = i === initialIndex ? ">" : " ";
144
+ process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
145
+ `);
146
+ }
147
+ const answer = await this.prompt(
148
+ ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
149
+ );
150
+ const trimmed = answer.trim().toLowerCase();
151
+ if (trimmed === "") return initialIndex;
152
+ const num = parseInt(trimmed, 10);
153
+ if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
154
+ return initialIndex;
155
+ }
156
+ spinner(_message) {
157
+ return { stop() {
158
+ }, update() {
159
+ } };
160
+ }
161
+ };
162
+
163
+ // src/ui/render.tsx
164
+ var import_ink11 = require("ink");
165
+
166
+ // src/ui/App.tsx
167
+ var import_react11 = require("react");
168
+ var import_ink10 = require("ink");
169
+ var import_agent_core3 = require("@robota-sdk/agent-core");
170
+
171
+ // src/ui/hooks/useSession.ts
172
+ var import_react = require("react");
173
+ var import_agent_sdk = require("@robota-sdk/agent-sdk");
174
+ var TOOL_ARG_DISPLAY_MAX = 80;
175
+ var TOOL_ARG_TRUNCATE_AT = 77;
176
+ var NOOP_TERMINAL = {
177
+ write: () => {
178
+ },
179
+ writeLine: () => {
180
+ },
181
+ writeMarkdown: () => {
182
+ },
183
+ writeError: () => {
184
+ },
185
+ prompt: () => Promise.resolve(""),
186
+ select: () => Promise.resolve(0),
187
+ spinner: () => ({ stop: () => {
188
+ }, update: () => {
189
+ } })
190
+ };
191
+ function useSession(props) {
192
+ const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
193
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
194
+ const [activeTools, setActiveTools] = (0, import_react.useState)([]);
195
+ const permissionQueueRef = (0, import_react.useRef)([]);
196
+ const processingRef = (0, import_react.useRef)(false);
197
+ const processNextPermission = (0, import_react.useCallback)(() => {
198
+ if (processingRef.current) return;
199
+ const next = permissionQueueRef.current[0];
200
+ if (!next) {
201
+ setPermissionRequest(null);
202
+ return;
203
+ }
204
+ processingRef.current = true;
205
+ setPermissionRequest({
206
+ toolName: next.toolName,
207
+ toolArgs: next.toolArgs,
208
+ resolve: (result) => {
209
+ permissionQueueRef.current.shift();
210
+ processingRef.current = false;
211
+ setPermissionRequest(null);
212
+ next.resolve(result);
213
+ setTimeout(() => processNextPermission(), 0);
214
+ }
215
+ });
216
+ }, []);
217
+ const sessionRef = (0, import_react.useRef)(null);
218
+ if (sessionRef.current === null) {
219
+ const permissionHandler = (toolName, toolArgs) => {
220
+ return new Promise((resolve) => {
221
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
222
+ processNextPermission();
223
+ });
224
+ };
225
+ const onTextDelta = (delta) => {
226
+ setStreamingText((prev) => prev + delta);
227
+ };
228
+ const onToolExecution = (event) => {
229
+ if (event.type === "start") {
230
+ let firstArg = "";
231
+ if (event.toolArgs) {
232
+ const firstVal = Object.values(event.toolArgs)[0];
233
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
234
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
235
+ }
236
+ setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
237
+ } else {
238
+ setActiveTools(
239
+ (prev) => prev.map(
240
+ (t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
241
+ )
242
+ );
243
+ }
244
+ };
245
+ const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
246
+ sessionRef.current = (0, import_agent_sdk.createSession)({
247
+ config: props.config,
248
+ context: props.context,
249
+ terminal: NOOP_TERMINAL,
250
+ sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
251
+ projectInfo: props.projectInfo,
252
+ sessionStore: props.sessionStore,
253
+ permissionMode: props.permissionMode,
254
+ maxTurns: props.maxTurns,
255
+ permissionHandler,
256
+ onTextDelta,
257
+ onToolExecution
258
+ });
259
+ }
260
+ const clearStreamingText = (0, import_react.useCallback)(() => {
261
+ setStreamingText("");
262
+ setActiveTools([]);
263
+ }, []);
264
+ return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
265
+ }
266
+
267
+ // src/ui/hooks/useMessages.ts
268
+ var import_react2 = require("react");
269
+ var msgIdCounter = 0;
270
+ function nextId() {
271
+ msgIdCounter += 1;
272
+ return `msg_${msgIdCounter}`;
273
+ }
274
+ function useMessages() {
275
+ const [messages, setMessages] = (0, import_react2.useState)([]);
276
+ const addMessage = (0, import_react2.useCallback)((msg) => {
277
+ setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
278
+ }, []);
279
+ return { messages, setMessages, addMessage };
280
+ }
281
+
282
+ // src/ui/hooks/useSlashCommands.ts
283
+ var import_react3 = require("react");
284
+
285
+ // src/commands/slash-executor.ts
286
+ var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
287
+ var HELP_TEXT = [
288
+ "Available commands:",
289
+ " /help \u2014 Show this help",
290
+ " /clear \u2014 Clear conversation",
291
+ " /compact [instr] \u2014 Compact context (optional focus instructions)",
292
+ " /mode [m] \u2014 Show/change permission mode",
293
+ " /cost \u2014 Show session info",
294
+ " /reset \u2014 Delete settings and exit",
295
+ " /exit \u2014 Exit CLI"
296
+ ].join("\n");
297
+ function handleHelp(addMessage) {
298
+ addMessage({ role: "system", content: HELP_TEXT });
299
+ return { handled: true };
300
+ }
301
+ function handleClear(addMessage, clearMessages, session) {
302
+ clearMessages();
303
+ session.clearHistory();
304
+ addMessage({ role: "system", content: "Conversation cleared." });
305
+ return { handled: true };
306
+ }
307
+ async function handleCompact(args, session, addMessage) {
308
+ const instructions = args.trim() || void 0;
309
+ const before = session.getContextState().usedPercentage;
310
+ addMessage({ role: "system", content: "Compacting context..." });
311
+ await session.compact(instructions);
312
+ const after = session.getContextState().usedPercentage;
313
+ addMessage({
314
+ role: "system",
315
+ content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
316
+ });
317
+ return { handled: true };
318
+ }
319
+ function handleMode(arg, session, addMessage) {
320
+ if (!arg) {
321
+ addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
322
+ } else if (VALID_MODES2.includes(arg)) {
323
+ session.setPermissionMode(arg);
324
+ addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
325
+ } else {
326
+ addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
327
+ }
328
+ return { handled: true };
329
+ }
330
+ function handleModel(modelId, addMessage) {
331
+ if (!modelId) {
332
+ addMessage({ role: "system", content: "Select a model from the /model submenu." });
333
+ return { handled: true };
334
+ }
335
+ return { handled: true, pendingModelId: modelId };
336
+ }
337
+ function handleCost(session, addMessage) {
338
+ addMessage({
339
+ role: "system",
340
+ content: `Session: ${session.getSessionId()}
341
+ Messages: ${session.getMessageCount()}`
342
+ });
343
+ return { handled: true };
344
+ }
345
+ function handlePermissions(session, addMessage) {
346
+ const mode = session.getPermissionMode();
347
+ const sessionAllowed = session.getSessionAllowedTools();
348
+ const lines = [`Permission mode: ${mode}`];
349
+ if (sessionAllowed.length > 0) {
350
+ lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
351
+ } else {
352
+ lines.push("No session-approved tools.");
353
+ }
354
+ addMessage({ role: "system", content: lines.join("\n") });
355
+ return { handled: true };
356
+ }
357
+ function handleContext(session, addMessage) {
358
+ const ctx = session.getContextState();
359
+ addMessage({
360
+ role: "system",
361
+ content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
362
+ });
363
+ return { handled: true };
364
+ }
365
+ function handleReset(addMessage) {
366
+ const settingsPath = getUserSettingsPath();
367
+ if (deleteSettings(settingsPath)) {
368
+ addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
369
+ } else {
370
+ addMessage({ role: "system", content: "No user settings found." });
371
+ }
372
+ return { handled: true, exitRequested: true };
373
+ }
374
+ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
375
+ switch (cmd) {
376
+ case "help":
377
+ return handleHelp(addMessage);
378
+ case "clear":
379
+ return handleClear(addMessage, clearMessages, session);
380
+ case "compact":
381
+ return handleCompact(args, session, addMessage);
382
+ case "mode":
383
+ return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
384
+ case "model":
385
+ return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
386
+ case "cost":
387
+ return handleCost(session, addMessage);
388
+ case "permissions":
389
+ return handlePermissions(session, addMessage);
390
+ case "context":
391
+ return handleContext(session, addMessage);
392
+ case "reset":
393
+ return handleReset(addMessage);
394
+ case "exit":
395
+ return { handled: true, exitRequested: true };
396
+ default: {
397
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
398
+ if (skillCmd) {
399
+ addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
400
+ return { handled: false };
401
+ }
402
+ addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
403
+ return { handled: true };
404
+ }
405
+ }
406
+ }
407
+
408
+ // src/ui/hooks/useSlashCommands.ts
409
+ var EXIT_DELAY_MS = 500;
410
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
411
+ return (0, import_react3.useCallback)(
412
+ async (input) => {
413
+ const parts = input.slice(1).split(/\s+/);
414
+ const cmd = parts[0]?.toLowerCase() ?? "";
415
+ const args = parts.slice(1).join(" ");
416
+ const clearMessages = () => setMessages([]);
417
+ const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
418
+ if (result.pendingModelId) {
419
+ pendingModelChangeRef.current = result.pendingModelId;
420
+ setPendingModelId(result.pendingModelId);
421
+ }
422
+ if (result.exitRequested) {
423
+ setTimeout(() => exit(), EXIT_DELAY_MS);
424
+ }
425
+ return result.handled;
426
+ },
427
+ [session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
428
+ );
429
+ }
430
+
431
+ // src/ui/hooks/useSubmitHandler.ts
432
+ var import_react4 = require("react");
433
+
434
+ // src/utils/tool-call-extractor.ts
435
+ var TOOL_ARG_MAX_LENGTH = 80;
436
+ var TOOL_ARG_TRUNCATE_LENGTH = 77;
437
+ function extractToolCalls(history, startIndex) {
438
+ const lines = [];
439
+ for (let i = startIndex; i < history.length; i++) {
440
+ const msg = history[i];
441
+ if (msg.role === "assistant" && msg.toolCalls) {
442
+ for (const tc of msg.toolCalls) {
443
+ const value = parseFirstArgValue(tc.function.arguments);
444
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
445
+ lines.push(`${tc.function.name}(${truncated})`);
446
+ }
447
+ }
448
+ }
449
+ return lines;
450
+ }
451
+ function parseFirstArgValue(argsJson) {
452
+ try {
453
+ const parsed = JSON.parse(argsJson);
454
+ const firstVal = Object.values(parsed)[0];
455
+ return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
456
+ } catch {
457
+ return argsJson;
458
+ }
459
+ }
460
+
461
+ // src/utils/skill-prompt.ts
462
+ function buildSkillPrompt(input, registry) {
463
+ const parts = input.slice(1).split(/\s+/);
464
+ const cmd = parts[0]?.toLowerCase() ?? "";
465
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
466
+ if (!skillCmd) return null;
467
+ const args = parts.slice(1).join(" ").trim();
468
+ const userInstruction = args || skillCmd.description;
469
+ if (skillCmd.skillContent) {
470
+ return `<skill name="${cmd}">
471
+ ${skillCmd.skillContent}
472
+ </skill>
473
+
474
+ Execute the "${cmd}" skill: ${userInstruction}`;
475
+ }
476
+ return `Use the "${cmd}" skill: ${userInstruction}`;
477
+ }
478
+
479
+ // src/ui/hooks/useSubmitHandler.ts
480
+ function syncContextState(session, setter) {
481
+ const ctx = session.getContextState();
482
+ setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
483
+ }
484
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
485
+ setIsThinking(true);
486
+ clearStreamingText();
487
+ const historyBefore = session.getHistory().length;
488
+ try {
489
+ const response = await session.run(prompt);
490
+ clearStreamingText();
491
+ const history = session.getHistory();
492
+ const toolLines = extractToolCalls(
493
+ history,
494
+ historyBefore
495
+ );
496
+ if (toolLines.length > 0) {
497
+ addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
498
+ }
499
+ addMessage({ role: "assistant", content: response || "(empty response)" });
500
+ syncContextState(session, setContextState);
501
+ } catch (err) {
502
+ clearStreamingText();
503
+ if (err instanceof DOMException && err.name === "AbortError") {
504
+ addMessage({ role: "system", content: "Cancelled." });
505
+ } else {
506
+ const errMsg = err instanceof Error ? err.message : String(err);
507
+ addMessage({ role: "system", content: `Error: ${errMsg}` });
508
+ }
509
+ } finally {
510
+ setIsThinking(false);
42
511
  }
43
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
44
512
  }
45
- async function promptForApproval(terminal, toolName, toolArgs) {
46
- terminal.writeLine("");
47
- terminal.writeLine(import_chalk.default.yellow(`[Permission Required] Tool: ${toolName}`));
48
- terminal.writeLine(import_chalk.default.dim(` ${formatArgs(toolArgs)}`));
49
- terminal.writeLine("");
50
- const selected = await terminal.select(PERMISSION_OPTIONS, ALLOW_INDEX);
51
- return selected === ALLOW_INDEX;
513
+ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
514
+ return (0, import_react4.useCallback)(
515
+ async (input) => {
516
+ if (input.startsWith("/")) {
517
+ const handled = await handleSlashCommand(input);
518
+ if (handled) {
519
+ syncContextState(session, setContextState);
520
+ return;
521
+ }
522
+ const prompt = buildSkillPrompt(input, registry);
523
+ if (!prompt) return;
524
+ return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
525
+ }
526
+ addMessage({ role: "user", content: input });
527
+ return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
528
+ },
529
+ [session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
530
+ );
52
531
  }
53
532
 
54
- // src/ui/render.tsx
55
- var import_ink9 = require("ink");
56
-
57
- // src/ui/App.tsx
533
+ // src/ui/hooks/useCommandRegistry.ts
58
534
  var import_react5 = require("react");
59
- var import_ink8 = require("ink");
60
- var import_agent_sdk = require("@robota-sdk/agent-sdk");
61
535
 
62
536
  // src/commands/command-registry.ts
63
537
  var CommandRegistry = class {
@@ -90,6 +564,21 @@ var CommandRegistry = class {
90
564
  };
91
565
 
92
566
  // src/commands/builtin-source.ts
567
+ var import_agent_core = require("@robota-sdk/agent-core");
568
+ function buildModelSubcommands() {
569
+ const seen = /* @__PURE__ */ new Set();
570
+ const commands = [];
571
+ for (const model of Object.values(import_agent_core.CLAUDE_MODELS)) {
572
+ if (seen.has(model.name)) continue;
573
+ seen.add(model.name);
574
+ commands.push({
575
+ name: model.id,
576
+ description: `${model.name} (${(0, import_agent_core.formatTokenCount)(model.contextWindow).toUpperCase()})`,
577
+ source: "builtin"
578
+ });
579
+ }
580
+ return commands;
581
+ }
93
582
  function createBuiltinCommands() {
94
583
  return [
95
584
  { name: "help", description: "Show available commands", source: "builtin" },
@@ -109,16 +598,13 @@ function createBuiltinCommands() {
109
598
  name: "model",
110
599
  description: "Select AI model",
111
600
  source: "builtin",
112
- subcommands: [
113
- { name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
114
- { name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
115
- { name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
116
- ]
601
+ subcommands: buildModelSubcommands()
117
602
  },
118
603
  { name: "compact", description: "Compress context window", source: "builtin" },
119
604
  { name: "cost", description: "Show session info", source: "builtin" },
120
605
  { name: "context", description: "Context window info", source: "builtin" },
121
606
  { name: "permissions", description: "Permission rules", source: "builtin" },
607
+ { name: "reset", description: "Delete settings and exit", source: "builtin" },
122
608
  { name: "exit", description: "Exit CLI", source: "builtin" }
123
609
  ];
124
610
  }
@@ -134,8 +620,8 @@ var BuiltinCommandSource = class {
134
620
  };
135
621
 
136
622
  // src/commands/skill-source.ts
137
- var import_node_fs = require("fs");
138
- var import_node_path = require("path");
623
+ var import_node_fs2 = require("fs");
624
+ var import_node_path2 = require("path");
139
625
  var import_node_os = require("os");
140
626
  function parseFrontmatter(content) {
141
627
  const lines = content.split("\n");
@@ -158,19 +644,20 @@ function parseFrontmatter(content) {
158
644
  return name ? { name, description } : null;
159
645
  }
160
646
  function scanSkillsDir(skillsDir) {
161
- if (!(0, import_node_fs.existsSync)(skillsDir)) return [];
647
+ if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
162
648
  const commands = [];
163
- const entries = (0, import_node_fs.readdirSync)(skillsDir, { withFileTypes: true });
649
+ const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
164
650
  for (const entry of entries) {
165
651
  if (!entry.isDirectory()) continue;
166
- const skillFile = (0, import_node_path.join)(skillsDir, entry.name, "SKILL.md");
167
- if (!(0, import_node_fs.existsSync)(skillFile)) continue;
168
- const content = (0, import_node_fs.readFileSync)(skillFile, "utf-8");
652
+ const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
653
+ if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
654
+ const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
169
655
  const frontmatter = parseFrontmatter(content);
170
656
  commands.push({
171
657
  name: frontmatter?.name ?? entry.name,
172
658
  description: frontmatter?.description ?? `Skill: ${entry.name}`,
173
- source: "skill"
659
+ source: "skill",
660
+ skillContent: content
174
661
  });
175
662
  }
176
663
  return commands;
@@ -184,8 +671,8 @@ var SkillCommandSource = class {
184
671
  }
185
672
  getCommands() {
186
673
  if (this.cachedCommands) return this.cachedCommands;
187
- const projectSkills = scanSkillsDir((0, import_node_path.join)(this.cwd, ".agents", "skills"));
188
- const userSkills = scanSkillsDir((0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "skills"));
674
+ const projectSkills = scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"));
675
+ const userSkills = scanSkillsDir((0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "skills"));
189
676
  const seen = new Set(projectSkills.map((cmd) => cmd.name));
190
677
  const merged = [...projectSkills];
191
678
  for (const cmd of userSkills) {
@@ -198,6 +685,18 @@ var SkillCommandSource = class {
198
685
  }
199
686
  };
200
687
 
688
+ // src/ui/hooks/useCommandRegistry.ts
689
+ function useCommandRegistry(cwd) {
690
+ const registryRef = (0, import_react5.useRef)(null);
691
+ if (registryRef.current === null) {
692
+ const registry = new CommandRegistry();
693
+ registry.addSource(new BuiltinCommandSource());
694
+ registry.addSource(new SkillCommandSource(cwd));
695
+ registryRef.current = registry;
696
+ }
697
+ return registryRef.current;
698
+ }
699
+
201
700
  // src/ui/MessageList.tsx
202
701
  var import_ink = require("ink");
203
702
 
@@ -259,6 +758,7 @@ function MessageList({ messages }) {
259
758
 
260
759
  // src/ui/StatusBar.tsx
261
760
  var import_ink2 = require("ink");
761
+ var import_agent_core2 = require("@robota-sdk/agent-core");
262
762
  var import_jsx_runtime2 = require("react/jsx-runtime");
263
763
  var CONTEXT_YELLOW_THRESHOLD = 70;
264
764
  var CONTEXT_RED_THRESHOLD = 90;
@@ -298,10 +798,10 @@ function StatusBar({
298
798
  "Context: ",
299
799
  Math.round(contextPercentage),
300
800
  "% (",
301
- (contextUsedTokens / 1e3).toFixed(1),
302
- "k/",
303
- (contextMaxTokens / 1e3).toFixed(0),
304
- "k)"
801
+ (0, import_agent_core2.formatTokenCount)(contextUsedTokens),
802
+ "/",
803
+ (0, import_agent_core2.formatTokenCount)(contextMaxTokens),
804
+ ")"
305
805
  ] })
306
806
  ] }),
307
807
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
@@ -317,15 +817,22 @@ function StatusBar({
317
817
  }
318
818
 
319
819
  // src/ui/InputArea.tsx
320
- var import_react3 = __toESM(require("react"), 1);
820
+ var import_react8 = __toESM(require("react"), 1);
321
821
  var import_ink6 = require("ink");
322
822
 
323
823
  // src/ui/CjkTextInput.tsx
324
- var import_react = require("react");
824
+ var import_react6 = require("react");
325
825
  var import_ink3 = require("ink");
326
- var import_string_width = __toESM(require("string-width"), 1);
327
- var import_chalk2 = __toESM(require("chalk"), 1);
826
+ var import_chalk = __toESM(require("chalk"), 1);
328
827
  var import_jsx_runtime3 = require("react/jsx-runtime");
828
+ function filterPrintable(input) {
829
+ if (!input || input.length === 0) return "";
830
+ return input.replace(/[\x00-\x1f\x7f]/g, "");
831
+ }
832
+ function insertAtCursor(value, cursor, input) {
833
+ const next = value.slice(0, cursor) + input + value.slice(cursor);
834
+ return { value: next, cursor: cursor + input.length };
835
+ }
329
836
  function CjkTextInput({
330
837
  value,
331
838
  onChange,
@@ -334,10 +841,9 @@ function CjkTextInput({
334
841
  focus = true,
335
842
  showCursor = true
336
843
  }) {
337
- const valueRef = (0, import_react.useRef)(value);
338
- const cursorRef = (0, import_react.useRef)(value.length);
339
- const [, forceRender] = (0, import_react.useState)(0);
340
- const { setCursorPosition } = (0, import_ink3.useCursor)();
844
+ const valueRef = (0, import_react6.useRef)(value);
845
+ const cursorRef = (0, import_react6.useRef)(value.length);
846
+ const [, forceRender] = (0, import_react6.useState)(0);
341
847
  if (value !== valueRef.current) {
342
848
  valueRef.current = value;
343
849
  if (cursorRef.current > value.length) {
@@ -346,85 +852,83 @@ function CjkTextInput({
346
852
  }
347
853
  (0, import_ink3.useInput)(
348
854
  (input, key) => {
349
- if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
350
- return;
351
- }
352
- if (key.return) {
353
- onSubmit?.(valueRef.current);
354
- return;
355
- }
356
- if (key.leftArrow) {
357
- if (cursorRef.current > 0) {
358
- cursorRef.current -= 1;
359
- forceRender((n) => n + 1);
855
+ try {
856
+ if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
857
+ return;
360
858
  }
361
- return;
362
- }
363
- if (key.rightArrow) {
364
- if (cursorRef.current < valueRef.current.length) {
365
- cursorRef.current += 1;
366
- forceRender((n) => n + 1);
859
+ if (key.return) {
860
+ onSubmit?.(valueRef.current);
861
+ return;
367
862
  }
368
- return;
369
- }
370
- if (key.backspace || key.delete) {
371
- if (cursorRef.current > 0) {
372
- const v2 = valueRef.current;
373
- const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
374
- cursorRef.current -= 1;
375
- valueRef.current = next2;
376
- onChange(next2);
863
+ if (key.leftArrow) {
864
+ if (cursorRef.current > 0) {
865
+ cursorRef.current -= 1;
866
+ forceRender((n) => n + 1);
867
+ }
868
+ return;
377
869
  }
378
- return;
870
+ if (key.rightArrow) {
871
+ if (cursorRef.current < valueRef.current.length) {
872
+ cursorRef.current += 1;
873
+ forceRender((n) => n + 1);
874
+ }
875
+ return;
876
+ }
877
+ if (key.backspace || key.delete) {
878
+ if (cursorRef.current > 0) {
879
+ const v = valueRef.current;
880
+ const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
881
+ cursorRef.current -= 1;
882
+ valueRef.current = next;
883
+ onChange(next);
884
+ }
885
+ return;
886
+ }
887
+ const printable = filterPrintable(input);
888
+ if (printable.length === 0) return;
889
+ const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
890
+ cursorRef.current = result.cursor;
891
+ valueRef.current = result.value;
892
+ onChange(result.value);
893
+ } catch {
379
894
  }
380
- const v = valueRef.current;
381
- const c = cursorRef.current;
382
- const next = v.slice(0, c) + input + v.slice(c);
383
- cursorRef.current = c + input.length;
384
- valueRef.current = next;
385
- onChange(next);
386
895
  },
387
896
  { isActive: focus }
388
897
  );
389
- if (showCursor && focus) {
390
- const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
391
- const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
392
- setCursorPosition({ x: cursorX, y: 0 });
393
- }
394
898
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
395
899
  }
396
900
  function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
397
901
  if (!showCursor) {
398
- return value.length > 0 ? value : placeholder ? import_chalk2.default.gray(placeholder) : "";
902
+ return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
399
903
  }
400
904
  if (value.length === 0) {
401
905
  if (placeholder.length > 0) {
402
- return import_chalk2.default.inverse(placeholder[0]) + import_chalk2.default.gray(placeholder.slice(1));
906
+ return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
403
907
  }
404
- return import_chalk2.default.inverse(" ");
908
+ return import_chalk.default.inverse(" ");
405
909
  }
406
910
  const chars = [...value];
407
911
  let rendered = "";
408
912
  for (let i = 0; i < chars.length; i++) {
409
913
  const char = chars[i] ?? "";
410
- rendered += i === cursorOffset ? import_chalk2.default.inverse(char) : char;
914
+ rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
411
915
  }
412
916
  if (cursorOffset >= chars.length) {
413
- rendered += import_chalk2.default.inverse(" ");
917
+ rendered += import_chalk.default.inverse(" ");
414
918
  }
415
919
  return rendered;
416
920
  }
417
921
 
418
922
  // src/ui/WaveText.tsx
419
- var import_react2 = require("react");
923
+ var import_react7 = require("react");
420
924
  var import_ink4 = require("ink");
421
925
  var import_jsx_runtime4 = require("react/jsx-runtime");
422
926
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
423
927
  var INTERVAL_MS = 400;
424
928
  var CHARS_PER_GROUP = 4;
425
929
  function WaveText({ text }) {
426
- const [tick, setTick] = (0, import_react2.useState)(0);
427
- (0, import_react2.useEffect)(() => {
930
+ const [tick, setTick] = (0, import_react7.useState)(0);
931
+ (0, import_react7.useEffect)(() => {
428
932
  const timer = setInterval(() => {
429
933
  setTick((prev) => prev + 1);
430
934
  }, INTERVAL_MS);
@@ -444,19 +948,13 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
444
948
  var MAX_VISIBLE = 8;
445
949
  function CommandRow(props) {
446
950
  const { cmd, isSelected, showSlash } = props;
447
- const prefix = showSlash ? "/" : "";
448
951
  const indicator = isSelected ? "\u25B8 " : " ";
449
952
  const nameColor = isSelected ? "cyan" : void 0;
450
953
  const dimmed = !isSelected;
451
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Box, { children: [
452
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: [
453
- indicator,
454
- prefix,
455
- cmd.name
456
- ] }),
457
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
458
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
459
- ] });
954
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: [
955
+ indicator,
956
+ showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
957
+ ] }) });
460
958
  }
461
959
  function SlashAutocomplete({
462
960
  commands,
@@ -496,16 +994,16 @@ function parseSlashInput(value) {
496
994
  return { isSlash: true, parentCommand: parent, filter: rest };
497
995
  }
498
996
  function useAutocomplete(value, registry) {
499
- const [selectedIndex, setSelectedIndex] = (0, import_react3.useState)(0);
500
- const [dismissed, setDismissed] = (0, import_react3.useState)(false);
501
- const prevValueRef = import_react3.default.useRef(value);
997
+ const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
998
+ const [dismissed, setDismissed] = (0, import_react8.useState)(false);
999
+ const prevValueRef = import_react8.default.useRef(value);
502
1000
  if (prevValueRef.current !== value) {
503
1001
  prevValueRef.current = value;
504
1002
  if (dismissed) setDismissed(false);
505
1003
  }
506
1004
  const parsed = parseSlashInput(value);
507
1005
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
508
- const filteredCommands = (0, import_react3.useMemo)(() => {
1006
+ const filteredCommands = (0, import_react8.useMemo)(() => {
509
1007
  if (!registry || !parsed.isSlash || dismissed) return [];
510
1008
  if (isSubcommandMode) {
511
1009
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -539,7 +1037,7 @@ function useAutocomplete(value, registry) {
539
1037
  };
540
1038
  }
541
1039
  function InputArea({ onSubmit, isDisabled, registry }) {
542
- const [value, setValue] = (0, import_react3.useState)("");
1040
+ const [value, setValue] = (0, import_react8.useState)("");
543
1041
  const {
544
1042
  showPopup,
545
1043
  filteredCommands,
@@ -548,7 +1046,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
548
1046
  isSubcommandMode,
549
1047
  setShowPopup
550
1048
  } = useAutocomplete(value, registry);
551
- const handleSubmit = (0, import_react3.useCallback)(
1049
+ const handleSubmit = (0, import_react8.useCallback)(
552
1050
  (text) => {
553
1051
  const trimmed = text.trim();
554
1052
  if (trimmed.length === 0) return;
@@ -561,7 +1059,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
561
1059
  },
562
1060
  [showPopup, filteredCommands, selectedIndex, onSubmit]
563
1061
  );
564
- const selectCommand = (0, import_react3.useCallback)(
1062
+ const selectCommand = (0, import_react8.useCallback)(
565
1063
  (cmd) => {
566
1064
  const parsed = parseSlashInput(value);
567
1065
  if (parsed.parentCommand) {
@@ -594,434 +1092,273 @@ function InputArea({ onSubmit, isDisabled, registry }) {
594
1092
  if (cmd) selectCommand(cmd);
595
1093
  }
596
1094
  },
597
- { isActive: showPopup && !isDisabled }
598
- );
599
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
600
- showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
601
- SlashAutocomplete,
602
- {
603
- commands: filteredCommands,
604
- selectedIndex,
605
- visible: showPopup,
606
- isSubcommandMode
607
- }
608
- ),
609
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { children: [
610
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: "green", bold: true, children: "> " }),
611
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
612
- CjkTextInput,
613
- {
614
- value,
615
- onChange: setValue,
616
- onSubmit: handleSubmit,
617
- placeholder: "Type a message or /help"
618
- }
619
- )
620
- ] }) })
621
- ] });
622
- }
623
-
624
- // src/ui/PermissionPrompt.tsx
625
- var import_react4 = __toESM(require("react"), 1);
626
- var import_ink7 = require("ink");
627
- var import_jsx_runtime7 = require("react/jsx-runtime");
628
- var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
629
- function formatArgs2(args) {
630
- const entries = Object.entries(args);
631
- if (entries.length === 0) return "(no arguments)";
632
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
633
- }
634
- function PermissionPrompt({ request }) {
635
- const [selected, setSelected] = import_react4.default.useState(0);
636
- const resolvedRef = import_react4.default.useRef(false);
637
- const prevRequestRef = import_react4.default.useRef(request);
638
- if (prevRequestRef.current !== request) {
639
- prevRequestRef.current = request;
640
- resolvedRef.current = false;
641
- setSelected(0);
642
- }
643
- const doResolve = import_react4.default.useCallback(
644
- (index) => {
645
- if (resolvedRef.current) return;
646
- resolvedRef.current = true;
647
- if (index === 0) request.resolve(true);
648
- else if (index === 1) request.resolve("allow-session");
649
- else request.resolve(false);
650
- },
651
- [request]
652
- );
653
- (0, import_ink7.useInput)((input, key) => {
654
- if (resolvedRef.current) return;
655
- if (key.upArrow || key.leftArrow) {
656
- setSelected((prev) => prev > 0 ? prev - 1 : prev);
657
- } else if (key.downArrow || key.rightArrow) {
658
- setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
659
- } else if (key.return) {
660
- doResolve(selected);
661
- } else if (input === "y" || input === "1") {
662
- doResolve(0);
663
- } else if (input === "a" || input === "2") {
664
- doResolve(1);
665
- } else if (input === "n" || input === "d" || input === "3") {
666
- doResolve(2);
667
- }
668
- });
669
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
670
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
671
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { children: [
672
- "Tool:",
673
- " ",
674
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "cyan", bold: true, children: request.toolName })
675
- ] }),
676
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { dimColor: true, children: [
677
- " ",
678
- formatArgs2(request.toolArgs)
679
- ] }),
680
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
681
- i === selected ? "> " : " ",
682
- opt
683
- ] }) }, opt)) }),
684
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
685
- ] });
686
- }
687
-
688
- // src/ui/App.tsx
689
- var import_jsx_runtime8 = require("react/jsx-runtime");
690
- var msgIdCounter = 0;
691
- function nextId() {
692
- msgIdCounter += 1;
693
- return `msg_${msgIdCounter}`;
694
- }
695
- var NOOP_TERMINAL = {
696
- write: () => {
697
- },
698
- writeLine: () => {
699
- },
700
- writeMarkdown: () => {
701
- },
702
- writeError: () => {
703
- },
704
- prompt: () => Promise.resolve(""),
705
- select: () => Promise.resolve(0),
706
- spinner: () => ({ stop: () => {
707
- }, update: () => {
708
- } })
709
- };
710
- function useSession(props) {
711
- const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
712
- const [streamingText, setStreamingText] = (0, import_react5.useState)("");
713
- const permissionQueueRef = (0, import_react5.useRef)([]);
714
- const processingRef = (0, import_react5.useRef)(false);
715
- const processNextPermission = (0, import_react5.useCallback)(() => {
716
- if (processingRef.current) return;
717
- const next = permissionQueueRef.current[0];
718
- if (!next) {
719
- setPermissionRequest(null);
720
- return;
721
- }
722
- processingRef.current = true;
723
- setPermissionRequest({
724
- toolName: next.toolName,
725
- toolArgs: next.toolArgs,
726
- resolve: (result) => {
727
- permissionQueueRef.current.shift();
728
- processingRef.current = false;
729
- setPermissionRequest(null);
730
- next.resolve(result);
731
- setTimeout(() => processNextPermission(), 0);
732
- }
733
- });
734
- }, []);
735
- const sessionRef = (0, import_react5.useRef)(null);
736
- if (sessionRef.current === null) {
737
- const permissionHandler = (toolName, toolArgs) => {
738
- return new Promise((resolve) => {
739
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
740
- processNextPermission();
741
- });
742
- };
743
- const onTextDelta = (delta) => {
744
- setStreamingText((prev) => prev + delta);
745
- };
746
- sessionRef.current = new import_agent_sdk.Session({
747
- config: props.config,
748
- context: props.context,
749
- terminal: NOOP_TERMINAL,
750
- projectInfo: props.projectInfo,
751
- sessionStore: props.sessionStore,
752
- permissionMode: props.permissionMode,
753
- maxTurns: props.maxTurns,
754
- permissionHandler,
755
- onTextDelta
756
- });
757
- }
758
- const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
759
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
760
- }
761
- function useMessages() {
762
- const [messages, setMessages] = (0, import_react5.useState)([]);
763
- const addMessage = (0, import_react5.useCallback)((msg) => {
764
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
765
- }, []);
766
- return { messages, setMessages, addMessage };
767
- }
768
- var HELP_TEXT = [
769
- "Available commands:",
770
- " /help \u2014 Show this help",
771
- " /clear \u2014 Clear conversation",
772
- " /compact [instr] \u2014 Compact context (optional focus instructions)",
773
- " /mode [m] \u2014 Show/change permission mode",
774
- " /cost \u2014 Show session info",
775
- " /exit \u2014 Exit CLI"
776
- ].join("\n");
777
- function handleModeCommand(arg, session, addMessage) {
778
- const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
779
- if (!arg) {
780
- addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
781
- } else if (validModes.includes(arg)) {
782
- session.setPermissionMode(arg);
783
- addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
784
- } else {
785
- addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
786
- }
787
- return true;
788
- }
789
- async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
790
- switch (cmd) {
791
- case "help":
792
- addMessage({ role: "system", content: HELP_TEXT });
793
- return true;
794
- case "clear":
795
- setMessages([]);
796
- session.clearHistory();
797
- addMessage({ role: "system", content: "Conversation cleared." });
798
- return true;
799
- case "compact": {
800
- const instructions = parts.slice(1).join(" ").trim() || void 0;
801
- const before = session.getContextState().usedPercentage;
802
- addMessage({ role: "system", content: "Compacting context..." });
803
- await session.compact(instructions);
804
- const after = session.getContextState().usedPercentage;
805
- addMessage({
806
- role: "system",
807
- content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
808
- });
809
- return true;
810
- }
811
- case "mode":
812
- return handleModeCommand(parts[1], session, addMessage);
813
- case "cost":
814
- addMessage({
815
- role: "system",
816
- content: `Session: ${session.getSessionId()}
817
- Messages: ${session.getMessageCount()}`
818
- });
819
- return true;
820
- case "permissions": {
821
- const mode = session.getPermissionMode();
822
- const sessionAllowed = session.getSessionAllowedTools();
823
- const lines = [`Permission mode: ${mode}`];
824
- if (sessionAllowed.length > 0) {
825
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
826
- } else {
827
- lines.push("No session-approved tools.");
828
- }
829
- addMessage({ role: "system", content: lines.join("\n") });
830
- return true;
831
- }
832
- case "context": {
833
- const ctx = session.getContextState();
834
- addMessage({
835
- role: "system",
836
- content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
837
- });
838
- return true;
839
- }
840
- case "exit":
841
- exit();
842
- return true;
843
- default: {
844
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
845
- if (skillCmd) {
846
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
847
- return false;
848
- }
849
- addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
850
- return true;
851
- }
852
- }
853
- }
854
- function useSlashCommands(session, addMessage, setMessages, exit, registry) {
855
- return (0, import_react5.useCallback)(
856
- async (input) => {
857
- const parts = input.slice(1).split(/\s+/);
858
- const cmd = parts[0]?.toLowerCase() ?? "";
859
- return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
860
- },
861
- [session, addMessage, setMessages, exit, registry]
1095
+ { isActive: showPopup && !isDisabled }
862
1096
  );
1097
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
1098
+ showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1099
+ SlashAutocomplete,
1100
+ {
1101
+ commands: filteredCommands,
1102
+ selectedIndex,
1103
+ visible: showPopup,
1104
+ isSubcommandMode
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { children: [
1108
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: "green", bold: true, children: "> " }),
1109
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1110
+ CjkTextInput,
1111
+ {
1112
+ value,
1113
+ onChange: setValue,
1114
+ onSubmit: handleSubmit,
1115
+ placeholder: "Type a message or /help"
1116
+ }
1117
+ )
1118
+ ] }) })
1119
+ ] });
863
1120
  }
864
- function StreamingIndicator({ text }) {
865
- if (text) {
866
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
867
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: "cyan", bold: true, children: [
868
- "Robota:",
869
- " "
870
- ] }),
871
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { children: " " }),
872
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
873
- ] });
874
- }
875
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
876
- }
877
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
878
- setIsThinking(true);
879
- clearStreamingText();
880
- try {
881
- const response = await session.run(prompt);
882
- clearStreamingText();
883
- addMessage({ role: "assistant", content: response || "(empty response)" });
884
- setContextPercentage(session.getContextState().usedPercentage);
885
- } catch (err) {
886
- clearStreamingText();
887
- if (err instanceof DOMException && err.name === "AbortError") {
888
- addMessage({ role: "system", content: "Cancelled." });
889
- } else {
890
- const errMsg = err instanceof Error ? err.message : String(err);
891
- addMessage({ role: "system", content: `Error: ${errMsg}` });
1121
+
1122
+ // src/ui/ConfirmPrompt.tsx
1123
+ var import_react9 = require("react");
1124
+ var import_ink7 = require("ink");
1125
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1126
+ function ConfirmPrompt({
1127
+ message,
1128
+ options = ["Yes", "No"],
1129
+ onSelect
1130
+ }) {
1131
+ const [selected, setSelected] = (0, import_react9.useState)(0);
1132
+ const resolvedRef = (0, import_react9.useRef)(false);
1133
+ const doSelect = (0, import_react9.useCallback)(
1134
+ (index) => {
1135
+ if (resolvedRef.current) return;
1136
+ resolvedRef.current = true;
1137
+ onSelect(index);
1138
+ },
1139
+ [onSelect]
1140
+ );
1141
+ (0, import_ink7.useInput)((input, key) => {
1142
+ if (resolvedRef.current) return;
1143
+ if (key.leftArrow || key.upArrow) {
1144
+ setSelected((prev) => prev > 0 ? prev - 1 : prev);
1145
+ } else if (key.rightArrow || key.downArrow) {
1146
+ setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
1147
+ } else if (key.return) {
1148
+ doSelect(selected);
1149
+ } else if (input === "y" && options.length === 2) {
1150
+ doSelect(0);
1151
+ } else if (input === "n" && options.length === 2) {
1152
+ doSelect(1);
892
1153
  }
893
- } finally {
894
- setIsThinking(false);
895
- }
1154
+ });
1155
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: message }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1158
+ i === selected ? "> " : " ",
1159
+ opt
1160
+ ] }) }, opt)) }),
1161
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
1162
+ ] });
896
1163
  }
897
- function buildSkillPrompt(input, registry) {
898
- const parts = input.slice(1).split(/\s+/);
899
- const cmd = parts[0]?.toLowerCase() ?? "";
900
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
901
- if (!skillCmd) return null;
902
- const args = parts.slice(1).join(" ").trim();
903
- return args ? `Use the "${cmd}" skill: ${args}` : `Use the "${cmd}" skill: ${skillCmd.description}`;
1164
+
1165
+ // src/ui/PermissionPrompt.tsx
1166
+ var import_react10 = __toESM(require("react"), 1);
1167
+ var import_ink8 = require("ink");
1168
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1169
+ var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
1170
+ function formatArgs(args) {
1171
+ const entries = Object.entries(args);
1172
+ if (entries.length === 0) return "(no arguments)";
1173
+ return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
904
1174
  }
905
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
906
- return (0, import_react5.useCallback)(
907
- async (input) => {
908
- if (input.startsWith("/")) {
909
- const handled = await handleSlashCommand(input);
910
- if (handled) {
911
- setContextPercentage(session.getContextState().usedPercentage);
912
- return;
913
- }
914
- const prompt = buildSkillPrompt(input, registry);
915
- if (!prompt) return;
916
- return runSessionPrompt(
917
- prompt,
918
- session,
919
- addMessage,
920
- clearStreamingText,
921
- setIsThinking,
922
- setContextPercentage
923
- );
924
- }
925
- addMessage({ role: "user", content: input });
926
- return runSessionPrompt(
927
- input,
928
- session,
929
- addMessage,
930
- clearStreamingText,
931
- setIsThinking,
932
- setContextPercentage
933
- );
1175
+ function PermissionPrompt({ request }) {
1176
+ const [selected, setSelected] = import_react10.default.useState(0);
1177
+ const resolvedRef = import_react10.default.useRef(false);
1178
+ const prevRequestRef = import_react10.default.useRef(request);
1179
+ if (prevRequestRef.current !== request) {
1180
+ prevRequestRef.current = request;
1181
+ resolvedRef.current = false;
1182
+ setSelected(0);
1183
+ }
1184
+ const doResolve = import_react10.default.useCallback(
1185
+ (index) => {
1186
+ if (resolvedRef.current) return;
1187
+ resolvedRef.current = true;
1188
+ if (index === 0) request.resolve(true);
1189
+ else if (index === 1) request.resolve("allow-session");
1190
+ else request.resolve(false);
934
1191
  },
935
- [
936
- session,
937
- addMessage,
938
- handleSlashCommand,
939
- clearStreamingText,
940
- setIsThinking,
941
- setContextPercentage,
942
- registry
943
- ]
1192
+ [request]
944
1193
  );
1194
+ (0, import_ink8.useInput)((input, key) => {
1195
+ if (resolvedRef.current) return;
1196
+ if (key.upArrow || key.leftArrow) {
1197
+ setSelected((prev) => prev > 0 ? prev - 1 : prev);
1198
+ } else if (key.downArrow || key.rightArrow) {
1199
+ setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
1200
+ } else if (key.return) {
1201
+ doResolve(selected);
1202
+ } else if (input === "y" || input === "1") {
1203
+ doResolve(0);
1204
+ } else if (input === "a" || input === "2") {
1205
+ doResolve(1);
1206
+ } else if (input === "n" || input === "d" || input === "3") {
1207
+ doResolve(2);
1208
+ }
1209
+ });
1210
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1211
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
1212
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { children: [
1213
+ "Tool:",
1214
+ " ",
1215
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: request.toolName })
1216
+ ] }),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
1218
+ " ",
1219
+ formatArgs(request.toolArgs)
1220
+ ] }),
1221
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1222
+ i === selected ? "> " : " ",
1223
+ opt
1224
+ ] }) }, opt)) }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
1226
+ ] });
945
1227
  }
946
- function useCommandRegistry(cwd) {
947
- const registryRef = (0, import_react5.useRef)(null);
948
- if (registryRef.current === null) {
949
- const registry = new CommandRegistry();
950
- registry.addSource(new BuiltinCommandSource());
951
- registry.addSource(new SkillCommandSource(cwd));
952
- registryRef.current = registry;
1228
+
1229
+ // src/ui/StreamingIndicator.tsx
1230
+ var import_ink9 = require("ink");
1231
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1232
+ function StreamingIndicator({ text, activeTools }) {
1233
+ const hasTools = activeTools.length > 0;
1234
+ const hasText = text.length > 0;
1235
+ if (!hasTools && !hasText) {
1236
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
953
1237
  }
954
- return registryRef.current;
1238
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
1239
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1240
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "gray", bold: true, children: "Tools:" }),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1242
+ activeTools.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
1243
+ " ",
1244
+ t.isRunning ? "\u27F3" : "\u2713",
1245
+ " ",
1246
+ t.toolName,
1247
+ "(",
1248
+ t.firstArg,
1249
+ ")"
1250
+ ] }, `${t.toolName}-${i}`))
1251
+ ] }),
1252
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
1254
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
1255
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
1256
+ ] })
1257
+ ] });
955
1258
  }
1259
+
1260
+ // src/ui/App.tsx
1261
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1262
+ var EXIT_DELAY_MS2 = 500;
956
1263
  function App(props) {
957
- const { exit } = (0, import_ink8.useApp)();
958
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
1264
+ const { exit } = (0, import_ink10.useApp)();
1265
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
959
1266
  const { messages, setMessages, addMessage } = useMessages();
960
- const [isThinking, setIsThinking] = (0, import_react5.useState)(false);
961
- const [contextPercentage, setContextPercentage] = (0, import_react5.useState)(0);
1267
+ const [isThinking, setIsThinking] = (0, import_react11.useState)(false);
1268
+ const initialCtx = session.getContextState();
1269
+ const [contextState, setContextState] = (0, import_react11.useState)({
1270
+ percentage: initialCtx.usedPercentage,
1271
+ usedTokens: initialCtx.usedTokens,
1272
+ maxTokens: initialCtx.maxTokens
1273
+ });
962
1274
  const registry = useCommandRegistry(props.cwd ?? process.cwd());
963
- const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry);
1275
+ const pendingModelChangeRef = (0, import_react11.useRef)(null);
1276
+ const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
1277
+ const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
964
1278
  const handleSubmit = useSubmitHandler(
965
1279
  session,
966
1280
  addMessage,
967
1281
  handleSlashCommand,
968
1282
  clearStreamingText,
969
1283
  setIsThinking,
970
- setContextPercentage,
1284
+ setContextState,
971
1285
  registry
972
1286
  );
973
- (0, import_ink8.useInput)(
1287
+ (0, import_ink10.useInput)(
974
1288
  (_input, key) => {
975
1289
  if (key.ctrl && _input === "c") exit();
976
1290
  if (key.escape && isThinking) session.abort();
977
1291
  },
978
1292
  { isActive: !permissionRequest }
979
1293
  );
980
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
981
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
982
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: `
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1296
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: `
983
1297
  ____ ___ ____ ___ _____ _
984
1298
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
985
1299
  | |_) | | | | _ \\| | | || | / _ \\
986
1300
  | _ <| |_| | |_) | |_| || |/ ___ \\
987
1301
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
988
1302
  ` }),
989
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
1303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { dimColor: true, children: [
990
1304
  " v",
991
1305
  props.version ?? "0.0.0"
992
1306
  ] })
993
1307
  ] }),
994
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
995
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageList, { messages }),
996
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StreamingIndicator, { text: streamingText }) })
1308
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1309
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageList, { messages }),
1310
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
997
1311
  ] }),
998
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PermissionPrompt, { request: permissionRequest }),
999
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1312
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PermissionPrompt, { request: permissionRequest }),
1313
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1314
+ ConfirmPrompt,
1315
+ {
1316
+ message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
1317
+ onSelect: (index) => {
1318
+ setPendingModelId(null);
1319
+ pendingModelChangeRef.current = null;
1320
+ if (index === 0) {
1321
+ try {
1322
+ const settingsPath = getUserSettingsPath();
1323
+ updateModelInSettings(settingsPath, pendingModelId);
1324
+ addMessage({ role: "system", content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...` });
1325
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
1326
+ } catch (err) {
1327
+ addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
1328
+ }
1329
+ } else {
1330
+ addMessage({ role: "system", content: "Model change cancelled." });
1331
+ }
1332
+ }
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1000
1336
  StatusBar,
1001
1337
  {
1002
1338
  permissionMode: session.getPermissionMode(),
1003
- modelName: props.config.provider.model,
1339
+ modelName: (0, import_agent_core3.getModelName)(props.config.provider.model),
1004
1340
  sessionId: session.getSessionId(),
1005
1341
  messageCount: messages.length,
1006
1342
  isThinking,
1007
- contextPercentage,
1008
- contextUsedTokens: session.getContextState().usedTokens,
1009
- contextMaxTokens: session.getContextState().maxTokens
1343
+ contextPercentage: contextState.percentage,
1344
+ contextUsedTokens: contextState.usedTokens,
1345
+ contextMaxTokens: contextState.maxTokens
1010
1346
  }
1011
1347
  ),
1012
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1348
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1013
1349
  InputArea,
1014
1350
  {
1015
1351
  onSubmit: handleSubmit,
1016
1352
  isDisabled: isThinking || !!permissionRequest,
1017
1353
  registry
1018
1354
  }
1019
- )
1355
+ ),
1356
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " })
1020
1357
  ] });
1021
1358
  }
1022
1359
 
1023
1360
  // src/ui/render.tsx
1024
- var import_jsx_runtime9 = require("react/jsx-runtime");
1361
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1025
1362
  function renderApp(options) {
1026
1363
  process.on("unhandledRejection", (reason) => {
1027
1364
  process.stderr.write(`
@@ -1032,7 +1369,7 @@ function renderApp(options) {
1032
1369
  `);
1033
1370
  }
1034
1371
  });
1035
- const instance = (0, import_ink9.render)(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App, { ...options }), {
1372
+ const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
1036
1373
  exitOnCtrlC: true
1037
1374
  });
1038
1375
  instance.waitUntilExit().catch((err) => {
@@ -1046,15 +1383,26 @@ function renderApp(options) {
1046
1383
 
1047
1384
  // src/cli.ts
1048
1385
  var import_meta = {};
1049
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
1386
+ function hasValidSettingsFile(filePath) {
1387
+ if (!(0, import_node_fs3.existsSync)(filePath)) return false;
1388
+ try {
1389
+ const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
1390
+ if (raw.length === 0) return false;
1391
+ const parsed = JSON.parse(raw);
1392
+ const provider = parsed.provider;
1393
+ return !!provider?.apiKey;
1394
+ } catch {
1395
+ return false;
1396
+ }
1397
+ }
1050
1398
  function readVersion() {
1051
1399
  try {
1052
1400
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
1053
- const dir = (0, import_node_path2.dirname)(thisFile);
1054
- const candidates = [(0, import_node_path2.join)(dir, "..", "..", "package.json"), (0, import_node_path2.join)(dir, "..", "package.json")];
1401
+ const dir = (0, import_node_path3.dirname)(thisFile);
1402
+ const candidates = [(0, import_node_path3.join)(dir, "..", "..", "package.json"), (0, import_node_path3.join)(dir, "..", "package.json")];
1055
1403
  for (const pkgPath of candidates) {
1056
1404
  try {
1057
- const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
1405
+ const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
1058
1406
  const pkg = JSON.parse(raw);
1059
1407
  if (pkg.version !== void 0 && pkg.name !== void 0) {
1060
1408
  return pkg.version;
@@ -1067,97 +1415,78 @@ function readVersion() {
1067
1415
  return "0.0.0";
1068
1416
  }
1069
1417
  }
1070
- function parsePermissionMode(raw) {
1071
- if (raw === void 0) return void 0;
1072
- if (!VALID_MODES.includes(raw)) {
1073
- process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
1074
- `);
1075
- process.exit(1);
1418
+ async function ensureConfig(cwd) {
1419
+ const userPath = getUserSettingsPath();
1420
+ const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
1421
+ const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
1422
+ if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
1423
+ return;
1076
1424
  }
1077
- return raw;
1078
- }
1079
- function parseMaxTurns(raw) {
1080
- if (raw === void 0) return void 0;
1081
- const n = parseInt(raw, 10);
1082
- if (isNaN(n) || n <= 0) {
1083
- process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
1084
- `);
1425
+ process.stdout.write("\n");
1426
+ process.stdout.write(" Welcome to Robota CLI!\n");
1427
+ process.stdout.write(" No configuration found. Let's set up your API key.\n");
1428
+ process.stdout.write("\n");
1429
+ const apiKey = await new Promise((resolve) => {
1430
+ process.stdout.write(" Anthropic API key: ");
1431
+ let input = "";
1432
+ const stdin = process.stdin;
1433
+ const wasRaw = stdin.isRaw;
1434
+ stdin.setRawMode(true);
1435
+ stdin.resume();
1436
+ stdin.setEncoding("utf8");
1437
+ const onData = (data) => {
1438
+ for (const ch of data) {
1439
+ if (ch === "\r" || ch === "\n") {
1440
+ stdin.removeListener("data", onData);
1441
+ stdin.setRawMode(wasRaw ?? false);
1442
+ stdin.pause();
1443
+ process.stdout.write("\n");
1444
+ resolve(input.trim());
1445
+ return;
1446
+ } else if (ch === "\x7F" || ch === "\b") {
1447
+ if (input.length > 0) {
1448
+ input = input.slice(0, -1);
1449
+ process.stdout.write("\b \b");
1450
+ }
1451
+ } else if (ch === "") {
1452
+ process.stdout.write("\n");
1453
+ process.exit(0);
1454
+ } else if (ch.charCodeAt(0) >= 32) {
1455
+ input += ch;
1456
+ process.stdout.write("*");
1457
+ }
1458
+ }
1459
+ };
1460
+ stdin.on("data", onData);
1461
+ });
1462
+ if (!apiKey) {
1463
+ process.stderr.write("\n No API key provided. Exiting.\n");
1085
1464
  process.exit(1);
1086
1465
  }
1087
- return n;
1088
- }
1089
- function parseCliArgs() {
1090
- const { values, positionals } = (0, import_node_util.parseArgs)({
1091
- allowPositionals: true,
1092
- options: {
1093
- p: { type: "boolean", short: "p", default: false },
1094
- c: { type: "boolean", short: "c", default: false },
1095
- r: { type: "string", short: "r" },
1096
- model: { type: "string" },
1097
- "permission-mode": { type: "string" },
1098
- "max-turns": { type: "string" },
1099
- version: { type: "boolean", default: false }
1466
+ const settingsDir = (0, import_node_path3.dirname)(userPath);
1467
+ (0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
1468
+ const settings = {
1469
+ provider: {
1470
+ name: "anthropic",
1471
+ model: "claude-sonnet-4-6",
1472
+ apiKey
1100
1473
  }
1101
- });
1102
- return {
1103
- positional: positionals,
1104
- printMode: values["p"] ?? false,
1105
- continueMode: values["c"] ?? false,
1106
- resumeId: values["r"],
1107
- model: values["model"],
1108
- permissionMode: parsePermissionMode(values["permission-mode"]),
1109
- maxTurns: parseMaxTurns(values["max-turns"]),
1110
- version: values["version"] ?? false
1111
1474
  };
1475
+ (0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1476
+ process.stdout.write(`
1477
+ Config saved to ${userPath}
1478
+
1479
+ `);
1112
1480
  }
1113
- var PrintTerminal = class {
1114
- write(text) {
1115
- process.stdout.write(text);
1116
- }
1117
- writeLine(text) {
1118
- process.stdout.write(text + "\n");
1119
- }
1120
- writeMarkdown(md) {
1121
- process.stdout.write(md);
1122
- }
1123
- writeError(text) {
1124
- process.stderr.write(text + "\n");
1125
- }
1126
- prompt(question) {
1127
- return new Promise((resolve) => {
1128
- const rl = readline.createInterface({
1129
- input: process.stdin,
1130
- output: process.stdout,
1131
- terminal: false,
1132
- historySize: 0
1133
- });
1134
- rl.question(question, (answer) => {
1135
- rl.close();
1136
- resolve(answer);
1137
- });
1138
- });
1139
- }
1140
- async select(options, initialIndex = 0) {
1141
- for (let i = 0; i < options.length; i++) {
1142
- const marker = i === initialIndex ? ">" : " ";
1143
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
1481
+ function resetConfig() {
1482
+ const userPath = getUserSettingsPath();
1483
+ if (deleteSettings(userPath)) {
1484
+ process.stdout.write(`Deleted ${userPath}
1144
1485
  `);
1145
- }
1146
- const answer = await this.prompt(
1147
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
1148
- );
1149
- const trimmed = answer.trim().toLowerCase();
1150
- if (trimmed === "") return initialIndex;
1151
- const num = parseInt(trimmed, 10);
1152
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
1153
- return initialIndex;
1154
- }
1155
- spinner(_message) {
1156
- return { stop() {
1157
- }, update() {
1158
- } };
1486
+ } else {
1487
+ process.stdout.write("No user settings found.\n");
1159
1488
  }
1160
- };
1489
+ }
1161
1490
  async function startCli() {
1162
1491
  const args = parseCliArgs();
1163
1492
  if (args.version) {
@@ -1165,7 +1494,12 @@ async function startCli() {
1165
1494
  `);
1166
1495
  return;
1167
1496
  }
1497
+ if (args.reset) {
1498
+ resetConfig();
1499
+ return;
1500
+ }
1168
1501
  const cwd = process.cwd();
1502
+ await ensureConfig(cwd);
1169
1503
  const [config, context, projectInfo] = await Promise.all([
1170
1504
  (0, import_agent_sdk2.loadConfig)(cwd),
1171
1505
  (0, import_agent_sdk2.loadContext)(cwd),
@@ -1182,14 +1516,15 @@ async function startCli() {
1182
1516
  process.exit(1);
1183
1517
  }
1184
1518
  const terminal = new PrintTerminal();
1185
- const session = new import_agent_sdk2.Session({
1519
+ const paths = (0, import_agent_sdk2.projectPaths)(cwd);
1520
+ const session = (0, import_agent_sdk2.createSession)({
1186
1521
  config,
1187
1522
  context,
1188
1523
  terminal,
1524
+ sessionLogger: new import_agent_sdk2.FileSessionLogger(paths.logs),
1189
1525
  projectInfo,
1190
1526
  permissionMode: args.permissionMode,
1191
- systemPromptBuilder: import_agent_sdk2.buildSystemPrompt,
1192
- promptForApproval
1527
+ promptForApproval: import_agent_sdk3.promptForApproval
1193
1528
  });
1194
1529
  const response = await session.run(prompt);
1195
1530
  process.stdout.write(response + "\n");
@@ -1207,6 +1542,16 @@ async function startCli() {
1207
1542
  }
1208
1543
 
1209
1544
  // src/bin.ts
1545
+ process.on("uncaughtException", (err) => {
1546
+ const msg = err.message ?? "";
1547
+ const isLikelyIME = msg.includes("string-width") || msg.includes("setCursorPosition") || msg.includes("getStringWidth") || msg.includes("slice") || msg.includes("charCodeAt");
1548
+ if (isLikelyIME) {
1549
+ process.stderr.write(`[robota] IME error suppressed: ${msg}
1550
+ `);
1551
+ return;
1552
+ }
1553
+ throw err;
1554
+ });
1210
1555
  startCli().catch((err) => {
1211
1556
  const message = err instanceof Error ? err.message : String(err);
1212
1557
  process.stderr.write(message + "\n");