@robota-sdk/agent-cli 3.0.0-beta.4 → 3.0.0-beta.40

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,51 +30,1032 @@ 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_sdk7.Session,
34
+ SessionStore: () => import_agent_sdk7.SessionStore,
35
+ TRUST_TO_MODE: () => import_agent_sdk7.TRUST_TO_MODE,
36
+ query: () => import_agent_sdk7.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_sdk7 = 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_fs4 = require("fs");
44
+ var import_node_path5 = require("path");
46
45
  var import_node_url = require("url");
47
- var readline = __toESM(require("readline"), 1);
48
- var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
46
+ var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
47
+ var import_agent_sdk6 = 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
58
  }
59
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
59
+ return raw;
60
60
  }
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;
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
+ language: { type: "string" },
80
+ "permission-mode": { type: "string" },
81
+ "max-turns": { type: "string" },
82
+ version: { type: "boolean", default: false },
83
+ reset: { type: "boolean", default: false }
84
+ }
85
+ });
86
+ return {
87
+ positional: positionals,
88
+ printMode: values["p"] ?? false,
89
+ continueMode: values["c"] ?? false,
90
+ resumeId: values["r"],
91
+ model: values["model"],
92
+ language: values["language"],
93
+ permissionMode: parsePermissionMode(values["permission-mode"]),
94
+ maxTurns: parseMaxTurns(values["max-turns"]),
95
+ version: values["version"] ?? false,
96
+ reset: values["reset"] ?? false
97
+ };
98
+ }
99
+
100
+ // src/utils/settings-io.ts
101
+ var import_node_fs = require("fs");
102
+ var import_node_path = require("path");
103
+ function getUserSettingsPath() {
104
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
105
+ return (0, import_node_path.join)(home, ".robota", "settings.json");
106
+ }
107
+ function readSettings(path) {
108
+ if (!(0, import_node_fs.existsSync)(path)) return {};
109
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
110
+ }
111
+ function writeSettings(path, settings) {
112
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
113
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
114
+ }
115
+ function updateModelInSettings(settingsPath, modelId) {
116
+ const settings = readSettings(settingsPath);
117
+ const provider = settings.provider ?? {};
118
+ provider.model = modelId;
119
+ settings.provider = provider;
120
+ writeSettings(settingsPath, settings);
121
+ }
122
+ function deleteSettings(path) {
123
+ if ((0, import_node_fs.existsSync)(path)) {
124
+ (0, import_node_fs.unlinkSync)(path);
125
+ return true;
126
+ }
127
+ return false;
68
128
  }
69
129
 
130
+ // src/print-terminal.ts
131
+ var readline = __toESM(require("readline"), 1);
132
+ var PrintTerminal = class {
133
+ write(text) {
134
+ process.stdout.write(text);
135
+ }
136
+ writeLine(text) {
137
+ process.stdout.write(text + "\n");
138
+ }
139
+ writeMarkdown(md) {
140
+ process.stdout.write(md);
141
+ }
142
+ writeError(text) {
143
+ process.stderr.write(text + "\n");
144
+ }
145
+ prompt(question) {
146
+ return new Promise((resolve) => {
147
+ const rl = readline.createInterface({
148
+ input: process.stdin,
149
+ output: process.stdout,
150
+ terminal: false,
151
+ historySize: 0
152
+ });
153
+ rl.question(question, (answer) => {
154
+ rl.close();
155
+ resolve(answer);
156
+ });
157
+ });
158
+ }
159
+ async select(options, initialIndex = 0) {
160
+ for (let i = 0; i < options.length; i++) {
161
+ const marker = i === initialIndex ? ">" : " ";
162
+ process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
163
+ `);
164
+ }
165
+ const answer = await this.prompt(
166
+ ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
167
+ );
168
+ const trimmed = answer.trim().toLowerCase();
169
+ if (trimmed === "") return initialIndex;
170
+ const num = parseInt(trimmed, 10);
171
+ if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
172
+ return initialIndex;
173
+ }
174
+ spinner(_message) {
175
+ return { stop() {
176
+ }, update() {
177
+ } };
178
+ }
179
+ };
180
+
70
181
  // src/ui/render.tsx
71
- var import_ink9 = require("ink");
182
+ var import_ink14 = require("ink");
72
183
 
73
184
  // src/ui/App.tsx
74
- var import_react5 = require("react");
75
- var import_ink8 = require("ink");
185
+ var import_react16 = require("react");
186
+ var import_ink13 = require("ink");
187
+ var import_agent_core6 = require("@robota-sdk/agent-core");
188
+ var import_agent_core7 = require("@robota-sdk/agent-core");
189
+
190
+ // src/ui/hooks/useSession.ts
191
+ var import_react = require("react");
76
192
  var import_agent_sdk = require("@robota-sdk/agent-sdk");
77
193
 
194
+ // src/utils/edit-diff.ts
195
+ var import_node_fs2 = require("fs");
196
+ var CONTEXT_LINES = 2;
197
+ function generateDiffLines(oldStr, newStr, startLine = 1) {
198
+ if (oldStr === newStr) return [];
199
+ const lines = [];
200
+ const oldLines = oldStr.split("\n");
201
+ const newLines = newStr.split("\n");
202
+ for (let i = 0; i < oldLines.length; i++) {
203
+ lines.push({ type: "remove", text: oldLines[i], lineNumber: startLine + i });
204
+ }
205
+ for (let i = 0; i < newLines.length; i++) {
206
+ lines.push({ type: "add", text: newLines[i], lineNumber: startLine + i });
207
+ }
208
+ return lines;
209
+ }
210
+ function generateDiffLinesWithContext(oldStr, newStr, startLine, filePath) {
211
+ if (oldStr === newStr) return [];
212
+ const diffLines = generateDiffLines(oldStr, newStr, startLine);
213
+ let fileLines;
214
+ try {
215
+ fileLines = (0, import_node_fs2.readFileSync)(filePath, "utf-8").split("\n");
216
+ } catch {
217
+ return diffLines;
218
+ }
219
+ const result = [];
220
+ const contextStart = Math.max(0, startLine - 1 - CONTEXT_LINES);
221
+ for (let i = contextStart; i < startLine - 1; i++) {
222
+ if (i < fileLines.length) {
223
+ result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
224
+ }
225
+ }
226
+ result.push(...diffLines);
227
+ const newLineCount = newStr.split("\n").length;
228
+ const afterStart = startLine - 1 + newLineCount;
229
+ for (let i = afterStart; i < afterStart + CONTEXT_LINES; i++) {
230
+ if (i < fileLines.length) {
231
+ result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
232
+ }
233
+ }
234
+ return result;
235
+ }
236
+ function extractEditDiff(toolName, toolArgs, startLine) {
237
+ if (toolName !== "Edit" || !toolArgs) return null;
238
+ const filePath = toolArgs.file_path ?? toolArgs.filePath;
239
+ const oldStr = toolArgs.old_string ?? toolArgs.oldString;
240
+ const newStr = toolArgs.new_string ?? toolArgs.newString;
241
+ if (typeof filePath !== "string") return null;
242
+ if (typeof oldStr !== "string" || typeof newStr !== "string") return null;
243
+ let sl = startLine ?? 0;
244
+ if (!sl) {
245
+ try {
246
+ const fileContent = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
247
+ const idx = fileContent.indexOf(newStr);
248
+ if (idx >= 0) {
249
+ sl = fileContent.substring(0, idx).split("\n").length;
250
+ } else {
251
+ sl = 1;
252
+ }
253
+ } catch {
254
+ sl = 1;
255
+ }
256
+ }
257
+ const lines = generateDiffLinesWithContext(oldStr, newStr, sl, filePath);
258
+ if (lines.length === 0) return null;
259
+ return { file: filePath, lines };
260
+ }
261
+
262
+ // src/ui/hooks/useSession.ts
263
+ var TOOL_ARG_DISPLAY_MAX = 80;
264
+ var TAIL_KEEP = 30;
265
+ var MAX_COMPLETED_TOOLS = 50;
266
+ var NOOP_TERMINAL = {
267
+ write: () => {
268
+ },
269
+ writeLine: () => {
270
+ },
271
+ writeMarkdown: () => {
272
+ },
273
+ writeError: () => {
274
+ },
275
+ prompt: () => Promise.resolve(""),
276
+ select: () => Promise.resolve(0),
277
+ spinner: () => ({ stop: () => {
278
+ }, update: () => {
279
+ } })
280
+ };
281
+ function useSession(props) {
282
+ const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
283
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
284
+ const streamingTextRef = (0, import_react.useRef)("");
285
+ const [activeTools, setActiveTools] = (0, import_react.useState)([]);
286
+ const permissionQueueRef = (0, import_react.useRef)([]);
287
+ const processingRef = (0, import_react.useRef)(false);
288
+ const processNextPermission = (0, import_react.useCallback)(() => {
289
+ if (processingRef.current) return;
290
+ const next = permissionQueueRef.current[0];
291
+ if (!next) {
292
+ setPermissionRequest(null);
293
+ return;
294
+ }
295
+ processingRef.current = true;
296
+ setPermissionRequest({
297
+ toolName: next.toolName,
298
+ toolArgs: next.toolArgs,
299
+ resolve: (result) => {
300
+ permissionQueueRef.current.shift();
301
+ processingRef.current = false;
302
+ setPermissionRequest(null);
303
+ next.resolve(result);
304
+ setTimeout(() => processNextPermission(), 0);
305
+ }
306
+ });
307
+ }, []);
308
+ const sessionRef = (0, import_react.useRef)(null);
309
+ if (sessionRef.current === null) {
310
+ const permissionHandler = (toolName, toolArgs) => {
311
+ return new Promise((resolve) => {
312
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
313
+ processNextPermission();
314
+ });
315
+ };
316
+ let flushTimer = null;
317
+ const onTextDelta = (delta) => {
318
+ streamingTextRef.current += delta;
319
+ if (!flushTimer) {
320
+ flushTimer = setTimeout(() => {
321
+ setStreamingText(streamingTextRef.current);
322
+ flushTimer = null;
323
+ }, 16);
324
+ }
325
+ };
326
+ const onToolExecution = (event) => {
327
+ if (event.type === "start") {
328
+ let firstArg = "";
329
+ if (event.toolArgs) {
330
+ const firstVal = Object.values(event.toolArgs)[0];
331
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
332
+ firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
333
+ }
334
+ setActiveTools((prev) => [
335
+ ...prev,
336
+ { toolName: event.toolName, firstArg, isRunning: true, _toolArgs: event.toolArgs }
337
+ ]);
338
+ } else {
339
+ const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
340
+ setActiveTools((prev) => {
341
+ const updated = prev.map((t) => {
342
+ if (!(t.toolName === event.toolName && t.isRunning)) return t;
343
+ let startLine;
344
+ if (event.toolResultData && event.toolName === "Edit") {
345
+ try {
346
+ const parsed = JSON.parse(event.toolResultData);
347
+ if (typeof parsed.startLine === "number") {
348
+ startLine = parsed.startLine;
349
+ }
350
+ } catch {
351
+ }
352
+ }
353
+ const editDiff = extractEditDiff(
354
+ event.toolName,
355
+ t._toolArgs,
356
+ startLine
357
+ );
358
+ const finished = {
359
+ ...t,
360
+ isRunning: false,
361
+ result: toolResult
362
+ };
363
+ if (editDiff) {
364
+ finished.diffLines = editDiff.lines;
365
+ finished.diffFile = editDiff.file;
366
+ }
367
+ delete finished._toolArgs;
368
+ return finished;
369
+ });
370
+ const completed = updated.filter((t) => !t.isRunning);
371
+ if (completed.length > MAX_COMPLETED_TOOLS) {
372
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
373
+ let removed = 0;
374
+ return updated.filter((t) => {
375
+ if (!t.isRunning && removed < excess) {
376
+ removed++;
377
+ return false;
378
+ }
379
+ return true;
380
+ });
381
+ }
382
+ return updated;
383
+ });
384
+ }
385
+ };
386
+ const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
387
+ sessionRef.current = (0, import_agent_sdk.createSession)({
388
+ config: props.config,
389
+ context: props.context,
390
+ terminal: NOOP_TERMINAL,
391
+ sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
392
+ projectInfo: props.projectInfo,
393
+ sessionStore: props.sessionStore,
394
+ permissionMode: props.permissionMode,
395
+ maxTurns: props.maxTurns,
396
+ permissionHandler,
397
+ onTextDelta,
398
+ onToolExecution
399
+ });
400
+ }
401
+ const clearStreamingText = (0, import_react.useCallback)(() => {
402
+ setStreamingText("");
403
+ streamingTextRef.current = "";
404
+ setActiveTools([]);
405
+ }, []);
406
+ return {
407
+ session: sessionRef.current,
408
+ permissionRequest,
409
+ streamingText,
410
+ clearStreamingText,
411
+ activeTools
412
+ };
413
+ }
414
+
415
+ // src/ui/hooks/useMessages.ts
416
+ var import_react2 = require("react");
417
+ var MAX_RENDERED_MESSAGES = 100;
418
+ function useMessages() {
419
+ const [messages, setMessages] = (0, import_react2.useState)([]);
420
+ const addMessage = (0, import_react2.useCallback)((msg) => {
421
+ setMessages((prev) => {
422
+ const updated = [...prev, msg];
423
+ if (updated.length > MAX_RENDERED_MESSAGES) {
424
+ return updated.slice(-MAX_RENDERED_MESSAGES);
425
+ }
426
+ return updated;
427
+ });
428
+ }, []);
429
+ return { messages, setMessages, addMessage };
430
+ }
431
+
432
+ // src/ui/hooks/useSlashCommands.ts
433
+ var import_react3 = require("react");
434
+ var import_agent_core = require("@robota-sdk/agent-core");
435
+
436
+ // src/commands/slash-executor.ts
437
+ var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
438
+ var HELP_TEXT = [
439
+ "Available commands:",
440
+ " /help \u2014 Show this help",
441
+ " /clear \u2014 Clear conversation",
442
+ " /compact [instr] \u2014 Compact context (optional focus instructions)",
443
+ " /mode [m] \u2014 Show/change permission mode",
444
+ " /language [lang] \u2014 Set response language (ko, en, ja, zh)",
445
+ " /cost \u2014 Show session info",
446
+ " /reset \u2014 Delete settings and exit",
447
+ " /exit \u2014 Exit CLI"
448
+ ].join("\n");
449
+ function handleHelp(addMessage) {
450
+ addMessage({ role: "system", content: HELP_TEXT });
451
+ return { handled: true };
452
+ }
453
+ function handleClear(addMessage, clearMessages, session) {
454
+ clearMessages();
455
+ session.clearHistory();
456
+ addMessage({ role: "system", content: "Conversation cleared." });
457
+ return { handled: true };
458
+ }
459
+ async function handleCompact(args, session, addMessage) {
460
+ const instructions = args.trim() || void 0;
461
+ const before = session.getContextState().usedPercentage;
462
+ addMessage({ role: "system", content: "Compacting context..." });
463
+ await session.compact(instructions);
464
+ const after = session.getContextState().usedPercentage;
465
+ addMessage({
466
+ role: "system",
467
+ content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
468
+ });
469
+ return { handled: true };
470
+ }
471
+ function handleMode(arg, session, addMessage) {
472
+ if (!arg) {
473
+ addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
474
+ } else if (VALID_MODES2.includes(arg)) {
475
+ session.setPermissionMode(arg);
476
+ addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
477
+ } else {
478
+ addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
479
+ }
480
+ return { handled: true };
481
+ }
482
+ function handleModel(modelId, addMessage) {
483
+ if (!modelId) {
484
+ addMessage({ role: "system", content: "Select a model from the /model submenu." });
485
+ return { handled: true };
486
+ }
487
+ return { handled: true, pendingModelId: modelId };
488
+ }
489
+ function handleCost(session, addMessage) {
490
+ addMessage({
491
+ role: "system",
492
+ content: `Session: ${session.getSessionId()}
493
+ Messages: ${session.getMessageCount()}`
494
+ });
495
+ return { handled: true };
496
+ }
497
+ function handlePermissions(session, addMessage) {
498
+ const mode = session.getPermissionMode();
499
+ const sessionAllowed = session.getSessionAllowedTools();
500
+ const lines = [`Permission mode: ${mode}`];
501
+ if (sessionAllowed.length > 0) {
502
+ lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
503
+ } else {
504
+ lines.push("No session-approved tools.");
505
+ }
506
+ addMessage({ role: "system", content: lines.join("\n") });
507
+ return { handled: true };
508
+ }
509
+ function handleContext(session, addMessage) {
510
+ const ctx = session.getContextState();
511
+ addMessage({
512
+ role: "system",
513
+ content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
514
+ });
515
+ return { handled: true };
516
+ }
517
+ function handleLanguage(lang, addMessage) {
518
+ if (!lang) {
519
+ addMessage({ role: "system", content: "Usage: /language <code> (e.g., ko, en, ja, zh)" });
520
+ return { handled: true };
521
+ }
522
+ const settingsPath = getUserSettingsPath();
523
+ const settings = readSettings(settingsPath);
524
+ settings.language = lang;
525
+ writeSettings(settingsPath, settings);
526
+ addMessage({ role: "system", content: `Language set to "${lang}". Restarting...` });
527
+ return { handled: true, exitRequested: true };
528
+ }
529
+ function handleReset(addMessage) {
530
+ const settingsPath = getUserSettingsPath();
531
+ if (deleteSettings(settingsPath)) {
532
+ addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
533
+ } else {
534
+ addMessage({ role: "system", content: "No user settings found." });
535
+ }
536
+ return { handled: true, exitRequested: true };
537
+ }
538
+ async function handlePluginCommand(args, addMessage, callbacks) {
539
+ const parts = args.trim().split(/\s+/);
540
+ const subcommand = parts[0] ?? "";
541
+ const subArgs = parts.slice(1).join(" ").trim();
542
+ try {
543
+ switch (subcommand) {
544
+ case "":
545
+ case void 0:
546
+ case "manage": {
547
+ return { handled: true, triggerPluginTUI: true };
548
+ }
549
+ case "install": {
550
+ if (!subArgs) {
551
+ addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
552
+ return { handled: true };
553
+ }
554
+ await callbacks.install(subArgs);
555
+ addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
556
+ return { handled: true };
557
+ }
558
+ case "uninstall": {
559
+ if (!subArgs) {
560
+ addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
561
+ return { handled: true };
562
+ }
563
+ await callbacks.uninstall(subArgs);
564
+ addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
565
+ return { handled: true };
566
+ }
567
+ case "enable": {
568
+ if (!subArgs) {
569
+ addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
570
+ return { handled: true };
571
+ }
572
+ await callbacks.enable(subArgs);
573
+ addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
574
+ return { handled: true };
575
+ }
576
+ case "disable": {
577
+ if (!subArgs) {
578
+ addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
579
+ return { handled: true };
580
+ }
581
+ await callbacks.disable(subArgs);
582
+ addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
583
+ return { handled: true };
584
+ }
585
+ case "marketplace": {
586
+ const mpParts = subArgs.split(/\s+/);
587
+ const mpSubcommand = mpParts[0] ?? "";
588
+ const mpArgs = mpParts.slice(1).join(" ").trim();
589
+ if (mpSubcommand === "add" && mpArgs) {
590
+ const registeredName = await callbacks.marketplaceAdd(mpArgs);
591
+ addMessage({
592
+ role: "system",
593
+ content: `Added marketplace: "${registeredName}" (from ${mpArgs})
594
+ Install plugins with: /plugin install <name>@${registeredName}`
595
+ });
596
+ return { handled: true };
597
+ } else if (mpSubcommand === "remove" && mpArgs) {
598
+ await callbacks.marketplaceRemove(mpArgs);
599
+ addMessage({
600
+ role: "system",
601
+ content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
602
+ });
603
+ return { handled: true };
604
+ } else if (mpSubcommand === "update" && mpArgs) {
605
+ await callbacks.marketplaceUpdate(mpArgs);
606
+ addMessage({
607
+ role: "system",
608
+ content: `Updated marketplace "${mpArgs}".`
609
+ });
610
+ return { handled: true };
611
+ } else if (mpSubcommand === "list") {
612
+ const sources = await callbacks.marketplaceList();
613
+ if (sources.length === 0) {
614
+ addMessage({ role: "system", content: "No marketplace sources configured." });
615
+ } else {
616
+ const lines = sources.map((s) => ` ${s.name} (${s.type})`);
617
+ addMessage({ role: "system", content: `Marketplace sources:
618
+ ${lines.join("\n")}` });
619
+ }
620
+ return { handled: true };
621
+ } else {
622
+ addMessage({
623
+ role: "system",
624
+ content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
625
+ });
626
+ return { handled: true };
627
+ }
628
+ }
629
+ default:
630
+ addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
631
+ return { handled: true };
632
+ }
633
+ } catch (error) {
634
+ const message = error instanceof Error ? error.message : String(error);
635
+ addMessage({ role: "system", content: `Plugin error: ${message}` });
636
+ return { handled: true };
637
+ }
638
+ }
639
+ async function handleReloadPlugins(addMessage, callbacks) {
640
+ await callbacks.reloadPlugins();
641
+ addMessage({ role: "system", content: "Plugins reload complete." });
642
+ return { handled: true };
643
+ }
644
+ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
645
+ switch (cmd) {
646
+ case "help":
647
+ return handleHelp(addMessage);
648
+ case "clear":
649
+ return handleClear(addMessage, clearMessages, session);
650
+ case "compact":
651
+ return handleCompact(args, session, addMessage);
652
+ case "mode":
653
+ return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
654
+ case "model":
655
+ return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
656
+ case "language":
657
+ return handleLanguage(args.split(/\s+/)[0] || void 0, addMessage);
658
+ case "cost":
659
+ return handleCost(session, addMessage);
660
+ case "permissions":
661
+ return handlePermissions(session, addMessage);
662
+ case "context":
663
+ return handleContext(session, addMessage);
664
+ case "reset":
665
+ return handleReset(addMessage);
666
+ case "exit":
667
+ return { handled: true, exitRequested: true };
668
+ case "plugin":
669
+ if (pluginCallbacks) {
670
+ return handlePluginCommand(args, addMessage, pluginCallbacks);
671
+ }
672
+ addMessage({ role: "system", content: "Plugin management is not available." });
673
+ return { handled: true };
674
+ case "reload-plugins":
675
+ if (pluginCallbacks) {
676
+ return handleReloadPlugins(addMessage, pluginCallbacks);
677
+ }
678
+ addMessage({ role: "system", content: "Plugin management is not available." });
679
+ return { handled: true };
680
+ default: {
681
+ const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
682
+ if (dynamicCmd) {
683
+ addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
684
+ return { handled: false };
685
+ }
686
+ addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
687
+ return { handled: true };
688
+ }
689
+ }
690
+ }
691
+
692
+ // src/ui/hooks/useSlashCommands.ts
693
+ var EXIT_DELAY_MS = 500;
694
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks, setShowPluginTUI) {
695
+ return (0, import_react3.useCallback)(
696
+ async (input) => {
697
+ const parts = input.slice(1).split(/\s+/);
698
+ const cmd = parts[0]?.toLowerCase() ?? "";
699
+ const args = parts.slice(1).join(" ");
700
+ const clearMessages = () => setMessages([]);
701
+ const slashAddMessage = (msg) => {
702
+ addMessage((0, import_agent_core.createSystemMessage)(msg.content));
703
+ };
704
+ const result = await executeSlashCommand(
705
+ cmd,
706
+ args,
707
+ session,
708
+ slashAddMessage,
709
+ clearMessages,
710
+ registry,
711
+ pluginCallbacks
712
+ );
713
+ if (result.pendingModelId) {
714
+ pendingModelChangeRef.current = result.pendingModelId;
715
+ setPendingModelId(result.pendingModelId);
716
+ }
717
+ if (result.triggerPluginTUI) {
718
+ setShowPluginTUI?.(true);
719
+ }
720
+ if (result.exitRequested) {
721
+ setTimeout(() => exit(), EXIT_DELAY_MS);
722
+ }
723
+ return result.handled;
724
+ },
725
+ [
726
+ session,
727
+ addMessage,
728
+ setMessages,
729
+ exit,
730
+ registry,
731
+ pendingModelChangeRef,
732
+ setPendingModelId,
733
+ pluginCallbacks,
734
+ setShowPluginTUI
735
+ ]
736
+ );
737
+ }
738
+
739
+ // src/ui/hooks/useSubmitHandler.ts
740
+ var import_react4 = require("react");
741
+ var import_node_crypto = require("crypto");
742
+ var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
743
+ var import_agent_core2 = require("@robota-sdk/agent-core");
744
+
745
+ // src/utils/tool-call-extractor.ts
746
+ var TOOL_ARG_MAX_LENGTH = 80;
747
+ var TAIL_KEEP2 = 30;
748
+ function extractToolCallsWithDiff(history, startIndex) {
749
+ const summaries = [];
750
+ for (let i = startIndex; i < history.length; i++) {
751
+ const msg = history[i];
752
+ if (msg.role === "assistant" && msg.toolCalls) {
753
+ for (const tc of msg.toolCalls) {
754
+ const value = parseFirstArgValue(tc.function.arguments);
755
+ const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
756
+ const summary = {
757
+ line: `${tc.function.name}(${truncated})`
758
+ };
759
+ if (tc.function.name === "Edit") {
760
+ try {
761
+ const args = JSON.parse(tc.function.arguments);
762
+ const diff = extractEditDiff("Edit", args);
763
+ if (diff) {
764
+ summary.diffLines = diff.lines;
765
+ summary.diffFile = diff.file;
766
+ }
767
+ } catch {
768
+ }
769
+ }
770
+ summaries.push(summary);
771
+ }
772
+ }
773
+ }
774
+ return summaries;
775
+ }
776
+ function parseFirstArgValue(argsJson) {
777
+ try {
778
+ const parsed = JSON.parse(argsJson);
779
+ const firstVal = Object.values(parsed)[0];
780
+ return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
781
+ } catch {
782
+ return argsJson;
783
+ }
784
+ }
785
+
786
+ // src/utils/skill-prompt.ts
787
+ var import_node_child_process = require("child_process");
788
+ function substituteVariables(content, args, context) {
789
+ const argParts = args ? args.split(/\s+/) : [];
790
+ let result = content;
791
+ result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
792
+ return argParts[Number(index)] ?? "";
793
+ });
794
+ result = result.replace(/\$ARGUMENTS/g, args);
795
+ result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
796
+ return argParts[Number(digit)] ?? "";
797
+ });
798
+ result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
799
+ result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
800
+ return result;
801
+ }
802
+ async function preprocessShellCommands(content) {
803
+ const shellPattern = /!`([^`]+)`/g;
804
+ if (!shellPattern.test(content)) {
805
+ return content;
806
+ }
807
+ shellPattern.lastIndex = 0;
808
+ let result = content;
809
+ let match;
810
+ const matches = [];
811
+ while ((match = shellPattern.exec(content)) !== null) {
812
+ matches.push({ full: match[0], command: match[1] });
813
+ }
814
+ for (const { full, command } of matches) {
815
+ let output = "";
816
+ try {
817
+ output = (0, import_node_child_process.execSync)(command, {
818
+ timeout: 5e3,
819
+ encoding: "utf-8",
820
+ stdio: ["pipe", "pipe", "pipe"]
821
+ }).trimEnd();
822
+ } catch {
823
+ output = "";
824
+ }
825
+ result = result.replace(full, output);
826
+ }
827
+ return result;
828
+ }
829
+ async function buildSkillPrompt(input, registry, context) {
830
+ const parts = input.slice(1).split(/\s+/);
831
+ const cmd = parts[0]?.toLowerCase() ?? "";
832
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
833
+ if (!skillCmd) return null;
834
+ const args = parts.slice(1).join(" ").trim();
835
+ const userInstruction = args || skillCmd.description;
836
+ if (skillCmd.skillContent) {
837
+ let processed = await preprocessShellCommands(skillCmd.skillContent);
838
+ processed = substituteVariables(processed, args, context);
839
+ return `<skill name="${cmd}">
840
+ ${processed}
841
+ </skill>
842
+
843
+ Execute the "${cmd}" skill: ${userInstruction}`;
844
+ }
845
+ return `Use the "${cmd}" skill: ${userInstruction}`;
846
+ }
847
+
848
+ // src/commands/skill-executor.ts
849
+ function buildProcessedContent(skill, args, context) {
850
+ if (!skill.skillContent) return null;
851
+ return substituteVariables(skill.skillContent, args, context);
852
+ }
853
+ function buildInjectPrompt(skill, args, context) {
854
+ const processed = buildProcessedContent(skill, args, context);
855
+ if (processed) {
856
+ const userInstruction = args || skill.description;
857
+ return `<skill name="${skill.name}">
858
+ ${processed}
859
+ </skill>
860
+
861
+ Execute the "${skill.name}" skill: ${userInstruction}`;
862
+ }
863
+ return `Use the "${skill.name}" skill: ${args || skill.description}`;
864
+ }
865
+ async function executeSkill(skill, args, callbacks, context) {
866
+ if (skill.context === "fork") {
867
+ if (!callbacks.runInFork) {
868
+ throw new Error("Fork execution is not available. Agent tool deps may not be initialized.");
869
+ }
870
+ const content = buildProcessedContent(skill, args, context);
871
+ const prompt2 = content ?? `Use the "${skill.name}" skill: ${args || skill.description}`;
872
+ const options = {};
873
+ if (skill.agent) options.agent = skill.agent;
874
+ if (skill.allowedTools) options.allowedTools = skill.allowedTools;
875
+ const result = await callbacks.runInFork(prompt2, options);
876
+ return { mode: "fork", result };
877
+ }
878
+ const prompt = buildInjectPrompt(skill, args, context);
879
+ return { mode: "inject", prompt };
880
+ }
881
+
882
+ // src/ui/hooks/useSubmitHandler.ts
883
+ function syncContextState(session, setter) {
884
+ const ctx = session.getContextState();
885
+ setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
886
+ }
887
+ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
888
+ setIsThinking(true);
889
+ clearStreamingText();
890
+ const historyBefore = session.getHistory().length;
891
+ try {
892
+ const response = await session.run(prompt, rawInput);
893
+ clearStreamingText();
894
+ const history = session.getHistory();
895
+ const toolSummaries = extractToolCallsWithDiff(
896
+ history,
897
+ historyBefore
898
+ );
899
+ if (toolSummaries.length > 0) {
900
+ addMessage(
901
+ (0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
902
+ toolCallId: (0, import_node_crypto.randomUUID)(),
903
+ name: `${toolSummaries.length} tools`
904
+ })
905
+ );
906
+ }
907
+ addMessage((0, import_agent_core2.createAssistantMessage)(response || "(empty response)"));
908
+ syncContextState(session, setContextState);
909
+ } catch (err) {
910
+ clearStreamingText();
911
+ const isAbortError = err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
912
+ if (isAbortError) {
913
+ const history = session.getHistory();
914
+ const toolSummaries = extractToolCallsWithDiff(
915
+ history,
916
+ historyBefore
917
+ );
918
+ if (toolSummaries.length > 0) {
919
+ addMessage(
920
+ (0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
921
+ toolCallId: (0, import_node_crypto.randomUUID)(),
922
+ name: `${toolSummaries.length} tools`
923
+ })
924
+ );
925
+ }
926
+ const assistantParts = [];
927
+ let lastAssistantState = "complete";
928
+ for (let i = historyBefore; i < history.length; i++) {
929
+ const msg = history[i];
930
+ if (msg && msg.role === "assistant" && msg.content) {
931
+ assistantParts.push(msg.content);
932
+ if (msg.state === "interrupted") lastAssistantState = "interrupted";
933
+ }
934
+ }
935
+ if (assistantParts.length > 0) {
936
+ addMessage(
937
+ (0, import_agent_core2.createAssistantMessage)(assistantParts.join("\n\n"), { state: lastAssistantState })
938
+ );
939
+ }
940
+ addMessage((0, import_agent_core2.createSystemMessage)("Interrupted by user."));
941
+ } else {
942
+ const errMsg = err instanceof Error ? err.message : String(err);
943
+ addMessage((0, import_agent_core2.createSystemMessage)(`Error: ${errMsg}`));
944
+ }
945
+ } finally {
946
+ setIsThinking(false);
947
+ }
948
+ }
949
+ function createForkRunner(sessionKey) {
950
+ const deps = (0, import_agent_sdk2.retrieveAgentToolDeps)(sessionKey);
951
+ if (!deps) return void 0;
952
+ return async (content, options) => {
953
+ const agentType = options.agent ?? "general-purpose";
954
+ const agentDef = (0, import_agent_sdk2.getBuiltInAgent)(agentType) ?? deps.customAgentRegistry?.(agentType);
955
+ if (!agentDef) {
956
+ throw new Error(`Unknown agent type for fork execution: ${agentType}`);
957
+ }
958
+ const effectiveDef = options.allowedTools ? { ...agentDef, tools: options.allowedTools } : agentDef;
959
+ const subSession = (0, import_agent_sdk2.createSubagentSession)({
960
+ agentDefinition: effectiveDef,
961
+ parentConfig: deps.config,
962
+ parentContext: deps.context,
963
+ parentTools: deps.tools,
964
+ terminal: deps.terminal,
965
+ isForkWorker: true,
966
+ permissionHandler: deps.permissionHandler,
967
+ onTextDelta: deps.onTextDelta,
968
+ onToolExecution: deps.onToolExecution
969
+ });
970
+ return await subSession.run(content);
971
+ };
972
+ }
973
+ function findSkillCommand(input, registry) {
974
+ const parts = input.slice(1).split(/\s+/);
975
+ const cmd = parts[0]?.toLowerCase() ?? "";
976
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
977
+ if (!skillCmd) return null;
978
+ return { skill: skillCmd, args: parts.slice(1).join(" ").trim() };
979
+ }
980
+ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
981
+ return (0, import_react4.useCallback)(
982
+ async (input) => {
983
+ if (input.startsWith("/")) {
984
+ const handled = await handleSlashCommand(input);
985
+ if (handled) {
986
+ syncContextState(session, setContextState);
987
+ return;
988
+ }
989
+ const found = findSkillCommand(input, registry);
990
+ if (!found) return;
991
+ const { skill, args } = found;
992
+ if (skill.context === "fork") {
993
+ const runInFork = createForkRunner(session);
994
+ const result = await executeSkill(skill, args, { runInFork });
995
+ if (result.mode === "fork") {
996
+ addMessage((0, import_agent_core2.createAssistantMessage)(result.result ?? "(empty response)"));
997
+ syncContextState(session, setContextState);
998
+ return;
999
+ }
1000
+ if (result.prompt) {
1001
+ const cmdName2 = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
1002
+ const qualifiedName2 = registry.resolveQualifiedName(cmdName2);
1003
+ const hookInput2 = qualifiedName2 ? `/${qualifiedName2}${input.slice(1 + cmdName2.length)}` : input;
1004
+ return runSessionPrompt(
1005
+ result.prompt,
1006
+ session,
1007
+ addMessage,
1008
+ clearStreamingText,
1009
+ setIsThinking,
1010
+ setContextState,
1011
+ hookInput2
1012
+ );
1013
+ }
1014
+ return;
1015
+ }
1016
+ const prompt = await buildSkillPrompt(input, registry);
1017
+ if (!prompt) return;
1018
+ const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
1019
+ const qualifiedName = registry.resolveQualifiedName(cmdName);
1020
+ const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
1021
+ return runSessionPrompt(
1022
+ prompt,
1023
+ session,
1024
+ addMessage,
1025
+ clearStreamingText,
1026
+ setIsThinking,
1027
+ setContextState,
1028
+ hookInput
1029
+ );
1030
+ }
1031
+ addMessage((0, import_agent_core2.createUserMessage)(input));
1032
+ return runSessionPrompt(
1033
+ input,
1034
+ session,
1035
+ addMessage,
1036
+ clearStreamingText,
1037
+ setIsThinking,
1038
+ setContextState
1039
+ );
1040
+ },
1041
+ [
1042
+ session,
1043
+ addMessage,
1044
+ handleSlashCommand,
1045
+ clearStreamingText,
1046
+ setIsThinking,
1047
+ setContextState,
1048
+ registry
1049
+ ]
1050
+ );
1051
+ }
1052
+
1053
+ // src/ui/hooks/useCommandRegistry.ts
1054
+ var import_react5 = require("react");
1055
+ var import_node_os2 = require("os");
1056
+ var import_node_path3 = require("path");
1057
+ var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
1058
+
78
1059
  // src/commands/command-registry.ts
79
1060
  var CommandRegistry = class {
80
1061
  sources = [];
@@ -91,6 +1072,14 @@ var CommandRegistry = class {
91
1072
  const lower = filter.toLowerCase();
92
1073
  return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
93
1074
  }
1075
+ /** Resolve a short name to its fully qualified plugin:name form */
1076
+ resolveQualifiedName(shortName) {
1077
+ const matches = this.getCommands().filter(
1078
+ (c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
1079
+ );
1080
+ if (matches.length !== 1) return null;
1081
+ return matches[0].name;
1082
+ }
94
1083
  /** Get subcommands for a specific command */
95
1084
  getSubcommands(commandName) {
96
1085
  const lower = commandName.toLowerCase();
@@ -106,6 +1095,21 @@ var CommandRegistry = class {
106
1095
  };
107
1096
 
108
1097
  // src/commands/builtin-source.ts
1098
+ var import_agent_core3 = require("@robota-sdk/agent-core");
1099
+ function buildModelSubcommands() {
1100
+ const seen = /* @__PURE__ */ new Set();
1101
+ const commands = [];
1102
+ for (const model of Object.values(import_agent_core3.CLAUDE_MODELS)) {
1103
+ if (seen.has(model.name)) continue;
1104
+ seen.add(model.name);
1105
+ commands.push({
1106
+ name: model.id,
1107
+ description: `${model.name} (${(0, import_agent_core3.formatTokenCount)(model.contextWindow).toUpperCase()})`,
1108
+ source: "builtin"
1109
+ });
1110
+ }
1111
+ return commands;
1112
+ }
109
1113
  function createBuiltinCommands() {
110
1114
  return [
111
1115
  { name: "help", description: "Show available commands", source: "builtin" },
@@ -125,16 +1129,26 @@ function createBuiltinCommands() {
125
1129
  name: "model",
126
1130
  description: "Select AI model",
127
1131
  source: "builtin",
1132
+ subcommands: buildModelSubcommands()
1133
+ },
1134
+ {
1135
+ name: "language",
1136
+ description: "Set response language",
1137
+ source: "builtin",
128
1138
  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" }
1139
+ { name: "ko", description: "Korean", source: "builtin" },
1140
+ { name: "en", description: "English", source: "builtin" },
1141
+ { name: "ja", description: "Japanese", source: "builtin" },
1142
+ { name: "zh", description: "Chinese", source: "builtin" }
132
1143
  ]
133
1144
  },
134
1145
  { name: "compact", description: "Compress context window", source: "builtin" },
135
1146
  { name: "cost", description: "Show session info", source: "builtin" },
136
1147
  { name: "context", description: "Context window info", source: "builtin" },
137
1148
  { name: "permissions", description: "Permission rules", source: "builtin" },
1149
+ { name: "plugin", description: "Manage plugins", source: "builtin" },
1150
+ { name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
1151
+ { name: "reset", description: "Delete settings and exit", source: "builtin" },
138
1152
  { name: "exit", description: "Exit CLI", source: "builtin" }
139
1153
  ];
140
1154
  }
@@ -150,72 +1164,342 @@ var BuiltinCommandSource = class {
150
1164
  };
151
1165
 
152
1166
  // src/commands/skill-source.ts
153
- var import_node_fs = require("fs");
154
- var import_node_path = require("path");
1167
+ var import_node_fs3 = require("fs");
1168
+ var import_node_path2 = require("path");
155
1169
  var import_node_os = require("os");
1170
+ var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
1171
+ var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
1172
+ function kebabToCamel(key) {
1173
+ return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
1174
+ }
156
1175
  function parseFrontmatter(content) {
157
1176
  const lines = content.split("\n");
158
1177
  if (lines[0]?.trim() !== "---") return null;
159
- let name = "";
160
- let description = "";
1178
+ const result = {};
161
1179
  for (let i = 1; i < lines.length; i++) {
162
1180
  const line = lines[i];
163
1181
  if (line.trim() === "---") break;
164
- const nameMatch = line.match(/^name:\s*(.+)/);
165
- if (nameMatch) {
166
- name = nameMatch[1].trim();
167
- continue;
168
- }
169
- const descMatch = line.match(/^description:\s*(.+)/);
170
- if (descMatch) {
171
- description = descMatch[1].trim();
1182
+ const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
1183
+ if (!match) continue;
1184
+ const key = match[1];
1185
+ const rawValue = match[2].trim();
1186
+ const camelKey = kebabToCamel(key);
1187
+ if (BOOLEAN_KEYS.has(key)) {
1188
+ result[camelKey] = rawValue === "true";
1189
+ } else if (LIST_KEYS.has(key)) {
1190
+ result[camelKey] = rawValue.split(",").map((s) => s.trim());
1191
+ } else {
1192
+ result[camelKey] = rawValue;
172
1193
  }
173
1194
  }
174
- return name ? { name, description } : null;
1195
+ return Object.keys(result).length > 0 ? result : null;
1196
+ }
1197
+ function buildCommand(frontmatter, content, fallbackName) {
1198
+ const cmd = {
1199
+ name: frontmatter?.name ?? fallbackName,
1200
+ description: frontmatter?.description ?? `Skill: ${fallbackName}`,
1201
+ source: "skill",
1202
+ skillContent: content
1203
+ };
1204
+ if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
1205
+ if (frontmatter?.disableModelInvocation !== void 0)
1206
+ cmd.disableModelInvocation = frontmatter.disableModelInvocation;
1207
+ if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
1208
+ if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
1209
+ if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
1210
+ if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
1211
+ if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
1212
+ if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
1213
+ return cmd;
175
1214
  }
176
1215
  function scanSkillsDir(skillsDir) {
177
- if (!(0, import_node_fs.existsSync)(skillsDir)) return [];
1216
+ if (!(0, import_node_fs3.existsSync)(skillsDir)) return [];
178
1217
  const commands = [];
179
- const entries = (0, import_node_fs.readdirSync)(skillsDir, { withFileTypes: true });
1218
+ const entries = (0, import_node_fs3.readdirSync)(skillsDir, { withFileTypes: true });
180
1219
  for (const entry of entries) {
181
1220
  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");
1221
+ const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
1222
+ if (!(0, import_node_fs3.existsSync)(skillFile)) continue;
1223
+ const content = (0, import_node_fs3.readFileSync)(skillFile, "utf-8");
185
1224
  const frontmatter = parseFrontmatter(content);
186
- commands.push({
187
- name: frontmatter?.name ?? entry.name,
188
- description: frontmatter?.description ?? `Skill: ${entry.name}`,
189
- source: "skill"
190
- });
1225
+ commands.push(buildCommand(frontmatter, content, entry.name));
1226
+ }
1227
+ return commands;
1228
+ }
1229
+ function scanCommandsDir(commandsDir) {
1230
+ if (!(0, import_node_fs3.existsSync)(commandsDir)) return [];
1231
+ const commands = [];
1232
+ const entries = (0, import_node_fs3.readdirSync)(commandsDir, { withFileTypes: true });
1233
+ for (const entry of entries) {
1234
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1235
+ const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
1236
+ const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
1237
+ const frontmatter = parseFrontmatter(content);
1238
+ const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
1239
+ commands.push(buildCommand(frontmatter, content, fallbackName));
191
1240
  }
192
1241
  return commands;
193
1242
  }
194
1243
  var SkillCommandSource = class {
195
1244
  name = "skill";
196
1245
  cwd;
1246
+ home;
197
1247
  cachedCommands = null;
198
- constructor(cwd) {
1248
+ constructor(cwd, home) {
199
1249
  this.cwd = cwd;
1250
+ this.home = home ?? (0, import_node_os.homedir)();
200
1251
  }
201
1252
  getCommands() {
202
1253
  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"));
205
- const seen = new Set(projectSkills.map((cmd) => cmd.name));
206
- const merged = [...projectSkills];
207
- for (const cmd of userSkills) {
208
- if (!seen.has(cmd.name)) {
209
- merged.push(cmd);
1254
+ const sources = [
1255
+ scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
1256
+ // 1. project .claude/skills
1257
+ scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
1258
+ // 2. project .claude/commands (legacy)
1259
+ scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
1260
+ // 3. user ~/.robota/skills
1261
+ scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
1262
+ // 4. project .agents/skills
1263
+ ];
1264
+ const seen = /* @__PURE__ */ new Set();
1265
+ const merged = [];
1266
+ for (const commands of sources) {
1267
+ for (const cmd of commands) {
1268
+ if (!seen.has(cmd.name)) {
1269
+ seen.add(cmd.name);
1270
+ merged.push(cmd);
1271
+ }
210
1272
  }
211
1273
  }
212
1274
  this.cachedCommands = merged;
213
1275
  return this.cachedCommands;
214
1276
  }
1277
+ /** Get skills that models can invoke (excludes disableModelInvocation: true) */
1278
+ getModelInvocableSkills() {
1279
+ return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
1280
+ }
1281
+ /** Get skills that users can invoke (excludes userInvocable: false) */
1282
+ getUserInvocableSkills() {
1283
+ return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
1284
+ }
1285
+ };
1286
+
1287
+ // src/commands/plugin-source.ts
1288
+ var PluginCommandSource = class {
1289
+ name = "plugin";
1290
+ plugins;
1291
+ constructor(plugins) {
1292
+ this.plugins = plugins;
1293
+ }
1294
+ getCommands() {
1295
+ const commands = [];
1296
+ for (const plugin of this.plugins) {
1297
+ for (const skill of plugin.skills) {
1298
+ const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
1299
+ commands.push({
1300
+ name: baseName,
1301
+ description: `(${plugin.manifest.name}) ${skill.description}`,
1302
+ source: "plugin",
1303
+ skillContent: skill.skillContent,
1304
+ pluginDir: plugin.pluginDir
1305
+ });
1306
+ }
1307
+ for (const cmd of plugin.commands) {
1308
+ commands.push({
1309
+ name: cmd.name,
1310
+ description: cmd.description,
1311
+ source: "plugin",
1312
+ skillContent: cmd.skillContent,
1313
+ pluginDir: plugin.pluginDir
1314
+ });
1315
+ }
1316
+ }
1317
+ return commands;
1318
+ }
215
1319
  };
216
1320
 
1321
+ // src/ui/hooks/useCommandRegistry.ts
1322
+ function buildPluginEnv(plugin) {
1323
+ const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
1324
+ return {
1325
+ CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
1326
+ CLAUDE_PLUGIN_PATH: plugin.pluginDir,
1327
+ CLAUDE_PLUGIN_DATA: dataDir
1328
+ };
1329
+ }
1330
+ function mergePluginHooks(plugins) {
1331
+ const merged = {};
1332
+ for (const plugin of plugins) {
1333
+ const hooksObj = plugin.hooks;
1334
+ if (!hooksObj) continue;
1335
+ const pluginEnv = buildPluginEnv(plugin);
1336
+ const innerHooks = hooksObj.hooks ?? hooksObj;
1337
+ for (const [event, groups] of Object.entries(innerHooks)) {
1338
+ if (!Array.isArray(groups)) continue;
1339
+ if (!merged[event]) merged[event] = [];
1340
+ const resolved = groups.map((group) => {
1341
+ const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
1342
+ if (typeof resolved2 === "object" && resolved2 !== null) {
1343
+ resolved2.env = pluginEnv;
1344
+ }
1345
+ return resolved2;
1346
+ });
1347
+ merged[event].push(...resolved);
1348
+ }
1349
+ }
1350
+ return merged;
1351
+ }
1352
+ function resolvePluginRoot(group, pluginDir) {
1353
+ if (typeof group !== "object" || group === null) return group;
1354
+ const obj = group;
1355
+ if (Array.isArray(obj.hooks)) {
1356
+ return {
1357
+ ...obj,
1358
+ hooks: obj.hooks.map((h) => {
1359
+ if (typeof h !== "object" || h === null) return h;
1360
+ const hook = h;
1361
+ if (typeof hook.command === "string") {
1362
+ return {
1363
+ ...hook,
1364
+ command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
1365
+ };
1366
+ }
1367
+ return hook;
1368
+ })
1369
+ };
1370
+ }
1371
+ return group;
1372
+ }
1373
+ function useCommandRegistry(cwd) {
1374
+ const resultRef = (0, import_react5.useRef)(null);
1375
+ if (resultRef.current === null) {
1376
+ const registry = new CommandRegistry();
1377
+ registry.addSource(new BuiltinCommandSource());
1378
+ registry.addSource(new SkillCommandSource(cwd));
1379
+ let pluginHooks = {};
1380
+ const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
1381
+ const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
1382
+ try {
1383
+ const plugins = loader.loadPluginsSync();
1384
+ if (plugins.length > 0) {
1385
+ registry.addSource(new PluginCommandSource(plugins));
1386
+ pluginHooks = mergePluginHooks(plugins);
1387
+ }
1388
+ } catch {
1389
+ }
1390
+ resultRef.current = { registry, pluginHooks };
1391
+ }
1392
+ return resultRef.current;
1393
+ }
1394
+
1395
+ // src/ui/hooks/usePluginCallbacks.ts
1396
+ var import_react6 = require("react");
1397
+ var import_node_os3 = require("os");
1398
+ var import_node_path4 = require("path");
1399
+ var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
1400
+ function usePluginCallbacks(cwd) {
1401
+ return (0, import_react6.useMemo)(() => {
1402
+ const home = (0, import_node_os3.homedir)();
1403
+ const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
1404
+ const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
1405
+ const settingsStore = new import_agent_sdk4.PluginSettingsStore(userSettingsPath);
1406
+ const marketplace = new import_agent_sdk4.MarketplaceClient({ pluginsDir });
1407
+ const installer = new import_agent_sdk4.BundlePluginInstaller({
1408
+ pluginsDir,
1409
+ settingsStore,
1410
+ marketplaceClient: marketplace
1411
+ });
1412
+ const loader = new import_agent_sdk4.BundlePluginLoader(pluginsDir);
1413
+ return {
1414
+ listInstalled: async () => {
1415
+ const plugins = await loader.loadAll();
1416
+ const enabledMap = settingsStore.getEnabledPlugins();
1417
+ return plugins.map((p) => {
1418
+ const parts = p.pluginDir.split("/");
1419
+ const cacheIdx = parts.indexOf("cache");
1420
+ const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
1421
+ const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
1422
+ return {
1423
+ name: fullId,
1424
+ description: p.manifest.description,
1425
+ enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
1426
+ };
1427
+ });
1428
+ },
1429
+ listAvailablePlugins: async (marketplaceName) => {
1430
+ let manifest;
1431
+ try {
1432
+ manifest = marketplace.fetchManifest(marketplaceName);
1433
+ } catch {
1434
+ return [];
1435
+ }
1436
+ const installed = installer.getInstalledPlugins();
1437
+ const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
1438
+ return manifest.plugins.map((p) => ({
1439
+ name: p.name,
1440
+ description: p.description,
1441
+ installed: installedNames.has(p.name)
1442
+ }));
1443
+ },
1444
+ install: async (pluginId, scope) => {
1445
+ const [name, marketplaceName] = pluginId.split("@");
1446
+ if (!name || !marketplaceName) {
1447
+ throw new Error("Plugin ID must be in format: name@marketplace");
1448
+ }
1449
+ if (scope === "project") {
1450
+ const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
1451
+ const projectInstaller = new import_agent_sdk4.BundlePluginInstaller({
1452
+ pluginsDir: projectPluginsDir,
1453
+ settingsStore,
1454
+ marketplaceClient: marketplace
1455
+ });
1456
+ await projectInstaller.install(name, marketplaceName);
1457
+ } else {
1458
+ await installer.install(name, marketplaceName);
1459
+ }
1460
+ },
1461
+ uninstall: async (pluginId) => {
1462
+ await installer.uninstall(pluginId);
1463
+ },
1464
+ enable: async (pluginId) => {
1465
+ await installer.enable(pluginId);
1466
+ },
1467
+ disable: async (pluginId) => {
1468
+ await installer.disable(pluginId);
1469
+ },
1470
+ marketplaceAdd: async (source) => {
1471
+ if (source.includes("/") && !source.includes(":")) {
1472
+ return marketplace.addMarketplace({ type: "github", repo: source });
1473
+ } else {
1474
+ return marketplace.addMarketplace({ type: "git", url: source });
1475
+ }
1476
+ },
1477
+ marketplaceRemove: async (name) => {
1478
+ const installedFromMarketplace = installer.getPluginsByMarketplace(name);
1479
+ for (const record of installedFromMarketplace) {
1480
+ await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
1481
+ }
1482
+ marketplace.removeMarketplace(name);
1483
+ },
1484
+ marketplaceUpdate: async (name) => {
1485
+ marketplace.updateMarketplace(name);
1486
+ },
1487
+ marketplaceList: async () => {
1488
+ return marketplace.listMarketplaces().map((m) => ({
1489
+ name: m.name,
1490
+ type: m.source.type
1491
+ }));
1492
+ },
1493
+ reloadPlugins: async () => {
1494
+ }
1495
+ };
1496
+ }, [cwd]);
1497
+ }
1498
+
217
1499
  // src/ui/MessageList.tsx
218
- var import_ink = require("ink");
1500
+ var import_react7 = __toESM(require("react"), 1);
1501
+ var import_ink2 = require("ink");
1502
+ var import_agent_core4 = require("@robota-sdk/agent-core");
219
1503
 
220
1504
  // src/ui/render-markdown.ts
221
1505
  var import_marked = require("marked");
@@ -228,54 +1512,161 @@ function renderMarkdown(md) {
228
1512
  return typeof result === "string" ? result.trimEnd() : md;
229
1513
  }
230
1514
 
231
- // src/ui/MessageList.tsx
1515
+ // src/ui/DiffBlock.tsx
1516
+ var import_ink = require("ink");
232
1517
  var import_jsx_runtime = require("react/jsx-runtime");
1518
+ var MAX_DIFF_LINES = 12;
1519
+ var TRUNCATED_SHOW = 10;
1520
+ function DiffBlock({ file, lines }) {
1521
+ const truncated = lines.length > MAX_DIFF_LINES;
1522
+ const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
1523
+ const remaining = lines.length - TRUNCATED_SHOW;
1524
+ const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
1525
+ const numWidth = String(maxLineNum).length;
1526
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
1527
+ file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
1528
+ "\u2502 ",
1529
+ file
1530
+ ] }),
1531
+ visible.map((line, i) => {
1532
+ const lineNum = String(line.lineNumber).padStart(numWidth, " ");
1533
+ if (line.type === "context") {
1534
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
1535
+ "\u2502 ",
1536
+ lineNum,
1537
+ " ",
1538
+ line.text
1539
+ ] }, i);
1540
+ }
1541
+ const prefix = line.type === "remove" ? "-" : "+";
1542
+ const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
1543
+ const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
1544
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: fgColor, backgroundColor: bgColor, children: [
1545
+ "\u2502 ",
1546
+ lineNum,
1547
+ " ",
1548
+ prefix,
1549
+ " ",
1550
+ line.text
1551
+ ] }, i);
1552
+ }),
1553
+ truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
1554
+ "\u2502 ... and ",
1555
+ remaining,
1556
+ " more lines"
1557
+ ] })
1558
+ ] });
1559
+ }
1560
+
1561
+ // src/ui/MessageList.tsx
1562
+ var import_jsx_runtime2 = require("react/jsx-runtime");
233
1563
  function RoleLabel({ role }) {
234
1564
  switch (role) {
235
1565
  case "user":
236
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", bold: true, children: [
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
237
1567
  "You:",
238
1568
  " "
239
1569
  ] });
240
1570
  case "assistant":
241
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "cyan", bold: true, children: [
1571
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
242
1572
  "Robota:",
243
1573
  " "
244
1574
  ] });
245
1575
  case "system":
246
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "yellow", bold: true, children: [
1576
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
247
1577
  "System:",
248
1578
  " "
249
1579
  ] });
250
1580
  case "tool":
251
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "magenta", bold: true, children: [
1581
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
252
1582
  "Tool:",
253
1583
  " "
254
1584
  ] });
255
1585
  }
256
1586
  }
257
- function MessageItem({ message }) {
258
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
259
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
260
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoleLabel, { role: message.role }),
261
- message.toolName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "magenta", dimColor: true, children: [
262
- "[",
263
- message.toolName,
264
- "]",
1587
+ function ToolMessage({ message }) {
1588
+ if (!(0, import_agent_core4.isToolMessage)(message)) {
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
1590
+ }
1591
+ const toolName = message.name;
1592
+ const content = message.content;
1593
+ let summaries = null;
1594
+ try {
1595
+ const parsed = JSON.parse(content);
1596
+ if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
1597
+ summaries = parsed;
1598
+ }
1599
+ } catch {
1600
+ }
1601
+ if (summaries) {
1602
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
1603
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
1604
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
1605
+ "Tool:",
1606
+ " "
1607
+ ] }),
1608
+ toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
1609
+ "[",
1610
+ toolName,
1611
+ "]"
1612
+ ] })
1613
+ ] }),
1614
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
1615
+ summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
1617
+ " ",
1618
+ "\u2713",
1619
+ " ",
1620
+ s.line
1621
+ ] }),
1622
+ s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
1623
+ ] }, i))
1624
+ ] });
1625
+ }
1626
+ const lines = content.split("\n").filter((l) => l.trim());
1627
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
1628
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
1629
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
1630
+ "Tool:",
265
1631
  " "
1632
+ ] }),
1633
+ toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
1634
+ "[",
1635
+ toolName,
1636
+ "]"
266
1637
  ] })
267
1638
  ] }),
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
1639
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
1640
+ lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
1641
+ " ",
1642
+ "\u2713",
1643
+ " ",
1644
+ line
1645
+ ] }, i))
270
1646
  ] });
271
1647
  }
1648
+ var MessageItem = import_react7.default.memo(function MessageItem2({
1649
+ message
1650
+ }) {
1651
+ if ((0, import_agent_core4.isToolMessage)(message)) {
1652
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
1653
+ }
1654
+ const content = message.content ?? "";
1655
+ const isInterrupted = message.state === "interrupted";
1656
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
1657
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
1659
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: (0, import_agent_core4.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
1660
+ ] });
1661
+ });
272
1662
  function MessageList({ messages }) {
273
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageItem, { message: msg }, msg.id)) });
1663
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message: msg }, msg.id)) });
274
1664
  }
275
1665
 
276
1666
  // src/ui/StatusBar.tsx
277
- var import_ink2 = require("ink");
278
- var import_jsx_runtime2 = require("react/jsx-runtime");
1667
+ var import_ink3 = require("ink");
1668
+ var import_agent_core5 = require("@robota-sdk/agent-core");
1669
+ var import_jsx_runtime3 = require("react/jsx-runtime");
279
1670
  var CONTEXT_YELLOW_THRESHOLD = 70;
280
1671
  var CONTEXT_RED_THRESHOLD = 90;
281
1672
  function getContextColor(percentage) {
@@ -294,8 +1685,8 @@ function StatusBar({
294
1685
  contextMaxTokens
295
1686
  }) {
296
1687
  const contextColor = getContextColor(contextPercentage);
297
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
298
- import_ink2.Box,
1688
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1689
+ import_ink3.Box,
299
1690
  {
300
1691
  borderStyle: "single",
301
1692
  borderColor: "gray",
@@ -303,26 +1694,26 @@ function StatusBar({
303
1694
  paddingRight: 1,
304
1695
  justifyContent: "space-between",
305
1696
  children: [
306
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: "cyan", bold: true, children: "Mode:" }),
1697
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
1698
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
308
1699
  " ",
309
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: permissionMode }),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
310
1701
  " | ",
311
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { dimColor: true, children: modelName }),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
312
1703
  " | ",
313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: contextColor, children: [
1704
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
314
1705
  "Context: ",
315
1706
  Math.round(contextPercentage),
316
1707
  "% (",
317
- (contextUsedTokens / 1e3).toFixed(1),
318
- "k/",
319
- (contextMaxTokens / 1e3).toFixed(0),
320
- "k)"
1708
+ (0, import_agent_core5.formatTokenCount)(contextUsedTokens),
1709
+ "/",
1710
+ (0, import_agent_core5.formatTokenCount)(contextMaxTokens),
1711
+ ")"
321
1712
  ] })
322
1713
  ] }),
323
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
324
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: "yellow", children: "Thinking... " }),
325
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { dimColor: true, children: [
1714
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
1715
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
1716
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
326
1717
  "msgs: ",
327
1718
  messageCount
328
1719
  ] })
@@ -333,146 +1724,224 @@ function StatusBar({
333
1724
  }
334
1725
 
335
1726
  // src/ui/InputArea.tsx
336
- var import_react3 = __toESM(require("react"), 1);
337
- var import_ink6 = require("ink");
1727
+ var import_react10 = __toESM(require("react"), 1);
1728
+ var import_ink7 = require("ink");
338
1729
 
339
1730
  // src/ui/CjkTextInput.tsx
340
- var import_react = require("react");
341
- var import_ink3 = require("ink");
1731
+ var import_react8 = require("react");
1732
+ var import_ink4 = require("ink");
1733
+ var import_chalk = __toESM(require("chalk"), 1);
342
1734
  var import_string_width = __toESM(require("string-width"), 1);
343
- var import_chalk2 = __toESM(require("chalk"), 1);
344
- var import_jsx_runtime3 = require("react/jsx-runtime");
1735
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1736
+ var PASTE_START = "[200~";
1737
+ var PASTE_END = "[201~";
1738
+ function filterPrintable(input) {
1739
+ if (!input || input.length === 0) return "";
1740
+ return input.replace(/[\x00-\x1f\x7f]/g, "");
1741
+ }
1742
+ function insertAtCursor(value, cursor, input) {
1743
+ const next = value.slice(0, cursor) + input + value.slice(cursor);
1744
+ return { value: next, cursor: cursor + input.length };
1745
+ }
1746
+ function displayOffset(chars, charIndex, width) {
1747
+ let offset = 0;
1748
+ for (let i = 0; i < charIndex && i < chars.length; i++) {
1749
+ const w = (0, import_string_width.default)(chars[i]);
1750
+ const col = offset % width;
1751
+ if (col + w > width) offset += width - col;
1752
+ offset += w;
1753
+ }
1754
+ return offset;
1755
+ }
1756
+ function charIndexAtDisplayOffset(chars, target, width) {
1757
+ let offset = 0;
1758
+ for (let i = 0; i < chars.length; i++) {
1759
+ if (offset >= target) return i;
1760
+ const w = (0, import_string_width.default)(chars[i]);
1761
+ const col = offset % width;
1762
+ if (col + w > width) offset += width - col;
1763
+ offset += w;
1764
+ }
1765
+ return chars.length;
1766
+ }
345
1767
  function CjkTextInput({
346
1768
  value,
347
1769
  onChange,
348
1770
  onSubmit,
1771
+ onPaste,
349
1772
  placeholder = "",
350
1773
  focus = true,
351
- showCursor = true
1774
+ showCursor = true,
1775
+ availableWidth
352
1776
  }) {
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)();
1777
+ const valueRef = (0, import_react8.useRef)(value);
1778
+ const cursorRef = (0, import_react8.useRef)(value.length);
1779
+ const [, forceRender] = (0, import_react8.useState)(0);
1780
+ const isPastingRef = (0, import_react8.useRef)(false);
1781
+ const pasteBufferRef = (0, import_react8.useRef)("");
357
1782
  if (value !== valueRef.current) {
358
1783
  valueRef.current = value;
359
1784
  if (cursorRef.current > value.length) {
360
1785
  cursorRef.current = value.length;
361
1786
  }
362
1787
  }
363
- (0, import_ink3.useInput)(
1788
+ (0, import_ink4.useInput)(
364
1789
  (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);
1790
+ try {
1791
+ if (input === PASTE_START || input.startsWith(PASTE_START)) {
1792
+ isPastingRef.current = true;
1793
+ const afterMarker = input.slice(PASTE_START.length);
1794
+ if (afterMarker.length > 0) {
1795
+ pasteBufferRef.current += afterMarker;
1796
+ }
1797
+ return;
376
1798
  }
377
- return;
378
- }
379
- if (key.rightArrow) {
380
- if (cursorRef.current < valueRef.current.length) {
381
- cursorRef.current += 1;
382
- forceRender((n) => n + 1);
1799
+ if (isPastingRef.current) {
1800
+ if (input === PASTE_END || input.includes(PASTE_END)) {
1801
+ const beforeMarker = input.split(PASTE_END)[0] ?? "";
1802
+ pasteBufferRef.current += beforeMarker;
1803
+ const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
1804
+ pasteBufferRef.current = "";
1805
+ isPastingRef.current = false;
1806
+ if (text.length > 0) {
1807
+ if (text.includes("\n") && onPaste) {
1808
+ onPaste(text);
1809
+ } else {
1810
+ const printable2 = filterPrintable(text);
1811
+ if (printable2.length > 0) {
1812
+ const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
1813
+ cursorRef.current = result2.cursor;
1814
+ valueRef.current = result2.value;
1815
+ onChange(result2.value);
1816
+ }
1817
+ }
1818
+ }
1819
+ } else {
1820
+ pasteBufferRef.current += input;
1821
+ }
1822
+ return;
383
1823
  }
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);
1824
+ if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
1825
+ return;
393
1826
  }
394
- return;
1827
+ if (key.upArrow || key.downArrow) {
1828
+ if (availableWidth && availableWidth > 0) {
1829
+ const chars = [...valueRef.current];
1830
+ const offset = displayOffset(chars, cursorRef.current, availableWidth);
1831
+ const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
1832
+ if (target >= 0) {
1833
+ const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
1834
+ if (newCursor !== cursorRef.current) {
1835
+ cursorRef.current = newCursor;
1836
+ forceRender((n) => n + 1);
1837
+ }
1838
+ }
1839
+ }
1840
+ return;
1841
+ }
1842
+ if (key.return) {
1843
+ onSubmit?.(valueRef.current);
1844
+ return;
1845
+ }
1846
+ if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
1847
+ onPaste(input.replace(/\r\n?/g, "\n"));
1848
+ return;
1849
+ }
1850
+ if (key.leftArrow) {
1851
+ if (cursorRef.current > 0) {
1852
+ cursorRef.current -= 1;
1853
+ forceRender((n) => n + 1);
1854
+ }
1855
+ return;
1856
+ }
1857
+ if (key.rightArrow) {
1858
+ if (cursorRef.current < valueRef.current.length) {
1859
+ cursorRef.current += 1;
1860
+ forceRender((n) => n + 1);
1861
+ }
1862
+ return;
1863
+ }
1864
+ if (key.backspace || key.delete) {
1865
+ if (cursorRef.current > 0) {
1866
+ const v = valueRef.current;
1867
+ const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
1868
+ cursorRef.current -= 1;
1869
+ valueRef.current = next;
1870
+ onChange(next);
1871
+ }
1872
+ return;
1873
+ }
1874
+ const printable = filterPrintable(input);
1875
+ if (printable.length === 0) return;
1876
+ const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
1877
+ cursorRef.current = result.cursor;
1878
+ valueRef.current = result.value;
1879
+ onChange(result.value);
1880
+ } catch {
395
1881
  }
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
1882
  },
403
1883
  { isActive: focus }
404
1884
  );
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
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
1885
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
411
1886
  }
412
1887
  function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
413
1888
  if (!showCursor) {
414
- return value.length > 0 ? value : placeholder ? import_chalk2.default.gray(placeholder) : "";
1889
+ return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
415
1890
  }
416
1891
  if (value.length === 0) {
417
1892
  if (placeholder.length > 0) {
418
- return import_chalk2.default.inverse(placeholder[0]) + import_chalk2.default.gray(placeholder.slice(1));
1893
+ return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
419
1894
  }
420
- return import_chalk2.default.inverse(" ");
1895
+ return import_chalk.default.inverse(" ");
421
1896
  }
422
1897
  const chars = [...value];
423
1898
  let rendered = "";
424
1899
  for (let i = 0; i < chars.length; i++) {
425
1900
  const char = chars[i] ?? "";
426
- rendered += i === cursorOffset ? import_chalk2.default.inverse(char) : char;
1901
+ rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
427
1902
  }
428
1903
  if (cursorOffset >= chars.length) {
429
- rendered += import_chalk2.default.inverse(" ");
1904
+ rendered += import_chalk.default.inverse(" ");
430
1905
  }
431
1906
  return rendered;
432
1907
  }
433
1908
 
434
1909
  // src/ui/WaveText.tsx
435
- var import_react2 = require("react");
436
- var import_ink4 = require("ink");
437
- var import_jsx_runtime4 = require("react/jsx-runtime");
1910
+ var import_react9 = require("react");
1911
+ var import_ink5 = require("ink");
1912
+ var import_jsx_runtime5 = require("react/jsx-runtime");
438
1913
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
439
1914
  var INTERVAL_MS = 400;
440
1915
  var CHARS_PER_GROUP = 4;
441
1916
  function WaveText({ text }) {
442
- const [tick, setTick] = (0, import_react2.useState)(0);
443
- (0, import_react2.useEffect)(() => {
1917
+ const [tick, setTick] = (0, import_react9.useState)(0);
1918
+ (0, import_react9.useEffect)(() => {
444
1919
  const timer = setInterval(() => {
445
1920
  setTick((prev) => prev + 1);
446
1921
  }, INTERVAL_MS);
447
1922
  return () => clearInterval(timer);
448
1923
  }, []);
449
1924
  const chars = [...text];
450
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: chars.map((char, i) => {
1925
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
451
1926
  const group = Math.floor(i / CHARS_PER_GROUP);
452
1927
  const colorIndex = (tick + group) % WAVE_COLORS.length;
453
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
1928
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
454
1929
  }) });
455
1930
  }
456
1931
 
457
1932
  // src/ui/SlashAutocomplete.tsx
458
- var import_ink5 = require("ink");
459
- var import_jsx_runtime5 = require("react/jsx-runtime");
1933
+ var import_ink6 = require("ink");
1934
+ var import_jsx_runtime6 = require("react/jsx-runtime");
460
1935
  var MAX_VISIBLE = 8;
461
1936
  function CommandRow(props) {
462
1937
  const { cmd, isSelected, showSlash } = props;
463
- const prefix = showSlash ? "/" : "";
464
1938
  const indicator = isSelected ? "\u25B8 " : " ";
465
1939
  const nameColor = isSelected ? "cyan" : void 0;
466
1940
  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
- ] });
1941
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: nameColor, dimColor: dimmed, children: [
1942
+ indicator,
1943
+ showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
1944
+ ] }) });
476
1945
  }
477
1946
  function SlashAutocomplete({
478
1947
  commands,
@@ -483,7 +1952,7 @@ function SlashAutocomplete({
483
1952
  if (!visible || commands.length === 0) return null;
484
1953
  const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
485
1954
  const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
486
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1955
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
487
1956
  CommandRow,
488
1957
  {
489
1958
  cmd,
@@ -500,8 +1969,14 @@ function computeScrollOffset(selectedIndex, total) {
500
1969
  return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
501
1970
  }
502
1971
 
1972
+ // src/utils/paste-labels.ts
1973
+ var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
1974
+ function expandPasteLabels(text, store) {
1975
+ return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
1976
+ }
1977
+
503
1978
  // src/ui/InputArea.tsx
504
- var import_jsx_runtime6 = require("react/jsx-runtime");
1979
+ var import_jsx_runtime7 = require("react/jsx-runtime");
505
1980
  function parseSlashInput(value) {
506
1981
  if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
507
1982
  const afterSlash = value.slice(1);
@@ -512,16 +1987,16 @@ function parseSlashInput(value) {
512
1987
  return { isSlash: true, parentCommand: parent, filter: rest };
513
1988
  }
514
1989
  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);
1990
+ const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
1991
+ const [dismissed, setDismissed] = (0, import_react10.useState)(false);
1992
+ const prevValueRef = import_react10.default.useRef(value);
518
1993
  if (prevValueRef.current !== value) {
519
1994
  prevValueRef.current = value;
520
1995
  if (dismissed) setDismissed(false);
521
1996
  }
522
1997
  const parsed = parseSlashInput(value);
523
1998
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
524
- const filteredCommands = (0, import_react3.useMemo)(() => {
1999
+ const filteredCommands = (0, import_react10.useMemo)(() => {
525
2000
  if (!registry || !parsed.isSlash || dismissed) return [];
526
2001
  if (isSubcommandMode) {
527
2002
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -554,8 +2029,24 @@ function useAutocomplete(value, registry) {
554
2029
  }
555
2030
  };
556
2031
  }
557
- function InputArea({ onSubmit, isDisabled, registry }) {
558
- const [value, setValue] = (0, import_react3.useState)("");
2032
+ var BORDER_HORIZONTAL = 2;
2033
+ var PADDING_LEFT = 1;
2034
+ var PROMPT_WIDTH = 2;
2035
+ var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
2036
+ function InputArea({
2037
+ onSubmit,
2038
+ onCancelQueue,
2039
+ isDisabled,
2040
+ isAborting,
2041
+ pendingPrompt,
2042
+ registry
2043
+ }) {
2044
+ const [value, setValue] = (0, import_react10.useState)("");
2045
+ const pasteStore = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
2046
+ const { stdout } = (0, import_ink7.useStdout)();
2047
+ const terminalColumns = stdout?.columns ?? 80;
2048
+ const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
2049
+ const pasteIdRef = (0, import_react10.useRef)(0);
559
2050
  const {
560
2051
  showPopup,
561
2052
  filteredCommands,
@@ -564,7 +2055,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
564
2055
  isSubcommandMode,
565
2056
  setShowPopup
566
2057
  } = useAutocomplete(value, registry);
567
- const handleSubmit = (0, import_react3.useCallback)(
2058
+ const handlePaste = (0, import_react10.useCallback)((text) => {
2059
+ pasteIdRef.current += 1;
2060
+ const id = pasteIdRef.current;
2061
+ pasteStore.current.set(id, text);
2062
+ const lineCount = text.split("\n").length;
2063
+ const label = `[Pasted text #${id} +${lineCount} lines]`;
2064
+ setValue((prev) => prev ? `${prev} ${label}` : label);
2065
+ }, []);
2066
+ const handleSubmit = (0, import_react10.useCallback)(
568
2067
  (text) => {
569
2068
  const trimmed = text.trim();
570
2069
  if (trimmed.length === 0) return;
@@ -572,12 +2071,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
572
2071
  selectCommand(filteredCommands[selectedIndex]);
573
2072
  return;
574
2073
  }
2074
+ const expanded = expandPasteLabels(trimmed, pasteStore.current);
575
2075
  setValue("");
576
- onSubmit(trimmed);
2076
+ pasteStore.current.clear();
2077
+ pasteIdRef.current = 0;
2078
+ onSubmit(expanded);
577
2079
  },
578
2080
  [showPopup, filteredCommands, selectedIndex, onSubmit]
579
2081
  );
580
- const selectCommand = (0, import_react3.useCallback)(
2082
+ const selectCommand = (0, import_react10.useCallback)(
581
2083
  (cmd) => {
582
2084
  const parsed = parseSlashInput(value);
583
2085
  if (parsed.parentCommand) {
@@ -596,7 +2098,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
596
2098
  },
597
2099
  [value, onSubmit, setSelectedIndex]
598
2100
  );
599
- (0, import_ink6.useInput)(
2101
+ (0, import_ink7.useInput)(
600
2102
  (_input, key) => {
601
2103
  if (!showPopup) return;
602
2104
  if (key.upArrow) {
@@ -612,8 +2114,16 @@ function InputArea({ onSubmit, isDisabled, registry }) {
612
2114
  },
613
2115
  { isActive: showPopup && !isDisabled }
614
2116
  );
615
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
616
- showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2117
+ (0, import_ink7.useInput)(
2118
+ (_input, key) => {
2119
+ if ((key.backspace || key.delete) && pendingPrompt) {
2120
+ onCancelQueue?.();
2121
+ }
2122
+ },
2123
+ { isActive: !!pendingPrompt }
2124
+ );
2125
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
2126
+ showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
617
2127
  SlashAutocomplete,
618
2128
  {
619
2129
  commands: filteredCommands,
@@ -622,41 +2132,100 @@ function InputArea({ onSubmit, isDisabled, registry }) {
622
2132
  isSubcommandMode
623
2133
  }
624
2134
  ),
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
- ] }) })
2135
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2136
+ import_ink7.Box,
2137
+ {
2138
+ borderStyle: "single",
2139
+ borderColor: isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green",
2140
+ paddingLeft: 1,
2141
+ children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: "cyan", children: [
2142
+ " ",
2143
+ "Queued: ",
2144
+ pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
2145
+ " ",
2146
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: "(Backspace to cancel)" })
2147
+ ] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
2148
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
2149
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2150
+ CjkTextInput,
2151
+ {
2152
+ value,
2153
+ onChange: setValue,
2154
+ onSubmit: handleSubmit,
2155
+ onPaste: handlePaste,
2156
+ placeholder: "Type a message or /help",
2157
+ availableWidth
2158
+ }
2159
+ )
2160
+ ] })
2161
+ }
2162
+ )
2163
+ ] });
2164
+ }
2165
+
2166
+ // src/ui/ConfirmPrompt.tsx
2167
+ var import_react11 = require("react");
2168
+ var import_ink8 = require("ink");
2169
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2170
+ function ConfirmPrompt({
2171
+ message,
2172
+ options = ["Yes", "No"],
2173
+ onSelect
2174
+ }) {
2175
+ const [selected, setSelected] = (0, import_react11.useState)(0);
2176
+ const resolvedRef = (0, import_react11.useRef)(false);
2177
+ const doSelect = (0, import_react11.useCallback)(
2178
+ (index) => {
2179
+ if (resolvedRef.current) return;
2180
+ resolvedRef.current = true;
2181
+ onSelect(index);
2182
+ },
2183
+ [onSelect]
2184
+ );
2185
+ (0, import_ink8.useInput)((input, key) => {
2186
+ if (resolvedRef.current) return;
2187
+ if (key.leftArrow || key.upArrow) {
2188
+ setSelected((prev) => prev > 0 ? prev - 1 : prev);
2189
+ } else if (key.rightArrow || key.downArrow) {
2190
+ setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
2191
+ } else if (key.return) {
2192
+ doSelect(selected);
2193
+ } else if (input === "y" && options.length === 2) {
2194
+ doSelect(0);
2195
+ } else if (input === "n" && options.length === 2) {
2196
+ doSelect(1);
2197
+ }
2198
+ });
2199
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2200
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
2201
+ /* @__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: [
2202
+ i === selected ? "> " : " ",
2203
+ opt
2204
+ ] }) }, opt)) }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
637
2206
  ] });
638
2207
  }
639
2208
 
640
2209
  // 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");
2210
+ var import_react12 = __toESM(require("react"), 1);
2211
+ var import_ink9 = require("ink");
2212
+ var import_jsx_runtime9 = require("react/jsx-runtime");
644
2213
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
645
- function formatArgs2(args) {
2214
+ function formatArgs(args) {
646
2215
  const entries = Object.entries(args);
647
2216
  if (entries.length === 0) return "(no arguments)";
648
2217
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
649
2218
  }
650
2219
  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);
2220
+ const [selected, setSelected] = import_react12.default.useState(0);
2221
+ const resolvedRef = import_react12.default.useRef(false);
2222
+ const prevRequestRef = import_react12.default.useRef(request);
654
2223
  if (prevRequestRef.current !== request) {
655
2224
  prevRequestRef.current = request;
656
2225
  resolvedRef.current = false;
657
2226
  setSelected(0);
658
2227
  }
659
- const doResolve = import_react4.default.useCallback(
2228
+ const doResolve = import_react12.default.useCallback(
660
2229
  (index) => {
661
2230
  if (resolvedRef.current) return;
662
2231
  resolvedRef.current = true;
@@ -666,7 +2235,7 @@ function PermissionPrompt({ request }) {
666
2235
  },
667
2236
  [request]
668
2237
  );
669
- (0, import_ink7.useInput)((input, key) => {
2238
+ (0, import_ink9.useInput)((input, key) => {
670
2239
  if (resolvedRef.current) return;
671
2240
  if (key.upArrow || key.leftArrow) {
672
2241
  setSelected((prev) => prev > 0 ? prev - 1 : prev);
@@ -682,364 +2251,709 @@ function PermissionPrompt({ request }) {
682
2251
  doResolve(2);
683
2252
  }
684
2253
  });
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: [
2254
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2255
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
2256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
688
2257
  "Tool:",
689
2258
  " ",
690
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "cyan", bold: true, children: request.toolName })
2259
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
691
2260
  ] }),
692
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { dimColor: true, children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
693
2262
  " ",
694
- formatArgs2(request.toolArgs)
2263
+ formatArgs(request.toolArgs)
695
2264
  ] }),
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: [
2265
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
697
2266
  i === selected ? "> " : " ",
698
2267
  opt
699
2268
  ] }) }, opt)) }),
700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
2269
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
701
2270
  ] });
702
2271
  }
703
2272
 
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
- const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
763
- sessionRef.current = (0, import_agent_sdk.createSession)({
764
- config: props.config,
765
- context: props.context,
766
- terminal: NOOP_TERMINAL,
767
- sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
768
- projectInfo: props.projectInfo,
769
- sessionStore: props.sessionStore,
770
- permissionMode: props.permissionMode,
771
- maxTurns: props.maxTurns,
772
- permissionHandler,
773
- onTextDelta
774
- });
775
- }
776
- const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
777
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
778
- }
779
- function useMessages() {
780
- const [messages, setMessages] = (0, import_react5.useState)([]);
781
- const addMessage = (0, import_react5.useCallback)((msg) => {
782
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
783
- }, []);
784
- return { messages, setMessages, addMessage };
2273
+ // src/ui/StreamingIndicator.tsx
2274
+ var import_ink10 = require("ink");
2275
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2276
+ function getToolStyle(t) {
2277
+ if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
2278
+ if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
2279
+ if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
2280
+ return { color: "green", icon: "\u2713", strikethrough: false };
785
2281
  }
786
- var HELP_TEXT = [
787
- "Available commands:",
788
- " /help \u2014 Show this help",
789
- " /clear \u2014 Clear conversation",
790
- " /compact [instr] \u2014 Compact context (optional focus instructions)",
791
- " /mode [m] \u2014 Show/change permission mode",
792
- " /cost \u2014 Show session info",
793
- " /exit \u2014 Exit CLI"
794
- ].join("\n");
795
- function handleModeCommand(arg, session, addMessage) {
796
- const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
797
- if (!arg) {
798
- addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
799
- } else if (validModes.includes(arg)) {
800
- session.setPermissionMode(arg);
801
- addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
802
- } else {
803
- addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
2282
+ function StreamingIndicator({ text, activeTools }) {
2283
+ const hasTools = activeTools.length > 0;
2284
+ const hasText = text.length > 0;
2285
+ if (!hasTools && !hasText) {
2286
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
804
2287
  }
805
- return true;
2288
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
2289
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
2290
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
2291
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
2292
+ activeTools.map((t, i) => {
2293
+ const { color, icon, strikethrough } = getToolStyle(t);
2294
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
2295
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
2296
+ " ",
2297
+ icon,
2298
+ " ",
2299
+ t.toolName,
2300
+ "(",
2301
+ t.firstArg,
2302
+ ")"
2303
+ ] }),
2304
+ t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
2305
+ ] }, `${t.toolName}-${i}`);
2306
+ })
2307
+ ] }),
2308
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
2309
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
2311
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
2312
+ ] })
2313
+ ] });
806
2314
  }
807
- async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
808
- switch (cmd) {
809
- case "help":
810
- addMessage({ role: "system", content: HELP_TEXT });
811
- return true;
812
- case "clear":
813
- setMessages([]);
814
- session.clearHistory();
815
- addMessage({ role: "system", content: "Conversation cleared." });
816
- return true;
817
- case "compact": {
818
- const instructions = parts.slice(1).join(" ").trim() || void 0;
819
- const before = session.getContextState().usedPercentage;
820
- addMessage({ role: "system", content: "Compacting context..." });
821
- await session.compact(instructions);
822
- const after = session.getContextState().usedPercentage;
823
- addMessage({
824
- role: "system",
825
- content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
826
- });
827
- return true;
2315
+
2316
+ // src/ui/PluginTUI.tsx
2317
+ var import_react15 = require("react");
2318
+
2319
+ // src/ui/MenuSelect.tsx
2320
+ var import_react13 = require("react");
2321
+ var import_ink11 = require("ink");
2322
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2323
+ function MenuSelect({
2324
+ title,
2325
+ items,
2326
+ onSelect,
2327
+ onBack,
2328
+ loading,
2329
+ error
2330
+ }) {
2331
+ const [selected, setSelected] = (0, import_react13.useState)(0);
2332
+ const selectedRef = (0, import_react13.useRef)(0);
2333
+ const resolvedRef = (0, import_react13.useRef)(false);
2334
+ const doSelect = (0, import_react13.useCallback)(
2335
+ (index) => {
2336
+ if (resolvedRef.current || items.length === 0) return;
2337
+ resolvedRef.current = true;
2338
+ onSelect(items[index].value);
2339
+ },
2340
+ [items, onSelect]
2341
+ );
2342
+ (0, import_ink11.useInput)((input, key) => {
2343
+ if (resolvedRef.current) return;
2344
+ if (key.escape) {
2345
+ resolvedRef.current = true;
2346
+ onBack();
2347
+ return;
828
2348
  }
829
- case "mode":
830
- return handleModeCommand(parts[1], session, addMessage);
831
- case "cost":
832
- addMessage({
833
- role: "system",
834
- content: `Session: ${session.getSessionId()}
835
- Messages: ${session.getMessageCount()}`
836
- });
837
- return true;
838
- case "permissions": {
839
- const mode = session.getPermissionMode();
840
- const sessionAllowed = session.getSessionAllowedTools();
841
- const lines = [`Permission mode: ${mode}`];
842
- if (sessionAllowed.length > 0) {
843
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
844
- } else {
845
- lines.push("No session-approved tools.");
2349
+ if (loading || error || items.length === 0) return;
2350
+ if (key.upArrow) {
2351
+ const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
2352
+ selectedRef.current = next;
2353
+ setSelected(next);
2354
+ } else if (key.downArrow) {
2355
+ const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
2356
+ selectedRef.current = next;
2357
+ setSelected(next);
2358
+ } else if (key.return) {
2359
+ doSelect(selectedRef.current);
2360
+ }
2361
+ });
2362
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2363
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: title }),
2364
+ loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Loading..." }) }),
2365
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { marginTop: 1, flexDirection: "column", children: [
2366
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "red", children: error }),
2367
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Press Esc to go back" })
2368
+ ] }),
2369
+ !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { children: [
2370
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
2371
+ i === selected ? "> " : " ",
2372
+ item.label
2373
+ ] }),
2374
+ item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
2375
+ " ",
2376
+ item.hint
2377
+ ] })
2378
+ ] }, item.value)) }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
2380
+ ] });
2381
+ }
2382
+
2383
+ // src/ui/TextPrompt.tsx
2384
+ var import_react14 = require("react");
2385
+ var import_ink12 = require("ink");
2386
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2387
+ function TextPrompt({
2388
+ title,
2389
+ placeholder,
2390
+ onSubmit,
2391
+ onCancel,
2392
+ validate
2393
+ }) {
2394
+ const [value, setValue] = (0, import_react14.useState)("");
2395
+ const [error, setError] = (0, import_react14.useState)();
2396
+ const resolvedRef = (0, import_react14.useRef)(false);
2397
+ const valueRef = (0, import_react14.useRef)("");
2398
+ const handleSubmit = (0, import_react14.useCallback)(() => {
2399
+ if (resolvedRef.current) return;
2400
+ const trimmed = valueRef.current.trim();
2401
+ if (!trimmed) return;
2402
+ if (validate) {
2403
+ const err = validate(trimmed);
2404
+ if (err) {
2405
+ setError(err);
2406
+ return;
846
2407
  }
847
- addMessage({ role: "system", content: lines.join("\n") });
848
- return true;
849
2408
  }
850
- case "context": {
851
- const ctx = session.getContextState();
852
- addMessage({
853
- role: "system",
854
- content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
855
- });
856
- return true;
2409
+ resolvedRef.current = true;
2410
+ onSubmit(trimmed);
2411
+ }, [validate, onSubmit]);
2412
+ (0, import_ink12.useInput)((input, key) => {
2413
+ if (resolvedRef.current) return;
2414
+ if (key.escape) {
2415
+ resolvedRef.current = true;
2416
+ onCancel();
2417
+ return;
857
2418
  }
858
- case "exit":
859
- exit();
860
- return true;
861
- default: {
862
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
863
- if (skillCmd) {
864
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
865
- return false;
866
- }
867
- addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
868
- return true;
2419
+ if (key.return) {
2420
+ handleSubmit();
2421
+ return;
869
2422
  }
2423
+ if (key.backspace || key.delete) {
2424
+ valueRef.current = valueRef.current.slice(0, -1);
2425
+ setValue(valueRef.current);
2426
+ setError(void 0);
2427
+ return;
2428
+ }
2429
+ if (input && !key.ctrl && !key.meta) {
2430
+ valueRef.current = valueRef.current + input;
2431
+ setValue(valueRef.current);
2432
+ setError(void 0);
2433
+ }
2434
+ });
2435
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
2437
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { marginTop: 1, children: [
2438
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "> " }),
2439
+ value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: placeholder }) : null,
2440
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "\u2588" })
2441
+ ] }),
2442
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "red", children: error }),
2443
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
2444
+ ] });
2445
+ }
2446
+
2447
+ // src/ui/plugin-tui-handlers.ts
2448
+ function handleMainSelect(value, nav) {
2449
+ if (value === "marketplace") {
2450
+ nav.push({ screen: "marketplace-list" });
2451
+ } else if (value === "installed") {
2452
+ nav.push({ screen: "installed-list" });
870
2453
  }
871
2454
  }
872
- function useSlashCommands(session, addMessage, setMessages, exit, registry) {
873
- return (0, import_react5.useCallback)(
874
- async (input) => {
875
- const parts = input.slice(1).split(/\s+/);
876
- const cmd = parts[0]?.toLowerCase() ?? "";
877
- return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
878
- },
879
- [session, addMessage, setMessages, exit, registry]
880
- );
2455
+ function handleMarketplaceListSelect(value, nav) {
2456
+ if (value === "__add__") {
2457
+ nav.push({ screen: "marketplace-add" });
2458
+ } else {
2459
+ nav.push({ screen: "marketplace-action", context: { marketplace: value } });
2460
+ }
881
2461
  }
882
- function StreamingIndicator({ text }) {
883
- if (text) {
884
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
885
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: "cyan", bold: true, children: [
886
- "Robota:",
887
- " "
888
- ] }),
889
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { children: " " }),
890
- /* @__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) }) })
891
- ] });
2462
+ function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
2463
+ if (value === "browse") {
2464
+ nav.push({ screen: "marketplace-browse", context: { marketplace } });
2465
+ } else if (value === "update") {
2466
+ callbacks.marketplaceUpdate(marketplace).then(() => {
2467
+ nav.notify(`Updated marketplace "${marketplace}".`);
2468
+ nav.pop();
2469
+ }).catch((err) => {
2470
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2471
+ });
2472
+ } else if (value === "remove") {
2473
+ nav.setConfirm({
2474
+ message: `Remove marketplace "${marketplace}" and all its plugins?`,
2475
+ onConfirm: () => {
2476
+ nav.setConfirm(void 0);
2477
+ callbacks.marketplaceRemove(marketplace).then(() => {
2478
+ nav.notify(`Removed marketplace "${marketplace}".`);
2479
+ nav.popN(2);
2480
+ }).catch((err) => {
2481
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2482
+ });
2483
+ },
2484
+ onCancel: () => nav.setConfirm(void 0)
2485
+ });
892
2486
  }
893
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
894
2487
  }
895
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
896
- setIsThinking(true);
897
- clearStreamingText();
898
- try {
899
- const response = await session.run(prompt);
900
- clearStreamingText();
901
- addMessage({ role: "assistant", content: response || "(empty response)" });
902
- setContextPercentage(session.getContextState().usedPercentage);
903
- } catch (err) {
904
- clearStreamingText();
905
- if (err instanceof DOMException && err.name === "AbortError") {
906
- addMessage({ role: "system", content: "Cancelled." });
907
- } else {
908
- const errMsg = err instanceof Error ? err.message : String(err);
909
- addMessage({ role: "system", content: `Error: ${errMsg}` });
910
- }
911
- } finally {
912
- setIsThinking(false);
2488
+ function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
2489
+ const fullId = `${value}@${marketplace}`;
2490
+ const item = items.find((i) => i.value === value);
2491
+ if (item?.hint === "installed") {
2492
+ nav.push({ screen: "installed-action", context: { pluginId: fullId } });
2493
+ } else {
2494
+ nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
913
2495
  }
914
2496
  }
915
- function buildSkillPrompt(input, registry) {
916
- const parts = input.slice(1).split(/\s+/);
917
- const cmd = parts[0]?.toLowerCase() ?? "";
918
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
919
- if (!skillCmd) return null;
920
- const args = parts.slice(1).join(" ").trim();
921
- return args ? `Use the "${cmd}" skill: ${args}` : `Use the "${cmd}" skill: ${skillCmd.description}`;
2497
+ function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
2498
+ const scope = value;
2499
+ callbacks.install(pluginId, scope).then(() => {
2500
+ nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
2501
+ nav.popN(2);
2502
+ }).catch((err) => {
2503
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2504
+ });
922
2505
  }
923
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
924
- return (0, import_react5.useCallback)(
925
- async (input) => {
926
- if (input.startsWith("/")) {
927
- const handled = await handleSlashCommand(input);
928
- if (handled) {
929
- setContextPercentage(session.getContextState().usedPercentage);
930
- return;
2506
+ function handleInstalledListSelect(value, callbacks, nav) {
2507
+ nav.setConfirm({
2508
+ message: `Uninstall plugin "${value}"?`,
2509
+ onConfirm: () => {
2510
+ nav.setConfirm(void 0);
2511
+ callbacks.uninstall(value).then(() => {
2512
+ nav.notify(`Uninstalled plugin "${value}".`);
2513
+ nav.refresh();
2514
+ }).catch((err) => {
2515
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2516
+ });
2517
+ },
2518
+ onCancel: () => nav.setConfirm(void 0)
2519
+ });
2520
+ }
2521
+ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
2522
+ if (value === "uninstall") {
2523
+ nav.setConfirm({
2524
+ message: `Uninstall plugin "${pluginId}"?`,
2525
+ onConfirm: () => {
2526
+ nav.setConfirm(void 0);
2527
+ callbacks.uninstall(pluginId).then(() => {
2528
+ nav.notify(`Uninstalled plugin "${pluginId}".`);
2529
+ nav.popN(2);
2530
+ }).catch((err) => {
2531
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2532
+ });
2533
+ },
2534
+ onCancel: () => nav.setConfirm(void 0)
2535
+ });
2536
+ }
2537
+ }
2538
+
2539
+ // src/ui/PluginTUI.tsx
2540
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2541
+ function PluginTUI({ callbacks, onClose, addMessage }) {
2542
+ const [stack, setStack] = (0, import_react15.useState)([{ screen: "main" }]);
2543
+ const [items, setItems] = (0, import_react15.useState)([]);
2544
+ const [loading, setLoading] = (0, import_react15.useState)(false);
2545
+ const [error, setError] = (0, import_react15.useState)();
2546
+ const [confirm, setConfirm] = (0, import_react15.useState)();
2547
+ const [refreshCounter, setRefreshCounter] = (0, import_react15.useState)(0);
2548
+ const current = stack[stack.length - 1] ?? { screen: "main" };
2549
+ const push = (0, import_react15.useCallback)((state) => {
2550
+ setStack((prev) => [...prev, state]);
2551
+ setItems([]);
2552
+ setError(void 0);
2553
+ }, []);
2554
+ const pop = (0, import_react15.useCallback)(() => {
2555
+ setStack((prev) => {
2556
+ if (prev.length <= 1) {
2557
+ onClose();
2558
+ return prev;
2559
+ }
2560
+ return prev.slice(0, -1);
2561
+ });
2562
+ setItems([]);
2563
+ setError(void 0);
2564
+ }, [onClose]);
2565
+ const popN = (0, import_react15.useCallback)(
2566
+ (n) => {
2567
+ setStack((prev) => {
2568
+ const next = prev.slice(0, Math.max(1, prev.length - n));
2569
+ if (next.length === 0) {
2570
+ onClose();
2571
+ return prev;
931
2572
  }
932
- const prompt = buildSkillPrompt(input, registry);
933
- if (!prompt) return;
934
- return runSessionPrompt(
935
- prompt,
936
- session,
937
- addMessage,
938
- clearStreamingText,
939
- setIsThinking,
940
- setContextPercentage
2573
+ return next;
2574
+ });
2575
+ setItems([]);
2576
+ setError(void 0);
2577
+ },
2578
+ [onClose]
2579
+ );
2580
+ const notify = (0, import_react15.useCallback)(
2581
+ (content) => {
2582
+ addMessage?.({ role: "system", content });
2583
+ },
2584
+ [addMessage]
2585
+ );
2586
+ const refresh = (0, import_react15.useCallback)(() => {
2587
+ setItems([]);
2588
+ setRefreshCounter((c) => c + 1);
2589
+ }, []);
2590
+ const nav = { push, pop, popN, notify, setConfirm, refresh };
2591
+ (0, import_react15.useEffect)(() => {
2592
+ const screen2 = current.screen;
2593
+ if (screen2 === "marketplace-list") {
2594
+ setLoading(true);
2595
+ callbacks.marketplaceList().then((sources) => {
2596
+ const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
2597
+ const sourceItems = sources.map((s) => ({
2598
+ label: s.name,
2599
+ value: s.name,
2600
+ hint: s.type
2601
+ }));
2602
+ setItems([...baseItems, ...sourceItems]);
2603
+ setLoading(false);
2604
+ }).catch((err) => {
2605
+ setError(err instanceof Error ? err.message : String(err));
2606
+ setLoading(false);
2607
+ });
2608
+ } else if (screen2 === "marketplace-browse") {
2609
+ const marketplace = current.context?.marketplace ?? "";
2610
+ setLoading(true);
2611
+ callbacks.listAvailablePlugins(marketplace).then((plugins) => {
2612
+ setItems(
2613
+ plugins.map((p) => ({
2614
+ label: p.name,
2615
+ value: p.name,
2616
+ hint: p.installed ? "installed" : p.description
2617
+ }))
2618
+ );
2619
+ setLoading(false);
2620
+ }).catch((err) => {
2621
+ setError(err instanceof Error ? err.message : String(err));
2622
+ setLoading(false);
2623
+ });
2624
+ } else if (screen2 === "installed-list") {
2625
+ setLoading(true);
2626
+ callbacks.listInstalled().then((plugins) => {
2627
+ setItems(
2628
+ plugins.map((p) => ({
2629
+ label: p.name,
2630
+ value: p.name,
2631
+ hint: p.description
2632
+ }))
941
2633
  );
2634
+ setLoading(false);
2635
+ }).catch((err) => {
2636
+ setError(err instanceof Error ? err.message : String(err));
2637
+ setLoading(false);
2638
+ });
2639
+ }
2640
+ }, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
2641
+ const handleSelect = (0, import_react15.useCallback)(
2642
+ (value) => {
2643
+ const screen2 = current.screen;
2644
+ const ctx = current.context;
2645
+ if (screen2 === "main") handleMainSelect(value, nav);
2646
+ else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
2647
+ else if (screen2 === "marketplace-action")
2648
+ handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
2649
+ else if (screen2 === "marketplace-browse")
2650
+ handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
2651
+ else if (screen2 === "marketplace-install-scope")
2652
+ handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
2653
+ else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
2654
+ else if (screen2 === "installed-action")
2655
+ handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
2656
+ },
2657
+ [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
2658
+ );
2659
+ const handleTextSubmit = (0, import_react15.useCallback)(
2660
+ (value) => {
2661
+ if (current.screen === "marketplace-add") {
2662
+ callbacks.marketplaceAdd(value).then((name) => {
2663
+ notify(`Added marketplace "${name}" from ${value}.`);
2664
+ pop();
2665
+ }).catch((err) => {
2666
+ notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2667
+ pop();
2668
+ });
942
2669
  }
943
- addMessage({ role: "user", content: input });
944
- return runSessionPrompt(
945
- input,
946
- session,
947
- addMessage,
948
- clearStreamingText,
949
- setIsThinking,
950
- setContextPercentage
951
- );
952
2670
  },
953
- [
954
- session,
955
- addMessage,
956
- handleSlashCommand,
957
- clearStreamingText,
958
- setIsThinking,
959
- setContextPercentage,
960
- registry
2671
+ [current.screen, callbacks, notify, pop]
2672
+ );
2673
+ if (confirm) {
2674
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2675
+ ConfirmPrompt,
2676
+ {
2677
+ message: confirm.message,
2678
+ onSelect: (index) => {
2679
+ if (index === 0) confirm.onConfirm();
2680
+ else confirm.onCancel();
2681
+ }
2682
+ }
2683
+ );
2684
+ }
2685
+ const screen = current.screen;
2686
+ if (screen === "marketplace-add") {
2687
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2688
+ TextPrompt,
2689
+ {
2690
+ title: "Add Marketplace Source",
2691
+ placeholder: "owner/repo or git URL",
2692
+ onSubmit: handleTextSubmit,
2693
+ onCancel: pop,
2694
+ validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
2695
+ }
2696
+ );
2697
+ }
2698
+ if (screen === "marketplace-action") {
2699
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2700
+ MenuSelect,
2701
+ {
2702
+ title: `Marketplace: ${current.context?.marketplace ?? ""}`,
2703
+ items: [
2704
+ { label: "Browse plugins", value: "browse" },
2705
+ { label: "Update", value: "update" },
2706
+ { label: "Remove", value: "remove" }
2707
+ ],
2708
+ onSelect: handleSelect,
2709
+ onBack: pop
2710
+ },
2711
+ stack.length
2712
+ );
2713
+ }
2714
+ if (screen === "marketplace-install-scope") {
2715
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2716
+ MenuSelect,
2717
+ {
2718
+ title: `Install scope for "${current.context?.pluginId ?? ""}"`,
2719
+ items: [
2720
+ { label: "User scope", value: "user" },
2721
+ { label: "Project scope", value: "project" }
2722
+ ],
2723
+ onSelect: handleSelect,
2724
+ onBack: pop
2725
+ },
2726
+ stack.length
2727
+ );
2728
+ }
2729
+ if (screen === "installed-action") {
2730
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2731
+ MenuSelect,
2732
+ {
2733
+ title: `Plugin: ${current.context?.pluginId ?? ""}`,
2734
+ items: [{ label: "Uninstall", value: "uninstall" }],
2735
+ onSelect: handleSelect,
2736
+ onBack: pop
2737
+ },
2738
+ stack.length
2739
+ );
2740
+ }
2741
+ const titleMap = {
2742
+ main: "Plugin Management",
2743
+ "marketplace-list": "Marketplace",
2744
+ "marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
2745
+ "installed-list": "Installed Plugins"
2746
+ };
2747
+ const staticItemsMap = {
2748
+ main: [
2749
+ { label: "Marketplace", value: "marketplace" },
2750
+ { label: "Installed Plugins", value: "installed" }
961
2751
  ]
2752
+ };
2753
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2754
+ MenuSelect,
2755
+ {
2756
+ title: titleMap[screen] ?? "Plugin Management",
2757
+ items: staticItemsMap[screen] ?? items,
2758
+ onSelect: handleSelect,
2759
+ onBack: pop,
2760
+ loading,
2761
+ error
2762
+ },
2763
+ `${screen}-${stack.length}-${refreshCounter}`
962
2764
  );
963
2765
  }
964
- function useCommandRegistry(cwd) {
965
- const registryRef = (0, import_react5.useRef)(null);
966
- if (registryRef.current === null) {
967
- const registry = new CommandRegistry();
968
- registry.addSource(new BuiltinCommandSource());
969
- registry.addSource(new SkillCommandSource(cwd));
970
- registryRef.current = registry;
2766
+
2767
+ // src/ui/App.tsx
2768
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2769
+ var EXIT_DELAY_MS2 = 500;
2770
+ function mergeHooksIntoConfig(configHooks, pluginHooks) {
2771
+ const pluginKeys = Object.keys(pluginHooks);
2772
+ if (pluginKeys.length === 0) return configHooks;
2773
+ const merged = {};
2774
+ for (const [event, groups] of Object.entries(pluginHooks)) {
2775
+ merged[event] = [...groups];
2776
+ }
2777
+ if (configHooks) {
2778
+ for (const [event, groups] of Object.entries(configHooks)) {
2779
+ if (!Array.isArray(groups)) continue;
2780
+ if (!merged[event]) merged[event] = [];
2781
+ merged[event].push(...groups);
2782
+ }
971
2783
  }
972
- return registryRef.current;
2784
+ return merged;
973
2785
  }
974
2786
  function App(props) {
975
- const { exit } = (0, import_ink8.useApp)();
976
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
2787
+ const { exit } = (0, import_ink13.useApp)();
2788
+ const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
2789
+ const configWithPluginHooks = {
2790
+ ...props.config,
2791
+ hooks: mergeHooksIntoConfig(
2792
+ props.config.hooks,
2793
+ pluginHooks
2794
+ )
2795
+ };
2796
+ const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
2797
+ { ...props, config: configWithPluginHooks }
2798
+ );
977
2799
  const { messages, setMessages, addMessage } = useMessages();
978
- const [isThinking, setIsThinking] = (0, import_react5.useState)(false);
979
- const [contextPercentage, setContextPercentage] = (0, import_react5.useState)(0);
980
- const registry = useCommandRegistry(props.cwd ?? process.cwd());
981
- const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry);
982
- const handleSubmit = useSubmitHandler(
2800
+ const [isThinking, setIsThinking] = (0, import_react16.useState)(false);
2801
+ const initialCtx = session.getContextState();
2802
+ const [contextState, setContextState] = (0, import_react16.useState)({
2803
+ percentage: initialCtx.usedPercentage,
2804
+ usedTokens: initialCtx.usedTokens,
2805
+ maxTokens: initialCtx.maxTokens
2806
+ });
2807
+ const pendingModelChangeRef = (0, import_react16.useRef)(null);
2808
+ const [pendingModelId, setPendingModelId] = (0, import_react16.useState)(null);
2809
+ const [showPluginTUI, setShowPluginTUI] = (0, import_react16.useState)(false);
2810
+ const [isAborting, setIsAborting] = (0, import_react16.useState)(false);
2811
+ const [pendingPrompt, setPendingPrompt] = (0, import_react16.useState)(null);
2812
+ const pendingPromptRef = (0, import_react16.useRef)(null);
2813
+ const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
2814
+ const handleSlashCommand = useSlashCommands(
2815
+ session,
2816
+ addMessage,
2817
+ setMessages,
2818
+ exit,
2819
+ registry,
2820
+ pendingModelChangeRef,
2821
+ setPendingModelId,
2822
+ pluginCallbacks,
2823
+ setShowPluginTUI
2824
+ );
2825
+ const executePrompt = useSubmitHandler(
983
2826
  session,
984
2827
  addMessage,
985
2828
  handleSlashCommand,
986
2829
  clearStreamingText,
987
2830
  setIsThinking,
988
- setContextPercentage,
2831
+ setContextState,
989
2832
  registry
990
2833
  );
991
- (0, import_ink8.useInput)(
2834
+ const handleSubmit = (0, import_react16.useCallback)(
2835
+ async (input) => {
2836
+ if (isThinking) {
2837
+ setPendingPrompt(input);
2838
+ pendingPromptRef.current = input;
2839
+ return;
2840
+ }
2841
+ await executePrompt(input);
2842
+ },
2843
+ [isThinking, executePrompt]
2844
+ );
2845
+ (0, import_ink13.useInput)(
992
2846
  (_input, key) => {
993
- if (key.ctrl && _input === "c") exit();
994
- if (key.escape && isThinking) session.abort();
2847
+ if (key.escape && isThinking) {
2848
+ setIsAborting(true);
2849
+ setPendingPrompt(null);
2850
+ pendingPromptRef.current = null;
2851
+ session.abort();
2852
+ }
995
2853
  },
996
- { isActive: !permissionRequest }
2854
+ { isActive: !permissionRequest && !showPluginTUI }
997
2855
  );
998
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
999
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1000
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: `
2856
+ (0, import_react16.useEffect)(() => {
2857
+ if (!isThinking) {
2858
+ setIsAborting(false);
2859
+ if (pendingPromptRef.current) {
2860
+ const prompt = pendingPromptRef.current;
2861
+ setPendingPrompt(null);
2862
+ pendingPromptRef.current = null;
2863
+ setTimeout(() => executePrompt(prompt), 0);
2864
+ }
2865
+ }
2866
+ }, [isThinking, pendingPrompt, executePrompt]);
2867
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", children: [
2868
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2869
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { color: "cyan", bold: true, children: `
1001
2870
  ____ ___ ____ ___ _____ _
1002
2871
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1003
2872
  | |_) | | | | _ \\| | | || | / _ \\
1004
2873
  | _ <| |_| | |_) | |_| || |/ ___ \\
1005
2874
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1006
2875
  ` }),
1007
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
2876
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
1008
2877
  " v",
1009
2878
  props.version ?? "0.0.0"
1010
2879
  ] })
1011
2880
  ] }),
1012
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1013
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageList, { messages }),
1014
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StreamingIndicator, { text: streamingText }) })
2881
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2882
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageList, { messages }),
2883
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
1015
2884
  ] }),
1016
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PermissionPrompt, { request: permissionRequest }),
1017
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2885
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PermissionPrompt, { request: permissionRequest }),
2886
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2887
+ ConfirmPrompt,
2888
+ {
2889
+ message: `Change model to ${(0, import_agent_core6.getModelName)(pendingModelId)}? This will restart the session.`,
2890
+ onSelect: (index) => {
2891
+ setPendingModelId(null);
2892
+ pendingModelChangeRef.current = null;
2893
+ if (index === 0) {
2894
+ try {
2895
+ const settingsPath = getUserSettingsPath();
2896
+ updateModelInSettings(settingsPath, pendingModelId);
2897
+ addMessage(
2898
+ (0, import_agent_core7.createSystemMessage)(
2899
+ `Model changed to ${(0, import_agent_core6.getModelName)(pendingModelId)}. Restarting...`
2900
+ )
2901
+ );
2902
+ setTimeout(() => exit(), EXIT_DELAY_MS2);
2903
+ } catch (err) {
2904
+ addMessage(
2905
+ (0, import_agent_core7.createSystemMessage)(
2906
+ `Failed: ${err instanceof Error ? err.message : String(err)}`
2907
+ )
2908
+ );
2909
+ }
2910
+ } else {
2911
+ addMessage((0, import_agent_core7.createSystemMessage)("Model change cancelled."));
2912
+ }
2913
+ }
2914
+ }
2915
+ ),
2916
+ showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2917
+ PluginTUI,
2918
+ {
2919
+ callbacks: pluginCallbacks,
2920
+ onClose: () => setShowPluginTUI(false),
2921
+ addMessage: (msg) => addMessage((0, import_agent_core7.createSystemMessage)(msg.content))
2922
+ }
2923
+ ),
2924
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1018
2925
  StatusBar,
1019
2926
  {
1020
2927
  permissionMode: session.getPermissionMode(),
1021
- modelName: props.config.provider.model,
2928
+ modelName: (0, import_agent_core6.getModelName)(props.config.provider.model),
1022
2929
  sessionId: session.getSessionId(),
1023
2930
  messageCount: messages.length,
1024
2931
  isThinking,
1025
- contextPercentage,
1026
- contextUsedTokens: session.getContextState().usedTokens,
1027
- contextMaxTokens: session.getContextState().maxTokens
2932
+ contextPercentage: contextState.percentage,
2933
+ contextUsedTokens: contextState.usedTokens,
2934
+ contextMaxTokens: contextState.maxTokens
1028
2935
  }
1029
2936
  ),
1030
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2937
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1031
2938
  InputArea,
1032
2939
  {
1033
2940
  onSubmit: handleSubmit,
1034
- isDisabled: isThinking || !!permissionRequest,
2941
+ onCancelQueue: () => {
2942
+ setPendingPrompt(null);
2943
+ pendingPromptRef.current = null;
2944
+ },
2945
+ isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
2946
+ isAborting,
2947
+ pendingPrompt,
1035
2948
  registry
1036
2949
  }
1037
- )
2950
+ ),
2951
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { children: " " })
1038
2952
  ] });
1039
2953
  }
1040
2954
 
1041
2955
  // src/ui/render.tsx
1042
- var import_jsx_runtime9 = require("react/jsx-runtime");
2956
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1043
2957
  function renderApp(options) {
1044
2958
  process.on("unhandledRejection", (reason) => {
1045
2959
  process.stderr.write(`
@@ -1050,29 +2964,50 @@ function renderApp(options) {
1050
2964
  `);
1051
2965
  }
1052
2966
  });
1053
- const instance = (0, import_ink9.render)(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App, { ...options }), {
2967
+ if (process.stdin.isTTY && process.stdout.isTTY) {
2968
+ process.stdout.write("\x1B[?2004h");
2969
+ }
2970
+ const instance = (0, import_ink14.render)(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(App, { ...options }), {
1054
2971
  exitOnCtrlC: true
1055
2972
  });
1056
- instance.waitUntilExit().catch((err) => {
2973
+ instance.waitUntilExit().then(() => {
2974
+ if (process.stdout.isTTY) {
2975
+ process.stdout.write("\x1B[?2004l");
2976
+ }
2977
+ process.exit(0);
2978
+ }).catch((err) => {
1057
2979
  if (err) {
1058
2980
  process.stderr.write(`
1059
2981
  [EXIT ERROR] ${err}
1060
2982
  `);
1061
2983
  }
2984
+ process.exit(1);
1062
2985
  });
1063
2986
  }
1064
2987
 
1065
2988
  // src/cli.ts
1066
2989
  var import_meta = {};
1067
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
2990
+ function checkSettingsFile(filePath) {
2991
+ if (!(0, import_node_fs4.existsSync)(filePath)) return "missing";
2992
+ try {
2993
+ const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8").trim();
2994
+ if (raw.length === 0) return "incomplete";
2995
+ const parsed = JSON.parse(raw);
2996
+ const provider = parsed.provider;
2997
+ if (!provider?.apiKey) return "incomplete";
2998
+ return "valid";
2999
+ } catch {
3000
+ return "corrupt";
3001
+ }
3002
+ }
1068
3003
  function readVersion() {
1069
3004
  try {
1070
3005
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
1071
- const dir = (0, import_node_path2.dirname)(thisFile);
1072
- const candidates = [(0, import_node_path2.join)(dir, "..", "..", "package.json"), (0, import_node_path2.join)(dir, "..", "package.json")];
3006
+ const dir = (0, import_node_path5.dirname)(thisFile);
3007
+ const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
1073
3008
  for (const pkgPath of candidates) {
1074
3009
  try {
1075
- const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
3010
+ const raw = (0, import_node_fs4.readFileSync)(pkgPath, "utf-8");
1076
3011
  const pkg = JSON.parse(raw);
1077
3012
  if (pkg.version !== void 0 && pkg.name !== void 0) {
1078
3013
  return pkg.version;
@@ -1085,97 +3020,107 @@ function readVersion() {
1085
3020
  return "0.0.0";
1086
3021
  }
1087
3022
  }
1088
- function parsePermissionMode(raw) {
1089
- if (raw === void 0) return void 0;
1090
- if (!VALID_MODES.includes(raw)) {
1091
- process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
1092
- `);
1093
- process.exit(1);
1094
- }
1095
- return raw;
1096
- }
1097
- function parseMaxTurns(raw) {
1098
- if (raw === void 0) return void 0;
1099
- const n = parseInt(raw, 10);
1100
- if (isNaN(n) || n <= 0) {
1101
- process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
1102
- `);
1103
- process.exit(1);
1104
- }
1105
- return n;
1106
- }
1107
- function parseCliArgs() {
1108
- const { values, positionals } = (0, import_node_util.parseArgs)({
1109
- allowPositionals: true,
1110
- options: {
1111
- p: { type: "boolean", short: "p", default: false },
1112
- c: { type: "boolean", short: "c", default: false },
1113
- r: { type: "string", short: "r" },
1114
- model: { type: "string" },
1115
- "permission-mode": { type: "string" },
1116
- "max-turns": { type: "string" },
1117
- version: { type: "boolean", default: false }
1118
- }
3023
+ function promptInput(label, masked = false) {
3024
+ return new Promise((resolve) => {
3025
+ process.stdout.write(label);
3026
+ let input = "";
3027
+ const stdin = process.stdin;
3028
+ const wasRaw = stdin.isRaw;
3029
+ stdin.setRawMode(true);
3030
+ stdin.resume();
3031
+ stdin.setEncoding("utf8");
3032
+ const onData = (data) => {
3033
+ for (const ch of data) {
3034
+ if (ch === "\r" || ch === "\n") {
3035
+ stdin.removeListener("data", onData);
3036
+ stdin.setRawMode(wasRaw ?? false);
3037
+ stdin.pause();
3038
+ process.stdout.write("\n");
3039
+ resolve(input.trim());
3040
+ return;
3041
+ } else if (ch === "\x7F" || ch === "\b") {
3042
+ if (input.length > 0) {
3043
+ input = input.slice(0, -1);
3044
+ process.stdout.write("\b \b");
3045
+ }
3046
+ } else if (ch === "") {
3047
+ process.stdout.write("\n");
3048
+ process.exit(0);
3049
+ } else if (ch.charCodeAt(0) >= 32) {
3050
+ input += ch;
3051
+ process.stdout.write(masked ? "*" : ch);
3052
+ }
3053
+ }
3054
+ };
3055
+ stdin.on("data", onData);
1119
3056
  });
1120
- return {
1121
- positional: positionals,
1122
- printMode: values["p"] ?? false,
1123
- continueMode: values["c"] ?? false,
1124
- resumeId: values["r"],
1125
- model: values["model"],
1126
- permissionMode: parsePermissionMode(values["permission-mode"]),
1127
- maxTurns: parseMaxTurns(values["max-turns"]),
1128
- version: values["version"] ?? false
1129
- };
1130
3057
  }
1131
- var PrintTerminal = class {
1132
- write(text) {
1133
- process.stdout.write(text);
3058
+ async function ensureConfig(cwd) {
3059
+ const userPath = getUserSettingsPath();
3060
+ const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
3061
+ const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
3062
+ const paths = [userPath, projectPath, localPath];
3063
+ const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
3064
+ if (checks.some((c) => c.status === "valid")) {
3065
+ return;
1134
3066
  }
1135
- writeLine(text) {
1136
- process.stdout.write(text + "\n");
3067
+ const corrupt = checks.filter((c) => c.status === "corrupt");
3068
+ const incomplete = checks.filter((c) => c.status === "incomplete");
3069
+ process.stdout.write("\n");
3070
+ if (corrupt.length > 0) {
3071
+ for (const c of corrupt) {
3072
+ process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
3073
+ `);
3074
+ }
3075
+ process.stdout.write("\n");
1137
3076
  }
1138
- writeMarkdown(md) {
1139
- process.stdout.write(md);
3077
+ if (incomplete.length > 0) {
3078
+ for (const c of incomplete) {
3079
+ process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
3080
+ `);
3081
+ }
3082
+ process.stdout.write("\n");
1140
3083
  }
1141
- writeError(text) {
1142
- process.stderr.write(text + "\n");
3084
+ if (corrupt.length === 0 && incomplete.length === 0) {
3085
+ process.stdout.write(" Welcome to Robota CLI!\n");
3086
+ process.stdout.write(" No configuration found. Let's set up.\n");
3087
+ } else {
3088
+ process.stdout.write(" Reconfiguring...\n");
1143
3089
  }
1144
- prompt(question) {
1145
- return new Promise((resolve) => {
1146
- const rl = readline.createInterface({
1147
- input: process.stdin,
1148
- output: process.stdout,
1149
- terminal: false,
1150
- historySize: 0
1151
- });
1152
- rl.question(question, (answer) => {
1153
- rl.close();
1154
- resolve(answer);
1155
- });
1156
- });
3090
+ process.stdout.write("\n");
3091
+ const apiKey = await promptInput(" Anthropic API key: ", true);
3092
+ if (!apiKey) {
3093
+ process.stderr.write("\n No API key provided. Exiting.\n");
3094
+ process.exit(1);
1157
3095
  }
1158
- async select(options, initialIndex = 0) {
1159
- for (let i = 0; i < options.length; i++) {
1160
- const marker = i === initialIndex ? ">" : " ";
1161
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
1162
- `);
3096
+ const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
3097
+ const settingsDir = (0, import_node_path5.dirname)(userPath);
3098
+ (0, import_node_fs4.mkdirSync)(settingsDir, { recursive: true });
3099
+ const settings = {
3100
+ provider: {
3101
+ name: "anthropic",
3102
+ model: "claude-sonnet-4-6",
3103
+ apiKey
1163
3104
  }
1164
- const answer = await this.prompt(
1165
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
1166
- );
1167
- const trimmed = answer.trim().toLowerCase();
1168
- if (trimmed === "") return initialIndex;
1169
- const num = parseInt(trimmed, 10);
1170
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
1171
- return initialIndex;
3105
+ };
3106
+ if (language) {
3107
+ settings.language = language;
1172
3108
  }
1173
- spinner(_message) {
1174
- return { stop() {
1175
- }, update() {
1176
- } };
3109
+ (0, import_node_fs4.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
3110
+ process.stdout.write(`
3111
+ Config saved to ${userPath}
3112
+
3113
+ `);
3114
+ }
3115
+ function resetConfig() {
3116
+ const userPath = getUserSettingsPath();
3117
+ if (deleteSettings(userPath)) {
3118
+ process.stdout.write(`Deleted ${userPath}
3119
+ `);
3120
+ } else {
3121
+ process.stdout.write("No user settings found.\n");
1177
3122
  }
1178
- };
3123
+ }
1179
3124
  async function startCli() {
1180
3125
  const args = parseCliArgs();
1181
3126
  if (args.version) {
@@ -1183,16 +3128,24 @@ async function startCli() {
1183
3128
  `);
1184
3129
  return;
1185
3130
  }
3131
+ if (args.reset) {
3132
+ resetConfig();
3133
+ return;
3134
+ }
1186
3135
  const cwd = process.cwd();
3136
+ await ensureConfig(cwd);
1187
3137
  const [config, context, projectInfo] = await Promise.all([
1188
- (0, import_agent_sdk2.loadConfig)(cwd),
1189
- (0, import_agent_sdk2.loadContext)(cwd),
1190
- (0, import_agent_sdk2.detectProject)(cwd)
3138
+ (0, import_agent_sdk5.loadConfig)(cwd),
3139
+ (0, import_agent_sdk5.loadContext)(cwd),
3140
+ (0, import_agent_sdk5.detectProject)(cwd)
1191
3141
  ]);
1192
3142
  if (args.model !== void 0) {
1193
3143
  config.provider.model = args.model;
1194
3144
  }
1195
- const sessionStore = new import_agent_sdk2.SessionStore();
3145
+ if (args.language !== void 0) {
3146
+ config.language = args.language;
3147
+ }
3148
+ const sessionStore = new import_agent_sdk5.SessionStore();
1196
3149
  if (args.printMode) {
1197
3150
  const prompt = args.positional.join(" ").trim();
1198
3151
  if (prompt.length === 0) {
@@ -1200,15 +3153,15 @@ async function startCli() {
1200
3153
  process.exit(1);
1201
3154
  }
1202
3155
  const terminal = new PrintTerminal();
1203
- const paths = (0, import_agent_sdk2.projectPaths)(cwd);
1204
- const session = (0, import_agent_sdk2.createSession)({
3156
+ const paths = (0, import_agent_sdk5.projectPaths)(cwd);
3157
+ const session = (0, import_agent_sdk5.createSession)({
1205
3158
  config,
1206
3159
  context,
1207
3160
  terminal,
1208
- sessionLogger: new import_agent_sdk2.FileSessionLogger(paths.logs),
3161
+ sessionLogger: new import_agent_sdk5.FileSessionLogger(paths.logs),
1209
3162
  projectInfo,
1210
3163
  permissionMode: args.permissionMode,
1211
- promptForApproval
3164
+ promptForApproval: import_agent_sdk6.promptForApproval
1212
3165
  });
1213
3166
  const response = await session.run(prompt);
1214
3167
  process.stdout.write(response + "\n");