@robota-sdk/agent-cli 3.0.0-beta.2 → 3.0.0-beta.21
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 +924 -579
- package/dist/node/bin.js +11 -1
- package/dist/node/{chunk-OH7DJXHF.js → chunk-RE4JCNEA.js} +909 -573
- package/dist/node/index.cjs +923 -588
- package/dist/node/index.js +1 -1
- package/package.json +4 -3
package/dist/node/bin.cjs
CHANGED
|
@@ -24,40 +24,514 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var import_node_path2 = require("path");
|
|
27
|
+
var import_node_fs3 = require("fs");
|
|
28
|
+
var import_node_path3 = require("path");
|
|
30
29
|
var import_node_url = require("url");
|
|
31
|
-
var readline = __toESM(require("readline"), 1);
|
|
32
30
|
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
31
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
33
32
|
|
|
34
|
-
// src/
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
// src/utils/cli-args.ts
|
|
34
|
+
var import_node_util = require("util");
|
|
35
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
36
|
+
function parsePermissionMode(raw) {
|
|
37
|
+
if (raw === void 0) return void 0;
|
|
38
|
+
if (!VALID_MODES.includes(raw)) {
|
|
39
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
40
|
+
`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
return raw;
|
|
44
|
+
}
|
|
45
|
+
function parseMaxTurns(raw) {
|
|
46
|
+
if (raw === void 0) return void 0;
|
|
47
|
+
const n = parseInt(raw, 10);
|
|
48
|
+
if (isNaN(n) || n <= 0) {
|
|
49
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
50
|
+
`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return n;
|
|
54
|
+
}
|
|
55
|
+
function parseCliArgs() {
|
|
56
|
+
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
57
|
+
allowPositionals: true,
|
|
58
|
+
options: {
|
|
59
|
+
p: { type: "boolean", short: "p", default: false },
|
|
60
|
+
c: { type: "boolean", short: "c", default: false },
|
|
61
|
+
r: { type: "string", short: "r" },
|
|
62
|
+
model: { type: "string" },
|
|
63
|
+
"permission-mode": { type: "string" },
|
|
64
|
+
"max-turns": { type: "string" },
|
|
65
|
+
version: { type: "boolean", default: false },
|
|
66
|
+
reset: { type: "boolean", default: false }
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
positional: positionals,
|
|
71
|
+
printMode: values["p"] ?? false,
|
|
72
|
+
continueMode: values["c"] ?? false,
|
|
73
|
+
resumeId: values["r"],
|
|
74
|
+
model: values["model"],
|
|
75
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
76
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
77
|
+
version: values["version"] ?? false,
|
|
78
|
+
reset: values["reset"] ?? false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/utils/settings-io.ts
|
|
83
|
+
var import_node_fs = require("fs");
|
|
84
|
+
var import_node_path = require("path");
|
|
85
|
+
function getUserSettingsPath() {
|
|
86
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
87
|
+
return (0, import_node_path.join)(home, ".robota", "settings.json");
|
|
88
|
+
}
|
|
89
|
+
function readSettings(path) {
|
|
90
|
+
if (!(0, import_node_fs.existsSync)(path)) return {};
|
|
91
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
92
|
+
}
|
|
93
|
+
function writeSettings(path, settings) {
|
|
94
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
95
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
96
|
+
}
|
|
97
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
98
|
+
const settings = readSettings(settingsPath);
|
|
99
|
+
const provider = settings.provider ?? {};
|
|
100
|
+
provider.model = modelId;
|
|
101
|
+
settings.provider = provider;
|
|
102
|
+
writeSettings(settingsPath, settings);
|
|
103
|
+
}
|
|
104
|
+
function deleteSettings(path) {
|
|
105
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
106
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/print-terminal.ts
|
|
113
|
+
var readline = __toESM(require("readline"), 1);
|
|
114
|
+
var PrintTerminal = class {
|
|
115
|
+
write(text) {
|
|
116
|
+
process.stdout.write(text);
|
|
117
|
+
}
|
|
118
|
+
writeLine(text) {
|
|
119
|
+
process.stdout.write(text + "\n");
|
|
120
|
+
}
|
|
121
|
+
writeMarkdown(md) {
|
|
122
|
+
process.stdout.write(md);
|
|
123
|
+
}
|
|
124
|
+
writeError(text) {
|
|
125
|
+
process.stderr.write(text + "\n");
|
|
126
|
+
}
|
|
127
|
+
prompt(question) {
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
const rl = readline.createInterface({
|
|
130
|
+
input: process.stdin,
|
|
131
|
+
output: process.stdout,
|
|
132
|
+
terminal: false,
|
|
133
|
+
historySize: 0
|
|
134
|
+
});
|
|
135
|
+
rl.question(question, (answer) => {
|
|
136
|
+
rl.close();
|
|
137
|
+
resolve(answer);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async select(options, initialIndex = 0) {
|
|
142
|
+
for (let i = 0; i < options.length; i++) {
|
|
143
|
+
const marker = i === initialIndex ? ">" : " ";
|
|
144
|
+
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
const answer = await this.prompt(
|
|
148
|
+
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
149
|
+
);
|
|
150
|
+
const trimmed = answer.trim().toLowerCase();
|
|
151
|
+
if (trimmed === "") return initialIndex;
|
|
152
|
+
const num = parseInt(trimmed, 10);
|
|
153
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
154
|
+
return initialIndex;
|
|
155
|
+
}
|
|
156
|
+
spinner(_message) {
|
|
157
|
+
return { stop() {
|
|
158
|
+
}, update() {
|
|
159
|
+
} };
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/ui/render.tsx
|
|
164
|
+
var import_ink11 = require("ink");
|
|
165
|
+
|
|
166
|
+
// src/ui/App.tsx
|
|
167
|
+
var import_react11 = require("react");
|
|
168
|
+
var import_ink10 = require("ink");
|
|
169
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
170
|
+
|
|
171
|
+
// src/ui/hooks/useSession.ts
|
|
172
|
+
var import_react = require("react");
|
|
173
|
+
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
174
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
175
|
+
var TOOL_ARG_TRUNCATE_AT = 77;
|
|
176
|
+
var NOOP_TERMINAL = {
|
|
177
|
+
write: () => {
|
|
178
|
+
},
|
|
179
|
+
writeLine: () => {
|
|
180
|
+
},
|
|
181
|
+
writeMarkdown: () => {
|
|
182
|
+
},
|
|
183
|
+
writeError: () => {
|
|
184
|
+
},
|
|
185
|
+
prompt: () => Promise.resolve(""),
|
|
186
|
+
select: () => Promise.resolve(0),
|
|
187
|
+
spinner: () => ({ stop: () => {
|
|
188
|
+
}, update: () => {
|
|
189
|
+
} })
|
|
190
|
+
};
|
|
191
|
+
function useSession(props) {
|
|
192
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
|
|
193
|
+
const [streamingText, setStreamingText] = (0, import_react.useState)("");
|
|
194
|
+
const [activeTools, setActiveTools] = (0, import_react.useState)([]);
|
|
195
|
+
const permissionQueueRef = (0, import_react.useRef)([]);
|
|
196
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
197
|
+
const processNextPermission = (0, import_react.useCallback)(() => {
|
|
198
|
+
if (processingRef.current) return;
|
|
199
|
+
const next = permissionQueueRef.current[0];
|
|
200
|
+
if (!next) {
|
|
201
|
+
setPermissionRequest(null);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
processingRef.current = true;
|
|
205
|
+
setPermissionRequest({
|
|
206
|
+
toolName: next.toolName,
|
|
207
|
+
toolArgs: next.toolArgs,
|
|
208
|
+
resolve: (result) => {
|
|
209
|
+
permissionQueueRef.current.shift();
|
|
210
|
+
processingRef.current = false;
|
|
211
|
+
setPermissionRequest(null);
|
|
212
|
+
next.resolve(result);
|
|
213
|
+
setTimeout(() => processNextPermission(), 0);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}, []);
|
|
217
|
+
const sessionRef = (0, import_react.useRef)(null);
|
|
218
|
+
if (sessionRef.current === null) {
|
|
219
|
+
const permissionHandler = (toolName, toolArgs) => {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
222
|
+
processNextPermission();
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
const onTextDelta = (delta) => {
|
|
226
|
+
setStreamingText((prev) => prev + delta);
|
|
227
|
+
};
|
|
228
|
+
const onToolExecution = (event) => {
|
|
229
|
+
if (event.type === "start") {
|
|
230
|
+
let firstArg = "";
|
|
231
|
+
if (event.toolArgs) {
|
|
232
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
233
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
234
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
|
|
235
|
+
}
|
|
236
|
+
setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
|
|
237
|
+
} else {
|
|
238
|
+
setActiveTools(
|
|
239
|
+
(prev) => prev.map(
|
|
240
|
+
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
246
|
+
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
247
|
+
config: props.config,
|
|
248
|
+
context: props.context,
|
|
249
|
+
terminal: NOOP_TERMINAL,
|
|
250
|
+
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
251
|
+
projectInfo: props.projectInfo,
|
|
252
|
+
sessionStore: props.sessionStore,
|
|
253
|
+
permissionMode: props.permissionMode,
|
|
254
|
+
maxTurns: props.maxTurns,
|
|
255
|
+
permissionHandler,
|
|
256
|
+
onTextDelta,
|
|
257
|
+
onToolExecution
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const clearStreamingText = (0, import_react.useCallback)(() => {
|
|
261
|
+
setStreamingText("");
|
|
262
|
+
setActiveTools([]);
|
|
263
|
+
}, []);
|
|
264
|
+
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/ui/hooks/useMessages.ts
|
|
268
|
+
var import_react2 = require("react");
|
|
269
|
+
var msgIdCounter = 0;
|
|
270
|
+
function nextId() {
|
|
271
|
+
msgIdCounter += 1;
|
|
272
|
+
return `msg_${msgIdCounter}`;
|
|
273
|
+
}
|
|
274
|
+
function useMessages() {
|
|
275
|
+
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
276
|
+
const addMessage = (0, import_react2.useCallback)((msg) => {
|
|
277
|
+
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
278
|
+
}, []);
|
|
279
|
+
return { messages, setMessages, addMessage };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
283
|
+
var import_react3 = require("react");
|
|
284
|
+
|
|
285
|
+
// src/commands/slash-executor.ts
|
|
286
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
287
|
+
var HELP_TEXT = [
|
|
288
|
+
"Available commands:",
|
|
289
|
+
" /help \u2014 Show this help",
|
|
290
|
+
" /clear \u2014 Clear conversation",
|
|
291
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
292
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
293
|
+
" /cost \u2014 Show session info",
|
|
294
|
+
" /reset \u2014 Delete settings and exit",
|
|
295
|
+
" /exit \u2014 Exit CLI"
|
|
296
|
+
].join("\n");
|
|
297
|
+
function handleHelp(addMessage) {
|
|
298
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
299
|
+
return { handled: true };
|
|
300
|
+
}
|
|
301
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
302
|
+
clearMessages();
|
|
303
|
+
session.clearHistory();
|
|
304
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
305
|
+
return { handled: true };
|
|
306
|
+
}
|
|
307
|
+
async function handleCompact(args, session, addMessage) {
|
|
308
|
+
const instructions = args.trim() || void 0;
|
|
309
|
+
const before = session.getContextState().usedPercentage;
|
|
310
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
311
|
+
await session.compact(instructions);
|
|
312
|
+
const after = session.getContextState().usedPercentage;
|
|
313
|
+
addMessage({
|
|
314
|
+
role: "system",
|
|
315
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
316
|
+
});
|
|
317
|
+
return { handled: true };
|
|
318
|
+
}
|
|
319
|
+
function handleMode(arg, session, addMessage) {
|
|
320
|
+
if (!arg) {
|
|
321
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
322
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
323
|
+
session.setPermissionMode(arg);
|
|
324
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
325
|
+
} else {
|
|
326
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
327
|
+
}
|
|
328
|
+
return { handled: true };
|
|
329
|
+
}
|
|
330
|
+
function handleModel(modelId, addMessage) {
|
|
331
|
+
if (!modelId) {
|
|
332
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
333
|
+
return { handled: true };
|
|
334
|
+
}
|
|
335
|
+
return { handled: true, pendingModelId: modelId };
|
|
336
|
+
}
|
|
337
|
+
function handleCost(session, addMessage) {
|
|
338
|
+
addMessage({
|
|
339
|
+
role: "system",
|
|
340
|
+
content: `Session: ${session.getSessionId()}
|
|
341
|
+
Messages: ${session.getMessageCount()}`
|
|
342
|
+
});
|
|
343
|
+
return { handled: true };
|
|
344
|
+
}
|
|
345
|
+
function handlePermissions(session, addMessage) {
|
|
346
|
+
const mode = session.getPermissionMode();
|
|
347
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
348
|
+
const lines = [`Permission mode: ${mode}`];
|
|
349
|
+
if (sessionAllowed.length > 0) {
|
|
350
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
351
|
+
} else {
|
|
352
|
+
lines.push("No session-approved tools.");
|
|
353
|
+
}
|
|
354
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
355
|
+
return { handled: true };
|
|
356
|
+
}
|
|
357
|
+
function handleContext(session, addMessage) {
|
|
358
|
+
const ctx = session.getContextState();
|
|
359
|
+
addMessage({
|
|
360
|
+
role: "system",
|
|
361
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
362
|
+
});
|
|
363
|
+
return { handled: true };
|
|
364
|
+
}
|
|
365
|
+
function handleReset(addMessage) {
|
|
366
|
+
const settingsPath = getUserSettingsPath();
|
|
367
|
+
if (deleteSettings(settingsPath)) {
|
|
368
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
369
|
+
} else {
|
|
370
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
371
|
+
}
|
|
372
|
+
return { handled: true, exitRequested: true };
|
|
373
|
+
}
|
|
374
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
|
|
375
|
+
switch (cmd) {
|
|
376
|
+
case "help":
|
|
377
|
+
return handleHelp(addMessage);
|
|
378
|
+
case "clear":
|
|
379
|
+
return handleClear(addMessage, clearMessages, session);
|
|
380
|
+
case "compact":
|
|
381
|
+
return handleCompact(args, session, addMessage);
|
|
382
|
+
case "mode":
|
|
383
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
384
|
+
case "model":
|
|
385
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
386
|
+
case "cost":
|
|
387
|
+
return handleCost(session, addMessage);
|
|
388
|
+
case "permissions":
|
|
389
|
+
return handlePermissions(session, addMessage);
|
|
390
|
+
case "context":
|
|
391
|
+
return handleContext(session, addMessage);
|
|
392
|
+
case "reset":
|
|
393
|
+
return handleReset(addMessage);
|
|
394
|
+
case "exit":
|
|
395
|
+
return { handled: true, exitRequested: true };
|
|
396
|
+
default: {
|
|
397
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
398
|
+
if (skillCmd) {
|
|
399
|
+
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
400
|
+
return { handled: false };
|
|
401
|
+
}
|
|
402
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
403
|
+
return { handled: true };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
409
|
+
var EXIT_DELAY_MS = 500;
|
|
410
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
411
|
+
return (0, import_react3.useCallback)(
|
|
412
|
+
async (input) => {
|
|
413
|
+
const parts = input.slice(1).split(/\s+/);
|
|
414
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
415
|
+
const args = parts.slice(1).join(" ");
|
|
416
|
+
const clearMessages = () => setMessages([]);
|
|
417
|
+
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
418
|
+
if (result.pendingModelId) {
|
|
419
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
420
|
+
setPendingModelId(result.pendingModelId);
|
|
421
|
+
}
|
|
422
|
+
if (result.exitRequested) {
|
|
423
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
424
|
+
}
|
|
425
|
+
return result.handled;
|
|
426
|
+
},
|
|
427
|
+
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
432
|
+
var import_react4 = require("react");
|
|
433
|
+
|
|
434
|
+
// src/utils/tool-call-extractor.ts
|
|
435
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
436
|
+
var TOOL_ARG_TRUNCATE_LENGTH = 77;
|
|
437
|
+
function extractToolCalls(history, startIndex) {
|
|
438
|
+
const lines = [];
|
|
439
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
440
|
+
const msg = history[i];
|
|
441
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
442
|
+
for (const tc of msg.toolCalls) {
|
|
443
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
444
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
|
|
445
|
+
lines.push(`${tc.function.name}(${truncated})`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return lines;
|
|
450
|
+
}
|
|
451
|
+
function parseFirstArgValue(argsJson) {
|
|
452
|
+
try {
|
|
453
|
+
const parsed = JSON.parse(argsJson);
|
|
454
|
+
const firstVal = Object.values(parsed)[0];
|
|
455
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
456
|
+
} catch {
|
|
457
|
+
return argsJson;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/utils/skill-prompt.ts
|
|
462
|
+
function buildSkillPrompt(input, registry) {
|
|
463
|
+
const parts = input.slice(1).split(/\s+/);
|
|
464
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
465
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
466
|
+
if (!skillCmd) return null;
|
|
467
|
+
const args = parts.slice(1).join(" ").trim();
|
|
468
|
+
const userInstruction = args || skillCmd.description;
|
|
469
|
+
if (skillCmd.skillContent) {
|
|
470
|
+
return `<skill name="${cmd}">
|
|
471
|
+
${skillCmd.skillContent}
|
|
472
|
+
</skill>
|
|
473
|
+
|
|
474
|
+
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
475
|
+
}
|
|
476
|
+
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
480
|
+
function syncContextState(session, setter) {
|
|
481
|
+
const ctx = session.getContextState();
|
|
482
|
+
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
483
|
+
}
|
|
484
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
485
|
+
setIsThinking(true);
|
|
486
|
+
clearStreamingText();
|
|
487
|
+
const historyBefore = session.getHistory().length;
|
|
488
|
+
try {
|
|
489
|
+
const response = await session.run(prompt);
|
|
490
|
+
clearStreamingText();
|
|
491
|
+
const history = session.getHistory();
|
|
492
|
+
const toolLines = extractToolCalls(
|
|
493
|
+
history,
|
|
494
|
+
historyBefore
|
|
495
|
+
);
|
|
496
|
+
if (toolLines.length > 0) {
|
|
497
|
+
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
498
|
+
}
|
|
499
|
+
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
500
|
+
syncContextState(session, setContextState);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
clearStreamingText();
|
|
503
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
504
|
+
addMessage({ role: "system", content: "Cancelled." });
|
|
505
|
+
} else {
|
|
506
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
507
|
+
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
508
|
+
}
|
|
509
|
+
} finally {
|
|
510
|
+
setIsThinking(false);
|
|
42
511
|
}
|
|
43
|
-
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
44
512
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
513
|
+
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
|
|
514
|
+
return (0, import_react4.useCallback)(
|
|
515
|
+
async (input) => {
|
|
516
|
+
if (input.startsWith("/")) {
|
|
517
|
+
const handled = await handleSlashCommand(input);
|
|
518
|
+
if (handled) {
|
|
519
|
+
syncContextState(session, setContextState);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const prompt = buildSkillPrompt(input, registry);
|
|
523
|
+
if (!prompt) return;
|
|
524
|
+
return runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState);
|
|
525
|
+
}
|
|
526
|
+
addMessage({ role: "user", content: input });
|
|
527
|
+
return runSessionPrompt(input, session, addMessage, clearStreamingText, setIsThinking, setContextState);
|
|
528
|
+
},
|
|
529
|
+
[session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry]
|
|
530
|
+
);
|
|
52
531
|
}
|
|
53
532
|
|
|
54
|
-
// src/ui/
|
|
55
|
-
var import_ink9 = require("ink");
|
|
56
|
-
|
|
57
|
-
// src/ui/App.tsx
|
|
533
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
58
534
|
var import_react5 = require("react");
|
|
59
|
-
var import_ink8 = require("ink");
|
|
60
|
-
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
61
535
|
|
|
62
536
|
// src/commands/command-registry.ts
|
|
63
537
|
var CommandRegistry = class {
|
|
@@ -90,6 +564,21 @@ var CommandRegistry = class {
|
|
|
90
564
|
};
|
|
91
565
|
|
|
92
566
|
// src/commands/builtin-source.ts
|
|
567
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
568
|
+
function buildModelSubcommands() {
|
|
569
|
+
const seen = /* @__PURE__ */ new Set();
|
|
570
|
+
const commands = [];
|
|
571
|
+
for (const model of Object.values(import_agent_core.CLAUDE_MODELS)) {
|
|
572
|
+
if (seen.has(model.name)) continue;
|
|
573
|
+
seen.add(model.name);
|
|
574
|
+
commands.push({
|
|
575
|
+
name: model.id,
|
|
576
|
+
description: `${model.name} (${(0, import_agent_core.formatTokenCount)(model.contextWindow).toUpperCase()})`,
|
|
577
|
+
source: "builtin"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return commands;
|
|
581
|
+
}
|
|
93
582
|
function createBuiltinCommands() {
|
|
94
583
|
return [
|
|
95
584
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -109,16 +598,13 @@ function createBuiltinCommands() {
|
|
|
109
598
|
name: "model",
|
|
110
599
|
description: "Select AI model",
|
|
111
600
|
source: "builtin",
|
|
112
|
-
subcommands:
|
|
113
|
-
{ name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
|
|
114
|
-
{ name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
|
|
115
|
-
{ name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
|
|
116
|
-
]
|
|
601
|
+
subcommands: buildModelSubcommands()
|
|
117
602
|
},
|
|
118
603
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
119
604
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
120
605
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
121
606
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
607
|
+
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
122
608
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
123
609
|
];
|
|
124
610
|
}
|
|
@@ -134,8 +620,8 @@ var BuiltinCommandSource = class {
|
|
|
134
620
|
};
|
|
135
621
|
|
|
136
622
|
// src/commands/skill-source.ts
|
|
137
|
-
var
|
|
138
|
-
var
|
|
623
|
+
var import_node_fs2 = require("fs");
|
|
624
|
+
var import_node_path2 = require("path");
|
|
139
625
|
var import_node_os = require("os");
|
|
140
626
|
function parseFrontmatter(content) {
|
|
141
627
|
const lines = content.split("\n");
|
|
@@ -158,19 +644,20 @@ function parseFrontmatter(content) {
|
|
|
158
644
|
return name ? { name, description } : null;
|
|
159
645
|
}
|
|
160
646
|
function scanSkillsDir(skillsDir) {
|
|
161
|
-
if (!(0,
|
|
647
|
+
if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
|
|
162
648
|
const commands = [];
|
|
163
|
-
const entries = (0,
|
|
649
|
+
const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
|
|
164
650
|
for (const entry of entries) {
|
|
165
651
|
if (!entry.isDirectory()) continue;
|
|
166
|
-
const skillFile = (0,
|
|
167
|
-
if (!(0,
|
|
168
|
-
const content = (0,
|
|
652
|
+
const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
|
|
653
|
+
if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
|
|
654
|
+
const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
|
|
169
655
|
const frontmatter = parseFrontmatter(content);
|
|
170
656
|
commands.push({
|
|
171
657
|
name: frontmatter?.name ?? entry.name,
|
|
172
658
|
description: frontmatter?.description ?? `Skill: ${entry.name}`,
|
|
173
|
-
source: "skill"
|
|
659
|
+
source: "skill",
|
|
660
|
+
skillContent: content
|
|
174
661
|
});
|
|
175
662
|
}
|
|
176
663
|
return commands;
|
|
@@ -184,8 +671,8 @@ var SkillCommandSource = class {
|
|
|
184
671
|
}
|
|
185
672
|
getCommands() {
|
|
186
673
|
if (this.cachedCommands) return this.cachedCommands;
|
|
187
|
-
const projectSkills = scanSkillsDir((0,
|
|
188
|
-
const userSkills = scanSkillsDir((0,
|
|
674
|
+
const projectSkills = scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"));
|
|
675
|
+
const userSkills = scanSkillsDir((0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "skills"));
|
|
189
676
|
const seen = new Set(projectSkills.map((cmd) => cmd.name));
|
|
190
677
|
const merged = [...projectSkills];
|
|
191
678
|
for (const cmd of userSkills) {
|
|
@@ -198,6 +685,18 @@ var SkillCommandSource = class {
|
|
|
198
685
|
}
|
|
199
686
|
};
|
|
200
687
|
|
|
688
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
689
|
+
function useCommandRegistry(cwd) {
|
|
690
|
+
const registryRef = (0, import_react5.useRef)(null);
|
|
691
|
+
if (registryRef.current === null) {
|
|
692
|
+
const registry = new CommandRegistry();
|
|
693
|
+
registry.addSource(new BuiltinCommandSource());
|
|
694
|
+
registry.addSource(new SkillCommandSource(cwd));
|
|
695
|
+
registryRef.current = registry;
|
|
696
|
+
}
|
|
697
|
+
return registryRef.current;
|
|
698
|
+
}
|
|
699
|
+
|
|
201
700
|
// src/ui/MessageList.tsx
|
|
202
701
|
var import_ink = require("ink");
|
|
203
702
|
|
|
@@ -259,6 +758,7 @@ function MessageList({ messages }) {
|
|
|
259
758
|
|
|
260
759
|
// src/ui/StatusBar.tsx
|
|
261
760
|
var import_ink2 = require("ink");
|
|
761
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
262
762
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
263
763
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
264
764
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
@@ -298,10 +798,10 @@ function StatusBar({
|
|
|
298
798
|
"Context: ",
|
|
299
799
|
Math.round(contextPercentage),
|
|
300
800
|
"% (",
|
|
301
|
-
(
|
|
302
|
-
"
|
|
303
|
-
(
|
|
304
|
-
"
|
|
801
|
+
(0, import_agent_core2.formatTokenCount)(contextUsedTokens),
|
|
802
|
+
"/",
|
|
803
|
+
(0, import_agent_core2.formatTokenCount)(contextMaxTokens),
|
|
804
|
+
")"
|
|
305
805
|
] })
|
|
306
806
|
] }),
|
|
307
807
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
|
|
@@ -317,15 +817,22 @@ function StatusBar({
|
|
|
317
817
|
}
|
|
318
818
|
|
|
319
819
|
// src/ui/InputArea.tsx
|
|
320
|
-
var
|
|
820
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
321
821
|
var import_ink6 = require("ink");
|
|
322
822
|
|
|
323
823
|
// src/ui/CjkTextInput.tsx
|
|
324
|
-
var
|
|
824
|
+
var import_react6 = require("react");
|
|
325
825
|
var import_ink3 = require("ink");
|
|
326
|
-
var
|
|
327
|
-
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
826
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
328
827
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
828
|
+
function filterPrintable(input) {
|
|
829
|
+
if (!input || input.length === 0) return "";
|
|
830
|
+
return input.replace(/[\x00-\x1f\x7f]/g, "");
|
|
831
|
+
}
|
|
832
|
+
function insertAtCursor(value, cursor, input) {
|
|
833
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
834
|
+
return { value: next, cursor: cursor + input.length };
|
|
835
|
+
}
|
|
329
836
|
function CjkTextInput({
|
|
330
837
|
value,
|
|
331
838
|
onChange,
|
|
@@ -334,10 +841,9 @@ function CjkTextInput({
|
|
|
334
841
|
focus = true,
|
|
335
842
|
showCursor = true
|
|
336
843
|
}) {
|
|
337
|
-
const valueRef = (0,
|
|
338
|
-
const cursorRef = (0,
|
|
339
|
-
const [, forceRender] = (0,
|
|
340
|
-
const { setCursorPosition } = (0, import_ink3.useCursor)();
|
|
844
|
+
const valueRef = (0, import_react6.useRef)(value);
|
|
845
|
+
const cursorRef = (0, import_react6.useRef)(value.length);
|
|
846
|
+
const [, forceRender] = (0, import_react6.useState)(0);
|
|
341
847
|
if (value !== valueRef.current) {
|
|
342
848
|
valueRef.current = value;
|
|
343
849
|
if (cursorRef.current > value.length) {
|
|
@@ -346,85 +852,83 @@ function CjkTextInput({
|
|
|
346
852
|
}
|
|
347
853
|
(0, import_ink3.useInput)(
|
|
348
854
|
(input, key) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (key.return) {
|
|
353
|
-
onSubmit?.(valueRef.current);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (key.leftArrow) {
|
|
357
|
-
if (cursorRef.current > 0) {
|
|
358
|
-
cursorRef.current -= 1;
|
|
359
|
-
forceRender((n) => n + 1);
|
|
855
|
+
try {
|
|
856
|
+
if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
|
|
857
|
+
return;
|
|
360
858
|
}
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (cursorRef.current < valueRef.current.length) {
|
|
365
|
-
cursorRef.current += 1;
|
|
366
|
-
forceRender((n) => n + 1);
|
|
859
|
+
if (key.return) {
|
|
860
|
+
onSubmit?.(valueRef.current);
|
|
861
|
+
return;
|
|
367
862
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
cursorRef.current -= 1;
|
|
375
|
-
valueRef.current = next2;
|
|
376
|
-
onChange(next2);
|
|
863
|
+
if (key.leftArrow) {
|
|
864
|
+
if (cursorRef.current > 0) {
|
|
865
|
+
cursorRef.current -= 1;
|
|
866
|
+
forceRender((n) => n + 1);
|
|
867
|
+
}
|
|
868
|
+
return;
|
|
377
869
|
}
|
|
378
|
-
|
|
870
|
+
if (key.rightArrow) {
|
|
871
|
+
if (cursorRef.current < valueRef.current.length) {
|
|
872
|
+
cursorRef.current += 1;
|
|
873
|
+
forceRender((n) => n + 1);
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (key.backspace || key.delete) {
|
|
878
|
+
if (cursorRef.current > 0) {
|
|
879
|
+
const v = valueRef.current;
|
|
880
|
+
const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
|
|
881
|
+
cursorRef.current -= 1;
|
|
882
|
+
valueRef.current = next;
|
|
883
|
+
onChange(next);
|
|
884
|
+
}
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const printable = filterPrintable(input);
|
|
888
|
+
if (printable.length === 0) return;
|
|
889
|
+
const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
|
|
890
|
+
cursorRef.current = result.cursor;
|
|
891
|
+
valueRef.current = result.value;
|
|
892
|
+
onChange(result.value);
|
|
893
|
+
} catch {
|
|
379
894
|
}
|
|
380
|
-
const v = valueRef.current;
|
|
381
|
-
const c = cursorRef.current;
|
|
382
|
-
const next = v.slice(0, c) + input + v.slice(c);
|
|
383
|
-
cursorRef.current = c + input.length;
|
|
384
|
-
valueRef.current = next;
|
|
385
|
-
onChange(next);
|
|
386
895
|
},
|
|
387
896
|
{ isActive: focus }
|
|
388
897
|
);
|
|
389
|
-
if (showCursor && focus) {
|
|
390
|
-
const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
|
|
391
|
-
const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
|
|
392
|
-
setCursorPosition({ x: cursorX, y: 0 });
|
|
393
|
-
}
|
|
394
898
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
395
899
|
}
|
|
396
900
|
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
397
901
|
if (!showCursor) {
|
|
398
|
-
return value.length > 0 ? value : placeholder ?
|
|
902
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
399
903
|
}
|
|
400
904
|
if (value.length === 0) {
|
|
401
905
|
if (placeholder.length > 0) {
|
|
402
|
-
return
|
|
906
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
403
907
|
}
|
|
404
|
-
return
|
|
908
|
+
return import_chalk.default.inverse(" ");
|
|
405
909
|
}
|
|
406
910
|
const chars = [...value];
|
|
407
911
|
let rendered = "";
|
|
408
912
|
for (let i = 0; i < chars.length; i++) {
|
|
409
913
|
const char = chars[i] ?? "";
|
|
410
|
-
rendered += i === cursorOffset ?
|
|
914
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
411
915
|
}
|
|
412
916
|
if (cursorOffset >= chars.length) {
|
|
413
|
-
rendered +=
|
|
917
|
+
rendered += import_chalk.default.inverse(" ");
|
|
414
918
|
}
|
|
415
919
|
return rendered;
|
|
416
920
|
}
|
|
417
921
|
|
|
418
922
|
// src/ui/WaveText.tsx
|
|
419
|
-
var
|
|
923
|
+
var import_react7 = require("react");
|
|
420
924
|
var import_ink4 = require("ink");
|
|
421
925
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
422
926
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
423
927
|
var INTERVAL_MS = 400;
|
|
424
928
|
var CHARS_PER_GROUP = 4;
|
|
425
929
|
function WaveText({ text }) {
|
|
426
|
-
const [tick, setTick] = (0,
|
|
427
|
-
(0,
|
|
930
|
+
const [tick, setTick] = (0, import_react7.useState)(0);
|
|
931
|
+
(0, import_react7.useEffect)(() => {
|
|
428
932
|
const timer = setInterval(() => {
|
|
429
933
|
setTick((prev) => prev + 1);
|
|
430
934
|
}, INTERVAL_MS);
|
|
@@ -444,19 +948,13 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
|
444
948
|
var MAX_VISIBLE = 8;
|
|
445
949
|
function CommandRow(props) {
|
|
446
950
|
const { cmd, isSelected, showSlash } = props;
|
|
447
|
-
const prefix = showSlash ? "/" : "";
|
|
448
951
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
449
952
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
450
953
|
const dimmed = !isSelected;
|
|
451
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
cmd.name
|
|
456
|
-
] }),
|
|
457
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
458
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
459
|
-
] });
|
|
954
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: [
|
|
955
|
+
indicator,
|
|
956
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
957
|
+
] }) });
|
|
460
958
|
}
|
|
461
959
|
function SlashAutocomplete({
|
|
462
960
|
commands,
|
|
@@ -496,16 +994,16 @@ function parseSlashInput(value) {
|
|
|
496
994
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
497
995
|
}
|
|
498
996
|
function useAutocomplete(value, registry) {
|
|
499
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
500
|
-
const [dismissed, setDismissed] = (0,
|
|
501
|
-
const prevValueRef =
|
|
997
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
|
|
998
|
+
const [dismissed, setDismissed] = (0, import_react8.useState)(false);
|
|
999
|
+
const prevValueRef = import_react8.default.useRef(value);
|
|
502
1000
|
if (prevValueRef.current !== value) {
|
|
503
1001
|
prevValueRef.current = value;
|
|
504
1002
|
if (dismissed) setDismissed(false);
|
|
505
1003
|
}
|
|
506
1004
|
const parsed = parseSlashInput(value);
|
|
507
1005
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
508
|
-
const filteredCommands = (0,
|
|
1006
|
+
const filteredCommands = (0, import_react8.useMemo)(() => {
|
|
509
1007
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
510
1008
|
if (isSubcommandMode) {
|
|
511
1009
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -539,7 +1037,7 @@ function useAutocomplete(value, registry) {
|
|
|
539
1037
|
};
|
|
540
1038
|
}
|
|
541
1039
|
function InputArea({ onSubmit, isDisabled, registry }) {
|
|
542
|
-
const [value, setValue] = (0,
|
|
1040
|
+
const [value, setValue] = (0, import_react8.useState)("");
|
|
543
1041
|
const {
|
|
544
1042
|
showPopup,
|
|
545
1043
|
filteredCommands,
|
|
@@ -548,7 +1046,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
548
1046
|
isSubcommandMode,
|
|
549
1047
|
setShowPopup
|
|
550
1048
|
} = useAutocomplete(value, registry);
|
|
551
|
-
const handleSubmit = (0,
|
|
1049
|
+
const handleSubmit = (0, import_react8.useCallback)(
|
|
552
1050
|
(text) => {
|
|
553
1051
|
const trimmed = text.trim();
|
|
554
1052
|
if (trimmed.length === 0) return;
|
|
@@ -561,7 +1059,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
561
1059
|
},
|
|
562
1060
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
563
1061
|
);
|
|
564
|
-
const selectCommand = (0,
|
|
1062
|
+
const selectCommand = (0, import_react8.useCallback)(
|
|
565
1063
|
(cmd) => {
|
|
566
1064
|
const parsed = parseSlashInput(value);
|
|
567
1065
|
if (parsed.parentCommand) {
|
|
@@ -594,434 +1092,273 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
594
1092
|
if (cmd) selectCommand(cmd);
|
|
595
1093
|
}
|
|
596
1094
|
},
|
|
597
|
-
{ isActive: showPopup && !isDisabled }
|
|
598
|
-
);
|
|
599
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
|
|
600
|
-
showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
601
|
-
SlashAutocomplete,
|
|
602
|
-
{
|
|
603
|
-
commands: filteredCommands,
|
|
604
|
-
selectedIndex,
|
|
605
|
-
visible: showPopup,
|
|
606
|
-
isSubcommandMode
|
|
607
|
-
}
|
|
608
|
-
),
|
|
609
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { children: [
|
|
610
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: "green", bold: true, children: "> " }),
|
|
611
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
612
|
-
CjkTextInput,
|
|
613
|
-
{
|
|
614
|
-
value,
|
|
615
|
-
onChange: setValue,
|
|
616
|
-
onSubmit: handleSubmit,
|
|
617
|
-
placeholder: "Type a message or /help"
|
|
618
|
-
}
|
|
619
|
-
)
|
|
620
|
-
] }) })
|
|
621
|
-
] });
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// src/ui/PermissionPrompt.tsx
|
|
625
|
-
var import_react4 = __toESM(require("react"), 1);
|
|
626
|
-
var import_ink7 = require("ink");
|
|
627
|
-
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
628
|
-
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
629
|
-
function formatArgs2(args) {
|
|
630
|
-
const entries = Object.entries(args);
|
|
631
|
-
if (entries.length === 0) return "(no arguments)";
|
|
632
|
-
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
633
|
-
}
|
|
634
|
-
function PermissionPrompt({ request }) {
|
|
635
|
-
const [selected, setSelected] = import_react4.default.useState(0);
|
|
636
|
-
const resolvedRef = import_react4.default.useRef(false);
|
|
637
|
-
const prevRequestRef = import_react4.default.useRef(request);
|
|
638
|
-
if (prevRequestRef.current !== request) {
|
|
639
|
-
prevRequestRef.current = request;
|
|
640
|
-
resolvedRef.current = false;
|
|
641
|
-
setSelected(0);
|
|
642
|
-
}
|
|
643
|
-
const doResolve = import_react4.default.useCallback(
|
|
644
|
-
(index) => {
|
|
645
|
-
if (resolvedRef.current) return;
|
|
646
|
-
resolvedRef.current = true;
|
|
647
|
-
if (index === 0) request.resolve(true);
|
|
648
|
-
else if (index === 1) request.resolve("allow-session");
|
|
649
|
-
else request.resolve(false);
|
|
650
|
-
},
|
|
651
|
-
[request]
|
|
652
|
-
);
|
|
653
|
-
(0, import_ink7.useInput)((input, key) => {
|
|
654
|
-
if (resolvedRef.current) return;
|
|
655
|
-
if (key.upArrow || key.leftArrow) {
|
|
656
|
-
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
657
|
-
} else if (key.downArrow || key.rightArrow) {
|
|
658
|
-
setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
|
|
659
|
-
} else if (key.return) {
|
|
660
|
-
doResolve(selected);
|
|
661
|
-
} else if (input === "y" || input === "1") {
|
|
662
|
-
doResolve(0);
|
|
663
|
-
} else if (input === "a" || input === "2") {
|
|
664
|
-
doResolve(1);
|
|
665
|
-
} else if (input === "n" || input === "d" || input === "3") {
|
|
666
|
-
doResolve(2);
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
670
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
671
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { children: [
|
|
672
|
-
"Tool:",
|
|
673
|
-
" ",
|
|
674
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
675
|
-
] }),
|
|
676
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { dimColor: true, children: [
|
|
677
|
-
" ",
|
|
678
|
-
formatArgs2(request.toolArgs)
|
|
679
|
-
] }),
|
|
680
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
681
|
-
i === selected ? "> " : " ",
|
|
682
|
-
opt
|
|
683
|
-
] }) }, opt)) }),
|
|
684
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
685
|
-
] });
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// src/ui/App.tsx
|
|
689
|
-
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
690
|
-
var msgIdCounter = 0;
|
|
691
|
-
function nextId() {
|
|
692
|
-
msgIdCounter += 1;
|
|
693
|
-
return `msg_${msgIdCounter}`;
|
|
694
|
-
}
|
|
695
|
-
var NOOP_TERMINAL = {
|
|
696
|
-
write: () => {
|
|
697
|
-
},
|
|
698
|
-
writeLine: () => {
|
|
699
|
-
},
|
|
700
|
-
writeMarkdown: () => {
|
|
701
|
-
},
|
|
702
|
-
writeError: () => {
|
|
703
|
-
},
|
|
704
|
-
prompt: () => Promise.resolve(""),
|
|
705
|
-
select: () => Promise.resolve(0),
|
|
706
|
-
spinner: () => ({ stop: () => {
|
|
707
|
-
}, update: () => {
|
|
708
|
-
} })
|
|
709
|
-
};
|
|
710
|
-
function useSession(props) {
|
|
711
|
-
const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
|
|
712
|
-
const [streamingText, setStreamingText] = (0, import_react5.useState)("");
|
|
713
|
-
const permissionQueueRef = (0, import_react5.useRef)([]);
|
|
714
|
-
const processingRef = (0, import_react5.useRef)(false);
|
|
715
|
-
const processNextPermission = (0, import_react5.useCallback)(() => {
|
|
716
|
-
if (processingRef.current) return;
|
|
717
|
-
const next = permissionQueueRef.current[0];
|
|
718
|
-
if (!next) {
|
|
719
|
-
setPermissionRequest(null);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
processingRef.current = true;
|
|
723
|
-
setPermissionRequest({
|
|
724
|
-
toolName: next.toolName,
|
|
725
|
-
toolArgs: next.toolArgs,
|
|
726
|
-
resolve: (result) => {
|
|
727
|
-
permissionQueueRef.current.shift();
|
|
728
|
-
processingRef.current = false;
|
|
729
|
-
setPermissionRequest(null);
|
|
730
|
-
next.resolve(result);
|
|
731
|
-
setTimeout(() => processNextPermission(), 0);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}, []);
|
|
735
|
-
const sessionRef = (0, import_react5.useRef)(null);
|
|
736
|
-
if (sessionRef.current === null) {
|
|
737
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
738
|
-
return new Promise((resolve) => {
|
|
739
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
740
|
-
processNextPermission();
|
|
741
|
-
});
|
|
742
|
-
};
|
|
743
|
-
const onTextDelta = (delta) => {
|
|
744
|
-
setStreamingText((prev) => prev + delta);
|
|
745
|
-
};
|
|
746
|
-
sessionRef.current = new import_agent_sdk.Session({
|
|
747
|
-
config: props.config,
|
|
748
|
-
context: props.context,
|
|
749
|
-
terminal: NOOP_TERMINAL,
|
|
750
|
-
projectInfo: props.projectInfo,
|
|
751
|
-
sessionStore: props.sessionStore,
|
|
752
|
-
permissionMode: props.permissionMode,
|
|
753
|
-
maxTurns: props.maxTurns,
|
|
754
|
-
permissionHandler,
|
|
755
|
-
onTextDelta
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
|
|
759
|
-
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
760
|
-
}
|
|
761
|
-
function useMessages() {
|
|
762
|
-
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
763
|
-
const addMessage = (0, import_react5.useCallback)((msg) => {
|
|
764
|
-
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
765
|
-
}, []);
|
|
766
|
-
return { messages, setMessages, addMessage };
|
|
767
|
-
}
|
|
768
|
-
var HELP_TEXT = [
|
|
769
|
-
"Available commands:",
|
|
770
|
-
" /help \u2014 Show this help",
|
|
771
|
-
" /clear \u2014 Clear conversation",
|
|
772
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
773
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
774
|
-
" /cost \u2014 Show session info",
|
|
775
|
-
" /exit \u2014 Exit CLI"
|
|
776
|
-
].join("\n");
|
|
777
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
778
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
779
|
-
if (!arg) {
|
|
780
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
781
|
-
} else if (validModes.includes(arg)) {
|
|
782
|
-
session.setPermissionMode(arg);
|
|
783
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
784
|
-
} else {
|
|
785
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
786
|
-
}
|
|
787
|
-
return true;
|
|
788
|
-
}
|
|
789
|
-
async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
|
|
790
|
-
switch (cmd) {
|
|
791
|
-
case "help":
|
|
792
|
-
addMessage({ role: "system", content: HELP_TEXT });
|
|
793
|
-
return true;
|
|
794
|
-
case "clear":
|
|
795
|
-
setMessages([]);
|
|
796
|
-
session.clearHistory();
|
|
797
|
-
addMessage({ role: "system", content: "Conversation cleared." });
|
|
798
|
-
return true;
|
|
799
|
-
case "compact": {
|
|
800
|
-
const instructions = parts.slice(1).join(" ").trim() || void 0;
|
|
801
|
-
const before = session.getContextState().usedPercentage;
|
|
802
|
-
addMessage({ role: "system", content: "Compacting context..." });
|
|
803
|
-
await session.compact(instructions);
|
|
804
|
-
const after = session.getContextState().usedPercentage;
|
|
805
|
-
addMessage({
|
|
806
|
-
role: "system",
|
|
807
|
-
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
808
|
-
});
|
|
809
|
-
return true;
|
|
810
|
-
}
|
|
811
|
-
case "mode":
|
|
812
|
-
return handleModeCommand(parts[1], session, addMessage);
|
|
813
|
-
case "cost":
|
|
814
|
-
addMessage({
|
|
815
|
-
role: "system",
|
|
816
|
-
content: `Session: ${session.getSessionId()}
|
|
817
|
-
Messages: ${session.getMessageCount()}`
|
|
818
|
-
});
|
|
819
|
-
return true;
|
|
820
|
-
case "permissions": {
|
|
821
|
-
const mode = session.getPermissionMode();
|
|
822
|
-
const sessionAllowed = session.getSessionAllowedTools();
|
|
823
|
-
const lines = [`Permission mode: ${mode}`];
|
|
824
|
-
if (sessionAllowed.length > 0) {
|
|
825
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
826
|
-
} else {
|
|
827
|
-
lines.push("No session-approved tools.");
|
|
828
|
-
}
|
|
829
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
830
|
-
return true;
|
|
831
|
-
}
|
|
832
|
-
case "context": {
|
|
833
|
-
const ctx = session.getContextState();
|
|
834
|
-
addMessage({
|
|
835
|
-
role: "system",
|
|
836
|
-
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
837
|
-
});
|
|
838
|
-
return true;
|
|
839
|
-
}
|
|
840
|
-
case "exit":
|
|
841
|
-
exit();
|
|
842
|
-
return true;
|
|
843
|
-
default: {
|
|
844
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
845
|
-
if (skillCmd) {
|
|
846
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
850
|
-
return true;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry) {
|
|
855
|
-
return (0, import_react5.useCallback)(
|
|
856
|
-
async (input) => {
|
|
857
|
-
const parts = input.slice(1).split(/\s+/);
|
|
858
|
-
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
859
|
-
return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
|
|
860
|
-
},
|
|
861
|
-
[session, addMessage, setMessages, exit, registry]
|
|
1095
|
+
{ isActive: showPopup && !isDisabled }
|
|
862
1096
|
);
|
|
1097
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
|
|
1098
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1099
|
+
SlashAutocomplete,
|
|
1100
|
+
{
|
|
1101
|
+
commands: filteredCommands,
|
|
1102
|
+
selectedIndex,
|
|
1103
|
+
visible: showPopup,
|
|
1104
|
+
isSubcommandMode
|
|
1105
|
+
}
|
|
1106
|
+
),
|
|
1107
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { children: [
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: "green", bold: true, children: "> " }),
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1110
|
+
CjkTextInput,
|
|
1111
|
+
{
|
|
1112
|
+
value,
|
|
1113
|
+
onChange: setValue,
|
|
1114
|
+
onSubmit: handleSubmit,
|
|
1115
|
+
placeholder: "Type a message or /help"
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
] }) })
|
|
1119
|
+
] });
|
|
863
1120
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1121
|
+
|
|
1122
|
+
// src/ui/ConfirmPrompt.tsx
|
|
1123
|
+
var import_react9 = require("react");
|
|
1124
|
+
var import_ink7 = require("ink");
|
|
1125
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1126
|
+
function ConfirmPrompt({
|
|
1127
|
+
message,
|
|
1128
|
+
options = ["Yes", "No"],
|
|
1129
|
+
onSelect
|
|
1130
|
+
}) {
|
|
1131
|
+
const [selected, setSelected] = (0, import_react9.useState)(0);
|
|
1132
|
+
const resolvedRef = (0, import_react9.useRef)(false);
|
|
1133
|
+
const doSelect = (0, import_react9.useCallback)(
|
|
1134
|
+
(index) => {
|
|
1135
|
+
if (resolvedRef.current) return;
|
|
1136
|
+
resolvedRef.current = true;
|
|
1137
|
+
onSelect(index);
|
|
1138
|
+
},
|
|
1139
|
+
[onSelect]
|
|
1140
|
+
);
|
|
1141
|
+
(0, import_ink7.useInput)((input, key) => {
|
|
1142
|
+
if (resolvedRef.current) return;
|
|
1143
|
+
if (key.leftArrow || key.upArrow) {
|
|
1144
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
1145
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
1146
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
1147
|
+
} else if (key.return) {
|
|
1148
|
+
doSelect(selected);
|
|
1149
|
+
} else if (input === "y" && options.length === 2) {
|
|
1150
|
+
doSelect(0);
|
|
1151
|
+
} else if (input === "n" && options.length === 2) {
|
|
1152
|
+
doSelect(1);
|
|
892
1153
|
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
|
|
1154
|
+
});
|
|
1155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: message }),
|
|
1157
|
+
/* @__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: [
|
|
1158
|
+
i === selected ? "> " : " ",
|
|
1159
|
+
opt
|
|
1160
|
+
] }) }, opt)) }),
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
1162
|
+
] });
|
|
896
1163
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1164
|
+
|
|
1165
|
+
// src/ui/PermissionPrompt.tsx
|
|
1166
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1167
|
+
var import_ink8 = require("ink");
|
|
1168
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1169
|
+
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
1170
|
+
function formatArgs(args) {
|
|
1171
|
+
const entries = Object.entries(args);
|
|
1172
|
+
if (entries.length === 0) return "(no arguments)";
|
|
1173
|
+
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
904
1174
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
setIsThinking,
|
|
922
|
-
setContextPercentage
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
addMessage({ role: "user", content: input });
|
|
926
|
-
return runSessionPrompt(
|
|
927
|
-
input,
|
|
928
|
-
session,
|
|
929
|
-
addMessage,
|
|
930
|
-
clearStreamingText,
|
|
931
|
-
setIsThinking,
|
|
932
|
-
setContextPercentage
|
|
933
|
-
);
|
|
1175
|
+
function PermissionPrompt({ request }) {
|
|
1176
|
+
const [selected, setSelected] = import_react10.default.useState(0);
|
|
1177
|
+
const resolvedRef = import_react10.default.useRef(false);
|
|
1178
|
+
const prevRequestRef = import_react10.default.useRef(request);
|
|
1179
|
+
if (prevRequestRef.current !== request) {
|
|
1180
|
+
prevRequestRef.current = request;
|
|
1181
|
+
resolvedRef.current = false;
|
|
1182
|
+
setSelected(0);
|
|
1183
|
+
}
|
|
1184
|
+
const doResolve = import_react10.default.useCallback(
|
|
1185
|
+
(index) => {
|
|
1186
|
+
if (resolvedRef.current) return;
|
|
1187
|
+
resolvedRef.current = true;
|
|
1188
|
+
if (index === 0) request.resolve(true);
|
|
1189
|
+
else if (index === 1) request.resolve("allow-session");
|
|
1190
|
+
else request.resolve(false);
|
|
934
1191
|
},
|
|
935
|
-
[
|
|
936
|
-
session,
|
|
937
|
-
addMessage,
|
|
938
|
-
handleSlashCommand,
|
|
939
|
-
clearStreamingText,
|
|
940
|
-
setIsThinking,
|
|
941
|
-
setContextPercentage,
|
|
942
|
-
registry
|
|
943
|
-
]
|
|
1192
|
+
[request]
|
|
944
1193
|
);
|
|
1194
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
1195
|
+
if (resolvedRef.current) return;
|
|
1196
|
+
if (key.upArrow || key.leftArrow) {
|
|
1197
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
1198
|
+
} else if (key.downArrow || key.rightArrow) {
|
|
1199
|
+
setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
|
|
1200
|
+
} else if (key.return) {
|
|
1201
|
+
doResolve(selected);
|
|
1202
|
+
} else if (input === "y" || input === "1") {
|
|
1203
|
+
doResolve(0);
|
|
1204
|
+
} else if (input === "a" || input === "2") {
|
|
1205
|
+
doResolve(1);
|
|
1206
|
+
} else if (input === "n" || input === "d" || input === "3") {
|
|
1207
|
+
doResolve(2);
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { children: [
|
|
1213
|
+
"Tool:",
|
|
1214
|
+
" ",
|
|
1215
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
1216
|
+
] }),
|
|
1217
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
|
|
1218
|
+
" ",
|
|
1219
|
+
formatArgs(request.toolArgs)
|
|
1220
|
+
] }),
|
|
1221
|
+
/* @__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: [
|
|
1222
|
+
i === selected ? "> " : " ",
|
|
1223
|
+
opt
|
|
1224
|
+
] }) }, opt)) }),
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
1226
|
+
] });
|
|
945
1227
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1228
|
+
|
|
1229
|
+
// src/ui/StreamingIndicator.tsx
|
|
1230
|
+
var import_ink9 = require("ink");
|
|
1231
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1232
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
1233
|
+
const hasTools = activeTools.length > 0;
|
|
1234
|
+
const hasText = text.length > 0;
|
|
1235
|
+
if (!hasTools && !hasText) {
|
|
1236
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
|
|
953
1237
|
}
|
|
954
|
-
return
|
|
1238
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
|
|
1239
|
+
hasTools && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "gray", bold: true, children: "Tools:" }),
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
|
|
1242
|
+
activeTools.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: t.isRunning ? "yellow" : "green", children: [
|
|
1243
|
+
" ",
|
|
1244
|
+
t.isRunning ? "\u27F3" : "\u2713",
|
|
1245
|
+
" ",
|
|
1246
|
+
t.toolName,
|
|
1247
|
+
"(",
|
|
1248
|
+
t.firstArg,
|
|
1249
|
+
")"
|
|
1250
|
+
] }, `${t.toolName}-${i}`))
|
|
1251
|
+
] }),
|
|
1252
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
1254
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
|
|
1255
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
1256
|
+
] })
|
|
1257
|
+
] });
|
|
955
1258
|
}
|
|
1259
|
+
|
|
1260
|
+
// src/ui/App.tsx
|
|
1261
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1262
|
+
var EXIT_DELAY_MS2 = 500;
|
|
956
1263
|
function App(props) {
|
|
957
|
-
const { exit } = (0,
|
|
958
|
-
const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
|
|
1264
|
+
const { exit } = (0, import_ink10.useApp)();
|
|
1265
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
|
|
959
1266
|
const { messages, setMessages, addMessage } = useMessages();
|
|
960
|
-
const [isThinking, setIsThinking] = (0,
|
|
961
|
-
const
|
|
1267
|
+
const [isThinking, setIsThinking] = (0, import_react11.useState)(false);
|
|
1268
|
+
const initialCtx = session.getContextState();
|
|
1269
|
+
const [contextState, setContextState] = (0, import_react11.useState)({
|
|
1270
|
+
percentage: initialCtx.usedPercentage,
|
|
1271
|
+
usedTokens: initialCtx.usedTokens,
|
|
1272
|
+
maxTokens: initialCtx.maxTokens
|
|
1273
|
+
});
|
|
962
1274
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
963
|
-
const
|
|
1275
|
+
const pendingModelChangeRef = (0, import_react11.useRef)(null);
|
|
1276
|
+
const [pendingModelId, setPendingModelId] = (0, import_react11.useState)(null);
|
|
1277
|
+
const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
|
|
964
1278
|
const handleSubmit = useSubmitHandler(
|
|
965
1279
|
session,
|
|
966
1280
|
addMessage,
|
|
967
1281
|
handleSlashCommand,
|
|
968
1282
|
clearStreamingText,
|
|
969
1283
|
setIsThinking,
|
|
970
|
-
|
|
1284
|
+
setContextState,
|
|
971
1285
|
registry
|
|
972
1286
|
);
|
|
973
|
-
(0,
|
|
1287
|
+
(0, import_ink10.useInput)(
|
|
974
1288
|
(_input, key) => {
|
|
975
1289
|
if (key.ctrl && _input === "c") exit();
|
|
976
1290
|
if (key.escape && isThinking) session.abort();
|
|
977
1291
|
},
|
|
978
1292
|
{ isActive: !permissionRequest }
|
|
979
1293
|
);
|
|
980
|
-
return /* @__PURE__ */ (0,
|
|
981
|
-
/* @__PURE__ */ (0,
|
|
982
|
-
/* @__PURE__ */ (0,
|
|
1294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: `
|
|
983
1297
|
____ ___ ____ ___ _____ _
|
|
984
1298
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
985
1299
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
986
1300
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
987
1301
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
988
1302
|
` }),
|
|
989
|
-
/* @__PURE__ */ (0,
|
|
1303
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { dimColor: true, children: [
|
|
990
1304
|
" v",
|
|
991
1305
|
props.version ?? "0.0.0"
|
|
992
1306
|
] })
|
|
993
1307
|
] }),
|
|
994
|
-
/* @__PURE__ */ (0,
|
|
995
|
-
/* @__PURE__ */ (0,
|
|
996
|
-
isThinking && /* @__PURE__ */ (0,
|
|
1308
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1309
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageList, { messages }),
|
|
1310
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
997
1311
|
] }),
|
|
998
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
999
|
-
/* @__PURE__ */ (0,
|
|
1312
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
1313
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1314
|
+
ConfirmPrompt,
|
|
1315
|
+
{
|
|
1316
|
+
message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
1317
|
+
onSelect: (index) => {
|
|
1318
|
+
setPendingModelId(null);
|
|
1319
|
+
pendingModelChangeRef.current = null;
|
|
1320
|
+
if (index === 0) {
|
|
1321
|
+
try {
|
|
1322
|
+
const settingsPath = getUserSettingsPath();
|
|
1323
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
1324
|
+
addMessage({ role: "system", content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...` });
|
|
1325
|
+
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
1328
|
+
}
|
|
1329
|
+
} else {
|
|
1330
|
+
addMessage({ role: "system", content: "Model change cancelled." });
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
),
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1000
1336
|
StatusBar,
|
|
1001
1337
|
{
|
|
1002
1338
|
permissionMode: session.getPermissionMode(),
|
|
1003
|
-
modelName: props.config.provider.model,
|
|
1339
|
+
modelName: (0, import_agent_core3.getModelName)(props.config.provider.model),
|
|
1004
1340
|
sessionId: session.getSessionId(),
|
|
1005
1341
|
messageCount: messages.length,
|
|
1006
1342
|
isThinking,
|
|
1007
|
-
contextPercentage,
|
|
1008
|
-
contextUsedTokens:
|
|
1009
|
-
contextMaxTokens:
|
|
1343
|
+
contextPercentage: contextState.percentage,
|
|
1344
|
+
contextUsedTokens: contextState.usedTokens,
|
|
1345
|
+
contextMaxTokens: contextState.maxTokens
|
|
1010
1346
|
}
|
|
1011
1347
|
),
|
|
1012
|
-
/* @__PURE__ */ (0,
|
|
1348
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1013
1349
|
InputArea,
|
|
1014
1350
|
{
|
|
1015
1351
|
onSubmit: handleSubmit,
|
|
1016
1352
|
isDisabled: isThinking || !!permissionRequest,
|
|
1017
1353
|
registry
|
|
1018
1354
|
}
|
|
1019
|
-
)
|
|
1355
|
+
),
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " })
|
|
1020
1357
|
] });
|
|
1021
1358
|
}
|
|
1022
1359
|
|
|
1023
1360
|
// src/ui/render.tsx
|
|
1024
|
-
var
|
|
1361
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1025
1362
|
function renderApp(options) {
|
|
1026
1363
|
process.on("unhandledRejection", (reason) => {
|
|
1027
1364
|
process.stderr.write(`
|
|
@@ -1032,7 +1369,7 @@ function renderApp(options) {
|
|
|
1032
1369
|
`);
|
|
1033
1370
|
}
|
|
1034
1371
|
});
|
|
1035
|
-
const instance = (0,
|
|
1372
|
+
const instance = (0, import_ink11.render)(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(App, { ...options }), {
|
|
1036
1373
|
exitOnCtrlC: true
|
|
1037
1374
|
});
|
|
1038
1375
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1046,15 +1383,26 @@ function renderApp(options) {
|
|
|
1046
1383
|
|
|
1047
1384
|
// src/cli.ts
|
|
1048
1385
|
var import_meta = {};
|
|
1049
|
-
|
|
1386
|
+
function hasValidSettingsFile(filePath) {
|
|
1387
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return false;
|
|
1388
|
+
try {
|
|
1389
|
+
const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
|
|
1390
|
+
if (raw.length === 0) return false;
|
|
1391
|
+
const parsed = JSON.parse(raw);
|
|
1392
|
+
const provider = parsed.provider;
|
|
1393
|
+
return !!provider?.apiKey;
|
|
1394
|
+
} catch {
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1050
1398
|
function readVersion() {
|
|
1051
1399
|
try {
|
|
1052
1400
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1053
|
-
const dir = (0,
|
|
1054
|
-
const candidates = [(0,
|
|
1401
|
+
const dir = (0, import_node_path3.dirname)(thisFile);
|
|
1402
|
+
const candidates = [(0, import_node_path3.join)(dir, "..", "..", "package.json"), (0, import_node_path3.join)(dir, "..", "package.json")];
|
|
1055
1403
|
for (const pkgPath of candidates) {
|
|
1056
1404
|
try {
|
|
1057
|
-
const raw = (0,
|
|
1405
|
+
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
1058
1406
|
const pkg = JSON.parse(raw);
|
|
1059
1407
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1060
1408
|
return pkg.version;
|
|
@@ -1067,97 +1415,78 @@ function readVersion() {
|
|
|
1067
1415
|
return "0.0.0";
|
|
1068
1416
|
}
|
|
1069
1417
|
}
|
|
1070
|
-
function
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1418
|
+
async function ensureConfig(cwd) {
|
|
1419
|
+
const userPath = getUserSettingsPath();
|
|
1420
|
+
const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
|
|
1421
|
+
const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
|
|
1422
|
+
if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
|
|
1423
|
+
return;
|
|
1076
1424
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1425
|
+
process.stdout.write("\n");
|
|
1426
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
1427
|
+
process.stdout.write(" No configuration found. Let's set up your API key.\n");
|
|
1428
|
+
process.stdout.write("\n");
|
|
1429
|
+
const apiKey = await new Promise((resolve) => {
|
|
1430
|
+
process.stdout.write(" Anthropic API key: ");
|
|
1431
|
+
let input = "";
|
|
1432
|
+
const stdin = process.stdin;
|
|
1433
|
+
const wasRaw = stdin.isRaw;
|
|
1434
|
+
stdin.setRawMode(true);
|
|
1435
|
+
stdin.resume();
|
|
1436
|
+
stdin.setEncoding("utf8");
|
|
1437
|
+
const onData = (data) => {
|
|
1438
|
+
for (const ch of data) {
|
|
1439
|
+
if (ch === "\r" || ch === "\n") {
|
|
1440
|
+
stdin.removeListener("data", onData);
|
|
1441
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
1442
|
+
stdin.pause();
|
|
1443
|
+
process.stdout.write("\n");
|
|
1444
|
+
resolve(input.trim());
|
|
1445
|
+
return;
|
|
1446
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
1447
|
+
if (input.length > 0) {
|
|
1448
|
+
input = input.slice(0, -1);
|
|
1449
|
+
process.stdout.write("\b \b");
|
|
1450
|
+
}
|
|
1451
|
+
} else if (ch === "") {
|
|
1452
|
+
process.stdout.write("\n");
|
|
1453
|
+
process.exit(0);
|
|
1454
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
1455
|
+
input += ch;
|
|
1456
|
+
process.stdout.write("*");
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
stdin.on("data", onData);
|
|
1461
|
+
});
|
|
1462
|
+
if (!apiKey) {
|
|
1463
|
+
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
1085
1464
|
process.exit(1);
|
|
1086
1465
|
}
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
c: { type: "boolean", short: "c", default: false },
|
|
1095
|
-
r: { type: "string", short: "r" },
|
|
1096
|
-
model: { type: "string" },
|
|
1097
|
-
"permission-mode": { type: "string" },
|
|
1098
|
-
"max-turns": { type: "string" },
|
|
1099
|
-
version: { type: "boolean", default: false }
|
|
1466
|
+
const settingsDir = (0, import_node_path3.dirname)(userPath);
|
|
1467
|
+
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
1468
|
+
const settings = {
|
|
1469
|
+
provider: {
|
|
1470
|
+
name: "anthropic",
|
|
1471
|
+
model: "claude-sonnet-4-6",
|
|
1472
|
+
apiKey
|
|
1100
1473
|
}
|
|
1101
|
-
});
|
|
1102
|
-
return {
|
|
1103
|
-
positional: positionals,
|
|
1104
|
-
printMode: values["p"] ?? false,
|
|
1105
|
-
continueMode: values["c"] ?? false,
|
|
1106
|
-
resumeId: values["r"],
|
|
1107
|
-
model: values["model"],
|
|
1108
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1109
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1110
|
-
version: values["version"] ?? false
|
|
1111
1474
|
};
|
|
1475
|
+
(0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1476
|
+
process.stdout.write(`
|
|
1477
|
+
Config saved to ${userPath}
|
|
1478
|
+
|
|
1479
|
+
`);
|
|
1112
1480
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
writeLine(text) {
|
|
1118
|
-
process.stdout.write(text + "\n");
|
|
1119
|
-
}
|
|
1120
|
-
writeMarkdown(md) {
|
|
1121
|
-
process.stdout.write(md);
|
|
1122
|
-
}
|
|
1123
|
-
writeError(text) {
|
|
1124
|
-
process.stderr.write(text + "\n");
|
|
1125
|
-
}
|
|
1126
|
-
prompt(question) {
|
|
1127
|
-
return new Promise((resolve) => {
|
|
1128
|
-
const rl = readline.createInterface({
|
|
1129
|
-
input: process.stdin,
|
|
1130
|
-
output: process.stdout,
|
|
1131
|
-
terminal: false,
|
|
1132
|
-
historySize: 0
|
|
1133
|
-
});
|
|
1134
|
-
rl.question(question, (answer) => {
|
|
1135
|
-
rl.close();
|
|
1136
|
-
resolve(answer);
|
|
1137
|
-
});
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
|
-
async select(options, initialIndex = 0) {
|
|
1141
|
-
for (let i = 0; i < options.length; i++) {
|
|
1142
|
-
const marker = i === initialIndex ? ">" : " ";
|
|
1143
|
-
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
1481
|
+
function resetConfig() {
|
|
1482
|
+
const userPath = getUserSettingsPath();
|
|
1483
|
+
if (deleteSettings(userPath)) {
|
|
1484
|
+
process.stdout.write(`Deleted ${userPath}
|
|
1144
1485
|
`);
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
1148
|
-
);
|
|
1149
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1150
|
-
if (trimmed === "") return initialIndex;
|
|
1151
|
-
const num = parseInt(trimmed, 10);
|
|
1152
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1153
|
-
return initialIndex;
|
|
1154
|
-
}
|
|
1155
|
-
spinner(_message) {
|
|
1156
|
-
return { stop() {
|
|
1157
|
-
}, update() {
|
|
1158
|
-
} };
|
|
1486
|
+
} else {
|
|
1487
|
+
process.stdout.write("No user settings found.\n");
|
|
1159
1488
|
}
|
|
1160
|
-
}
|
|
1489
|
+
}
|
|
1161
1490
|
async function startCli() {
|
|
1162
1491
|
const args = parseCliArgs();
|
|
1163
1492
|
if (args.version) {
|
|
@@ -1165,7 +1494,12 @@ async function startCli() {
|
|
|
1165
1494
|
`);
|
|
1166
1495
|
return;
|
|
1167
1496
|
}
|
|
1497
|
+
if (args.reset) {
|
|
1498
|
+
resetConfig();
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1168
1501
|
const cwd = process.cwd();
|
|
1502
|
+
await ensureConfig(cwd);
|
|
1169
1503
|
const [config, context, projectInfo] = await Promise.all([
|
|
1170
1504
|
(0, import_agent_sdk2.loadConfig)(cwd),
|
|
1171
1505
|
(0, import_agent_sdk2.loadContext)(cwd),
|
|
@@ -1182,14 +1516,15 @@ async function startCli() {
|
|
|
1182
1516
|
process.exit(1);
|
|
1183
1517
|
}
|
|
1184
1518
|
const terminal = new PrintTerminal();
|
|
1185
|
-
const
|
|
1519
|
+
const paths = (0, import_agent_sdk2.projectPaths)(cwd);
|
|
1520
|
+
const session = (0, import_agent_sdk2.createSession)({
|
|
1186
1521
|
config,
|
|
1187
1522
|
context,
|
|
1188
1523
|
terminal,
|
|
1524
|
+
sessionLogger: new import_agent_sdk2.FileSessionLogger(paths.logs),
|
|
1189
1525
|
projectInfo,
|
|
1190
1526
|
permissionMode: args.permissionMode,
|
|
1191
|
-
|
|
1192
|
-
promptForApproval
|
|
1527
|
+
promptForApproval: import_agent_sdk3.promptForApproval
|
|
1193
1528
|
});
|
|
1194
1529
|
const response = await session.run(prompt);
|
|
1195
1530
|
process.stdout.write(response + "\n");
|
|
@@ -1207,6 +1542,16 @@ async function startCli() {
|
|
|
1207
1542
|
}
|
|
1208
1543
|
|
|
1209
1544
|
// src/bin.ts
|
|
1545
|
+
process.on("uncaughtException", (err) => {
|
|
1546
|
+
const msg = err.message ?? "";
|
|
1547
|
+
const isLikelyIME = msg.includes("string-width") || msg.includes("setCursorPosition") || msg.includes("getStringWidth") || msg.includes("slice") || msg.includes("charCodeAt");
|
|
1548
|
+
if (isLikelyIME) {
|
|
1549
|
+
process.stderr.write(`[robota] IME error suppressed: ${msg}
|
|
1550
|
+
`);
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
throw err;
|
|
1554
|
+
});
|
|
1210
1555
|
startCli().catch((err) => {
|
|
1211
1556
|
const message = err instanceof Error ? err.message : String(err);
|
|
1212
1557
|
process.stderr.write(message + "\n");
|