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