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