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