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