@robota-sdk/agent-cli 3.0.0-beta.3 → 3.0.0-beta.31
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/README.md +12 -3
- package/dist/node/bin.cjs +1725 -625
- package/dist/node/bin.js +11 -1
- package/dist/node/chunk-2CGAQADC.js +2292 -0
- package/dist/node/index.cjs +1723 -633
- package/dist/node/index.js +1 -1
- package/package.json +4 -3
- package/dist/node/chunk-MO7RIZFR.js +0 -1198
package/dist/node/bin.cjs
CHANGED
|
@@ -24,41 +24,839 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var import_node_path2 = require("path");
|
|
27
|
+
var import_node_fs3 = require("fs");
|
|
28
|
+
var import_node_path5 = require("path");
|
|
30
29
|
var import_node_url = require("url");
|
|
31
|
-
var
|
|
32
|
-
var
|
|
30
|
+
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
31
|
+
var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
|
|
33
32
|
|
|
34
|
-
// src/
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
// src/utils/cli-args.ts
|
|
34
|
+
var import_node_util = require("util");
|
|
35
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
36
|
+
function parsePermissionMode(raw) {
|
|
37
|
+
if (raw === void 0) return void 0;
|
|
38
|
+
if (!VALID_MODES.includes(raw)) {
|
|
39
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
40
|
+
`);
|
|
41
|
+
process.exit(1);
|
|
42
42
|
}
|
|
43
|
-
return
|
|
43
|
+
return raw;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
};
|
|
52
82
|
}
|
|
53
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
|
+
|
|
54
165
|
// src/ui/render.tsx
|
|
55
|
-
var
|
|
166
|
+
var import_ink12 = require("ink");
|
|
56
167
|
|
|
57
168
|
// src/ui/App.tsx
|
|
58
|
-
var
|
|
59
|
-
var
|
|
169
|
+
var import_react13 = require("react");
|
|
170
|
+
var import_ink11 = require("ink");
|
|
171
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
172
|
+
|
|
173
|
+
// src/ui/hooks/useSession.ts
|
|
174
|
+
var import_react = require("react");
|
|
60
175
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
61
176
|
|
|
177
|
+
// src/utils/edit-diff.ts
|
|
178
|
+
function generateDiffLines(oldStr, newStr) {
|
|
179
|
+
if (oldStr === newStr) return [];
|
|
180
|
+
const lines = [];
|
|
181
|
+
for (const line of oldStr.split("\n")) {
|
|
182
|
+
lines.push({ type: "remove", text: line });
|
|
183
|
+
}
|
|
184
|
+
for (const line of newStr.split("\n")) {
|
|
185
|
+
lines.push({ type: "add", text: line });
|
|
186
|
+
}
|
|
187
|
+
return lines;
|
|
188
|
+
}
|
|
189
|
+
function extractEditDiff(toolName, toolArgs) {
|
|
190
|
+
if (toolName !== "Edit" || !toolArgs) return null;
|
|
191
|
+
const filePath = toolArgs.file_path ?? toolArgs.filePath;
|
|
192
|
+
const oldStr = toolArgs.old_string ?? toolArgs.oldString;
|
|
193
|
+
const newStr = toolArgs.new_string ?? toolArgs.newString;
|
|
194
|
+
if (typeof filePath !== "string") return null;
|
|
195
|
+
if (typeof oldStr !== "string" || typeof newStr !== "string") return null;
|
|
196
|
+
const lines = generateDiffLines(oldStr, newStr);
|
|
197
|
+
if (lines.length === 0) return null;
|
|
198
|
+
return { file: filePath, lines };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/ui/hooks/useSession.ts
|
|
202
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
203
|
+
var TAIL_KEEP = 30;
|
|
204
|
+
var MAX_COMPLETED_TOOLS = 50;
|
|
205
|
+
var NOOP_TERMINAL = {
|
|
206
|
+
write: () => {
|
|
207
|
+
},
|
|
208
|
+
writeLine: () => {
|
|
209
|
+
},
|
|
210
|
+
writeMarkdown: () => {
|
|
211
|
+
},
|
|
212
|
+
writeError: () => {
|
|
213
|
+
},
|
|
214
|
+
prompt: () => Promise.resolve(""),
|
|
215
|
+
select: () => Promise.resolve(0),
|
|
216
|
+
spinner: () => ({ stop: () => {
|
|
217
|
+
}, update: () => {
|
|
218
|
+
} })
|
|
219
|
+
};
|
|
220
|
+
function useSession(props) {
|
|
221
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
|
|
222
|
+
const [streamingText, setStreamingText] = (0, import_react.useState)("");
|
|
223
|
+
const [activeTools, setActiveTools] = (0, import_react.useState)([]);
|
|
224
|
+
const permissionQueueRef = (0, import_react.useRef)([]);
|
|
225
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
226
|
+
const processNextPermission = (0, import_react.useCallback)(() => {
|
|
227
|
+
if (processingRef.current) return;
|
|
228
|
+
const next = permissionQueueRef.current[0];
|
|
229
|
+
if (!next) {
|
|
230
|
+
setPermissionRequest(null);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
processingRef.current = true;
|
|
234
|
+
setPermissionRequest({
|
|
235
|
+
toolName: next.toolName,
|
|
236
|
+
toolArgs: next.toolArgs,
|
|
237
|
+
resolve: (result) => {
|
|
238
|
+
permissionQueueRef.current.shift();
|
|
239
|
+
processingRef.current = false;
|
|
240
|
+
setPermissionRequest(null);
|
|
241
|
+
next.resolve(result);
|
|
242
|
+
setTimeout(() => processNextPermission(), 0);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}, []);
|
|
246
|
+
const sessionRef = (0, import_react.useRef)(null);
|
|
247
|
+
if (sessionRef.current === null) {
|
|
248
|
+
const permissionHandler = (toolName, toolArgs) => {
|
|
249
|
+
return new Promise((resolve) => {
|
|
250
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
251
|
+
processNextPermission();
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const onTextDelta = (delta) => {
|
|
255
|
+
setStreamingText((prev) => prev + delta);
|
|
256
|
+
};
|
|
257
|
+
const onToolExecution = (event) => {
|
|
258
|
+
if (event.type === "start") {
|
|
259
|
+
let firstArg = "";
|
|
260
|
+
if (event.toolArgs) {
|
|
261
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
262
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
263
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
264
|
+
}
|
|
265
|
+
setActiveTools((prev) => [
|
|
266
|
+
...prev,
|
|
267
|
+
{ toolName: event.toolName, firstArg, isRunning: true, _toolArgs: event.toolArgs }
|
|
268
|
+
]);
|
|
269
|
+
} else {
|
|
270
|
+
const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
271
|
+
setActiveTools((prev) => {
|
|
272
|
+
const updated = prev.map((t) => {
|
|
273
|
+
if (!(t.toolName === event.toolName && t.isRunning)) return t;
|
|
274
|
+
const editDiff = extractEditDiff(
|
|
275
|
+
event.toolName,
|
|
276
|
+
t._toolArgs
|
|
277
|
+
);
|
|
278
|
+
const finished = {
|
|
279
|
+
...t,
|
|
280
|
+
isRunning: false,
|
|
281
|
+
result: toolResult
|
|
282
|
+
};
|
|
283
|
+
if (editDiff) {
|
|
284
|
+
finished.diffLines = editDiff.lines;
|
|
285
|
+
finished.diffFile = editDiff.file;
|
|
286
|
+
}
|
|
287
|
+
delete finished._toolArgs;
|
|
288
|
+
return finished;
|
|
289
|
+
});
|
|
290
|
+
const completed = updated.filter((t) => !t.isRunning);
|
|
291
|
+
if (completed.length > MAX_COMPLETED_TOOLS) {
|
|
292
|
+
const excess = completed.length - MAX_COMPLETED_TOOLS;
|
|
293
|
+
let removed = 0;
|
|
294
|
+
return updated.filter((t) => {
|
|
295
|
+
if (!t.isRunning && removed < excess) {
|
|
296
|
+
removed++;
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return updated;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
307
|
+
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
308
|
+
config: props.config,
|
|
309
|
+
context: props.context,
|
|
310
|
+
terminal: NOOP_TERMINAL,
|
|
311
|
+
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
312
|
+
projectInfo: props.projectInfo,
|
|
313
|
+
sessionStore: props.sessionStore,
|
|
314
|
+
permissionMode: props.permissionMode,
|
|
315
|
+
maxTurns: props.maxTurns,
|
|
316
|
+
permissionHandler,
|
|
317
|
+
onTextDelta,
|
|
318
|
+
onToolExecution
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const clearStreamingText = (0, import_react.useCallback)(() => {
|
|
322
|
+
setStreamingText("");
|
|
323
|
+
setActiveTools([]);
|
|
324
|
+
}, []);
|
|
325
|
+
return {
|
|
326
|
+
session: sessionRef.current,
|
|
327
|
+
permissionRequest,
|
|
328
|
+
streamingText,
|
|
329
|
+
clearStreamingText,
|
|
330
|
+
activeTools
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/ui/hooks/useMessages.ts
|
|
335
|
+
var import_react2 = require("react");
|
|
336
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
337
|
+
var msgIdCounter = 0;
|
|
338
|
+
function nextId() {
|
|
339
|
+
msgIdCounter += 1;
|
|
340
|
+
return `msg_${msgIdCounter}`;
|
|
341
|
+
}
|
|
342
|
+
function useMessages() {
|
|
343
|
+
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
344
|
+
const addMessage = (0, import_react2.useCallback)((msg) => {
|
|
345
|
+
setMessages((prev) => {
|
|
346
|
+
const updated = [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }];
|
|
347
|
+
if (updated.length > MAX_RENDERED_MESSAGES) {
|
|
348
|
+
return updated.slice(-MAX_RENDERED_MESSAGES);
|
|
349
|
+
}
|
|
350
|
+
return updated;
|
|
351
|
+
});
|
|
352
|
+
}, []);
|
|
353
|
+
return { messages, setMessages, addMessage };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
357
|
+
var import_react3 = require("react");
|
|
358
|
+
|
|
359
|
+
// src/commands/slash-executor.ts
|
|
360
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
361
|
+
var HELP_TEXT = [
|
|
362
|
+
"Available commands:",
|
|
363
|
+
" /help \u2014 Show this help",
|
|
364
|
+
" /clear \u2014 Clear conversation",
|
|
365
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
366
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
367
|
+
" /language [lang] \u2014 Set response language (ko, en, ja, zh)",
|
|
368
|
+
" /cost \u2014 Show session info",
|
|
369
|
+
" /reset \u2014 Delete settings and exit",
|
|
370
|
+
" /exit \u2014 Exit CLI"
|
|
371
|
+
].join("\n");
|
|
372
|
+
function handleHelp(addMessage) {
|
|
373
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
374
|
+
return { handled: true };
|
|
375
|
+
}
|
|
376
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
377
|
+
clearMessages();
|
|
378
|
+
session.clearHistory();
|
|
379
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
380
|
+
return { handled: true };
|
|
381
|
+
}
|
|
382
|
+
async function handleCompact(args, session, addMessage) {
|
|
383
|
+
const instructions = args.trim() || void 0;
|
|
384
|
+
const before = session.getContextState().usedPercentage;
|
|
385
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
386
|
+
await session.compact(instructions);
|
|
387
|
+
const after = session.getContextState().usedPercentage;
|
|
388
|
+
addMessage({
|
|
389
|
+
role: "system",
|
|
390
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
391
|
+
});
|
|
392
|
+
return { handled: true };
|
|
393
|
+
}
|
|
394
|
+
function handleMode(arg, session, addMessage) {
|
|
395
|
+
if (!arg) {
|
|
396
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
397
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
398
|
+
session.setPermissionMode(arg);
|
|
399
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
400
|
+
} else {
|
|
401
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
402
|
+
}
|
|
403
|
+
return { handled: true };
|
|
404
|
+
}
|
|
405
|
+
function handleModel(modelId, addMessage) {
|
|
406
|
+
if (!modelId) {
|
|
407
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
408
|
+
return { handled: true };
|
|
409
|
+
}
|
|
410
|
+
return { handled: true, pendingModelId: modelId };
|
|
411
|
+
}
|
|
412
|
+
function handleCost(session, addMessage) {
|
|
413
|
+
addMessage({
|
|
414
|
+
role: "system",
|
|
415
|
+
content: `Session: ${session.getSessionId()}
|
|
416
|
+
Messages: ${session.getMessageCount()}`
|
|
417
|
+
});
|
|
418
|
+
return { handled: true };
|
|
419
|
+
}
|
|
420
|
+
function handlePermissions(session, addMessage) {
|
|
421
|
+
const mode = session.getPermissionMode();
|
|
422
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
423
|
+
const lines = [`Permission mode: ${mode}`];
|
|
424
|
+
if (sessionAllowed.length > 0) {
|
|
425
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
426
|
+
} else {
|
|
427
|
+
lines.push("No session-approved tools.");
|
|
428
|
+
}
|
|
429
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
430
|
+
return { handled: true };
|
|
431
|
+
}
|
|
432
|
+
function handleContext(session, addMessage) {
|
|
433
|
+
const ctx = session.getContextState();
|
|
434
|
+
addMessage({
|
|
435
|
+
role: "system",
|
|
436
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
437
|
+
});
|
|
438
|
+
return { handled: true };
|
|
439
|
+
}
|
|
440
|
+
function handleLanguage(lang, addMessage) {
|
|
441
|
+
if (!lang) {
|
|
442
|
+
addMessage({ role: "system", content: "Usage: /language <code> (e.g., ko, en, ja, zh)" });
|
|
443
|
+
return { handled: true };
|
|
444
|
+
}
|
|
445
|
+
const settingsPath = getUserSettingsPath();
|
|
446
|
+
const settings = readSettings(settingsPath);
|
|
447
|
+
settings.language = lang;
|
|
448
|
+
writeSettings(settingsPath, settings);
|
|
449
|
+
addMessage({ role: "system", content: `Language set to "${lang}". Restarting...` });
|
|
450
|
+
return { handled: true, exitRequested: true };
|
|
451
|
+
}
|
|
452
|
+
function handleReset(addMessage) {
|
|
453
|
+
const settingsPath = getUserSettingsPath();
|
|
454
|
+
if (deleteSettings(settingsPath)) {
|
|
455
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
456
|
+
} else {
|
|
457
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
458
|
+
}
|
|
459
|
+
return { handled: true, exitRequested: true };
|
|
460
|
+
}
|
|
461
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
462
|
+
const parts = args.trim().split(/\s+/);
|
|
463
|
+
const subcommand = parts[0] ?? "";
|
|
464
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
465
|
+
try {
|
|
466
|
+
switch (subcommand) {
|
|
467
|
+
case "":
|
|
468
|
+
case void 0: {
|
|
469
|
+
const plugins = await callbacks.listInstalled();
|
|
470
|
+
if (plugins.length === 0) {
|
|
471
|
+
addMessage({ role: "system", content: "No plugins installed." });
|
|
472
|
+
} else {
|
|
473
|
+
const lines = plugins.map(
|
|
474
|
+
(p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
|
|
475
|
+
);
|
|
476
|
+
addMessage({ role: "system", content: `Installed plugins:
|
|
477
|
+
${lines.join("\n")}` });
|
|
478
|
+
}
|
|
479
|
+
return { handled: true };
|
|
480
|
+
}
|
|
481
|
+
case "install": {
|
|
482
|
+
if (!subArgs) {
|
|
483
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
484
|
+
return { handled: true };
|
|
485
|
+
}
|
|
486
|
+
await callbacks.install(subArgs);
|
|
487
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
488
|
+
return { handled: true };
|
|
489
|
+
}
|
|
490
|
+
case "uninstall": {
|
|
491
|
+
if (!subArgs) {
|
|
492
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
493
|
+
return { handled: true };
|
|
494
|
+
}
|
|
495
|
+
await callbacks.uninstall(subArgs);
|
|
496
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
497
|
+
return { handled: true };
|
|
498
|
+
}
|
|
499
|
+
case "enable": {
|
|
500
|
+
if (!subArgs) {
|
|
501
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
502
|
+
return { handled: true };
|
|
503
|
+
}
|
|
504
|
+
await callbacks.enable(subArgs);
|
|
505
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
506
|
+
return { handled: true };
|
|
507
|
+
}
|
|
508
|
+
case "disable": {
|
|
509
|
+
if (!subArgs) {
|
|
510
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
511
|
+
return { handled: true };
|
|
512
|
+
}
|
|
513
|
+
await callbacks.disable(subArgs);
|
|
514
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
515
|
+
return { handled: true };
|
|
516
|
+
}
|
|
517
|
+
case "marketplace": {
|
|
518
|
+
const mpParts = subArgs.split(/\s+/);
|
|
519
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
520
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
521
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
522
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
523
|
+
addMessage({
|
|
524
|
+
role: "system",
|
|
525
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
526
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
527
|
+
});
|
|
528
|
+
return { handled: true };
|
|
529
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
530
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
531
|
+
addMessage({
|
|
532
|
+
role: "system",
|
|
533
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
534
|
+
});
|
|
535
|
+
return { handled: true };
|
|
536
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
537
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
538
|
+
addMessage({
|
|
539
|
+
role: "system",
|
|
540
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
541
|
+
});
|
|
542
|
+
return { handled: true };
|
|
543
|
+
} else if (mpSubcommand === "list") {
|
|
544
|
+
const sources = await callbacks.marketplaceList();
|
|
545
|
+
if (sources.length === 0) {
|
|
546
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
547
|
+
} else {
|
|
548
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
549
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
550
|
+
${lines.join("\n")}` });
|
|
551
|
+
}
|
|
552
|
+
return { handled: true };
|
|
553
|
+
} else {
|
|
554
|
+
addMessage({
|
|
555
|
+
role: "system",
|
|
556
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
557
|
+
});
|
|
558
|
+
return { handled: true };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
default:
|
|
562
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
563
|
+
return { handled: true };
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
567
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
568
|
+
return { handled: true };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
572
|
+
await callbacks.reloadPlugins();
|
|
573
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
574
|
+
return { handled: true };
|
|
575
|
+
}
|
|
576
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
577
|
+
switch (cmd) {
|
|
578
|
+
case "help":
|
|
579
|
+
return handleHelp(addMessage);
|
|
580
|
+
case "clear":
|
|
581
|
+
return handleClear(addMessage, clearMessages, session);
|
|
582
|
+
case "compact":
|
|
583
|
+
return handleCompact(args, session, addMessage);
|
|
584
|
+
case "mode":
|
|
585
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
586
|
+
case "model":
|
|
587
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
588
|
+
case "language":
|
|
589
|
+
return handleLanguage(args.split(/\s+/)[0] || void 0, addMessage);
|
|
590
|
+
case "cost":
|
|
591
|
+
return handleCost(session, addMessage);
|
|
592
|
+
case "permissions":
|
|
593
|
+
return handlePermissions(session, addMessage);
|
|
594
|
+
case "context":
|
|
595
|
+
return handleContext(session, addMessage);
|
|
596
|
+
case "reset":
|
|
597
|
+
return handleReset(addMessage);
|
|
598
|
+
case "exit":
|
|
599
|
+
return { handled: true, exitRequested: true };
|
|
600
|
+
case "plugin":
|
|
601
|
+
if (pluginCallbacks) {
|
|
602
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
603
|
+
}
|
|
604
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
605
|
+
return { handled: true };
|
|
606
|
+
case "reload-plugins":
|
|
607
|
+
if (pluginCallbacks) {
|
|
608
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
609
|
+
}
|
|
610
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
611
|
+
return { handled: true };
|
|
612
|
+
default: {
|
|
613
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
614
|
+
if (dynamicCmd) {
|
|
615
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
616
|
+
return { handled: false };
|
|
617
|
+
}
|
|
618
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
619
|
+
return { handled: true };
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
625
|
+
var EXIT_DELAY_MS = 500;
|
|
626
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
|
|
627
|
+
return (0, import_react3.useCallback)(
|
|
628
|
+
async (input) => {
|
|
629
|
+
const parts = input.slice(1).split(/\s+/);
|
|
630
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
631
|
+
const args = parts.slice(1).join(" ");
|
|
632
|
+
const clearMessages = () => setMessages([]);
|
|
633
|
+
const result = await executeSlashCommand(
|
|
634
|
+
cmd,
|
|
635
|
+
args,
|
|
636
|
+
session,
|
|
637
|
+
addMessage,
|
|
638
|
+
clearMessages,
|
|
639
|
+
registry,
|
|
640
|
+
pluginCallbacks
|
|
641
|
+
);
|
|
642
|
+
if (result.pendingModelId) {
|
|
643
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
644
|
+
setPendingModelId(result.pendingModelId);
|
|
645
|
+
}
|
|
646
|
+
if (result.exitRequested) {
|
|
647
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
648
|
+
}
|
|
649
|
+
return result.handled;
|
|
650
|
+
},
|
|
651
|
+
[
|
|
652
|
+
session,
|
|
653
|
+
addMessage,
|
|
654
|
+
setMessages,
|
|
655
|
+
exit,
|
|
656
|
+
registry,
|
|
657
|
+
pendingModelChangeRef,
|
|
658
|
+
setPendingModelId,
|
|
659
|
+
pluginCallbacks
|
|
660
|
+
]
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
665
|
+
var import_react4 = require("react");
|
|
666
|
+
|
|
667
|
+
// src/utils/tool-call-extractor.ts
|
|
668
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
669
|
+
var TAIL_KEEP2 = 30;
|
|
670
|
+
function extractToolCallsWithDiff(history, startIndex) {
|
|
671
|
+
const summaries = [];
|
|
672
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
673
|
+
const msg = history[i];
|
|
674
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
675
|
+
for (const tc of msg.toolCalls) {
|
|
676
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
677
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
|
|
678
|
+
const summary = {
|
|
679
|
+
line: `${tc.function.name}(${truncated})`
|
|
680
|
+
};
|
|
681
|
+
if (tc.function.name === "Edit") {
|
|
682
|
+
try {
|
|
683
|
+
const args = JSON.parse(tc.function.arguments);
|
|
684
|
+
const diff = extractEditDiff("Edit", args);
|
|
685
|
+
if (diff) {
|
|
686
|
+
summary.diffLines = diff.lines;
|
|
687
|
+
summary.diffFile = diff.file;
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
summaries.push(summary);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return summaries;
|
|
697
|
+
}
|
|
698
|
+
function parseFirstArgValue(argsJson) {
|
|
699
|
+
try {
|
|
700
|
+
const parsed = JSON.parse(argsJson);
|
|
701
|
+
const firstVal = Object.values(parsed)[0];
|
|
702
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
703
|
+
} catch {
|
|
704
|
+
return argsJson;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/utils/skill-prompt.ts
|
|
709
|
+
var import_node_child_process = require("child_process");
|
|
710
|
+
function substituteVariables(content, args, context) {
|
|
711
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
712
|
+
let result = content;
|
|
713
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
714
|
+
return argParts[Number(index)] ?? "";
|
|
715
|
+
});
|
|
716
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
717
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
718
|
+
return argParts[Number(digit)] ?? "";
|
|
719
|
+
});
|
|
720
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
721
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
async function preprocessShellCommands(content) {
|
|
725
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
726
|
+
if (!shellPattern.test(content)) {
|
|
727
|
+
return content;
|
|
728
|
+
}
|
|
729
|
+
shellPattern.lastIndex = 0;
|
|
730
|
+
let result = content;
|
|
731
|
+
let match;
|
|
732
|
+
const matches = [];
|
|
733
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
734
|
+
matches.push({ full: match[0], command: match[1] });
|
|
735
|
+
}
|
|
736
|
+
for (const { full, command } of matches) {
|
|
737
|
+
let output = "";
|
|
738
|
+
try {
|
|
739
|
+
output = (0, import_node_child_process.execSync)(command, {
|
|
740
|
+
timeout: 5e3,
|
|
741
|
+
encoding: "utf-8",
|
|
742
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
743
|
+
}).trimEnd();
|
|
744
|
+
} catch {
|
|
745
|
+
output = "";
|
|
746
|
+
}
|
|
747
|
+
result = result.replace(full, output);
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
752
|
+
const parts = input.slice(1).split(/\s+/);
|
|
753
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
754
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
755
|
+
if (!skillCmd) return null;
|
|
756
|
+
const args = parts.slice(1).join(" ").trim();
|
|
757
|
+
const userInstruction = args || skillCmd.description;
|
|
758
|
+
if (skillCmd.skillContent) {
|
|
759
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
760
|
+
processed = substituteVariables(processed, args, context);
|
|
761
|
+
return `<skill name="${cmd}">
|
|
762
|
+
${processed}
|
|
763
|
+
</skill>
|
|
764
|
+
|
|
765
|
+
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
766
|
+
}
|
|
767
|
+
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
771
|
+
function syncContextState(session, setter) {
|
|
772
|
+
const ctx = session.getContextState();
|
|
773
|
+
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
774
|
+
}
|
|
775
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
776
|
+
setIsThinking(true);
|
|
777
|
+
clearStreamingText();
|
|
778
|
+
const historyBefore = session.getHistory().length;
|
|
779
|
+
try {
|
|
780
|
+
const response = await session.run(prompt, rawInput);
|
|
781
|
+
clearStreamingText();
|
|
782
|
+
const history = session.getHistory();
|
|
783
|
+
const toolSummaries = extractToolCallsWithDiff(
|
|
784
|
+
history,
|
|
785
|
+
historyBefore
|
|
786
|
+
);
|
|
787
|
+
if (toolSummaries.length > 0) {
|
|
788
|
+
addMessage({
|
|
789
|
+
role: "tool",
|
|
790
|
+
content: JSON.stringify(toolSummaries),
|
|
791
|
+
toolName: `${toolSummaries.length} tools`
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
795
|
+
syncContextState(session, setContextState);
|
|
796
|
+
} catch (err) {
|
|
797
|
+
clearStreamingText();
|
|
798
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
799
|
+
addMessage({ role: "system", content: "Cancelled." });
|
|
800
|
+
} else {
|
|
801
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
802
|
+
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
803
|
+
}
|
|
804
|
+
} finally {
|
|
805
|
+
setIsThinking(false);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
|
|
809
|
+
return (0, import_react4.useCallback)(
|
|
810
|
+
async (input) => {
|
|
811
|
+
if (input.startsWith("/")) {
|
|
812
|
+
const handled = await handleSlashCommand(input);
|
|
813
|
+
if (handled) {
|
|
814
|
+
syncContextState(session, setContextState);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
818
|
+
if (!prompt) return;
|
|
819
|
+
const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
820
|
+
const qualifiedName = registry.resolveQualifiedName(cmdName);
|
|
821
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
|
|
822
|
+
return runSessionPrompt(
|
|
823
|
+
prompt,
|
|
824
|
+
session,
|
|
825
|
+
addMessage,
|
|
826
|
+
clearStreamingText,
|
|
827
|
+
setIsThinking,
|
|
828
|
+
setContextState,
|
|
829
|
+
hookInput
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
addMessage({ role: "user", content: input });
|
|
833
|
+
return runSessionPrompt(
|
|
834
|
+
input,
|
|
835
|
+
session,
|
|
836
|
+
addMessage,
|
|
837
|
+
clearStreamingText,
|
|
838
|
+
setIsThinking,
|
|
839
|
+
setContextState
|
|
840
|
+
);
|
|
841
|
+
},
|
|
842
|
+
[
|
|
843
|
+
session,
|
|
844
|
+
addMessage,
|
|
845
|
+
handleSlashCommand,
|
|
846
|
+
clearStreamingText,
|
|
847
|
+
setIsThinking,
|
|
848
|
+
setContextState,
|
|
849
|
+
registry
|
|
850
|
+
]
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
855
|
+
var import_react5 = require("react");
|
|
856
|
+
var import_node_os2 = require("os");
|
|
857
|
+
var import_node_path3 = require("path");
|
|
858
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
859
|
+
|
|
62
860
|
// src/commands/command-registry.ts
|
|
63
861
|
var CommandRegistry = class {
|
|
64
862
|
sources = [];
|
|
@@ -75,6 +873,14 @@ var CommandRegistry = class {
|
|
|
75
873
|
const lower = filter.toLowerCase();
|
|
76
874
|
return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
|
|
77
875
|
}
|
|
876
|
+
/** Resolve a short name to its fully qualified plugin:name form */
|
|
877
|
+
resolveQualifiedName(shortName) {
|
|
878
|
+
const matches = this.getCommands().filter(
|
|
879
|
+
(c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
|
|
880
|
+
);
|
|
881
|
+
if (matches.length !== 1) return null;
|
|
882
|
+
return matches[0].name;
|
|
883
|
+
}
|
|
78
884
|
/** Get subcommands for a specific command */
|
|
79
885
|
getSubcommands(commandName) {
|
|
80
886
|
const lower = commandName.toLowerCase();
|
|
@@ -90,6 +896,21 @@ var CommandRegistry = class {
|
|
|
90
896
|
};
|
|
91
897
|
|
|
92
898
|
// src/commands/builtin-source.ts
|
|
899
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
900
|
+
function buildModelSubcommands() {
|
|
901
|
+
const seen = /* @__PURE__ */ new Set();
|
|
902
|
+
const commands = [];
|
|
903
|
+
for (const model of Object.values(import_agent_core.CLAUDE_MODELS)) {
|
|
904
|
+
if (seen.has(model.name)) continue;
|
|
905
|
+
seen.add(model.name);
|
|
906
|
+
commands.push({
|
|
907
|
+
name: model.id,
|
|
908
|
+
description: `${model.name} (${(0, import_agent_core.formatTokenCount)(model.contextWindow).toUpperCase()})`,
|
|
909
|
+
source: "builtin"
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
return commands;
|
|
913
|
+
}
|
|
93
914
|
function createBuiltinCommands() {
|
|
94
915
|
return [
|
|
95
916
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -109,16 +930,41 @@ function createBuiltinCommands() {
|
|
|
109
930
|
name: "model",
|
|
110
931
|
description: "Select AI model",
|
|
111
932
|
source: "builtin",
|
|
933
|
+
subcommands: buildModelSubcommands()
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "language",
|
|
937
|
+
description: "Set response language",
|
|
938
|
+
source: "builtin",
|
|
112
939
|
subcommands: [
|
|
113
|
-
{ name: "
|
|
114
|
-
{ name: "
|
|
115
|
-
{ name: "
|
|
940
|
+
{ name: "ko", description: "Korean", source: "builtin" },
|
|
941
|
+
{ name: "en", description: "English", source: "builtin" },
|
|
942
|
+
{ name: "ja", description: "Japanese", source: "builtin" },
|
|
943
|
+
{ name: "zh", description: "Chinese", source: "builtin" }
|
|
116
944
|
]
|
|
117
945
|
},
|
|
118
946
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
119
947
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
120
948
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
121
949
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
950
|
+
{
|
|
951
|
+
name: "plugin",
|
|
952
|
+
description: "Manage plugins",
|
|
953
|
+
source: "builtin",
|
|
954
|
+
subcommands: [
|
|
955
|
+
{ name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
|
|
956
|
+
{
|
|
957
|
+
name: "uninstall",
|
|
958
|
+
description: "Uninstall a plugin (name@marketplace)",
|
|
959
|
+
source: "builtin"
|
|
960
|
+
},
|
|
961
|
+
{ name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
|
|
962
|
+
{ name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
|
|
963
|
+
{ name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
|
|
964
|
+
]
|
|
965
|
+
},
|
|
966
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
967
|
+
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
122
968
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
123
969
|
];
|
|
124
970
|
}
|
|
@@ -134,72 +980,309 @@ var BuiltinCommandSource = class {
|
|
|
134
980
|
};
|
|
135
981
|
|
|
136
982
|
// src/commands/skill-source.ts
|
|
137
|
-
var
|
|
138
|
-
var
|
|
983
|
+
var import_node_fs2 = require("fs");
|
|
984
|
+
var import_node_path2 = require("path");
|
|
139
985
|
var import_node_os = require("os");
|
|
986
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
987
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
988
|
+
function kebabToCamel(key) {
|
|
989
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
990
|
+
}
|
|
140
991
|
function parseFrontmatter(content) {
|
|
141
992
|
const lines = content.split("\n");
|
|
142
993
|
if (lines[0]?.trim() !== "---") return null;
|
|
143
|
-
|
|
144
|
-
let description = "";
|
|
994
|
+
const result = {};
|
|
145
995
|
for (let i = 1; i < lines.length; i++) {
|
|
146
996
|
const line = lines[i];
|
|
147
997
|
if (line.trim() === "---") break;
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
998
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
999
|
+
if (!match) continue;
|
|
1000
|
+
const key = match[1];
|
|
1001
|
+
const rawValue = match[2].trim();
|
|
1002
|
+
const camelKey = kebabToCamel(key);
|
|
1003
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
1004
|
+
result[camelKey] = rawValue === "true";
|
|
1005
|
+
} else if (LIST_KEYS.has(key)) {
|
|
1006
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
1007
|
+
} else {
|
|
1008
|
+
result[camelKey] = rawValue;
|
|
156
1009
|
}
|
|
157
1010
|
}
|
|
158
|
-
return
|
|
1011
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
1012
|
+
}
|
|
1013
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
1014
|
+
const cmd = {
|
|
1015
|
+
name: frontmatter?.name ?? fallbackName,
|
|
1016
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
1017
|
+
source: "skill",
|
|
1018
|
+
skillContent: content
|
|
1019
|
+
};
|
|
1020
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
1021
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
1022
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
1023
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
1024
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
1025
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
1026
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
1027
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
1028
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
1029
|
+
return cmd;
|
|
159
1030
|
}
|
|
160
1031
|
function scanSkillsDir(skillsDir) {
|
|
161
|
-
if (!(0,
|
|
1032
|
+
if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
|
|
162
1033
|
const commands = [];
|
|
163
|
-
const entries = (0,
|
|
1034
|
+
const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
|
|
164
1035
|
for (const entry of entries) {
|
|
165
1036
|
if (!entry.isDirectory()) continue;
|
|
166
|
-
const skillFile = (0,
|
|
167
|
-
if (!(0,
|
|
168
|
-
const content = (0,
|
|
1037
|
+
const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
|
|
1038
|
+
if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
|
|
1039
|
+
const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
|
|
169
1040
|
const frontmatter = parseFrontmatter(content);
|
|
170
|
-
commands.push(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
1041
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
1042
|
+
}
|
|
1043
|
+
return commands;
|
|
1044
|
+
}
|
|
1045
|
+
function scanCommandsDir(commandsDir) {
|
|
1046
|
+
if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
|
|
1047
|
+
const commands = [];
|
|
1048
|
+
const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
|
|
1049
|
+
for (const entry of entries) {
|
|
1050
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1051
|
+
const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
|
|
1052
|
+
const content = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
1053
|
+
const frontmatter = parseFrontmatter(content);
|
|
1054
|
+
const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
|
|
1055
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
175
1056
|
}
|
|
176
1057
|
return commands;
|
|
177
1058
|
}
|
|
178
1059
|
var SkillCommandSource = class {
|
|
179
1060
|
name = "skill";
|
|
180
1061
|
cwd;
|
|
1062
|
+
home;
|
|
181
1063
|
cachedCommands = null;
|
|
182
|
-
constructor(cwd) {
|
|
1064
|
+
constructor(cwd, home) {
|
|
183
1065
|
this.cwd = cwd;
|
|
1066
|
+
this.home = home ?? (0, import_node_os.homedir)();
|
|
184
1067
|
}
|
|
185
1068
|
getCommands() {
|
|
186
1069
|
if (this.cachedCommands) return this.cachedCommands;
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
1070
|
+
const sources = [
|
|
1071
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
|
|
1072
|
+
// 1. project .claude/skills
|
|
1073
|
+
scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
|
|
1074
|
+
// 2. project .claude/commands (legacy)
|
|
1075
|
+
scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
|
|
1076
|
+
// 3. user ~/.robota/skills
|
|
1077
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
|
|
1078
|
+
// 4. project .agents/skills
|
|
1079
|
+
];
|
|
1080
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1081
|
+
const merged = [];
|
|
1082
|
+
for (const commands of sources) {
|
|
1083
|
+
for (const cmd of commands) {
|
|
1084
|
+
if (!seen.has(cmd.name)) {
|
|
1085
|
+
seen.add(cmd.name);
|
|
1086
|
+
merged.push(cmd);
|
|
1087
|
+
}
|
|
194
1088
|
}
|
|
195
1089
|
}
|
|
196
1090
|
this.cachedCommands = merged;
|
|
197
1091
|
return this.cachedCommands;
|
|
198
1092
|
}
|
|
199
|
-
|
|
1093
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
1094
|
+
getModelInvocableSkills() {
|
|
1095
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
1096
|
+
}
|
|
1097
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1098
|
+
getUserInvocableSkills() {
|
|
1099
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// src/commands/plugin-source.ts
|
|
1104
|
+
var PluginCommandSource = class {
|
|
1105
|
+
name = "plugin";
|
|
1106
|
+
plugins;
|
|
1107
|
+
constructor(plugins) {
|
|
1108
|
+
this.plugins = plugins;
|
|
1109
|
+
}
|
|
1110
|
+
getCommands() {
|
|
1111
|
+
const commands = [];
|
|
1112
|
+
for (const plugin of this.plugins) {
|
|
1113
|
+
for (const skill of plugin.skills) {
|
|
1114
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1115
|
+
commands.push({
|
|
1116
|
+
name: baseName,
|
|
1117
|
+
description: `(${plugin.manifest.name}) ${skill.description}`,
|
|
1118
|
+
source: "plugin",
|
|
1119
|
+
skillContent: skill.skillContent,
|
|
1120
|
+
pluginDir: plugin.pluginDir
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
for (const cmd of plugin.commands) {
|
|
1124
|
+
commands.push({
|
|
1125
|
+
name: cmd.name,
|
|
1126
|
+
description: cmd.description,
|
|
1127
|
+
source: "plugin",
|
|
1128
|
+
skillContent: cmd.skillContent,
|
|
1129
|
+
pluginDir: plugin.pluginDir
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return commands;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
1138
|
+
function buildPluginEnv(plugin) {
|
|
1139
|
+
const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1140
|
+
return {
|
|
1141
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1142
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1143
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function mergePluginHooks(plugins) {
|
|
1147
|
+
const merged = {};
|
|
1148
|
+
for (const plugin of plugins) {
|
|
1149
|
+
const hooksObj = plugin.hooks;
|
|
1150
|
+
if (!hooksObj) continue;
|
|
1151
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1152
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1153
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1154
|
+
if (!Array.isArray(groups)) continue;
|
|
1155
|
+
if (!merged[event]) merged[event] = [];
|
|
1156
|
+
const resolved = groups.map((group) => {
|
|
1157
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1158
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1159
|
+
resolved2.env = pluginEnv;
|
|
1160
|
+
}
|
|
1161
|
+
return resolved2;
|
|
1162
|
+
});
|
|
1163
|
+
merged[event].push(...resolved);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return merged;
|
|
1167
|
+
}
|
|
1168
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1169
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1170
|
+
const obj = group;
|
|
1171
|
+
if (Array.isArray(obj.hooks)) {
|
|
1172
|
+
return {
|
|
1173
|
+
...obj,
|
|
1174
|
+
hooks: obj.hooks.map((h) => {
|
|
1175
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1176
|
+
const hook = h;
|
|
1177
|
+
if (typeof hook.command === "string") {
|
|
1178
|
+
return {
|
|
1179
|
+
...hook,
|
|
1180
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
return hook;
|
|
1184
|
+
})
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
return group;
|
|
1188
|
+
}
|
|
1189
|
+
function useCommandRegistry(cwd) {
|
|
1190
|
+
const resultRef = (0, import_react5.useRef)(null);
|
|
1191
|
+
if (resultRef.current === null) {
|
|
1192
|
+
const registry = new CommandRegistry();
|
|
1193
|
+
registry.addSource(new BuiltinCommandSource());
|
|
1194
|
+
registry.addSource(new SkillCommandSource(cwd));
|
|
1195
|
+
let pluginHooks = {};
|
|
1196
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
1197
|
+
const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
|
|
1198
|
+
try {
|
|
1199
|
+
const plugins = loader.loadPluginsSync();
|
|
1200
|
+
if (plugins.length > 0) {
|
|
1201
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1202
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1203
|
+
}
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
resultRef.current = { registry, pluginHooks };
|
|
1207
|
+
}
|
|
1208
|
+
return resultRef.current;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1212
|
+
var import_react6 = require("react");
|
|
1213
|
+
var import_node_os3 = require("os");
|
|
1214
|
+
var import_node_path4 = require("path");
|
|
1215
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
1216
|
+
function usePluginCallbacks(cwd) {
|
|
1217
|
+
return (0, import_react6.useMemo)(() => {
|
|
1218
|
+
const home = (0, import_node_os3.homedir)();
|
|
1219
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
1220
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
1221
|
+
const settingsStore = new import_agent_sdk3.PluginSettingsStore(userSettingsPath);
|
|
1222
|
+
const marketplace = new import_agent_sdk3.MarketplaceClient({ pluginsDir });
|
|
1223
|
+
const installer = new import_agent_sdk3.BundlePluginInstaller({
|
|
1224
|
+
pluginsDir,
|
|
1225
|
+
settingsStore,
|
|
1226
|
+
marketplaceClient: marketplace
|
|
1227
|
+
});
|
|
1228
|
+
const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
|
|
1229
|
+
return {
|
|
1230
|
+
listInstalled: async () => {
|
|
1231
|
+
const plugins = await loader.loadAll();
|
|
1232
|
+
return plugins.map((p) => ({
|
|
1233
|
+
name: p.manifest.name,
|
|
1234
|
+
description: p.manifest.description,
|
|
1235
|
+
enabled: true
|
|
1236
|
+
}));
|
|
1237
|
+
},
|
|
1238
|
+
install: async (pluginId) => {
|
|
1239
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1240
|
+
if (!name || !marketplaceName) {
|
|
1241
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1242
|
+
}
|
|
1243
|
+
await installer.install(name, marketplaceName);
|
|
1244
|
+
},
|
|
1245
|
+
uninstall: async (pluginId) => {
|
|
1246
|
+
await installer.uninstall(pluginId);
|
|
1247
|
+
},
|
|
1248
|
+
enable: async (pluginId) => {
|
|
1249
|
+
await installer.enable(pluginId);
|
|
1250
|
+
},
|
|
1251
|
+
disable: async (pluginId) => {
|
|
1252
|
+
await installer.disable(pluginId);
|
|
1253
|
+
},
|
|
1254
|
+
marketplaceAdd: async (source) => {
|
|
1255
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1256
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1257
|
+
} else {
|
|
1258
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
marketplaceRemove: async (name) => {
|
|
1262
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1263
|
+
for (const record of installedFromMarketplace) {
|
|
1264
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1265
|
+
}
|
|
1266
|
+
marketplace.removeMarketplace(name);
|
|
1267
|
+
},
|
|
1268
|
+
marketplaceUpdate: async (name) => {
|
|
1269
|
+
marketplace.updateMarketplace(name);
|
|
1270
|
+
},
|
|
1271
|
+
marketplaceList: async () => {
|
|
1272
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1273
|
+
name: m.name,
|
|
1274
|
+
type: m.source.type
|
|
1275
|
+
}));
|
|
1276
|
+
},
|
|
1277
|
+
reloadPlugins: async () => {
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
}, [cwd]);
|
|
1281
|
+
}
|
|
200
1282
|
|
|
201
1283
|
// src/ui/MessageList.tsx
|
|
202
|
-
var
|
|
1284
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
1285
|
+
var import_ink2 = require("ink");
|
|
203
1286
|
|
|
204
1287
|
// src/ui/render-markdown.ts
|
|
205
1288
|
var import_marked = require("marked");
|
|
@@ -212,54 +1295,144 @@ function renderMarkdown(md) {
|
|
|
212
1295
|
return typeof result === "string" ? result.trimEnd() : md;
|
|
213
1296
|
}
|
|
214
1297
|
|
|
215
|
-
// src/ui/
|
|
1298
|
+
// src/ui/DiffBlock.tsx
|
|
1299
|
+
var import_ink = require("ink");
|
|
216
1300
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1301
|
+
var MAX_DIFF_LINES = 10;
|
|
1302
|
+
var TRUNCATED_SHOW = 8;
|
|
1303
|
+
function DiffBlock({ file, lines }) {
|
|
1304
|
+
const truncated = lines.length > MAX_DIFF_LINES;
|
|
1305
|
+
const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
|
|
1306
|
+
const remaining = lines.length - TRUNCATED_SHOW;
|
|
1307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
1308
|
+
file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1309
|
+
"\u2502 ",
|
|
1310
|
+
file
|
|
1311
|
+
] }),
|
|
1312
|
+
visible.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: line.type === "remove" ? "red" : "greenBright", children: [
|
|
1313
|
+
"\u2502 ",
|
|
1314
|
+
line.type === "remove" ? "-" : "+",
|
|
1315
|
+
" ",
|
|
1316
|
+
line.text
|
|
1317
|
+
] }, i)),
|
|
1318
|
+
truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1319
|
+
"\u2502 ... and ",
|
|
1320
|
+
remaining,
|
|
1321
|
+
" more lines"
|
|
1322
|
+
] })
|
|
1323
|
+
] });
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/ui/MessageList.tsx
|
|
1327
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
217
1328
|
function RoleLabel({ role }) {
|
|
218
1329
|
switch (role) {
|
|
219
1330
|
case "user":
|
|
220
|
-
return /* @__PURE__ */ (0,
|
|
1331
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
|
|
221
1332
|
"You:",
|
|
222
1333
|
" "
|
|
223
1334
|
] });
|
|
224
1335
|
case "assistant":
|
|
225
|
-
return /* @__PURE__ */ (0,
|
|
1336
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
|
|
226
1337
|
"Robota:",
|
|
227
1338
|
" "
|
|
228
1339
|
] });
|
|
229
1340
|
case "system":
|
|
230
|
-
return /* @__PURE__ */ (0,
|
|
1341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
|
|
231
1342
|
"System:",
|
|
232
1343
|
" "
|
|
233
1344
|
] });
|
|
234
1345
|
case "tool":
|
|
235
|
-
return /* @__PURE__ */ (0,
|
|
1346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
236
1347
|
"Tool:",
|
|
237
1348
|
" "
|
|
238
1349
|
] });
|
|
239
1350
|
}
|
|
240
1351
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
1352
|
+
function ToolMessage({ message }) {
|
|
1353
|
+
let summaries = null;
|
|
1354
|
+
try {
|
|
1355
|
+
const parsed = JSON.parse(message.content);
|
|
1356
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
1357
|
+
summaries = parsed;
|
|
1358
|
+
}
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
if (summaries) {
|
|
1362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1363
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1364
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1365
|
+
"Tool:",
|
|
1366
|
+
" "
|
|
1367
|
+
] }),
|
|
1368
|
+
message.toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1369
|
+
"[",
|
|
1370
|
+
message.toolName,
|
|
1371
|
+
"]"
|
|
1372
|
+
] })
|
|
1373
|
+
] }),
|
|
1374
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1375
|
+
summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
|
|
1376
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1377
|
+
" ",
|
|
1378
|
+
"\u2713",
|
|
1379
|
+
" ",
|
|
1380
|
+
s.line
|
|
1381
|
+
] }),
|
|
1382
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
1383
|
+
] }, i))
|
|
1384
|
+
] });
|
|
1385
|
+
}
|
|
1386
|
+
const lines = message.content.split("\n").filter((l) => l.trim());
|
|
1387
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1388
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1389
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1390
|
+
"Tool:",
|
|
1391
|
+
" "
|
|
1392
|
+
] }),
|
|
1393
|
+
message.toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1394
|
+
"[",
|
|
1395
|
+
message.toolName,
|
|
1396
|
+
"]"
|
|
1397
|
+
] })
|
|
1398
|
+
] }),
|
|
1399
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1400
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1401
|
+
" ",
|
|
1402
|
+
"\u2713",
|
|
1403
|
+
" ",
|
|
1404
|
+
line
|
|
1405
|
+
] }, i))
|
|
1406
|
+
] });
|
|
1407
|
+
}
|
|
1408
|
+
var MessageItem = import_react7.default.memo(function MessageItem2({
|
|
1409
|
+
message
|
|
1410
|
+
}) {
|
|
1411
|
+
if (message.role === "tool") {
|
|
1412
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
|
|
1413
|
+
}
|
|
1414
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1415
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1416
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }),
|
|
1417
|
+
message.toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "magenta", dimColor: true, children: [
|
|
246
1418
|
"[",
|
|
247
1419
|
message.toolName,
|
|
248
1420
|
"]",
|
|
249
1421
|
" "
|
|
250
1422
|
] })
|
|
251
1423
|
] }),
|
|
252
|
-
/* @__PURE__ */ (0,
|
|
253
|
-
/* @__PURE__ */ (0,
|
|
1424
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1425
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
|
|
254
1426
|
] });
|
|
255
|
-
}
|
|
1427
|
+
});
|
|
256
1428
|
function MessageList({ messages }) {
|
|
257
|
-
return /* @__PURE__ */ (0,
|
|
1429
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message: msg }, msg.id)) });
|
|
258
1430
|
}
|
|
259
1431
|
|
|
260
1432
|
// src/ui/StatusBar.tsx
|
|
261
|
-
var
|
|
262
|
-
var
|
|
1433
|
+
var import_ink3 = require("ink");
|
|
1434
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
1435
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
263
1436
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
264
1437
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
265
1438
|
function getContextColor(percentage) {
|
|
@@ -278,8 +1451,8 @@ function StatusBar({
|
|
|
278
1451
|
contextMaxTokens
|
|
279
1452
|
}) {
|
|
280
1453
|
const contextColor = getContextColor(contextPercentage);
|
|
281
|
-
return /* @__PURE__ */ (0,
|
|
282
|
-
|
|
1454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1455
|
+
import_ink3.Box,
|
|
283
1456
|
{
|
|
284
1457
|
borderStyle: "single",
|
|
285
1458
|
borderColor: "gray",
|
|
@@ -287,26 +1460,26 @@ function StatusBar({
|
|
|
287
1460
|
paddingRight: 1,
|
|
288
1461
|
justifyContent: "space-between",
|
|
289
1462
|
children: [
|
|
290
|
-
/* @__PURE__ */ (0,
|
|
291
|
-
/* @__PURE__ */ (0,
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1464
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
|
|
292
1465
|
" ",
|
|
293
|
-
/* @__PURE__ */ (0,
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
|
|
294
1467
|
" | ",
|
|
295
|
-
/* @__PURE__ */ (0,
|
|
1468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
|
|
296
1469
|
" | ",
|
|
297
|
-
/* @__PURE__ */ (0,
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
|
|
298
1471
|
"Context: ",
|
|
299
1472
|
Math.round(contextPercentage),
|
|
300
1473
|
"% (",
|
|
301
|
-
(
|
|
302
|
-
"
|
|
303
|
-
(
|
|
304
|
-
"
|
|
1474
|
+
(0, import_agent_core2.formatTokenCount)(contextUsedTokens),
|
|
1475
|
+
"/",
|
|
1476
|
+
(0, import_agent_core2.formatTokenCount)(contextMaxTokens),
|
|
1477
|
+
")"
|
|
305
1478
|
] })
|
|
306
1479
|
] }),
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
308
|
-
isThinking && /* @__PURE__ */ (0,
|
|
309
|
-
/* @__PURE__ */ (0,
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1481
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
|
|
310
1483
|
"msgs: ",
|
|
311
1484
|
messageCount
|
|
312
1485
|
] })
|
|
@@ -317,15 +1490,22 @@ function StatusBar({
|
|
|
317
1490
|
}
|
|
318
1491
|
|
|
319
1492
|
// src/ui/InputArea.tsx
|
|
320
|
-
var
|
|
321
|
-
var
|
|
1493
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1494
|
+
var import_ink7 = require("ink");
|
|
322
1495
|
|
|
323
1496
|
// src/ui/CjkTextInput.tsx
|
|
324
|
-
var
|
|
325
|
-
var
|
|
326
|
-
var
|
|
327
|
-
var
|
|
328
|
-
|
|
1497
|
+
var import_react8 = require("react");
|
|
1498
|
+
var import_ink4 = require("ink");
|
|
1499
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
1500
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1501
|
+
function filterPrintable(input) {
|
|
1502
|
+
if (!input || input.length === 0) return "";
|
|
1503
|
+
return input.replace(/[\x00-\x1f\x7f]/g, "");
|
|
1504
|
+
}
|
|
1505
|
+
function insertAtCursor(value, cursor, input) {
|
|
1506
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
1507
|
+
return { value: next, cursor: cursor + input.length };
|
|
1508
|
+
}
|
|
329
1509
|
function CjkTextInput({
|
|
330
1510
|
value,
|
|
331
1511
|
onChange,
|
|
@@ -334,129 +1514,120 @@ function CjkTextInput({
|
|
|
334
1514
|
focus = true,
|
|
335
1515
|
showCursor = true
|
|
336
1516
|
}) {
|
|
337
|
-
const valueRef = (0,
|
|
338
|
-
const cursorRef = (0,
|
|
339
|
-
const [, forceRender] = (0,
|
|
340
|
-
const { setCursorPosition } = (0, import_ink3.useCursor)();
|
|
1517
|
+
const valueRef = (0, import_react8.useRef)(value);
|
|
1518
|
+
const cursorRef = (0, import_react8.useRef)(value.length);
|
|
1519
|
+
const [, forceRender] = (0, import_react8.useState)(0);
|
|
341
1520
|
if (value !== valueRef.current) {
|
|
342
1521
|
valueRef.current = value;
|
|
343
1522
|
if (cursorRef.current > value.length) {
|
|
344
1523
|
cursorRef.current = value.length;
|
|
345
1524
|
}
|
|
346
1525
|
}
|
|
347
|
-
(0,
|
|
1526
|
+
(0, import_ink4.useInput)(
|
|
348
1527
|
(input, key) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (key.return) {
|
|
353
|
-
onSubmit?.(valueRef.current);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (key.leftArrow) {
|
|
357
|
-
if (cursorRef.current > 0) {
|
|
358
|
-
cursorRef.current -= 1;
|
|
359
|
-
forceRender((n) => n + 1);
|
|
1528
|
+
try {
|
|
1529
|
+
if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
|
|
1530
|
+
return;
|
|
360
1531
|
}
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (cursorRef.current < valueRef.current.length) {
|
|
365
|
-
cursorRef.current += 1;
|
|
366
|
-
forceRender((n) => n + 1);
|
|
1532
|
+
if (key.return) {
|
|
1533
|
+
onSubmit?.(valueRef.current);
|
|
1534
|
+
return;
|
|
367
1535
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
cursorRef.current -= 1;
|
|
375
|
-
valueRef.current = next2;
|
|
376
|
-
onChange(next2);
|
|
1536
|
+
if (key.leftArrow) {
|
|
1537
|
+
if (cursorRef.current > 0) {
|
|
1538
|
+
cursorRef.current -= 1;
|
|
1539
|
+
forceRender((n) => n + 1);
|
|
1540
|
+
}
|
|
1541
|
+
return;
|
|
377
1542
|
}
|
|
378
|
-
|
|
1543
|
+
if (key.rightArrow) {
|
|
1544
|
+
if (cursorRef.current < valueRef.current.length) {
|
|
1545
|
+
cursorRef.current += 1;
|
|
1546
|
+
forceRender((n) => n + 1);
|
|
1547
|
+
}
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
if (key.backspace || key.delete) {
|
|
1551
|
+
if (cursorRef.current > 0) {
|
|
1552
|
+
const v = valueRef.current;
|
|
1553
|
+
const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
|
|
1554
|
+
cursorRef.current -= 1;
|
|
1555
|
+
valueRef.current = next;
|
|
1556
|
+
onChange(next);
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const printable = filterPrintable(input);
|
|
1561
|
+
if (printable.length === 0) return;
|
|
1562
|
+
const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
|
|
1563
|
+
cursorRef.current = result.cursor;
|
|
1564
|
+
valueRef.current = result.value;
|
|
1565
|
+
onChange(result.value);
|
|
1566
|
+
} catch {
|
|
379
1567
|
}
|
|
380
|
-
const v = valueRef.current;
|
|
381
|
-
const c = cursorRef.current;
|
|
382
|
-
const next = v.slice(0, c) + input + v.slice(c);
|
|
383
|
-
cursorRef.current = c + input.length;
|
|
384
|
-
valueRef.current = next;
|
|
385
|
-
onChange(next);
|
|
386
1568
|
},
|
|
387
1569
|
{ isActive: focus }
|
|
388
1570
|
);
|
|
389
|
-
|
|
390
|
-
const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
|
|
391
|
-
const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
|
|
392
|
-
setCursorPosition({ x: cursorX, y: 0 });
|
|
393
|
-
}
|
|
394
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
1571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
395
1572
|
}
|
|
396
1573
|
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
397
1574
|
if (!showCursor) {
|
|
398
|
-
return value.length > 0 ? value : placeholder ?
|
|
1575
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
399
1576
|
}
|
|
400
1577
|
if (value.length === 0) {
|
|
401
1578
|
if (placeholder.length > 0) {
|
|
402
|
-
return
|
|
1579
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
403
1580
|
}
|
|
404
|
-
return
|
|
1581
|
+
return import_chalk.default.inverse(" ");
|
|
405
1582
|
}
|
|
406
1583
|
const chars = [...value];
|
|
407
1584
|
let rendered = "";
|
|
408
1585
|
for (let i = 0; i < chars.length; i++) {
|
|
409
1586
|
const char = chars[i] ?? "";
|
|
410
|
-
rendered += i === cursorOffset ?
|
|
1587
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
411
1588
|
}
|
|
412
1589
|
if (cursorOffset >= chars.length) {
|
|
413
|
-
rendered +=
|
|
1590
|
+
rendered += import_chalk.default.inverse(" ");
|
|
414
1591
|
}
|
|
415
1592
|
return rendered;
|
|
416
1593
|
}
|
|
417
1594
|
|
|
418
1595
|
// src/ui/WaveText.tsx
|
|
419
|
-
var
|
|
420
|
-
var
|
|
421
|
-
var
|
|
1596
|
+
var import_react9 = require("react");
|
|
1597
|
+
var import_ink5 = require("ink");
|
|
1598
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
422
1599
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
423
1600
|
var INTERVAL_MS = 400;
|
|
424
1601
|
var CHARS_PER_GROUP = 4;
|
|
425
1602
|
function WaveText({ text }) {
|
|
426
|
-
const [tick, setTick] = (0,
|
|
427
|
-
(0,
|
|
1603
|
+
const [tick, setTick] = (0, import_react9.useState)(0);
|
|
1604
|
+
(0, import_react9.useEffect)(() => {
|
|
428
1605
|
const timer = setInterval(() => {
|
|
429
1606
|
setTick((prev) => prev + 1);
|
|
430
1607
|
}, INTERVAL_MS);
|
|
431
1608
|
return () => clearInterval(timer);
|
|
432
1609
|
}, []);
|
|
433
1610
|
const chars = [...text];
|
|
434
|
-
return /* @__PURE__ */ (0,
|
|
1611
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
|
|
435
1612
|
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
436
1613
|
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
437
|
-
return /* @__PURE__ */ (0,
|
|
1614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
438
1615
|
}) });
|
|
439
1616
|
}
|
|
440
1617
|
|
|
441
1618
|
// src/ui/SlashAutocomplete.tsx
|
|
442
|
-
var
|
|
443
|
-
var
|
|
1619
|
+
var import_ink6 = require("ink");
|
|
1620
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
444
1621
|
var MAX_VISIBLE = 8;
|
|
445
1622
|
function CommandRow(props) {
|
|
446
1623
|
const { cmd, isSelected, showSlash } = props;
|
|
447
|
-
const prefix = showSlash ? "/" : "";
|
|
448
1624
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
449
1625
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
450
1626
|
const dimmed = !isSelected;
|
|
451
|
-
return /* @__PURE__ */ (0,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
cmd.name
|
|
456
|
-
] }),
|
|
457
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
458
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
459
|
-
] });
|
|
1627
|
+
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: [
|
|
1628
|
+
indicator,
|
|
1629
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
1630
|
+
] }) });
|
|
460
1631
|
}
|
|
461
1632
|
function SlashAutocomplete({
|
|
462
1633
|
commands,
|
|
@@ -467,7 +1638,7 @@ function SlashAutocomplete({
|
|
|
467
1638
|
if (!visible || commands.length === 0) return null;
|
|
468
1639
|
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
469
1640
|
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
470
|
-
return /* @__PURE__ */ (0,
|
|
1641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
471
1642
|
CommandRow,
|
|
472
1643
|
{
|
|
473
1644
|
cmd,
|
|
@@ -485,7 +1656,7 @@ function computeScrollOffset(selectedIndex, total) {
|
|
|
485
1656
|
}
|
|
486
1657
|
|
|
487
1658
|
// src/ui/InputArea.tsx
|
|
488
|
-
var
|
|
1659
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
489
1660
|
function parseSlashInput(value) {
|
|
490
1661
|
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
491
1662
|
const afterSlash = value.slice(1);
|
|
@@ -496,16 +1667,16 @@ function parseSlashInput(value) {
|
|
|
496
1667
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
497
1668
|
}
|
|
498
1669
|
function useAutocomplete(value, registry) {
|
|
499
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
500
|
-
const [dismissed, setDismissed] = (0,
|
|
501
|
-
const prevValueRef =
|
|
1670
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
|
|
1671
|
+
const [dismissed, setDismissed] = (0, import_react10.useState)(false);
|
|
1672
|
+
const prevValueRef = import_react10.default.useRef(value);
|
|
502
1673
|
if (prevValueRef.current !== value) {
|
|
503
1674
|
prevValueRef.current = value;
|
|
504
1675
|
if (dismissed) setDismissed(false);
|
|
505
1676
|
}
|
|
506
1677
|
const parsed = parseSlashInput(value);
|
|
507
1678
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
508
|
-
const filteredCommands = (0,
|
|
1679
|
+
const filteredCommands = (0, import_react10.useMemo)(() => {
|
|
509
1680
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
510
1681
|
if (isSubcommandMode) {
|
|
511
1682
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -539,7 +1710,7 @@ function useAutocomplete(value, registry) {
|
|
|
539
1710
|
};
|
|
540
1711
|
}
|
|
541
1712
|
function InputArea({ onSubmit, isDisabled, registry }) {
|
|
542
|
-
const [value, setValue] = (0,
|
|
1713
|
+
const [value, setValue] = (0, import_react10.useState)("");
|
|
543
1714
|
const {
|
|
544
1715
|
showPopup,
|
|
545
1716
|
filteredCommands,
|
|
@@ -548,7 +1719,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
548
1719
|
isSubcommandMode,
|
|
549
1720
|
setShowPopup
|
|
550
1721
|
} = useAutocomplete(value, registry);
|
|
551
|
-
const handleSubmit = (0,
|
|
1722
|
+
const handleSubmit = (0, import_react10.useCallback)(
|
|
552
1723
|
(text) => {
|
|
553
1724
|
const trimmed = text.trim();
|
|
554
1725
|
if (trimmed.length === 0) return;
|
|
@@ -561,7 +1732,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
561
1732
|
},
|
|
562
1733
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
563
1734
|
);
|
|
564
|
-
const selectCommand = (0,
|
|
1735
|
+
const selectCommand = (0, import_react10.useCallback)(
|
|
565
1736
|
(cmd) => {
|
|
566
1737
|
const parsed = parseSlashInput(value);
|
|
567
1738
|
if (parsed.parentCommand) {
|
|
@@ -580,7 +1751,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
580
1751
|
},
|
|
581
1752
|
[value, onSubmit, setSelectedIndex]
|
|
582
1753
|
);
|
|
583
|
-
(0,
|
|
1754
|
+
(0, import_ink7.useInput)(
|
|
584
1755
|
(_input, key) => {
|
|
585
1756
|
if (!showPopup) return;
|
|
586
1757
|
if (key.upArrow) {
|
|
@@ -596,8 +1767,8 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
596
1767
|
},
|
|
597
1768
|
{ isActive: showPopup && !isDisabled }
|
|
598
1769
|
);
|
|
599
|
-
return /* @__PURE__ */ (0,
|
|
600
|
-
showPopup && /* @__PURE__ */ (0,
|
|
1770
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
|
|
1771
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
601
1772
|
SlashAutocomplete,
|
|
602
1773
|
{
|
|
603
1774
|
commands: filteredCommands,
|
|
@@ -606,9 +1777,9 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
606
1777
|
isSubcommandMode
|
|
607
1778
|
}
|
|
608
1779
|
),
|
|
609
|
-
/* @__PURE__ */ (0,
|
|
610
|
-
/* @__PURE__ */ (0,
|
|
611
|
-
/* @__PURE__ */ (0,
|
|
1780
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
|
|
1781
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
|
|
1782
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
612
1783
|
CjkTextInput,
|
|
613
1784
|
{
|
|
614
1785
|
value,
|
|
@@ -619,411 +1790,301 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
619
1790
|
)
|
|
620
1791
|
] }) })
|
|
621
1792
|
] });
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// src/ui/
|
|
625
|
-
var
|
|
626
|
-
var
|
|
627
|
-
var
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
resolvedRef.current = false;
|
|
641
|
-
setSelected(0);
|
|
642
|
-
}
|
|
643
|
-
const doResolve = import_react4.default.useCallback(
|
|
644
|
-
(index) => {
|
|
645
|
-
if (resolvedRef.current) return;
|
|
646
|
-
resolvedRef.current = true;
|
|
647
|
-
if (index === 0) request.resolve(true);
|
|
648
|
-
else if (index === 1) request.resolve("allow-session");
|
|
649
|
-
else request.resolve(false);
|
|
650
|
-
},
|
|
651
|
-
[request]
|
|
652
|
-
);
|
|
653
|
-
(0, import_ink7.useInput)((input, key) => {
|
|
654
|
-
if (resolvedRef.current) return;
|
|
655
|
-
if (key.upArrow || key.leftArrow) {
|
|
656
|
-
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
657
|
-
} else if (key.downArrow || key.rightArrow) {
|
|
658
|
-
setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
|
|
659
|
-
} else if (key.return) {
|
|
660
|
-
doResolve(selected);
|
|
661
|
-
} else if (input === "y" || input === "1") {
|
|
662
|
-
doResolve(0);
|
|
663
|
-
} else if (input === "a" || input === "2") {
|
|
664
|
-
doResolve(1);
|
|
665
|
-
} else if (input === "n" || input === "d" || input === "3") {
|
|
666
|
-
doResolve(2);
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
670
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
671
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { children: [
|
|
672
|
-
"Tool:",
|
|
673
|
-
" ",
|
|
674
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
675
|
-
] }),
|
|
676
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { dimColor: true, children: [
|
|
677
|
-
" ",
|
|
678
|
-
formatArgs2(request.toolArgs)
|
|
679
|
-
] }),
|
|
680
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
681
|
-
i === selected ? "> " : " ",
|
|
682
|
-
opt
|
|
683
|
-
] }) }, opt)) }),
|
|
684
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
685
|
-
] });
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// src/ui/App.tsx
|
|
689
|
-
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
690
|
-
var msgIdCounter = 0;
|
|
691
|
-
function nextId() {
|
|
692
|
-
msgIdCounter += 1;
|
|
693
|
-
return `msg_${msgIdCounter}`;
|
|
694
|
-
}
|
|
695
|
-
var NOOP_TERMINAL = {
|
|
696
|
-
write: () => {
|
|
697
|
-
},
|
|
698
|
-
writeLine: () => {
|
|
699
|
-
},
|
|
700
|
-
writeMarkdown: () => {
|
|
701
|
-
},
|
|
702
|
-
writeError: () => {
|
|
703
|
-
},
|
|
704
|
-
prompt: () => Promise.resolve(""),
|
|
705
|
-
select: () => Promise.resolve(0),
|
|
706
|
-
spinner: () => ({ stop: () => {
|
|
707
|
-
}, update: () => {
|
|
708
|
-
} })
|
|
709
|
-
};
|
|
710
|
-
function useSession(props) {
|
|
711
|
-
const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
|
|
712
|
-
const [streamingText, setStreamingText] = (0, import_react5.useState)("");
|
|
713
|
-
const permissionQueueRef = (0, import_react5.useRef)([]);
|
|
714
|
-
const processingRef = (0, import_react5.useRef)(false);
|
|
715
|
-
const processNextPermission = (0, import_react5.useCallback)(() => {
|
|
716
|
-
if (processingRef.current) return;
|
|
717
|
-
const next = permissionQueueRef.current[0];
|
|
718
|
-
if (!next) {
|
|
719
|
-
setPermissionRequest(null);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
processingRef.current = true;
|
|
723
|
-
setPermissionRequest({
|
|
724
|
-
toolName: next.toolName,
|
|
725
|
-
toolArgs: next.toolArgs,
|
|
726
|
-
resolve: (result) => {
|
|
727
|
-
permissionQueueRef.current.shift();
|
|
728
|
-
processingRef.current = false;
|
|
729
|
-
setPermissionRequest(null);
|
|
730
|
-
next.resolve(result);
|
|
731
|
-
setTimeout(() => processNextPermission(), 0);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}, []);
|
|
735
|
-
const sessionRef = (0, import_react5.useRef)(null);
|
|
736
|
-
if (sessionRef.current === null) {
|
|
737
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
738
|
-
return new Promise((resolve) => {
|
|
739
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
740
|
-
processNextPermission();
|
|
741
|
-
});
|
|
742
|
-
};
|
|
743
|
-
const onTextDelta = (delta) => {
|
|
744
|
-
setStreamingText((prev) => prev + delta);
|
|
745
|
-
};
|
|
746
|
-
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
747
|
-
sessionRef.current = new import_agent_sdk.Session({
|
|
748
|
-
config: props.config,
|
|
749
|
-
context: props.context,
|
|
750
|
-
terminal: NOOP_TERMINAL,
|
|
751
|
-
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
752
|
-
projectInfo: props.projectInfo,
|
|
753
|
-
sessionStore: props.sessionStore,
|
|
754
|
-
permissionMode: props.permissionMode,
|
|
755
|
-
maxTurns: props.maxTurns,
|
|
756
|
-
permissionHandler,
|
|
757
|
-
onTextDelta
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
|
|
761
|
-
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
762
|
-
}
|
|
763
|
-
function useMessages() {
|
|
764
|
-
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
765
|
-
const addMessage = (0, import_react5.useCallback)((msg) => {
|
|
766
|
-
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
767
|
-
}, []);
|
|
768
|
-
return { messages, setMessages, addMessage };
|
|
769
|
-
}
|
|
770
|
-
var HELP_TEXT = [
|
|
771
|
-
"Available commands:",
|
|
772
|
-
" /help \u2014 Show this help",
|
|
773
|
-
" /clear \u2014 Clear conversation",
|
|
774
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
775
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
776
|
-
" /cost \u2014 Show session info",
|
|
777
|
-
" /exit \u2014 Exit CLI"
|
|
778
|
-
].join("\n");
|
|
779
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
780
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
781
|
-
if (!arg) {
|
|
782
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
783
|
-
} else if (validModes.includes(arg)) {
|
|
784
|
-
session.setPermissionMode(arg);
|
|
785
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
786
|
-
} else {
|
|
787
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
788
|
-
}
|
|
789
|
-
return true;
|
|
790
|
-
}
|
|
791
|
-
async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
|
|
792
|
-
switch (cmd) {
|
|
793
|
-
case "help":
|
|
794
|
-
addMessage({ role: "system", content: HELP_TEXT });
|
|
795
|
-
return true;
|
|
796
|
-
case "clear":
|
|
797
|
-
setMessages([]);
|
|
798
|
-
session.clearHistory();
|
|
799
|
-
addMessage({ role: "system", content: "Conversation cleared." });
|
|
800
|
-
return true;
|
|
801
|
-
case "compact": {
|
|
802
|
-
const instructions = parts.slice(1).join(" ").trim() || void 0;
|
|
803
|
-
const before = session.getContextState().usedPercentage;
|
|
804
|
-
addMessage({ role: "system", content: "Compacting context..." });
|
|
805
|
-
await session.compact(instructions);
|
|
806
|
-
const after = session.getContextState().usedPercentage;
|
|
807
|
-
addMessage({
|
|
808
|
-
role: "system",
|
|
809
|
-
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
810
|
-
});
|
|
811
|
-
return true;
|
|
812
|
-
}
|
|
813
|
-
case "mode":
|
|
814
|
-
return handleModeCommand(parts[1], session, addMessage);
|
|
815
|
-
case "cost":
|
|
816
|
-
addMessage({
|
|
817
|
-
role: "system",
|
|
818
|
-
content: `Session: ${session.getSessionId()}
|
|
819
|
-
Messages: ${session.getMessageCount()}`
|
|
820
|
-
});
|
|
821
|
-
return true;
|
|
822
|
-
case "permissions": {
|
|
823
|
-
const mode = session.getPermissionMode();
|
|
824
|
-
const sessionAllowed = session.getSessionAllowedTools();
|
|
825
|
-
const lines = [`Permission mode: ${mode}`];
|
|
826
|
-
if (sessionAllowed.length > 0) {
|
|
827
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
828
|
-
} else {
|
|
829
|
-
lines.push("No session-approved tools.");
|
|
830
|
-
}
|
|
831
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
case "context": {
|
|
835
|
-
const ctx = session.getContextState();
|
|
836
|
-
addMessage({
|
|
837
|
-
role: "system",
|
|
838
|
-
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
839
|
-
});
|
|
840
|
-
return true;
|
|
841
|
-
}
|
|
842
|
-
case "exit":
|
|
843
|
-
exit();
|
|
844
|
-
return true;
|
|
845
|
-
default: {
|
|
846
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
847
|
-
if (skillCmd) {
|
|
848
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
852
|
-
return true;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry) {
|
|
857
|
-
return (0, import_react5.useCallback)(
|
|
858
|
-
async (input) => {
|
|
859
|
-
const parts = input.slice(1).split(/\s+/);
|
|
860
|
-
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
861
|
-
return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/ui/ConfirmPrompt.tsx
|
|
1796
|
+
var import_react11 = require("react");
|
|
1797
|
+
var import_ink8 = require("ink");
|
|
1798
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1799
|
+
function ConfirmPrompt({
|
|
1800
|
+
message,
|
|
1801
|
+
options = ["Yes", "No"],
|
|
1802
|
+
onSelect
|
|
1803
|
+
}) {
|
|
1804
|
+
const [selected, setSelected] = (0, import_react11.useState)(0);
|
|
1805
|
+
const resolvedRef = (0, import_react11.useRef)(false);
|
|
1806
|
+
const doSelect = (0, import_react11.useCallback)(
|
|
1807
|
+
(index) => {
|
|
1808
|
+
if (resolvedRef.current) return;
|
|
1809
|
+
resolvedRef.current = true;
|
|
1810
|
+
onSelect(index);
|
|
862
1811
|
},
|
|
863
|
-
[
|
|
1812
|
+
[onSelect]
|
|
864
1813
|
);
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
|
|
878
|
-
}
|
|
879
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
|
|
880
|
-
setIsThinking(true);
|
|
881
|
-
clearStreamingText();
|
|
882
|
-
try {
|
|
883
|
-
const response = await session.run(prompt);
|
|
884
|
-
clearStreamingText();
|
|
885
|
-
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
886
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
887
|
-
} catch (err) {
|
|
888
|
-
clearStreamingText();
|
|
889
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
890
|
-
addMessage({ role: "system", content: "Cancelled." });
|
|
891
|
-
} else {
|
|
892
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
893
|
-
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
1814
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
1815
|
+
if (resolvedRef.current) return;
|
|
1816
|
+
if (key.leftArrow || key.upArrow) {
|
|
1817
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
1818
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
1819
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
1820
|
+
} else if (key.return) {
|
|
1821
|
+
doSelect(selected);
|
|
1822
|
+
} else if (input === "y" && options.length === 2) {
|
|
1823
|
+
doSelect(0);
|
|
1824
|
+
} else if (input === "n" && options.length === 2) {
|
|
1825
|
+
doSelect(1);
|
|
894
1826
|
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
1827
|
+
});
|
|
1828
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1829
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
|
|
1830
|
+
/* @__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: [
|
|
1831
|
+
i === selected ? "> " : " ",
|
|
1832
|
+
opt
|
|
1833
|
+
] }) }, opt)) }),
|
|
1834
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
1835
|
+
] });
|
|
898
1836
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1837
|
+
|
|
1838
|
+
// src/ui/PermissionPrompt.tsx
|
|
1839
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
1840
|
+
var import_ink9 = require("ink");
|
|
1841
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1842
|
+
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
1843
|
+
function formatArgs(args) {
|
|
1844
|
+
const entries = Object.entries(args);
|
|
1845
|
+
if (entries.length === 0) return "(no arguments)";
|
|
1846
|
+
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
906
1847
|
}
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
setIsThinking,
|
|
924
|
-
setContextPercentage
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
addMessage({ role: "user", content: input });
|
|
928
|
-
return runSessionPrompt(
|
|
929
|
-
input,
|
|
930
|
-
session,
|
|
931
|
-
addMessage,
|
|
932
|
-
clearStreamingText,
|
|
933
|
-
setIsThinking,
|
|
934
|
-
setContextPercentage
|
|
935
|
-
);
|
|
1848
|
+
function PermissionPrompt({ request }) {
|
|
1849
|
+
const [selected, setSelected] = import_react12.default.useState(0);
|
|
1850
|
+
const resolvedRef = import_react12.default.useRef(false);
|
|
1851
|
+
const prevRequestRef = import_react12.default.useRef(request);
|
|
1852
|
+
if (prevRequestRef.current !== request) {
|
|
1853
|
+
prevRequestRef.current = request;
|
|
1854
|
+
resolvedRef.current = false;
|
|
1855
|
+
setSelected(0);
|
|
1856
|
+
}
|
|
1857
|
+
const doResolve = import_react12.default.useCallback(
|
|
1858
|
+
(index) => {
|
|
1859
|
+
if (resolvedRef.current) return;
|
|
1860
|
+
resolvedRef.current = true;
|
|
1861
|
+
if (index === 0) request.resolve(true);
|
|
1862
|
+
else if (index === 1) request.resolve("allow-session");
|
|
1863
|
+
else request.resolve(false);
|
|
936
1864
|
},
|
|
937
|
-
[
|
|
938
|
-
session,
|
|
939
|
-
addMessage,
|
|
940
|
-
handleSlashCommand,
|
|
941
|
-
clearStreamingText,
|
|
942
|
-
setIsThinking,
|
|
943
|
-
setContextPercentage,
|
|
944
|
-
registry
|
|
945
|
-
]
|
|
1865
|
+
[request]
|
|
946
1866
|
);
|
|
1867
|
+
(0, import_ink9.useInput)((input, key) => {
|
|
1868
|
+
if (resolvedRef.current) return;
|
|
1869
|
+
if (key.upArrow || key.leftArrow) {
|
|
1870
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
1871
|
+
} else if (key.downArrow || key.rightArrow) {
|
|
1872
|
+
setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
|
|
1873
|
+
} else if (key.return) {
|
|
1874
|
+
doResolve(selected);
|
|
1875
|
+
} else if (input === "y" || input === "1") {
|
|
1876
|
+
doResolve(0);
|
|
1877
|
+
} else if (input === "a" || input === "2") {
|
|
1878
|
+
doResolve(1);
|
|
1879
|
+
} else if (input === "n" || input === "d" || input === "3") {
|
|
1880
|
+
doResolve(2);
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1884
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
1885
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
|
|
1886
|
+
"Tool:",
|
|
1887
|
+
" ",
|
|
1888
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
1889
|
+
] }),
|
|
1890
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
|
|
1891
|
+
" ",
|
|
1892
|
+
formatArgs(request.toolArgs)
|
|
1893
|
+
] }),
|
|
1894
|
+
/* @__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: [
|
|
1895
|
+
i === selected ? "> " : " ",
|
|
1896
|
+
opt
|
|
1897
|
+
] }) }, opt)) }),
|
|
1898
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
1899
|
+
] });
|
|
947
1900
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1901
|
+
|
|
1902
|
+
// src/ui/StreamingIndicator.tsx
|
|
1903
|
+
var import_ink10 = require("ink");
|
|
1904
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1905
|
+
function getToolStyle(t) {
|
|
1906
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
1907
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
1908
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
1909
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
1910
|
+
}
|
|
1911
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
1912
|
+
const hasTools = activeTools.length > 0;
|
|
1913
|
+
const hasText = text.length > 0;
|
|
1914
|
+
if (!hasTools && !hasText) {
|
|
1915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "yellow", children: "Thinking..." });
|
|
1916
|
+
}
|
|
1917
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
1918
|
+
hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
1920
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
1921
|
+
activeTools.map((t, i) => {
|
|
1922
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
1923
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
1924
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
|
|
1925
|
+
" ",
|
|
1926
|
+
icon,
|
|
1927
|
+
" ",
|
|
1928
|
+
t.toolName,
|
|
1929
|
+
"(",
|
|
1930
|
+
t.firstArg,
|
|
1931
|
+
")"
|
|
1932
|
+
] }),
|
|
1933
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
1934
|
+
] }, `${t.toolName}-${i}`);
|
|
1935
|
+
})
|
|
1936
|
+
] }),
|
|
1937
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1938
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
1939
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
1940
|
+
/* @__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) }) })
|
|
1941
|
+
] })
|
|
1942
|
+
] });
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/ui/App.tsx
|
|
1946
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1947
|
+
var EXIT_DELAY_MS2 = 500;
|
|
1948
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
1949
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
1950
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
1951
|
+
const merged = {};
|
|
1952
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
1953
|
+
merged[event] = [...groups];
|
|
1954
|
+
}
|
|
1955
|
+
if (configHooks) {
|
|
1956
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
1957
|
+
if (!Array.isArray(groups)) continue;
|
|
1958
|
+
if (!merged[event]) merged[event] = [];
|
|
1959
|
+
merged[event].push(...groups);
|
|
1960
|
+
}
|
|
955
1961
|
}
|
|
956
|
-
return
|
|
1962
|
+
return merged;
|
|
957
1963
|
}
|
|
958
1964
|
function App(props) {
|
|
959
|
-
const { exit } = (0,
|
|
960
|
-
const {
|
|
1965
|
+
const { exit } = (0, import_ink11.useApp)();
|
|
1966
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1967
|
+
const configWithPluginHooks = {
|
|
1968
|
+
...props.config,
|
|
1969
|
+
hooks: mergeHooksIntoConfig(
|
|
1970
|
+
props.config.hooks,
|
|
1971
|
+
pluginHooks
|
|
1972
|
+
)
|
|
1973
|
+
};
|
|
1974
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
1975
|
+
{ ...props, config: configWithPluginHooks }
|
|
1976
|
+
);
|
|
961
1977
|
const { messages, setMessages, addMessage } = useMessages();
|
|
962
|
-
const [isThinking, setIsThinking] = (0,
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
|
|
1978
|
+
const [isThinking, setIsThinking] = (0, import_react13.useState)(false);
|
|
1979
|
+
const initialCtx = session.getContextState();
|
|
1980
|
+
const [contextState, setContextState] = (0, import_react13.useState)({
|
|
1981
|
+
percentage: initialCtx.usedPercentage,
|
|
1982
|
+
usedTokens: initialCtx.usedTokens,
|
|
1983
|
+
maxTokens: initialCtx.maxTokens
|
|
1984
|
+
});
|
|
1985
|
+
const pendingModelChangeRef = (0, import_react13.useRef)(null);
|
|
1986
|
+
const [pendingModelId, setPendingModelId] = (0, import_react13.useState)(null);
|
|
1987
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
1988
|
+
const handleSlashCommand = useSlashCommands(
|
|
1989
|
+
session,
|
|
1990
|
+
addMessage,
|
|
1991
|
+
setMessages,
|
|
1992
|
+
exit,
|
|
1993
|
+
registry,
|
|
1994
|
+
pendingModelChangeRef,
|
|
1995
|
+
setPendingModelId,
|
|
1996
|
+
pluginCallbacks
|
|
1997
|
+
);
|
|
966
1998
|
const handleSubmit = useSubmitHandler(
|
|
967
1999
|
session,
|
|
968
2000
|
addMessage,
|
|
969
2001
|
handleSlashCommand,
|
|
970
2002
|
clearStreamingText,
|
|
971
2003
|
setIsThinking,
|
|
972
|
-
|
|
2004
|
+
setContextState,
|
|
973
2005
|
registry
|
|
974
2006
|
);
|
|
975
|
-
(0,
|
|
2007
|
+
(0, import_ink11.useInput)(
|
|
976
2008
|
(_input, key) => {
|
|
977
2009
|
if (key.ctrl && _input === "c") exit();
|
|
978
2010
|
if (key.escape && isThinking) session.abort();
|
|
979
2011
|
},
|
|
980
2012
|
{ isActive: !permissionRequest }
|
|
981
2013
|
);
|
|
982
|
-
return /* @__PURE__ */ (0,
|
|
983
|
-
/* @__PURE__ */ (0,
|
|
984
|
-
/* @__PURE__ */ (0,
|
|
2014
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", children: [
|
|
2015
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
2016
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "cyan", bold: true, children: `
|
|
985
2017
|
____ ___ ____ ___ _____ _
|
|
986
2018
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
987
2019
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
988
2020
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
989
2021
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
990
2022
|
` }),
|
|
991
|
-
/* @__PURE__ */ (0,
|
|
2023
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
|
|
992
2024
|
" v",
|
|
993
2025
|
props.version ?? "0.0.0"
|
|
994
2026
|
] })
|
|
995
2027
|
] }),
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
isThinking && /* @__PURE__ */ (0,
|
|
2028
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
2029
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageList, { messages }),
|
|
2030
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
999
2031
|
] }),
|
|
1000
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
1001
|
-
/* @__PURE__ */ (0,
|
|
2032
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
2033
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2034
|
+
ConfirmPrompt,
|
|
2035
|
+
{
|
|
2036
|
+
message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
2037
|
+
onSelect: (index) => {
|
|
2038
|
+
setPendingModelId(null);
|
|
2039
|
+
pendingModelChangeRef.current = null;
|
|
2040
|
+
if (index === 0) {
|
|
2041
|
+
try {
|
|
2042
|
+
const settingsPath = getUserSettingsPath();
|
|
2043
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
2044
|
+
addMessage({
|
|
2045
|
+
role: "system",
|
|
2046
|
+
content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...`
|
|
2047
|
+
});
|
|
2048
|
+
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
addMessage({
|
|
2051
|
+
role: "system",
|
|
2052
|
+
content: `Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
} else {
|
|
2056
|
+
addMessage({ role: "system", content: "Model change cancelled." });
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
),
|
|
2061
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1002
2062
|
StatusBar,
|
|
1003
2063
|
{
|
|
1004
2064
|
permissionMode: session.getPermissionMode(),
|
|
1005
|
-
modelName: props.config.provider.model,
|
|
2065
|
+
modelName: (0, import_agent_core3.getModelName)(props.config.provider.model),
|
|
1006
2066
|
sessionId: session.getSessionId(),
|
|
1007
2067
|
messageCount: messages.length,
|
|
1008
2068
|
isThinking,
|
|
1009
|
-
contextPercentage,
|
|
1010
|
-
contextUsedTokens:
|
|
1011
|
-
contextMaxTokens:
|
|
2069
|
+
contextPercentage: contextState.percentage,
|
|
2070
|
+
contextUsedTokens: contextState.usedTokens,
|
|
2071
|
+
contextMaxTokens: contextState.maxTokens
|
|
1012
2072
|
}
|
|
1013
2073
|
),
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
2074
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1015
2075
|
InputArea,
|
|
1016
2076
|
{
|
|
1017
2077
|
onSubmit: handleSubmit,
|
|
1018
2078
|
isDisabled: isThinking || !!permissionRequest,
|
|
1019
2079
|
registry
|
|
1020
2080
|
}
|
|
1021
|
-
)
|
|
2081
|
+
),
|
|
2082
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { children: " " })
|
|
1022
2083
|
] });
|
|
1023
2084
|
}
|
|
1024
2085
|
|
|
1025
2086
|
// src/ui/render.tsx
|
|
1026
|
-
var
|
|
2087
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1027
2088
|
function renderApp(options) {
|
|
1028
2089
|
process.on("unhandledRejection", (reason) => {
|
|
1029
2090
|
process.stderr.write(`
|
|
@@ -1034,7 +2095,7 @@ function renderApp(options) {
|
|
|
1034
2095
|
`);
|
|
1035
2096
|
}
|
|
1036
2097
|
});
|
|
1037
|
-
const instance = (0,
|
|
2098
|
+
const instance = (0, import_ink12.render)(/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(App, { ...options }), {
|
|
1038
2099
|
exitOnCtrlC: true
|
|
1039
2100
|
});
|
|
1040
2101
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1048,15 +2109,27 @@ function renderApp(options) {
|
|
|
1048
2109
|
|
|
1049
2110
|
// src/cli.ts
|
|
1050
2111
|
var import_meta = {};
|
|
1051
|
-
|
|
2112
|
+
function checkSettingsFile(filePath) {
|
|
2113
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
|
|
2114
|
+
try {
|
|
2115
|
+
const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
|
|
2116
|
+
if (raw.length === 0) return "incomplete";
|
|
2117
|
+
const parsed = JSON.parse(raw);
|
|
2118
|
+
const provider = parsed.provider;
|
|
2119
|
+
if (!provider?.apiKey) return "incomplete";
|
|
2120
|
+
return "valid";
|
|
2121
|
+
} catch {
|
|
2122
|
+
return "corrupt";
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
1052
2125
|
function readVersion() {
|
|
1053
2126
|
try {
|
|
1054
2127
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1055
|
-
const dir = (0,
|
|
1056
|
-
const candidates = [(0,
|
|
2128
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
2129
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1057
2130
|
for (const pkgPath of candidates) {
|
|
1058
2131
|
try {
|
|
1059
|
-
const raw = (0,
|
|
2132
|
+
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
1060
2133
|
const pkg = JSON.parse(raw);
|
|
1061
2134
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1062
2135
|
return pkg.version;
|
|
@@ -1069,97 +2142,107 @@ function readVersion() {
|
|
|
1069
2142
|
return "0.0.0";
|
|
1070
2143
|
}
|
|
1071
2144
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
2145
|
+
function promptInput(label, masked = false) {
|
|
2146
|
+
return new Promise((resolve) => {
|
|
2147
|
+
process.stdout.write(label);
|
|
2148
|
+
let input = "";
|
|
2149
|
+
const stdin = process.stdin;
|
|
2150
|
+
const wasRaw = stdin.isRaw;
|
|
2151
|
+
stdin.setRawMode(true);
|
|
2152
|
+
stdin.resume();
|
|
2153
|
+
stdin.setEncoding("utf8");
|
|
2154
|
+
const onData = (data) => {
|
|
2155
|
+
for (const ch of data) {
|
|
2156
|
+
if (ch === "\r" || ch === "\n") {
|
|
2157
|
+
stdin.removeListener("data", onData);
|
|
2158
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2159
|
+
stdin.pause();
|
|
2160
|
+
process.stdout.write("\n");
|
|
2161
|
+
resolve(input.trim());
|
|
2162
|
+
return;
|
|
2163
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
2164
|
+
if (input.length > 0) {
|
|
2165
|
+
input = input.slice(0, -1);
|
|
2166
|
+
process.stdout.write("\b \b");
|
|
2167
|
+
}
|
|
2168
|
+
} else if (ch === "") {
|
|
2169
|
+
process.stdout.write("\n");
|
|
2170
|
+
process.exit(0);
|
|
2171
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
2172
|
+
input += ch;
|
|
2173
|
+
process.stdout.write(masked ? "*" : ch);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
stdin.on("data", onData);
|
|
1103
2178
|
});
|
|
1104
|
-
return {
|
|
1105
|
-
positional: positionals,
|
|
1106
|
-
printMode: values["p"] ?? false,
|
|
1107
|
-
continueMode: values["c"] ?? false,
|
|
1108
|
-
resumeId: values["r"],
|
|
1109
|
-
model: values["model"],
|
|
1110
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1111
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1112
|
-
version: values["version"] ?? false
|
|
1113
|
-
};
|
|
1114
2179
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
2180
|
+
async function ensureConfig(cwd) {
|
|
2181
|
+
const userPath = getUserSettingsPath();
|
|
2182
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
2183
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
2184
|
+
const paths = [userPath, projectPath, localPath];
|
|
2185
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
2186
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
2187
|
+
return;
|
|
1118
2188
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
2189
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
2190
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
2191
|
+
process.stdout.write("\n");
|
|
2192
|
+
if (corrupt.length > 0) {
|
|
2193
|
+
for (const c of corrupt) {
|
|
2194
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
2195
|
+
`);
|
|
2196
|
+
}
|
|
2197
|
+
process.stdout.write("\n");
|
|
1121
2198
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
2199
|
+
if (incomplete.length > 0) {
|
|
2200
|
+
for (const c of incomplete) {
|
|
2201
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
2202
|
+
`);
|
|
2203
|
+
}
|
|
2204
|
+
process.stdout.write("\n");
|
|
1124
2205
|
}
|
|
1125
|
-
|
|
1126
|
-
process.
|
|
2206
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
2207
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
2208
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
2209
|
+
} else {
|
|
2210
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
1127
2211
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
terminal: false,
|
|
1134
|
-
historySize: 0
|
|
1135
|
-
});
|
|
1136
|
-
rl.question(question, (answer) => {
|
|
1137
|
-
rl.close();
|
|
1138
|
-
resolve(answer);
|
|
1139
|
-
});
|
|
1140
|
-
});
|
|
2212
|
+
process.stdout.write("\n");
|
|
2213
|
+
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
2214
|
+
if (!apiKey) {
|
|
2215
|
+
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
2216
|
+
process.exit(1);
|
|
1141
2217
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
2218
|
+
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
2219
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
2220
|
+
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
2221
|
+
const settings = {
|
|
2222
|
+
provider: {
|
|
2223
|
+
name: "anthropic",
|
|
2224
|
+
model: "claude-sonnet-4-6",
|
|
2225
|
+
apiKey
|
|
1147
2226
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1152
|
-
if (trimmed === "") return initialIndex;
|
|
1153
|
-
const num = parseInt(trimmed, 10);
|
|
1154
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1155
|
-
return initialIndex;
|
|
2227
|
+
};
|
|
2228
|
+
if (language) {
|
|
2229
|
+
settings.language = language;
|
|
1156
2230
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
2231
|
+
(0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
2232
|
+
process.stdout.write(`
|
|
2233
|
+
Config saved to ${userPath}
|
|
2234
|
+
|
|
2235
|
+
`);
|
|
2236
|
+
}
|
|
2237
|
+
function resetConfig() {
|
|
2238
|
+
const userPath = getUserSettingsPath();
|
|
2239
|
+
if (deleteSettings(userPath)) {
|
|
2240
|
+
process.stdout.write(`Deleted ${userPath}
|
|
2241
|
+
`);
|
|
2242
|
+
} else {
|
|
2243
|
+
process.stdout.write("No user settings found.\n");
|
|
1161
2244
|
}
|
|
1162
|
-
}
|
|
2245
|
+
}
|
|
1163
2246
|
async function startCli() {
|
|
1164
2247
|
const args = parseCliArgs();
|
|
1165
2248
|
if (args.version) {
|
|
@@ -1167,16 +2250,24 @@ async function startCli() {
|
|
|
1167
2250
|
`);
|
|
1168
2251
|
return;
|
|
1169
2252
|
}
|
|
2253
|
+
if (args.reset) {
|
|
2254
|
+
resetConfig();
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
1170
2257
|
const cwd = process.cwd();
|
|
2258
|
+
await ensureConfig(cwd);
|
|
1171
2259
|
const [config, context, projectInfo] = await Promise.all([
|
|
1172
|
-
(0,
|
|
1173
|
-
(0,
|
|
1174
|
-
(0,
|
|
2260
|
+
(0, import_agent_sdk4.loadConfig)(cwd),
|
|
2261
|
+
(0, import_agent_sdk4.loadContext)(cwd),
|
|
2262
|
+
(0, import_agent_sdk4.detectProject)(cwd)
|
|
1175
2263
|
]);
|
|
1176
2264
|
if (args.model !== void 0) {
|
|
1177
2265
|
config.provider.model = args.model;
|
|
1178
2266
|
}
|
|
1179
|
-
|
|
2267
|
+
if (args.language !== void 0) {
|
|
2268
|
+
config.language = args.language;
|
|
2269
|
+
}
|
|
2270
|
+
const sessionStore = new import_agent_sdk4.SessionStore();
|
|
1180
2271
|
if (args.printMode) {
|
|
1181
2272
|
const prompt = args.positional.join(" ").trim();
|
|
1182
2273
|
if (prompt.length === 0) {
|
|
@@ -1184,16 +2275,15 @@ async function startCli() {
|
|
|
1184
2275
|
process.exit(1);
|
|
1185
2276
|
}
|
|
1186
2277
|
const terminal = new PrintTerminal();
|
|
1187
|
-
const paths = (0,
|
|
1188
|
-
const session =
|
|
2278
|
+
const paths = (0, import_agent_sdk4.projectPaths)(cwd);
|
|
2279
|
+
const session = (0, import_agent_sdk4.createSession)({
|
|
1189
2280
|
config,
|
|
1190
2281
|
context,
|
|
1191
2282
|
terminal,
|
|
1192
|
-
sessionLogger: new
|
|
2283
|
+
sessionLogger: new import_agent_sdk4.FileSessionLogger(paths.logs),
|
|
1193
2284
|
projectInfo,
|
|
1194
2285
|
permissionMode: args.permissionMode,
|
|
1195
|
-
|
|
1196
|
-
promptForApproval
|
|
2286
|
+
promptForApproval: import_agent_sdk5.promptForApproval
|
|
1197
2287
|
});
|
|
1198
2288
|
const response = await session.run(prompt);
|
|
1199
2289
|
process.stdout.write(response + "\n");
|
|
@@ -1211,6 +2301,16 @@ async function startCli() {
|
|
|
1211
2301
|
}
|
|
1212
2302
|
|
|
1213
2303
|
// src/bin.ts
|
|
2304
|
+
process.on("uncaughtException", (err) => {
|
|
2305
|
+
const msg = err.message ?? "";
|
|
2306
|
+
const isLikelyIME = msg.includes("string-width") || msg.includes("setCursorPosition") || msg.includes("getStringWidth") || msg.includes("slice") || msg.includes("charCodeAt");
|
|
2307
|
+
if (isLikelyIME) {
|
|
2308
|
+
process.stderr.write(`[robota] IME error suppressed: ${msg}
|
|
2309
|
+
`);
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
throw err;
|
|
2313
|
+
});
|
|
1214
2314
|
startCli().catch((err) => {
|
|
1215
2315
|
const message = err instanceof Error ? err.message : String(err);
|
|
1216
2316
|
process.stderr.write(message + "\n");
|