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