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