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