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

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 DELETED
@@ -1,3180 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
- // If the importer is in node compatibility mode or this is not an ESM
19
- // file that has been converted to a CommonJS file using a Babel-
20
- // compatible transform (i.e. "__esModule" has not been set), then set
21
- // "default" to the CommonJS "module.exports" for node compatibility.
22
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
- mod
24
- ));
25
-
26
- // src/cli.ts
27
- var import_node_fs4 = require("fs");
28
- var import_node_path5 = require("path");
29
- var import_node_url = require("url");
30
- var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
31
- var import_agent_sdk6 = require("@robota-sdk/agent-sdk");
32
-
33
- // src/utils/cli-args.ts
34
- var import_node_util = require("util");
35
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
36
- function parsePermissionMode(raw) {
37
- if (raw === void 0) return void 0;
38
- if (!VALID_MODES.includes(raw)) {
39
- process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
40
- `);
41
- process.exit(1);
42
- }
43
- return raw;
44
- }
45
- function parseMaxTurns(raw) {
46
- if (raw === void 0) return void 0;
47
- const n = parseInt(raw, 10);
48
- if (isNaN(n) || n <= 0) {
49
- process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
50
- `);
51
- process.exit(1);
52
- }
53
- return n;
54
- }
55
- function parseCliArgs() {
56
- const { values, positionals } = (0, import_node_util.parseArgs)({
57
- allowPositionals: true,
58
- options: {
59
- p: { type: "boolean", short: "p", default: false },
60
- c: { type: "boolean", short: "c", default: false },
61
- r: { type: "string", short: "r" },
62
- model: { type: "string" },
63
- 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;
112
- }
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
-
165
- // src/ui/render.tsx
166
- var import_ink14 = require("ink");
167
-
168
- // src/ui/App.tsx
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");
176
- var import_agent_sdk = require("@robota-sdk/agent-sdk");
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
-
1043
- // src/commands/command-registry.ts
1044
- var CommandRegistry = class {
1045
- sources = [];
1046
- addSource(source) {
1047
- this.sources.push(source);
1048
- }
1049
- /** Get all commands, optionally filtered by prefix */
1050
- getCommands(filter) {
1051
- const all = [];
1052
- for (const source of this.sources) {
1053
- all.push(...source.getCommands());
1054
- }
1055
- if (!filter) return all;
1056
- const lower = filter.toLowerCase();
1057
- return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
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
- }
1067
- /** Get subcommands for a specific command */
1068
- getSubcommands(commandName) {
1069
- const lower = commandName.toLowerCase();
1070
- for (const source of this.sources) {
1071
- for (const cmd of source.getCommands()) {
1072
- if (cmd.name.toLowerCase() === lower && cmd.subcommands) {
1073
- return cmd.subcommands;
1074
- }
1075
- }
1076
- }
1077
- return [];
1078
- }
1079
- };
1080
-
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
- }
1097
- function createBuiltinCommands() {
1098
- return [
1099
- { name: "help", description: "Show available commands", source: "builtin" },
1100
- { name: "clear", description: "Clear conversation history", source: "builtin" },
1101
- {
1102
- name: "mode",
1103
- description: "Permission mode",
1104
- source: "builtin",
1105
- subcommands: [
1106
- { name: "plan", description: "Plan only, no execution", source: "builtin" },
1107
- { name: "default", description: "Ask before risky actions", source: "builtin" },
1108
- { name: "acceptEdits", description: "Auto-approve file edits", source: "builtin" },
1109
- { name: "bypassPermissions", description: "Skip all permission checks", source: "builtin" }
1110
- ]
1111
- },
1112
- {
1113
- name: "model",
1114
- description: "Select AI model",
1115
- source: "builtin",
1116
- subcommands: buildModelSubcommands()
1117
- },
1118
- {
1119
- name: "language",
1120
- description: "Set response language",
1121
- source: "builtin",
1122
- subcommands: [
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" }
1127
- ]
1128
- },
1129
- { name: "compact", description: "Compress context window", source: "builtin" },
1130
- { name: "cost", description: "Show session info", source: "builtin" },
1131
- { name: "context", description: "Context window info", source: "builtin" },
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" },
1136
- { name: "exit", description: "Exit CLI", source: "builtin" }
1137
- ];
1138
- }
1139
- var BuiltinCommandSource = class {
1140
- name = "builtin";
1141
- commands;
1142
- constructor() {
1143
- this.commands = createBuiltinCommands();
1144
- }
1145
- getCommands() {
1146
- return this.commands;
1147
- }
1148
- };
1149
-
1150
- // src/commands/skill-source.ts
1151
- var import_node_fs3 = require("fs");
1152
- var import_node_path2 = require("path");
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
- }
1159
- function parseFrontmatter(content) {
1160
- const lines = content.split("\n");
1161
- if (lines[0]?.trim() !== "---") return null;
1162
- const result = {};
1163
- for (let i = 1; i < lines.length; i++) {
1164
- const line = lines[i];
1165
- if (line.trim() === "---") break;
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;
1177
- }
1178
- }
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;
1198
- }
1199
- function scanSkillsDir(skillsDir) {
1200
- if (!(0, import_node_fs3.existsSync)(skillsDir)) return [];
1201
- const commands = [];
1202
- const entries = (0, import_node_fs3.readdirSync)(skillsDir, { withFileTypes: true });
1203
- for (const entry of entries) {
1204
- if (!entry.isDirectory()) continue;
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");
1208
- const frontmatter = parseFrontmatter(content);
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));
1224
- }
1225
- return commands;
1226
- }
1227
- var SkillCommandSource = class {
1228
- name = "skill";
1229
- cwd;
1230
- home;
1231
- cachedCommands = null;
1232
- constructor(cwd, home) {
1233
- this.cwd = cwd;
1234
- this.home = home ?? (0, import_node_os.homedir)();
1235
- }
1236
- getCommands() {
1237
- if (this.cachedCommands) return this.cachedCommands;
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
- }
1256
- }
1257
- }
1258
- this.cachedCommands = merged;
1259
- return this.cachedCommands;
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
- }
1303
- };
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
-
1483
- // src/ui/MessageList.tsx
1484
- var import_react7 = __toESM(require("react"), 1);
1485
- var import_ink2 = require("ink");
1486
- var import_agent_core4 = require("@robota-sdk/agent-core");
1487
-
1488
- // src/ui/render-markdown.ts
1489
- var import_marked = require("marked");
1490
- var import_marked_terminal = __toESM(require("marked-terminal"), 1);
1491
- import_marked.marked.setOptions({
1492
- renderer: new import_marked_terminal.default()
1493
- });
1494
- function renderMarkdown(md) {
1495
- const result = import_marked.marked.parse(md);
1496
- return typeof result === "string" ? result.trimEnd() : md;
1497
- }
1498
-
1499
- // src/ui/DiffBlock.tsx
1500
- var import_ink = require("ink");
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");
1547
- function RoleLabel({ role }) {
1548
- switch (role) {
1549
- case "user":
1550
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
1551
- "You:",
1552
- " "
1553
- ] });
1554
- case "assistant":
1555
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
1556
- "Robota:",
1557
- " "
1558
- ] });
1559
- case "system":
1560
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
1561
- "System:",
1562
- " "
1563
- ] });
1564
- case "tool":
1565
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
1566
- "Tool:",
1567
- " "
1568
- ] });
1569
- }
1570
- }
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:",
1615
- " "
1616
- ] }),
1617
- toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
1618
- "[",
1619
- toolName,
1620
- "]"
1621
- ] })
1622
- ] }),
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))
1630
- ] });
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
- });
1646
- function MessageList({ messages }) {
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)) });
1648
- }
1649
-
1650
- // src/ui/StatusBar.tsx
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");
1654
- var CONTEXT_YELLOW_THRESHOLD = 70;
1655
- var CONTEXT_RED_THRESHOLD = 90;
1656
- function getContextColor(percentage) {
1657
- if (percentage >= CONTEXT_RED_THRESHOLD) return "red";
1658
- if (percentage >= CONTEXT_YELLOW_THRESHOLD) return "yellow";
1659
- return "green";
1660
- }
1661
- function StatusBar({
1662
- permissionMode,
1663
- modelName,
1664
- sessionId: _sessionId,
1665
- messageCount,
1666
- isThinking,
1667
- contextPercentage,
1668
- contextUsedTokens,
1669
- contextMaxTokens
1670
- }) {
1671
- const contextColor = getContextColor(contextPercentage);
1672
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1673
- import_ink3.Box,
1674
- {
1675
- borderStyle: "single",
1676
- borderColor: "gray",
1677
- paddingLeft: 1,
1678
- paddingRight: 1,
1679
- justifyContent: "space-between",
1680
- children: [
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:" }),
1683
- " ",
1684
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
1685
- " | ",
1686
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
1687
- " | ",
1688
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
1689
- "Context: ",
1690
- Math.round(contextPercentage),
1691
- "% (",
1692
- (0, import_agent_core5.formatTokenCount)(contextUsedTokens),
1693
- "/",
1694
- (0, import_agent_core5.formatTokenCount)(contextMaxTokens),
1695
- ")"
1696
- ] })
1697
- ] }),
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: [
1701
- "msgs: ",
1702
- messageCount
1703
- ] })
1704
- ] })
1705
- ]
1706
- }
1707
- );
1708
- }
1709
-
1710
- // src/ui/InputArea.tsx
1711
- var import_react10 = __toESM(require("react"), 1);
1712
- var import_ink7 = require("ink");
1713
-
1714
- // src/ui/CjkTextInput.tsx
1715
- var import_react8 = require("react");
1716
- var import_ink4 = require("ink");
1717
- var import_chalk = __toESM(require("chalk"), 1);
1718
- var import_string_width = __toESM(require("string-width"), 1);
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
- }
1751
- function CjkTextInput({
1752
- value,
1753
- onChange,
1754
- onSubmit,
1755
- onPaste,
1756
- placeholder = "",
1757
- focus = true,
1758
- showCursor = true,
1759
- availableWidth
1760
- }) {
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)("");
1766
- if (value !== valueRef.current) {
1767
- valueRef.current = value;
1768
- if (cursorRef.current > value.length) {
1769
- cursorRef.current = value.length;
1770
- }
1771
- }
1772
- (0, import_ink4.useInput)(
1773
- (input, key) => {
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;
1782
- }
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;
1807
- }
1808
- if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
1809
- return;
1810
- }
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 {
1865
- }
1866
- },
1867
- { isActive: focus }
1868
- );
1869
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
1870
- }
1871
- function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
1872
- if (!showCursor) {
1873
- return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
1874
- }
1875
- if (value.length === 0) {
1876
- if (placeholder.length > 0) {
1877
- return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
1878
- }
1879
- return import_chalk.default.inverse(" ");
1880
- }
1881
- const chars = [...value];
1882
- let rendered = "";
1883
- for (let i = 0; i < chars.length; i++) {
1884
- const char = chars[i] ?? "";
1885
- rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
1886
- }
1887
- if (cursorOffset >= chars.length) {
1888
- rendered += import_chalk.default.inverse(" ");
1889
- }
1890
- return rendered;
1891
- }
1892
-
1893
- // src/ui/WaveText.tsx
1894
- var import_react9 = require("react");
1895
- var import_ink5 = require("ink");
1896
- var import_jsx_runtime5 = require("react/jsx-runtime");
1897
- var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
1898
- var INTERVAL_MS = 400;
1899
- var CHARS_PER_GROUP = 4;
1900
- function WaveText({ text }) {
1901
- const [tick, setTick] = (0, import_react9.useState)(0);
1902
- (0, import_react9.useEffect)(() => {
1903
- const timer = setInterval(() => {
1904
- setTick((prev) => prev + 1);
1905
- }, INTERVAL_MS);
1906
- return () => clearInterval(timer);
1907
- }, []);
1908
- const chars = [...text];
1909
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
1910
- const group = Math.floor(i / CHARS_PER_GROUP);
1911
- const colorIndex = (tick + group) % WAVE_COLORS.length;
1912
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
1913
- }) });
1914
- }
1915
-
1916
- // src/ui/SlashAutocomplete.tsx
1917
- var import_ink6 = require("ink");
1918
- var import_jsx_runtime6 = require("react/jsx-runtime");
1919
- var MAX_VISIBLE = 8;
1920
- function CommandRow(props) {
1921
- const { cmd, isSelected, showSlash } = props;
1922
- const indicator = isSelected ? "\u25B8 " : " ";
1923
- const nameColor = isSelected ? "cyan" : void 0;
1924
- const dimmed = !isSelected;
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
- ] }) });
1929
- }
1930
- function SlashAutocomplete({
1931
- commands,
1932
- selectedIndex,
1933
- visible,
1934
- isSubcommandMode
1935
- }) {
1936
- if (!visible || commands.length === 0) return null;
1937
- const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
1938
- const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
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)(
1940
- CommandRow,
1941
- {
1942
- cmd,
1943
- isSelected: scrollOffset + i === selectedIndex,
1944
- showSlash: !isSubcommandMode
1945
- },
1946
- cmd.name
1947
- )) });
1948
- }
1949
- function computeScrollOffset(selectedIndex, total) {
1950
- if (total <= MAX_VISIBLE) return 0;
1951
- if (selectedIndex < MAX_VISIBLE) return 0;
1952
- const maxOffset = total - MAX_VISIBLE;
1953
- return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
1954
- }
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
-
1962
- // src/ui/InputArea.tsx
1963
- var import_jsx_runtime7 = require("react/jsx-runtime");
1964
- function parseSlashInput(value) {
1965
- if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
1966
- const afterSlash = value.slice(1);
1967
- const spaceIndex = afterSlash.indexOf(" ");
1968
- if (spaceIndex === -1) return { isSlash: true, parentCommand: "", filter: afterSlash };
1969
- const parent = afterSlash.slice(0, spaceIndex);
1970
- const rest = afterSlash.slice(spaceIndex + 1);
1971
- return { isSlash: true, parentCommand: parent, filter: rest };
1972
- }
1973
- function useAutocomplete(value, registry) {
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);
1977
- if (prevValueRef.current !== value) {
1978
- prevValueRef.current = value;
1979
- if (dismissed) setDismissed(false);
1980
- }
1981
- const parsed = parseSlashInput(value);
1982
- const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
1983
- const filteredCommands = (0, import_react10.useMemo)(() => {
1984
- if (!registry || !parsed.isSlash || dismissed) return [];
1985
- if (isSubcommandMode) {
1986
- const subs = registry.getSubcommands(parsed.parentCommand);
1987
- if (subs.length === 0) return [];
1988
- if (!parsed.filter) return subs;
1989
- const lower = parsed.filter.toLowerCase();
1990
- return subs.filter((c) => c.name.toLowerCase().startsWith(lower));
1991
- }
1992
- return registry.getCommands(parsed.filter);
1993
- }, [registry, parsed.isSlash, parsed.parentCommand, parsed.filter, dismissed, isSubcommandMode]);
1994
- const showPopup = parsed.isSlash && filteredCommands.length > 0 && !dismissed;
1995
- if (selectedIndex >= filteredCommands.length && filteredCommands.length > 0) {
1996
- setSelectedIndex(filteredCommands.length - 1);
1997
- }
1998
- return {
1999
- showPopup,
2000
- filteredCommands,
2001
- selectedIndex,
2002
- setSelectedIndex,
2003
- isSubcommandMode,
2004
- setShowPopup: (val) => {
2005
- if (typeof val === "function") {
2006
- setDismissed((prev) => {
2007
- const nextVal = val(!prev);
2008
- return !nextVal;
2009
- });
2010
- } else {
2011
- setDismissed(!val);
2012
- }
2013
- }
2014
- };
2015
- }
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);
2034
- const {
2035
- showPopup,
2036
- filteredCommands,
2037
- selectedIndex,
2038
- setSelectedIndex,
2039
- isSubcommandMode,
2040
- setShowPopup
2041
- } = useAutocomplete(value, registry);
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)(
2051
- (text) => {
2052
- const trimmed = text.trim();
2053
- if (trimmed.length === 0) return;
2054
- if (showPopup && filteredCommands[selectedIndex]) {
2055
- selectCommand(filteredCommands[selectedIndex]);
2056
- return;
2057
- }
2058
- const expanded = expandPasteLabels(trimmed, pasteStore.current);
2059
- setValue("");
2060
- pasteStore.current.clear();
2061
- pasteIdRef.current = 0;
2062
- onSubmit(expanded);
2063
- },
2064
- [showPopup, filteredCommands, selectedIndex, onSubmit]
2065
- );
2066
- const selectCommand = (0, import_react10.useCallback)(
2067
- (cmd) => {
2068
- const parsed = parseSlashInput(value);
2069
- if (parsed.parentCommand) {
2070
- const fullCommand = `/${parsed.parentCommand} ${cmd.name}`;
2071
- setValue("");
2072
- onSubmit(fullCommand);
2073
- return;
2074
- }
2075
- if (cmd.subcommands && cmd.subcommands.length > 0) {
2076
- setValue(`/${cmd.name} `);
2077
- setSelectedIndex(0);
2078
- return;
2079
- }
2080
- setValue("");
2081
- onSubmit(`/${cmd.name}`);
2082
- },
2083
- [value, onSubmit, setSelectedIndex]
2084
- );
2085
- (0, import_ink7.useInput)(
2086
- (_input, key) => {
2087
- if (!showPopup) return;
2088
- if (key.upArrow) {
2089
- setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredCommands.length - 1);
2090
- } else if (key.downArrow) {
2091
- setSelectedIndex((prev) => prev < filteredCommands.length - 1 ? prev + 1 : 0);
2092
- } else if (key.escape) {
2093
- setShowPopup(false);
2094
- } else if (key.tab) {
2095
- const cmd = filteredCommands[selectedIndex];
2096
- if (cmd) selectCommand(cmd);
2097
- }
2098
- },
2099
- { isActive: showPopup && !isDisabled }
2100
- );
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)(
2111
- SlashAutocomplete,
2112
- {
2113
- commands: filteredCommands,
2114
- selectedIndex,
2115
- visible: showPopup,
2116
- isSubcommandMode
2117
- }
2118
- ),
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" })
2190
- ] });
2191
- }
2192
-
2193
- // src/ui/PermissionPrompt.tsx
2194
- var import_react12 = __toESM(require("react"), 1);
2195
- var import_ink9 = require("ink");
2196
- var import_jsx_runtime9 = require("react/jsx-runtime");
2197
- var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
2198
- function formatArgs(args) {
2199
- const entries = Object.entries(args);
2200
- if (entries.length === 0) return "(no arguments)";
2201
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
2202
- }
2203
- function PermissionPrompt({ 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);
2207
- if (prevRequestRef.current !== request) {
2208
- prevRequestRef.current = request;
2209
- resolvedRef.current = false;
2210
- setSelected(0);
2211
- }
2212
- const doResolve = import_react12.default.useCallback(
2213
- (index) => {
2214
- if (resolvedRef.current) return;
2215
- resolvedRef.current = true;
2216
- if (index === 0) request.resolve(true);
2217
- else if (index === 1) request.resolve("allow-session");
2218
- else request.resolve(false);
2219
- },
2220
- [request]
2221
- );
2222
- (0, import_ink9.useInput)((input, key) => {
2223
- if (resolvedRef.current) return;
2224
- if (key.upArrow || key.leftArrow) {
2225
- setSelected((prev) => prev > 0 ? prev - 1 : prev);
2226
- } else if (key.downArrow || key.rightArrow) {
2227
- setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
2228
- } else if (key.return) {
2229
- doResolve(selected);
2230
- } else if (input === "y" || input === "1") {
2231
- doResolve(0);
2232
- } else if (input === "a" || input === "2") {
2233
- doResolve(1);
2234
- } else if (input === "n" || input === "d" || input === "3") {
2235
- doResolve(2);
2236
- }
2237
- });
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: [
2241
- "Tool:",
2242
- " ",
2243
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
2244
- ] }),
2245
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
2246
- " ",
2247
- formatArgs(request.toolArgs)
2248
- ] }),
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: [
2250
- i === selected ? "> " : " ",
2251
- opt
2252
- ] }) }, opt)) }),
2253
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
2254
- ] });
2255
- }
2256
-
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 };
2265
- }
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, {});
2271
- }
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
- ] });
2298
- }
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;
2332
- }
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;
2391
- }
2392
- }
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;
2402
- }
2403
- if (key.return) {
2404
- handleSubmit();
2405
- return;
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" });
2437
- }
2438
- }
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
- }
2445
- }
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
- });
2470
- }
2471
- }
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 } });
2479
- }
2480
- }
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
- });
2489
- }
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;
2556
- }
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
- }))
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
- });
2653
- }
2654
- },
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" }
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}`
2748
- );
2749
- }
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
- }
2767
- }
2768
- return merged;
2769
- }
2770
- function App(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
- );
2783
- const { messages, setMessages, addMessage } = useMessages();
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(
2810
- session,
2811
- addMessage,
2812
- handleSlashCommand,
2813
- clearStreamingText,
2814
- setIsThinking,
2815
- setContextState,
2816
- registry
2817
- );
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)(
2830
- (_input, key) => {
2831
- if (key.escape && isThinking) {
2832
- setIsAborting(true);
2833
- setPendingPrompt(null);
2834
- pendingPromptRef.current = null;
2835
- session.abort();
2836
- }
2837
- },
2838
- { isActive: !permissionRequest && !showPluginTUI }
2839
- );
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: `
2854
- ____ ___ ____ ___ _____ _
2855
- | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
2856
- | |_) | | | | _ \\| | | || | / _ \\
2857
- | _ <| |_| | |_) | |_| || |/ ___ \\
2858
- |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
2859
- ` }),
2860
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
2861
- " v",
2862
- props.version ?? "0.0.0"
2863
- ] })
2864
- ] }),
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 }) })
2868
- ] }),
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)(
2909
- StatusBar,
2910
- {
2911
- permissionMode: session.getPermissionMode(),
2912
- modelName: (0, import_agent_core6.getModelName)(props.config.provider.model),
2913
- sessionId: session.getSessionId(),
2914
- messageCount: messages.length,
2915
- isThinking,
2916
- contextPercentage: contextState.percentage,
2917
- contextUsedTokens: contextState.usedTokens,
2918
- contextMaxTokens: contextState.maxTokens
2919
- }
2920
- ),
2921
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2922
- InputArea,
2923
- {
2924
- onSubmit: handleSubmit,
2925
- onCancelQueue: () => {
2926
- setPendingPrompt(null);
2927
- pendingPromptRef.current = null;
2928
- },
2929
- isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
2930
- isAborting,
2931
- pendingPrompt,
2932
- registry
2933
- }
2934
- ),
2935
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { children: " " })
2936
- ] });
2937
- }
2938
-
2939
- // src/ui/render.tsx
2940
- var import_jsx_runtime15 = require("react/jsx-runtime");
2941
- function renderApp(options) {
2942
- process.on("unhandledRejection", (reason) => {
2943
- process.stderr.write(`
2944
- [UNHANDLED REJECTION] ${reason}
2945
- `);
2946
- if (reason instanceof Error) {
2947
- process.stderr.write(`${reason.stack}
2948
- `);
2949
- }
2950
- });
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 }), {
2955
- exitOnCtrlC: true
2956
- });
2957
- instance.waitUntilExit().then(() => {
2958
- if (process.stdout.isTTY) {
2959
- process.stdout.write("\x1B[?2004l");
2960
- }
2961
- process.exit(0);
2962
- }).catch((err) => {
2963
- if (err) {
2964
- process.stderr.write(`
2965
- [EXIT ERROR] ${err}
2966
- `);
2967
- }
2968
- process.exit(1);
2969
- });
2970
- }
2971
-
2972
- // src/cli.ts
2973
- var import_meta = {};
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
- }
2987
- function readVersion() {
2988
- try {
2989
- const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
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")];
2992
- for (const pkgPath of candidates) {
2993
- try {
2994
- const raw = (0, import_node_fs4.readFileSync)(pkgPath, "utf-8");
2995
- const pkg = JSON.parse(raw);
2996
- if (pkg.version !== void 0 && pkg.name !== void 0) {
2997
- return pkg.version;
2998
- }
2999
- } catch {
3000
- }
3001
- }
3002
- return "0.0.0";
3003
- } catch {
3004
- return "0.0.0";
3005
- }
3006
- }
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);
3040
- });
3041
- }
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;
3050
- }
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");
3060
- }
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");
3067
- }
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");
3073
- }
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);
3079
- }
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
3088
- }
3089
- };
3090
- if (language) {
3091
- settings.language = language;
3092
- }
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");
3106
- }
3107
- }
3108
- async function startCli() {
3109
- const args = parseCliArgs();
3110
- if (args.version) {
3111
- process.stdout.write(`robota ${readVersion()}
3112
- `);
3113
- return;
3114
- }
3115
- if (args.reset) {
3116
- resetConfig();
3117
- return;
3118
- }
3119
- const cwd = process.cwd();
3120
- await ensureConfig(cwd);
3121
- const [config, context, projectInfo] = await Promise.all([
3122
- (0, import_agent_sdk5.loadConfig)(cwd),
3123
- (0, import_agent_sdk5.loadContext)(cwd),
3124
- (0, import_agent_sdk5.detectProject)(cwd)
3125
- ]);
3126
- if (args.model !== void 0) {
3127
- config.provider.model = args.model;
3128
- }
3129
- if (args.language !== void 0) {
3130
- config.language = args.language;
3131
- }
3132
- const sessionStore = new import_agent_sdk5.SessionStore();
3133
- if (args.printMode) {
3134
- const prompt = args.positional.join(" ").trim();
3135
- if (prompt.length === 0) {
3136
- process.stderr.write("Print mode (-p) requires a prompt argument.\n");
3137
- process.exit(1);
3138
- }
3139
- const terminal = new PrintTerminal();
3140
- const paths = (0, import_agent_sdk5.projectPaths)(cwd);
3141
- const session = (0, import_agent_sdk5.createSession)({
3142
- config,
3143
- context,
3144
- terminal,
3145
- sessionLogger: new import_agent_sdk5.FileSessionLogger(paths.logs),
3146
- projectInfo,
3147
- permissionMode: args.permissionMode,
3148
- promptForApproval: import_agent_sdk6.promptForApproval
3149
- });
3150
- const response = await session.run(prompt);
3151
- process.stdout.write(response + "\n");
3152
- return;
3153
- }
3154
- renderApp({
3155
- config,
3156
- context,
3157
- projectInfo,
3158
- sessionStore,
3159
- permissionMode: args.permissionMode,
3160
- maxTurns: args.maxTurns,
3161
- version: readVersion()
3162
- });
3163
- }
3164
-
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
- });
3176
- startCli().catch((err) => {
3177
- const message = err instanceof Error ? err.message : String(err);
3178
- process.stderr.write(message + "\n");
3179
- process.exit(1);
3180
- });