@robota-sdk/agent-cli 3.0.0-beta.4 → 3.0.0-beta.40
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 +43 -9
- package/dist/node/bin.cjs +2541 -578
- package/dist/node/bin.js +11 -1
- package/dist/node/chunk-CRPNSO52.js +3163 -0
- package/dist/node/index.cjs +2536 -583
- package/dist/node/index.js +1 -1
- package/package.json +4 -3
- package/dist/node/chunk-4DYVT4WI.js +0 -1196
package/dist/node/index.cjs
CHANGED
|
@@ -30,51 +30,1032 @@ 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_sdk7.Session,
|
|
34
|
+
SessionStore: () => import_agent_sdk7.SessionStore,
|
|
35
|
+
TRUST_TO_MODE: () => import_agent_sdk7.TRUST_TO_MODE,
|
|
36
|
+
query: () => import_agent_sdk7.query,
|
|
37
37
|
startCli: () => startCli
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
var
|
|
40
|
+
var import_agent_sdk7 = 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_fs4 = require("fs");
|
|
44
|
+
var import_node_path5 = require("path");
|
|
46
45
|
var import_node_url = require("url");
|
|
47
|
-
var
|
|
48
|
-
var
|
|
46
|
+
var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
|
|
47
|
+
var import_agent_sdk6 = require("@robota-sdk/agent-sdk");
|
|
49
48
|
|
|
50
|
-
// src/
|
|
51
|
-
var
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
// src/utils/cli-args.ts
|
|
50
|
+
var import_node_util = require("util");
|
|
51
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
52
|
+
function parsePermissionMode(raw) {
|
|
53
|
+
if (raw === void 0) return void 0;
|
|
54
|
+
if (!VALID_MODES.includes(raw)) {
|
|
55
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
56
|
+
`);
|
|
57
|
+
process.exit(1);
|
|
58
58
|
}
|
|
59
|
-
return
|
|
59
|
+
return raw;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
function parseMaxTurns(raw) {
|
|
62
|
+
if (raw === void 0) return void 0;
|
|
63
|
+
const n = parseInt(raw, 10);
|
|
64
|
+
if (isNaN(n) || n <= 0) {
|
|
65
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
66
|
+
`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
function parseCliArgs() {
|
|
72
|
+
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
73
|
+
allowPositionals: true,
|
|
74
|
+
options: {
|
|
75
|
+
p: { type: "boolean", short: "p", default: false },
|
|
76
|
+
c: { type: "boolean", short: "c", default: false },
|
|
77
|
+
r: { type: "string", short: "r" },
|
|
78
|
+
model: { type: "string" },
|
|
79
|
+
language: { type: "string" },
|
|
80
|
+
"permission-mode": { type: "string" },
|
|
81
|
+
"max-turns": { type: "string" },
|
|
82
|
+
version: { type: "boolean", default: false },
|
|
83
|
+
reset: { type: "boolean", default: false }
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
positional: positionals,
|
|
88
|
+
printMode: values["p"] ?? false,
|
|
89
|
+
continueMode: values["c"] ?? false,
|
|
90
|
+
resumeId: values["r"],
|
|
91
|
+
model: values["model"],
|
|
92
|
+
language: values["language"],
|
|
93
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
94
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
95
|
+
version: values["version"] ?? false,
|
|
96
|
+
reset: values["reset"] ?? false
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/utils/settings-io.ts
|
|
101
|
+
var import_node_fs = require("fs");
|
|
102
|
+
var import_node_path = require("path");
|
|
103
|
+
function getUserSettingsPath() {
|
|
104
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
105
|
+
return (0, import_node_path.join)(home, ".robota", "settings.json");
|
|
106
|
+
}
|
|
107
|
+
function readSettings(path) {
|
|
108
|
+
if (!(0, import_node_fs.existsSync)(path)) return {};
|
|
109
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
110
|
+
}
|
|
111
|
+
function writeSettings(path, settings) {
|
|
112
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
113
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
114
|
+
}
|
|
115
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
116
|
+
const settings = readSettings(settingsPath);
|
|
117
|
+
const provider = settings.provider ?? {};
|
|
118
|
+
provider.model = modelId;
|
|
119
|
+
settings.provider = provider;
|
|
120
|
+
writeSettings(settingsPath, settings);
|
|
121
|
+
}
|
|
122
|
+
function deleteSettings(path) {
|
|
123
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
124
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
68
128
|
}
|
|
69
129
|
|
|
130
|
+
// src/print-terminal.ts
|
|
131
|
+
var readline = __toESM(require("readline"), 1);
|
|
132
|
+
var PrintTerminal = class {
|
|
133
|
+
write(text) {
|
|
134
|
+
process.stdout.write(text);
|
|
135
|
+
}
|
|
136
|
+
writeLine(text) {
|
|
137
|
+
process.stdout.write(text + "\n");
|
|
138
|
+
}
|
|
139
|
+
writeMarkdown(md) {
|
|
140
|
+
process.stdout.write(md);
|
|
141
|
+
}
|
|
142
|
+
writeError(text) {
|
|
143
|
+
process.stderr.write(text + "\n");
|
|
144
|
+
}
|
|
145
|
+
prompt(question) {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
const rl = readline.createInterface({
|
|
148
|
+
input: process.stdin,
|
|
149
|
+
output: process.stdout,
|
|
150
|
+
terminal: false,
|
|
151
|
+
historySize: 0
|
|
152
|
+
});
|
|
153
|
+
rl.question(question, (answer) => {
|
|
154
|
+
rl.close();
|
|
155
|
+
resolve(answer);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async select(options, initialIndex = 0) {
|
|
160
|
+
for (let i = 0; i < options.length; i++) {
|
|
161
|
+
const marker = i === initialIndex ? ">" : " ";
|
|
162
|
+
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
165
|
+
const answer = await this.prompt(
|
|
166
|
+
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
167
|
+
);
|
|
168
|
+
const trimmed = answer.trim().toLowerCase();
|
|
169
|
+
if (trimmed === "") return initialIndex;
|
|
170
|
+
const num = parseInt(trimmed, 10);
|
|
171
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
172
|
+
return initialIndex;
|
|
173
|
+
}
|
|
174
|
+
spinner(_message) {
|
|
175
|
+
return { stop() {
|
|
176
|
+
}, update() {
|
|
177
|
+
} };
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
70
181
|
// src/ui/render.tsx
|
|
71
|
-
var
|
|
182
|
+
var import_ink14 = require("ink");
|
|
72
183
|
|
|
73
184
|
// src/ui/App.tsx
|
|
74
|
-
var
|
|
75
|
-
var
|
|
185
|
+
var import_react16 = require("react");
|
|
186
|
+
var import_ink13 = require("ink");
|
|
187
|
+
var import_agent_core6 = require("@robota-sdk/agent-core");
|
|
188
|
+
var import_agent_core7 = require("@robota-sdk/agent-core");
|
|
189
|
+
|
|
190
|
+
// src/ui/hooks/useSession.ts
|
|
191
|
+
var import_react = require("react");
|
|
76
192
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
77
193
|
|
|
194
|
+
// src/utils/edit-diff.ts
|
|
195
|
+
var import_node_fs2 = require("fs");
|
|
196
|
+
var CONTEXT_LINES = 2;
|
|
197
|
+
function generateDiffLines(oldStr, newStr, startLine = 1) {
|
|
198
|
+
if (oldStr === newStr) return [];
|
|
199
|
+
const lines = [];
|
|
200
|
+
const oldLines = oldStr.split("\n");
|
|
201
|
+
const newLines = newStr.split("\n");
|
|
202
|
+
for (let i = 0; i < oldLines.length; i++) {
|
|
203
|
+
lines.push({ type: "remove", text: oldLines[i], lineNumber: startLine + i });
|
|
204
|
+
}
|
|
205
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
206
|
+
lines.push({ type: "add", text: newLines[i], lineNumber: startLine + i });
|
|
207
|
+
}
|
|
208
|
+
return lines;
|
|
209
|
+
}
|
|
210
|
+
function generateDiffLinesWithContext(oldStr, newStr, startLine, filePath) {
|
|
211
|
+
if (oldStr === newStr) return [];
|
|
212
|
+
const diffLines = generateDiffLines(oldStr, newStr, startLine);
|
|
213
|
+
let fileLines;
|
|
214
|
+
try {
|
|
215
|
+
fileLines = (0, import_node_fs2.readFileSync)(filePath, "utf-8").split("\n");
|
|
216
|
+
} catch {
|
|
217
|
+
return diffLines;
|
|
218
|
+
}
|
|
219
|
+
const result = [];
|
|
220
|
+
const contextStart = Math.max(0, startLine - 1 - CONTEXT_LINES);
|
|
221
|
+
for (let i = contextStart; i < startLine - 1; i++) {
|
|
222
|
+
if (i < fileLines.length) {
|
|
223
|
+
result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
result.push(...diffLines);
|
|
227
|
+
const newLineCount = newStr.split("\n").length;
|
|
228
|
+
const afterStart = startLine - 1 + newLineCount;
|
|
229
|
+
for (let i = afterStart; i < afterStart + CONTEXT_LINES; i++) {
|
|
230
|
+
if (i < fileLines.length) {
|
|
231
|
+
result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
function extractEditDiff(toolName, toolArgs, startLine) {
|
|
237
|
+
if (toolName !== "Edit" || !toolArgs) return null;
|
|
238
|
+
const filePath = toolArgs.file_path ?? toolArgs.filePath;
|
|
239
|
+
const oldStr = toolArgs.old_string ?? toolArgs.oldString;
|
|
240
|
+
const newStr = toolArgs.new_string ?? toolArgs.newString;
|
|
241
|
+
if (typeof filePath !== "string") return null;
|
|
242
|
+
if (typeof oldStr !== "string" || typeof newStr !== "string") return null;
|
|
243
|
+
let sl = startLine ?? 0;
|
|
244
|
+
if (!sl) {
|
|
245
|
+
try {
|
|
246
|
+
const fileContent = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
247
|
+
const idx = fileContent.indexOf(newStr);
|
|
248
|
+
if (idx >= 0) {
|
|
249
|
+
sl = fileContent.substring(0, idx).split("\n").length;
|
|
250
|
+
} else {
|
|
251
|
+
sl = 1;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
sl = 1;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const lines = generateDiffLinesWithContext(oldStr, newStr, sl, filePath);
|
|
258
|
+
if (lines.length === 0) return null;
|
|
259
|
+
return { file: filePath, lines };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/ui/hooks/useSession.ts
|
|
263
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
264
|
+
var TAIL_KEEP = 30;
|
|
265
|
+
var MAX_COMPLETED_TOOLS = 50;
|
|
266
|
+
var NOOP_TERMINAL = {
|
|
267
|
+
write: () => {
|
|
268
|
+
},
|
|
269
|
+
writeLine: () => {
|
|
270
|
+
},
|
|
271
|
+
writeMarkdown: () => {
|
|
272
|
+
},
|
|
273
|
+
writeError: () => {
|
|
274
|
+
},
|
|
275
|
+
prompt: () => Promise.resolve(""),
|
|
276
|
+
select: () => Promise.resolve(0),
|
|
277
|
+
spinner: () => ({ stop: () => {
|
|
278
|
+
}, update: () => {
|
|
279
|
+
} })
|
|
280
|
+
};
|
|
281
|
+
function useSession(props) {
|
|
282
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
|
|
283
|
+
const [streamingText, setStreamingText] = (0, import_react.useState)("");
|
|
284
|
+
const streamingTextRef = (0, import_react.useRef)("");
|
|
285
|
+
const [activeTools, setActiveTools] = (0, import_react.useState)([]);
|
|
286
|
+
const permissionQueueRef = (0, import_react.useRef)([]);
|
|
287
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
288
|
+
const processNextPermission = (0, import_react.useCallback)(() => {
|
|
289
|
+
if (processingRef.current) return;
|
|
290
|
+
const next = permissionQueueRef.current[0];
|
|
291
|
+
if (!next) {
|
|
292
|
+
setPermissionRequest(null);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
processingRef.current = true;
|
|
296
|
+
setPermissionRequest({
|
|
297
|
+
toolName: next.toolName,
|
|
298
|
+
toolArgs: next.toolArgs,
|
|
299
|
+
resolve: (result) => {
|
|
300
|
+
permissionQueueRef.current.shift();
|
|
301
|
+
processingRef.current = false;
|
|
302
|
+
setPermissionRequest(null);
|
|
303
|
+
next.resolve(result);
|
|
304
|
+
setTimeout(() => processNextPermission(), 0);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}, []);
|
|
308
|
+
const sessionRef = (0, import_react.useRef)(null);
|
|
309
|
+
if (sessionRef.current === null) {
|
|
310
|
+
const permissionHandler = (toolName, toolArgs) => {
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
313
|
+
processNextPermission();
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
let flushTimer = null;
|
|
317
|
+
const onTextDelta = (delta) => {
|
|
318
|
+
streamingTextRef.current += delta;
|
|
319
|
+
if (!flushTimer) {
|
|
320
|
+
flushTimer = setTimeout(() => {
|
|
321
|
+
setStreamingText(streamingTextRef.current);
|
|
322
|
+
flushTimer = null;
|
|
323
|
+
}, 16);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const onToolExecution = (event) => {
|
|
327
|
+
if (event.type === "start") {
|
|
328
|
+
let firstArg = "";
|
|
329
|
+
if (event.toolArgs) {
|
|
330
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
331
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
332
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
333
|
+
}
|
|
334
|
+
setActiveTools((prev) => [
|
|
335
|
+
...prev,
|
|
336
|
+
{ toolName: event.toolName, firstArg, isRunning: true, _toolArgs: event.toolArgs }
|
|
337
|
+
]);
|
|
338
|
+
} else {
|
|
339
|
+
const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
340
|
+
setActiveTools((prev) => {
|
|
341
|
+
const updated = prev.map((t) => {
|
|
342
|
+
if (!(t.toolName === event.toolName && t.isRunning)) return t;
|
|
343
|
+
let startLine;
|
|
344
|
+
if (event.toolResultData && event.toolName === "Edit") {
|
|
345
|
+
try {
|
|
346
|
+
const parsed = JSON.parse(event.toolResultData);
|
|
347
|
+
if (typeof parsed.startLine === "number") {
|
|
348
|
+
startLine = parsed.startLine;
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const editDiff = extractEditDiff(
|
|
354
|
+
event.toolName,
|
|
355
|
+
t._toolArgs,
|
|
356
|
+
startLine
|
|
357
|
+
);
|
|
358
|
+
const finished = {
|
|
359
|
+
...t,
|
|
360
|
+
isRunning: false,
|
|
361
|
+
result: toolResult
|
|
362
|
+
};
|
|
363
|
+
if (editDiff) {
|
|
364
|
+
finished.diffLines = editDiff.lines;
|
|
365
|
+
finished.diffFile = editDiff.file;
|
|
366
|
+
}
|
|
367
|
+
delete finished._toolArgs;
|
|
368
|
+
return finished;
|
|
369
|
+
});
|
|
370
|
+
const completed = updated.filter((t) => !t.isRunning);
|
|
371
|
+
if (completed.length > MAX_COMPLETED_TOOLS) {
|
|
372
|
+
const excess = completed.length - MAX_COMPLETED_TOOLS;
|
|
373
|
+
let removed = 0;
|
|
374
|
+
return updated.filter((t) => {
|
|
375
|
+
if (!t.isRunning && removed < excess) {
|
|
376
|
+
removed++;
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return updated;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
387
|
+
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
388
|
+
config: props.config,
|
|
389
|
+
context: props.context,
|
|
390
|
+
terminal: NOOP_TERMINAL,
|
|
391
|
+
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
392
|
+
projectInfo: props.projectInfo,
|
|
393
|
+
sessionStore: props.sessionStore,
|
|
394
|
+
permissionMode: props.permissionMode,
|
|
395
|
+
maxTurns: props.maxTurns,
|
|
396
|
+
permissionHandler,
|
|
397
|
+
onTextDelta,
|
|
398
|
+
onToolExecution
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const clearStreamingText = (0, import_react.useCallback)(() => {
|
|
402
|
+
setStreamingText("");
|
|
403
|
+
streamingTextRef.current = "";
|
|
404
|
+
setActiveTools([]);
|
|
405
|
+
}, []);
|
|
406
|
+
return {
|
|
407
|
+
session: sessionRef.current,
|
|
408
|
+
permissionRequest,
|
|
409
|
+
streamingText,
|
|
410
|
+
clearStreamingText,
|
|
411
|
+
activeTools
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/ui/hooks/useMessages.ts
|
|
416
|
+
var import_react2 = require("react");
|
|
417
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
418
|
+
function useMessages() {
|
|
419
|
+
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
420
|
+
const addMessage = (0, import_react2.useCallback)((msg) => {
|
|
421
|
+
setMessages((prev) => {
|
|
422
|
+
const updated = [...prev, msg];
|
|
423
|
+
if (updated.length > MAX_RENDERED_MESSAGES) {
|
|
424
|
+
return updated.slice(-MAX_RENDERED_MESSAGES);
|
|
425
|
+
}
|
|
426
|
+
return updated;
|
|
427
|
+
});
|
|
428
|
+
}, []);
|
|
429
|
+
return { messages, setMessages, addMessage };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
433
|
+
var import_react3 = require("react");
|
|
434
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
435
|
+
|
|
436
|
+
// src/commands/slash-executor.ts
|
|
437
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
438
|
+
var HELP_TEXT = [
|
|
439
|
+
"Available commands:",
|
|
440
|
+
" /help \u2014 Show this help",
|
|
441
|
+
" /clear \u2014 Clear conversation",
|
|
442
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
443
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
444
|
+
" /language [lang] \u2014 Set response language (ko, en, ja, zh)",
|
|
445
|
+
" /cost \u2014 Show session info",
|
|
446
|
+
" /reset \u2014 Delete settings and exit",
|
|
447
|
+
" /exit \u2014 Exit CLI"
|
|
448
|
+
].join("\n");
|
|
449
|
+
function handleHelp(addMessage) {
|
|
450
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
451
|
+
return { handled: true };
|
|
452
|
+
}
|
|
453
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
454
|
+
clearMessages();
|
|
455
|
+
session.clearHistory();
|
|
456
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
457
|
+
return { handled: true };
|
|
458
|
+
}
|
|
459
|
+
async function handleCompact(args, session, addMessage) {
|
|
460
|
+
const instructions = args.trim() || void 0;
|
|
461
|
+
const before = session.getContextState().usedPercentage;
|
|
462
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
463
|
+
await session.compact(instructions);
|
|
464
|
+
const after = session.getContextState().usedPercentage;
|
|
465
|
+
addMessage({
|
|
466
|
+
role: "system",
|
|
467
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
468
|
+
});
|
|
469
|
+
return { handled: true };
|
|
470
|
+
}
|
|
471
|
+
function handleMode(arg, session, addMessage) {
|
|
472
|
+
if (!arg) {
|
|
473
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
474
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
475
|
+
session.setPermissionMode(arg);
|
|
476
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
477
|
+
} else {
|
|
478
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
479
|
+
}
|
|
480
|
+
return { handled: true };
|
|
481
|
+
}
|
|
482
|
+
function handleModel(modelId, addMessage) {
|
|
483
|
+
if (!modelId) {
|
|
484
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
485
|
+
return { handled: true };
|
|
486
|
+
}
|
|
487
|
+
return { handled: true, pendingModelId: modelId };
|
|
488
|
+
}
|
|
489
|
+
function handleCost(session, addMessage) {
|
|
490
|
+
addMessage({
|
|
491
|
+
role: "system",
|
|
492
|
+
content: `Session: ${session.getSessionId()}
|
|
493
|
+
Messages: ${session.getMessageCount()}`
|
|
494
|
+
});
|
|
495
|
+
return { handled: true };
|
|
496
|
+
}
|
|
497
|
+
function handlePermissions(session, addMessage) {
|
|
498
|
+
const mode = session.getPermissionMode();
|
|
499
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
500
|
+
const lines = [`Permission mode: ${mode}`];
|
|
501
|
+
if (sessionAllowed.length > 0) {
|
|
502
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
503
|
+
} else {
|
|
504
|
+
lines.push("No session-approved tools.");
|
|
505
|
+
}
|
|
506
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
507
|
+
return { handled: true };
|
|
508
|
+
}
|
|
509
|
+
function handleContext(session, addMessage) {
|
|
510
|
+
const ctx = session.getContextState();
|
|
511
|
+
addMessage({
|
|
512
|
+
role: "system",
|
|
513
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
514
|
+
});
|
|
515
|
+
return { handled: true };
|
|
516
|
+
}
|
|
517
|
+
function handleLanguage(lang, addMessage) {
|
|
518
|
+
if (!lang) {
|
|
519
|
+
addMessage({ role: "system", content: "Usage: /language <code> (e.g., ko, en, ja, zh)" });
|
|
520
|
+
return { handled: true };
|
|
521
|
+
}
|
|
522
|
+
const settingsPath = getUserSettingsPath();
|
|
523
|
+
const settings = readSettings(settingsPath);
|
|
524
|
+
settings.language = lang;
|
|
525
|
+
writeSettings(settingsPath, settings);
|
|
526
|
+
addMessage({ role: "system", content: `Language set to "${lang}". Restarting...` });
|
|
527
|
+
return { handled: true, exitRequested: true };
|
|
528
|
+
}
|
|
529
|
+
function handleReset(addMessage) {
|
|
530
|
+
const settingsPath = getUserSettingsPath();
|
|
531
|
+
if (deleteSettings(settingsPath)) {
|
|
532
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
533
|
+
} else {
|
|
534
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
535
|
+
}
|
|
536
|
+
return { handled: true, exitRequested: true };
|
|
537
|
+
}
|
|
538
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
539
|
+
const parts = args.trim().split(/\s+/);
|
|
540
|
+
const subcommand = parts[0] ?? "";
|
|
541
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
542
|
+
try {
|
|
543
|
+
switch (subcommand) {
|
|
544
|
+
case "":
|
|
545
|
+
case void 0:
|
|
546
|
+
case "manage": {
|
|
547
|
+
return { handled: true, triggerPluginTUI: true };
|
|
548
|
+
}
|
|
549
|
+
case "install": {
|
|
550
|
+
if (!subArgs) {
|
|
551
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
552
|
+
return { handled: true };
|
|
553
|
+
}
|
|
554
|
+
await callbacks.install(subArgs);
|
|
555
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
556
|
+
return { handled: true };
|
|
557
|
+
}
|
|
558
|
+
case "uninstall": {
|
|
559
|
+
if (!subArgs) {
|
|
560
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
561
|
+
return { handled: true };
|
|
562
|
+
}
|
|
563
|
+
await callbacks.uninstall(subArgs);
|
|
564
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
565
|
+
return { handled: true };
|
|
566
|
+
}
|
|
567
|
+
case "enable": {
|
|
568
|
+
if (!subArgs) {
|
|
569
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
570
|
+
return { handled: true };
|
|
571
|
+
}
|
|
572
|
+
await callbacks.enable(subArgs);
|
|
573
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
574
|
+
return { handled: true };
|
|
575
|
+
}
|
|
576
|
+
case "disable": {
|
|
577
|
+
if (!subArgs) {
|
|
578
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
579
|
+
return { handled: true };
|
|
580
|
+
}
|
|
581
|
+
await callbacks.disable(subArgs);
|
|
582
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
583
|
+
return { handled: true };
|
|
584
|
+
}
|
|
585
|
+
case "marketplace": {
|
|
586
|
+
const mpParts = subArgs.split(/\s+/);
|
|
587
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
588
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
589
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
590
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
591
|
+
addMessage({
|
|
592
|
+
role: "system",
|
|
593
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
594
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
595
|
+
});
|
|
596
|
+
return { handled: true };
|
|
597
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
598
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
599
|
+
addMessage({
|
|
600
|
+
role: "system",
|
|
601
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
602
|
+
});
|
|
603
|
+
return { handled: true };
|
|
604
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
605
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
606
|
+
addMessage({
|
|
607
|
+
role: "system",
|
|
608
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
609
|
+
});
|
|
610
|
+
return { handled: true };
|
|
611
|
+
} else if (mpSubcommand === "list") {
|
|
612
|
+
const sources = await callbacks.marketplaceList();
|
|
613
|
+
if (sources.length === 0) {
|
|
614
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
615
|
+
} else {
|
|
616
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
617
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
618
|
+
${lines.join("\n")}` });
|
|
619
|
+
}
|
|
620
|
+
return { handled: true };
|
|
621
|
+
} else {
|
|
622
|
+
addMessage({
|
|
623
|
+
role: "system",
|
|
624
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
625
|
+
});
|
|
626
|
+
return { handled: true };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
default:
|
|
630
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
631
|
+
return { handled: true };
|
|
632
|
+
}
|
|
633
|
+
} catch (error) {
|
|
634
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
635
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
636
|
+
return { handled: true };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
640
|
+
await callbacks.reloadPlugins();
|
|
641
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
642
|
+
return { handled: true };
|
|
643
|
+
}
|
|
644
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
645
|
+
switch (cmd) {
|
|
646
|
+
case "help":
|
|
647
|
+
return handleHelp(addMessage);
|
|
648
|
+
case "clear":
|
|
649
|
+
return handleClear(addMessage, clearMessages, session);
|
|
650
|
+
case "compact":
|
|
651
|
+
return handleCompact(args, session, addMessage);
|
|
652
|
+
case "mode":
|
|
653
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
654
|
+
case "model":
|
|
655
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
656
|
+
case "language":
|
|
657
|
+
return handleLanguage(args.split(/\s+/)[0] || void 0, addMessage);
|
|
658
|
+
case "cost":
|
|
659
|
+
return handleCost(session, addMessage);
|
|
660
|
+
case "permissions":
|
|
661
|
+
return handlePermissions(session, addMessage);
|
|
662
|
+
case "context":
|
|
663
|
+
return handleContext(session, addMessage);
|
|
664
|
+
case "reset":
|
|
665
|
+
return handleReset(addMessage);
|
|
666
|
+
case "exit":
|
|
667
|
+
return { handled: true, exitRequested: true };
|
|
668
|
+
case "plugin":
|
|
669
|
+
if (pluginCallbacks) {
|
|
670
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
671
|
+
}
|
|
672
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
673
|
+
return { handled: true };
|
|
674
|
+
case "reload-plugins":
|
|
675
|
+
if (pluginCallbacks) {
|
|
676
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
677
|
+
}
|
|
678
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
679
|
+
return { handled: true };
|
|
680
|
+
default: {
|
|
681
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
682
|
+
if (dynamicCmd) {
|
|
683
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
684
|
+
return { handled: false };
|
|
685
|
+
}
|
|
686
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
687
|
+
return { handled: true };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
693
|
+
var EXIT_DELAY_MS = 500;
|
|
694
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks, setShowPluginTUI) {
|
|
695
|
+
return (0, import_react3.useCallback)(
|
|
696
|
+
async (input) => {
|
|
697
|
+
const parts = input.slice(1).split(/\s+/);
|
|
698
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
699
|
+
const args = parts.slice(1).join(" ");
|
|
700
|
+
const clearMessages = () => setMessages([]);
|
|
701
|
+
const slashAddMessage = (msg) => {
|
|
702
|
+
addMessage((0, import_agent_core.createSystemMessage)(msg.content));
|
|
703
|
+
};
|
|
704
|
+
const result = await executeSlashCommand(
|
|
705
|
+
cmd,
|
|
706
|
+
args,
|
|
707
|
+
session,
|
|
708
|
+
slashAddMessage,
|
|
709
|
+
clearMessages,
|
|
710
|
+
registry,
|
|
711
|
+
pluginCallbacks
|
|
712
|
+
);
|
|
713
|
+
if (result.pendingModelId) {
|
|
714
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
715
|
+
setPendingModelId(result.pendingModelId);
|
|
716
|
+
}
|
|
717
|
+
if (result.triggerPluginTUI) {
|
|
718
|
+
setShowPluginTUI?.(true);
|
|
719
|
+
}
|
|
720
|
+
if (result.exitRequested) {
|
|
721
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
722
|
+
}
|
|
723
|
+
return result.handled;
|
|
724
|
+
},
|
|
725
|
+
[
|
|
726
|
+
session,
|
|
727
|
+
addMessage,
|
|
728
|
+
setMessages,
|
|
729
|
+
exit,
|
|
730
|
+
registry,
|
|
731
|
+
pendingModelChangeRef,
|
|
732
|
+
setPendingModelId,
|
|
733
|
+
pluginCallbacks,
|
|
734
|
+
setShowPluginTUI
|
|
735
|
+
]
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
740
|
+
var import_react4 = require("react");
|
|
741
|
+
var import_node_crypto = require("crypto");
|
|
742
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
743
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
744
|
+
|
|
745
|
+
// src/utils/tool-call-extractor.ts
|
|
746
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
747
|
+
var TAIL_KEEP2 = 30;
|
|
748
|
+
function extractToolCallsWithDiff(history, startIndex) {
|
|
749
|
+
const summaries = [];
|
|
750
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
751
|
+
const msg = history[i];
|
|
752
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
753
|
+
for (const tc of msg.toolCalls) {
|
|
754
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
755
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
|
|
756
|
+
const summary = {
|
|
757
|
+
line: `${tc.function.name}(${truncated})`
|
|
758
|
+
};
|
|
759
|
+
if (tc.function.name === "Edit") {
|
|
760
|
+
try {
|
|
761
|
+
const args = JSON.parse(tc.function.arguments);
|
|
762
|
+
const diff = extractEditDiff("Edit", args);
|
|
763
|
+
if (diff) {
|
|
764
|
+
summary.diffLines = diff.lines;
|
|
765
|
+
summary.diffFile = diff.file;
|
|
766
|
+
}
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
summaries.push(summary);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return summaries;
|
|
775
|
+
}
|
|
776
|
+
function parseFirstArgValue(argsJson) {
|
|
777
|
+
try {
|
|
778
|
+
const parsed = JSON.parse(argsJson);
|
|
779
|
+
const firstVal = Object.values(parsed)[0];
|
|
780
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
781
|
+
} catch {
|
|
782
|
+
return argsJson;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/utils/skill-prompt.ts
|
|
787
|
+
var import_node_child_process = require("child_process");
|
|
788
|
+
function substituteVariables(content, args, context) {
|
|
789
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
790
|
+
let result = content;
|
|
791
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
792
|
+
return argParts[Number(index)] ?? "";
|
|
793
|
+
});
|
|
794
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
795
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
796
|
+
return argParts[Number(digit)] ?? "";
|
|
797
|
+
});
|
|
798
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
799
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
async function preprocessShellCommands(content) {
|
|
803
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
804
|
+
if (!shellPattern.test(content)) {
|
|
805
|
+
return content;
|
|
806
|
+
}
|
|
807
|
+
shellPattern.lastIndex = 0;
|
|
808
|
+
let result = content;
|
|
809
|
+
let match;
|
|
810
|
+
const matches = [];
|
|
811
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
812
|
+
matches.push({ full: match[0], command: match[1] });
|
|
813
|
+
}
|
|
814
|
+
for (const { full, command } of matches) {
|
|
815
|
+
let output = "";
|
|
816
|
+
try {
|
|
817
|
+
output = (0, import_node_child_process.execSync)(command, {
|
|
818
|
+
timeout: 5e3,
|
|
819
|
+
encoding: "utf-8",
|
|
820
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
821
|
+
}).trimEnd();
|
|
822
|
+
} catch {
|
|
823
|
+
output = "";
|
|
824
|
+
}
|
|
825
|
+
result = result.replace(full, output);
|
|
826
|
+
}
|
|
827
|
+
return result;
|
|
828
|
+
}
|
|
829
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
830
|
+
const parts = input.slice(1).split(/\s+/);
|
|
831
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
832
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
833
|
+
if (!skillCmd) return null;
|
|
834
|
+
const args = parts.slice(1).join(" ").trim();
|
|
835
|
+
const userInstruction = args || skillCmd.description;
|
|
836
|
+
if (skillCmd.skillContent) {
|
|
837
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
838
|
+
processed = substituteVariables(processed, args, context);
|
|
839
|
+
return `<skill name="${cmd}">
|
|
840
|
+
${processed}
|
|
841
|
+
</skill>
|
|
842
|
+
|
|
843
|
+
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
844
|
+
}
|
|
845
|
+
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/commands/skill-executor.ts
|
|
849
|
+
function buildProcessedContent(skill, args, context) {
|
|
850
|
+
if (!skill.skillContent) return null;
|
|
851
|
+
return substituteVariables(skill.skillContent, args, context);
|
|
852
|
+
}
|
|
853
|
+
function buildInjectPrompt(skill, args, context) {
|
|
854
|
+
const processed = buildProcessedContent(skill, args, context);
|
|
855
|
+
if (processed) {
|
|
856
|
+
const userInstruction = args || skill.description;
|
|
857
|
+
return `<skill name="${skill.name}">
|
|
858
|
+
${processed}
|
|
859
|
+
</skill>
|
|
860
|
+
|
|
861
|
+
Execute the "${skill.name}" skill: ${userInstruction}`;
|
|
862
|
+
}
|
|
863
|
+
return `Use the "${skill.name}" skill: ${args || skill.description}`;
|
|
864
|
+
}
|
|
865
|
+
async function executeSkill(skill, args, callbacks, context) {
|
|
866
|
+
if (skill.context === "fork") {
|
|
867
|
+
if (!callbacks.runInFork) {
|
|
868
|
+
throw new Error("Fork execution is not available. Agent tool deps may not be initialized.");
|
|
869
|
+
}
|
|
870
|
+
const content = buildProcessedContent(skill, args, context);
|
|
871
|
+
const prompt2 = content ?? `Use the "${skill.name}" skill: ${args || skill.description}`;
|
|
872
|
+
const options = {};
|
|
873
|
+
if (skill.agent) options.agent = skill.agent;
|
|
874
|
+
if (skill.allowedTools) options.allowedTools = skill.allowedTools;
|
|
875
|
+
const result = await callbacks.runInFork(prompt2, options);
|
|
876
|
+
return { mode: "fork", result };
|
|
877
|
+
}
|
|
878
|
+
const prompt = buildInjectPrompt(skill, args, context);
|
|
879
|
+
return { mode: "inject", prompt };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
883
|
+
function syncContextState(session, setter) {
|
|
884
|
+
const ctx = session.getContextState();
|
|
885
|
+
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
886
|
+
}
|
|
887
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
888
|
+
setIsThinking(true);
|
|
889
|
+
clearStreamingText();
|
|
890
|
+
const historyBefore = session.getHistory().length;
|
|
891
|
+
try {
|
|
892
|
+
const response = await session.run(prompt, rawInput);
|
|
893
|
+
clearStreamingText();
|
|
894
|
+
const history = session.getHistory();
|
|
895
|
+
const toolSummaries = extractToolCallsWithDiff(
|
|
896
|
+
history,
|
|
897
|
+
historyBefore
|
|
898
|
+
);
|
|
899
|
+
if (toolSummaries.length > 0) {
|
|
900
|
+
addMessage(
|
|
901
|
+
(0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
|
|
902
|
+
toolCallId: (0, import_node_crypto.randomUUID)(),
|
|
903
|
+
name: `${toolSummaries.length} tools`
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
addMessage((0, import_agent_core2.createAssistantMessage)(response || "(empty response)"));
|
|
908
|
+
syncContextState(session, setContextState);
|
|
909
|
+
} catch (err) {
|
|
910
|
+
clearStreamingText();
|
|
911
|
+
const isAbortError = err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
|
|
912
|
+
if (isAbortError) {
|
|
913
|
+
const history = session.getHistory();
|
|
914
|
+
const toolSummaries = extractToolCallsWithDiff(
|
|
915
|
+
history,
|
|
916
|
+
historyBefore
|
|
917
|
+
);
|
|
918
|
+
if (toolSummaries.length > 0) {
|
|
919
|
+
addMessage(
|
|
920
|
+
(0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
|
|
921
|
+
toolCallId: (0, import_node_crypto.randomUUID)(),
|
|
922
|
+
name: `${toolSummaries.length} tools`
|
|
923
|
+
})
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
const assistantParts = [];
|
|
927
|
+
let lastAssistantState = "complete";
|
|
928
|
+
for (let i = historyBefore; i < history.length; i++) {
|
|
929
|
+
const msg = history[i];
|
|
930
|
+
if (msg && msg.role === "assistant" && msg.content) {
|
|
931
|
+
assistantParts.push(msg.content);
|
|
932
|
+
if (msg.state === "interrupted") lastAssistantState = "interrupted";
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (assistantParts.length > 0) {
|
|
936
|
+
addMessage(
|
|
937
|
+
(0, import_agent_core2.createAssistantMessage)(assistantParts.join("\n\n"), { state: lastAssistantState })
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
addMessage((0, import_agent_core2.createSystemMessage)("Interrupted by user."));
|
|
941
|
+
} else {
|
|
942
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
943
|
+
addMessage((0, import_agent_core2.createSystemMessage)(`Error: ${errMsg}`));
|
|
944
|
+
}
|
|
945
|
+
} finally {
|
|
946
|
+
setIsThinking(false);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function createForkRunner(sessionKey) {
|
|
950
|
+
const deps = (0, import_agent_sdk2.retrieveAgentToolDeps)(sessionKey);
|
|
951
|
+
if (!deps) return void 0;
|
|
952
|
+
return async (content, options) => {
|
|
953
|
+
const agentType = options.agent ?? "general-purpose";
|
|
954
|
+
const agentDef = (0, import_agent_sdk2.getBuiltInAgent)(agentType) ?? deps.customAgentRegistry?.(agentType);
|
|
955
|
+
if (!agentDef) {
|
|
956
|
+
throw new Error(`Unknown agent type for fork execution: ${agentType}`);
|
|
957
|
+
}
|
|
958
|
+
const effectiveDef = options.allowedTools ? { ...agentDef, tools: options.allowedTools } : agentDef;
|
|
959
|
+
const subSession = (0, import_agent_sdk2.createSubagentSession)({
|
|
960
|
+
agentDefinition: effectiveDef,
|
|
961
|
+
parentConfig: deps.config,
|
|
962
|
+
parentContext: deps.context,
|
|
963
|
+
parentTools: deps.tools,
|
|
964
|
+
terminal: deps.terminal,
|
|
965
|
+
isForkWorker: true,
|
|
966
|
+
permissionHandler: deps.permissionHandler,
|
|
967
|
+
onTextDelta: deps.onTextDelta,
|
|
968
|
+
onToolExecution: deps.onToolExecution
|
|
969
|
+
});
|
|
970
|
+
return await subSession.run(content);
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function findSkillCommand(input, registry) {
|
|
974
|
+
const parts = input.slice(1).split(/\s+/);
|
|
975
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
976
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
977
|
+
if (!skillCmd) return null;
|
|
978
|
+
return { skill: skillCmd, args: parts.slice(1).join(" ").trim() };
|
|
979
|
+
}
|
|
980
|
+
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
|
|
981
|
+
return (0, import_react4.useCallback)(
|
|
982
|
+
async (input) => {
|
|
983
|
+
if (input.startsWith("/")) {
|
|
984
|
+
const handled = await handleSlashCommand(input);
|
|
985
|
+
if (handled) {
|
|
986
|
+
syncContextState(session, setContextState);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const found = findSkillCommand(input, registry);
|
|
990
|
+
if (!found) return;
|
|
991
|
+
const { skill, args } = found;
|
|
992
|
+
if (skill.context === "fork") {
|
|
993
|
+
const runInFork = createForkRunner(session);
|
|
994
|
+
const result = await executeSkill(skill, args, { runInFork });
|
|
995
|
+
if (result.mode === "fork") {
|
|
996
|
+
addMessage((0, import_agent_core2.createAssistantMessage)(result.result ?? "(empty response)"));
|
|
997
|
+
syncContextState(session, setContextState);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (result.prompt) {
|
|
1001
|
+
const cmdName2 = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
1002
|
+
const qualifiedName2 = registry.resolveQualifiedName(cmdName2);
|
|
1003
|
+
const hookInput2 = qualifiedName2 ? `/${qualifiedName2}${input.slice(1 + cmdName2.length)}` : input;
|
|
1004
|
+
return runSessionPrompt(
|
|
1005
|
+
result.prompt,
|
|
1006
|
+
session,
|
|
1007
|
+
addMessage,
|
|
1008
|
+
clearStreamingText,
|
|
1009
|
+
setIsThinking,
|
|
1010
|
+
setContextState,
|
|
1011
|
+
hookInput2
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
1017
|
+
if (!prompt) return;
|
|
1018
|
+
const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
1019
|
+
const qualifiedName = registry.resolveQualifiedName(cmdName);
|
|
1020
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
|
|
1021
|
+
return runSessionPrompt(
|
|
1022
|
+
prompt,
|
|
1023
|
+
session,
|
|
1024
|
+
addMessage,
|
|
1025
|
+
clearStreamingText,
|
|
1026
|
+
setIsThinking,
|
|
1027
|
+
setContextState,
|
|
1028
|
+
hookInput
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
addMessage((0, import_agent_core2.createUserMessage)(input));
|
|
1032
|
+
return runSessionPrompt(
|
|
1033
|
+
input,
|
|
1034
|
+
session,
|
|
1035
|
+
addMessage,
|
|
1036
|
+
clearStreamingText,
|
|
1037
|
+
setIsThinking,
|
|
1038
|
+
setContextState
|
|
1039
|
+
);
|
|
1040
|
+
},
|
|
1041
|
+
[
|
|
1042
|
+
session,
|
|
1043
|
+
addMessage,
|
|
1044
|
+
handleSlashCommand,
|
|
1045
|
+
clearStreamingText,
|
|
1046
|
+
setIsThinking,
|
|
1047
|
+
setContextState,
|
|
1048
|
+
registry
|
|
1049
|
+
]
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
1054
|
+
var import_react5 = require("react");
|
|
1055
|
+
var import_node_os2 = require("os");
|
|
1056
|
+
var import_node_path3 = require("path");
|
|
1057
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
1058
|
+
|
|
78
1059
|
// src/commands/command-registry.ts
|
|
79
1060
|
var CommandRegistry = class {
|
|
80
1061
|
sources = [];
|
|
@@ -91,6 +1072,14 @@ var CommandRegistry = class {
|
|
|
91
1072
|
const lower = filter.toLowerCase();
|
|
92
1073
|
return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
|
|
93
1074
|
}
|
|
1075
|
+
/** Resolve a short name to its fully qualified plugin:name form */
|
|
1076
|
+
resolveQualifiedName(shortName) {
|
|
1077
|
+
const matches = this.getCommands().filter(
|
|
1078
|
+
(c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
|
|
1079
|
+
);
|
|
1080
|
+
if (matches.length !== 1) return null;
|
|
1081
|
+
return matches[0].name;
|
|
1082
|
+
}
|
|
94
1083
|
/** Get subcommands for a specific command */
|
|
95
1084
|
getSubcommands(commandName) {
|
|
96
1085
|
const lower = commandName.toLowerCase();
|
|
@@ -106,6 +1095,21 @@ var CommandRegistry = class {
|
|
|
106
1095
|
};
|
|
107
1096
|
|
|
108
1097
|
// src/commands/builtin-source.ts
|
|
1098
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
1099
|
+
function buildModelSubcommands() {
|
|
1100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1101
|
+
const commands = [];
|
|
1102
|
+
for (const model of Object.values(import_agent_core3.CLAUDE_MODELS)) {
|
|
1103
|
+
if (seen.has(model.name)) continue;
|
|
1104
|
+
seen.add(model.name);
|
|
1105
|
+
commands.push({
|
|
1106
|
+
name: model.id,
|
|
1107
|
+
description: `${model.name} (${(0, import_agent_core3.formatTokenCount)(model.contextWindow).toUpperCase()})`,
|
|
1108
|
+
source: "builtin"
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
return commands;
|
|
1112
|
+
}
|
|
109
1113
|
function createBuiltinCommands() {
|
|
110
1114
|
return [
|
|
111
1115
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -125,16 +1129,26 @@ function createBuiltinCommands() {
|
|
|
125
1129
|
name: "model",
|
|
126
1130
|
description: "Select AI model",
|
|
127
1131
|
source: "builtin",
|
|
1132
|
+
subcommands: buildModelSubcommands()
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
name: "language",
|
|
1136
|
+
description: "Set response language",
|
|
1137
|
+
source: "builtin",
|
|
128
1138
|
subcommands: [
|
|
129
|
-
{ name: "
|
|
130
|
-
{ name: "
|
|
131
|
-
{ name: "
|
|
1139
|
+
{ name: "ko", description: "Korean", source: "builtin" },
|
|
1140
|
+
{ name: "en", description: "English", source: "builtin" },
|
|
1141
|
+
{ name: "ja", description: "Japanese", source: "builtin" },
|
|
1142
|
+
{ name: "zh", description: "Chinese", source: "builtin" }
|
|
132
1143
|
]
|
|
133
1144
|
},
|
|
134
1145
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
135
1146
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
136
1147
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
137
1148
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
1149
|
+
{ name: "plugin", description: "Manage plugins", source: "builtin" },
|
|
1150
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
1151
|
+
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
138
1152
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
139
1153
|
];
|
|
140
1154
|
}
|
|
@@ -150,72 +1164,342 @@ var BuiltinCommandSource = class {
|
|
|
150
1164
|
};
|
|
151
1165
|
|
|
152
1166
|
// src/commands/skill-source.ts
|
|
153
|
-
var
|
|
154
|
-
var
|
|
1167
|
+
var import_node_fs3 = require("fs");
|
|
1168
|
+
var import_node_path2 = require("path");
|
|
155
1169
|
var import_node_os = require("os");
|
|
1170
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
1171
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
1172
|
+
function kebabToCamel(key) {
|
|
1173
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
1174
|
+
}
|
|
156
1175
|
function parseFrontmatter(content) {
|
|
157
1176
|
const lines = content.split("\n");
|
|
158
1177
|
if (lines[0]?.trim() !== "---") return null;
|
|
159
|
-
|
|
160
|
-
let description = "";
|
|
1178
|
+
const result = {};
|
|
161
1179
|
for (let i = 1; i < lines.length; i++) {
|
|
162
1180
|
const line = lines[i];
|
|
163
1181
|
if (line.trim() === "---") break;
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1182
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
1183
|
+
if (!match) continue;
|
|
1184
|
+
const key = match[1];
|
|
1185
|
+
const rawValue = match[2].trim();
|
|
1186
|
+
const camelKey = kebabToCamel(key);
|
|
1187
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
1188
|
+
result[camelKey] = rawValue === "true";
|
|
1189
|
+
} else if (LIST_KEYS.has(key)) {
|
|
1190
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
1191
|
+
} else {
|
|
1192
|
+
result[camelKey] = rawValue;
|
|
172
1193
|
}
|
|
173
1194
|
}
|
|
174
|
-
return
|
|
1195
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
1196
|
+
}
|
|
1197
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
1198
|
+
const cmd = {
|
|
1199
|
+
name: frontmatter?.name ?? fallbackName,
|
|
1200
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
1201
|
+
source: "skill",
|
|
1202
|
+
skillContent: content
|
|
1203
|
+
};
|
|
1204
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
1205
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
1206
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
1207
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
1208
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
1209
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
1210
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
1211
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
1212
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
1213
|
+
return cmd;
|
|
175
1214
|
}
|
|
176
1215
|
function scanSkillsDir(skillsDir) {
|
|
177
|
-
if (!(0,
|
|
1216
|
+
if (!(0, import_node_fs3.existsSync)(skillsDir)) return [];
|
|
178
1217
|
const commands = [];
|
|
179
|
-
const entries = (0,
|
|
1218
|
+
const entries = (0, import_node_fs3.readdirSync)(skillsDir, { withFileTypes: true });
|
|
180
1219
|
for (const entry of entries) {
|
|
181
1220
|
if (!entry.isDirectory()) continue;
|
|
182
|
-
const skillFile = (0,
|
|
183
|
-
if (!(0,
|
|
184
|
-
const content = (0,
|
|
1221
|
+
const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
|
|
1222
|
+
if (!(0, import_node_fs3.existsSync)(skillFile)) continue;
|
|
1223
|
+
const content = (0, import_node_fs3.readFileSync)(skillFile, "utf-8");
|
|
185
1224
|
const frontmatter = parseFrontmatter(content);
|
|
186
|
-
commands.push(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
1225
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
1226
|
+
}
|
|
1227
|
+
return commands;
|
|
1228
|
+
}
|
|
1229
|
+
function scanCommandsDir(commandsDir) {
|
|
1230
|
+
if (!(0, import_node_fs3.existsSync)(commandsDir)) return [];
|
|
1231
|
+
const commands = [];
|
|
1232
|
+
const entries = (0, import_node_fs3.readdirSync)(commandsDir, { withFileTypes: true });
|
|
1233
|
+
for (const entry of entries) {
|
|
1234
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1235
|
+
const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
|
|
1236
|
+
const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
|
|
1237
|
+
const frontmatter = parseFrontmatter(content);
|
|
1238
|
+
const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
|
|
1239
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
191
1240
|
}
|
|
192
1241
|
return commands;
|
|
193
1242
|
}
|
|
194
1243
|
var SkillCommandSource = class {
|
|
195
1244
|
name = "skill";
|
|
196
1245
|
cwd;
|
|
1246
|
+
home;
|
|
197
1247
|
cachedCommands = null;
|
|
198
|
-
constructor(cwd) {
|
|
1248
|
+
constructor(cwd, home) {
|
|
199
1249
|
this.cwd = cwd;
|
|
1250
|
+
this.home = home ?? (0, import_node_os.homedir)();
|
|
200
1251
|
}
|
|
201
1252
|
getCommands() {
|
|
202
1253
|
if (this.cachedCommands) return this.cachedCommands;
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
1254
|
+
const sources = [
|
|
1255
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
|
|
1256
|
+
// 1. project .claude/skills
|
|
1257
|
+
scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
|
|
1258
|
+
// 2. project .claude/commands (legacy)
|
|
1259
|
+
scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
|
|
1260
|
+
// 3. user ~/.robota/skills
|
|
1261
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
|
|
1262
|
+
// 4. project .agents/skills
|
|
1263
|
+
];
|
|
1264
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1265
|
+
const merged = [];
|
|
1266
|
+
for (const commands of sources) {
|
|
1267
|
+
for (const cmd of commands) {
|
|
1268
|
+
if (!seen.has(cmd.name)) {
|
|
1269
|
+
seen.add(cmd.name);
|
|
1270
|
+
merged.push(cmd);
|
|
1271
|
+
}
|
|
210
1272
|
}
|
|
211
1273
|
}
|
|
212
1274
|
this.cachedCommands = merged;
|
|
213
1275
|
return this.cachedCommands;
|
|
214
1276
|
}
|
|
1277
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
1278
|
+
getModelInvocableSkills() {
|
|
1279
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
1280
|
+
}
|
|
1281
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1282
|
+
getUserInvocableSkills() {
|
|
1283
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
// src/commands/plugin-source.ts
|
|
1288
|
+
var PluginCommandSource = class {
|
|
1289
|
+
name = "plugin";
|
|
1290
|
+
plugins;
|
|
1291
|
+
constructor(plugins) {
|
|
1292
|
+
this.plugins = plugins;
|
|
1293
|
+
}
|
|
1294
|
+
getCommands() {
|
|
1295
|
+
const commands = [];
|
|
1296
|
+
for (const plugin of this.plugins) {
|
|
1297
|
+
for (const skill of plugin.skills) {
|
|
1298
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1299
|
+
commands.push({
|
|
1300
|
+
name: baseName,
|
|
1301
|
+
description: `(${plugin.manifest.name}) ${skill.description}`,
|
|
1302
|
+
source: "plugin",
|
|
1303
|
+
skillContent: skill.skillContent,
|
|
1304
|
+
pluginDir: plugin.pluginDir
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
for (const cmd of plugin.commands) {
|
|
1308
|
+
commands.push({
|
|
1309
|
+
name: cmd.name,
|
|
1310
|
+
description: cmd.description,
|
|
1311
|
+
source: "plugin",
|
|
1312
|
+
skillContent: cmd.skillContent,
|
|
1313
|
+
pluginDir: plugin.pluginDir
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return commands;
|
|
1318
|
+
}
|
|
215
1319
|
};
|
|
216
1320
|
|
|
1321
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
1322
|
+
function buildPluginEnv(plugin) {
|
|
1323
|
+
const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1324
|
+
return {
|
|
1325
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1326
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1327
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function mergePluginHooks(plugins) {
|
|
1331
|
+
const merged = {};
|
|
1332
|
+
for (const plugin of plugins) {
|
|
1333
|
+
const hooksObj = plugin.hooks;
|
|
1334
|
+
if (!hooksObj) continue;
|
|
1335
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1336
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1337
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1338
|
+
if (!Array.isArray(groups)) continue;
|
|
1339
|
+
if (!merged[event]) merged[event] = [];
|
|
1340
|
+
const resolved = groups.map((group) => {
|
|
1341
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1342
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1343
|
+
resolved2.env = pluginEnv;
|
|
1344
|
+
}
|
|
1345
|
+
return resolved2;
|
|
1346
|
+
});
|
|
1347
|
+
merged[event].push(...resolved);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return merged;
|
|
1351
|
+
}
|
|
1352
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1353
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1354
|
+
const obj = group;
|
|
1355
|
+
if (Array.isArray(obj.hooks)) {
|
|
1356
|
+
return {
|
|
1357
|
+
...obj,
|
|
1358
|
+
hooks: obj.hooks.map((h) => {
|
|
1359
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1360
|
+
const hook = h;
|
|
1361
|
+
if (typeof hook.command === "string") {
|
|
1362
|
+
return {
|
|
1363
|
+
...hook,
|
|
1364
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
return hook;
|
|
1368
|
+
})
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return group;
|
|
1372
|
+
}
|
|
1373
|
+
function useCommandRegistry(cwd) {
|
|
1374
|
+
const resultRef = (0, import_react5.useRef)(null);
|
|
1375
|
+
if (resultRef.current === null) {
|
|
1376
|
+
const registry = new CommandRegistry();
|
|
1377
|
+
registry.addSource(new BuiltinCommandSource());
|
|
1378
|
+
registry.addSource(new SkillCommandSource(cwd));
|
|
1379
|
+
let pluginHooks = {};
|
|
1380
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
1381
|
+
const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
|
|
1382
|
+
try {
|
|
1383
|
+
const plugins = loader.loadPluginsSync();
|
|
1384
|
+
if (plugins.length > 0) {
|
|
1385
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1386
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1387
|
+
}
|
|
1388
|
+
} catch {
|
|
1389
|
+
}
|
|
1390
|
+
resultRef.current = { registry, pluginHooks };
|
|
1391
|
+
}
|
|
1392
|
+
return resultRef.current;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1396
|
+
var import_react6 = require("react");
|
|
1397
|
+
var import_node_os3 = require("os");
|
|
1398
|
+
var import_node_path4 = require("path");
|
|
1399
|
+
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
1400
|
+
function usePluginCallbacks(cwd) {
|
|
1401
|
+
return (0, import_react6.useMemo)(() => {
|
|
1402
|
+
const home = (0, import_node_os3.homedir)();
|
|
1403
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
1404
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
1405
|
+
const settingsStore = new import_agent_sdk4.PluginSettingsStore(userSettingsPath);
|
|
1406
|
+
const marketplace = new import_agent_sdk4.MarketplaceClient({ pluginsDir });
|
|
1407
|
+
const installer = new import_agent_sdk4.BundlePluginInstaller({
|
|
1408
|
+
pluginsDir,
|
|
1409
|
+
settingsStore,
|
|
1410
|
+
marketplaceClient: marketplace
|
|
1411
|
+
});
|
|
1412
|
+
const loader = new import_agent_sdk4.BundlePluginLoader(pluginsDir);
|
|
1413
|
+
return {
|
|
1414
|
+
listInstalled: async () => {
|
|
1415
|
+
const plugins = await loader.loadAll();
|
|
1416
|
+
const enabledMap = settingsStore.getEnabledPlugins();
|
|
1417
|
+
return plugins.map((p) => {
|
|
1418
|
+
const parts = p.pluginDir.split("/");
|
|
1419
|
+
const cacheIdx = parts.indexOf("cache");
|
|
1420
|
+
const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
|
|
1421
|
+
const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
|
|
1422
|
+
return {
|
|
1423
|
+
name: fullId,
|
|
1424
|
+
description: p.manifest.description,
|
|
1425
|
+
enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
|
|
1426
|
+
};
|
|
1427
|
+
});
|
|
1428
|
+
},
|
|
1429
|
+
listAvailablePlugins: async (marketplaceName) => {
|
|
1430
|
+
let manifest;
|
|
1431
|
+
try {
|
|
1432
|
+
manifest = marketplace.fetchManifest(marketplaceName);
|
|
1433
|
+
} catch {
|
|
1434
|
+
return [];
|
|
1435
|
+
}
|
|
1436
|
+
const installed = installer.getInstalledPlugins();
|
|
1437
|
+
const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
|
|
1438
|
+
return manifest.plugins.map((p) => ({
|
|
1439
|
+
name: p.name,
|
|
1440
|
+
description: p.description,
|
|
1441
|
+
installed: installedNames.has(p.name)
|
|
1442
|
+
}));
|
|
1443
|
+
},
|
|
1444
|
+
install: async (pluginId, scope) => {
|
|
1445
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1446
|
+
if (!name || !marketplaceName) {
|
|
1447
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1448
|
+
}
|
|
1449
|
+
if (scope === "project") {
|
|
1450
|
+
const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
|
|
1451
|
+
const projectInstaller = new import_agent_sdk4.BundlePluginInstaller({
|
|
1452
|
+
pluginsDir: projectPluginsDir,
|
|
1453
|
+
settingsStore,
|
|
1454
|
+
marketplaceClient: marketplace
|
|
1455
|
+
});
|
|
1456
|
+
await projectInstaller.install(name, marketplaceName);
|
|
1457
|
+
} else {
|
|
1458
|
+
await installer.install(name, marketplaceName);
|
|
1459
|
+
}
|
|
1460
|
+
},
|
|
1461
|
+
uninstall: async (pluginId) => {
|
|
1462
|
+
await installer.uninstall(pluginId);
|
|
1463
|
+
},
|
|
1464
|
+
enable: async (pluginId) => {
|
|
1465
|
+
await installer.enable(pluginId);
|
|
1466
|
+
},
|
|
1467
|
+
disable: async (pluginId) => {
|
|
1468
|
+
await installer.disable(pluginId);
|
|
1469
|
+
},
|
|
1470
|
+
marketplaceAdd: async (source) => {
|
|
1471
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1472
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1473
|
+
} else {
|
|
1474
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
marketplaceRemove: async (name) => {
|
|
1478
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1479
|
+
for (const record of installedFromMarketplace) {
|
|
1480
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1481
|
+
}
|
|
1482
|
+
marketplace.removeMarketplace(name);
|
|
1483
|
+
},
|
|
1484
|
+
marketplaceUpdate: async (name) => {
|
|
1485
|
+
marketplace.updateMarketplace(name);
|
|
1486
|
+
},
|
|
1487
|
+
marketplaceList: async () => {
|
|
1488
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1489
|
+
name: m.name,
|
|
1490
|
+
type: m.source.type
|
|
1491
|
+
}));
|
|
1492
|
+
},
|
|
1493
|
+
reloadPlugins: async () => {
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
}, [cwd]);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
217
1499
|
// src/ui/MessageList.tsx
|
|
218
|
-
var
|
|
1500
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
1501
|
+
var import_ink2 = require("ink");
|
|
1502
|
+
var import_agent_core4 = require("@robota-sdk/agent-core");
|
|
219
1503
|
|
|
220
1504
|
// src/ui/render-markdown.ts
|
|
221
1505
|
var import_marked = require("marked");
|
|
@@ -228,54 +1512,161 @@ function renderMarkdown(md) {
|
|
|
228
1512
|
return typeof result === "string" ? result.trimEnd() : md;
|
|
229
1513
|
}
|
|
230
1514
|
|
|
231
|
-
// src/ui/
|
|
1515
|
+
// src/ui/DiffBlock.tsx
|
|
1516
|
+
var import_ink = require("ink");
|
|
232
1517
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1518
|
+
var MAX_DIFF_LINES = 12;
|
|
1519
|
+
var TRUNCATED_SHOW = 10;
|
|
1520
|
+
function DiffBlock({ file, lines }) {
|
|
1521
|
+
const truncated = lines.length > MAX_DIFF_LINES;
|
|
1522
|
+
const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
|
|
1523
|
+
const remaining = lines.length - TRUNCATED_SHOW;
|
|
1524
|
+
const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
|
|
1525
|
+
const numWidth = String(maxLineNum).length;
|
|
1526
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
1527
|
+
file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1528
|
+
"\u2502 ",
|
|
1529
|
+
file
|
|
1530
|
+
] }),
|
|
1531
|
+
visible.map((line, i) => {
|
|
1532
|
+
const lineNum = String(line.lineNumber).padStart(numWidth, " ");
|
|
1533
|
+
if (line.type === "context") {
|
|
1534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1535
|
+
"\u2502 ",
|
|
1536
|
+
lineNum,
|
|
1537
|
+
" ",
|
|
1538
|
+
line.text
|
|
1539
|
+
] }, i);
|
|
1540
|
+
}
|
|
1541
|
+
const prefix = line.type === "remove" ? "-" : "+";
|
|
1542
|
+
const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
|
|
1543
|
+
const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
|
|
1544
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: fgColor, backgroundColor: bgColor, children: [
|
|
1545
|
+
"\u2502 ",
|
|
1546
|
+
lineNum,
|
|
1547
|
+
" ",
|
|
1548
|
+
prefix,
|
|
1549
|
+
" ",
|
|
1550
|
+
line.text
|
|
1551
|
+
] }, i);
|
|
1552
|
+
}),
|
|
1553
|
+
truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1554
|
+
"\u2502 ... and ",
|
|
1555
|
+
remaining,
|
|
1556
|
+
" more lines"
|
|
1557
|
+
] })
|
|
1558
|
+
] });
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// src/ui/MessageList.tsx
|
|
1562
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
233
1563
|
function RoleLabel({ role }) {
|
|
234
1564
|
switch (role) {
|
|
235
1565
|
case "user":
|
|
236
|
-
return /* @__PURE__ */ (0,
|
|
1566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
|
|
237
1567
|
"You:",
|
|
238
1568
|
" "
|
|
239
1569
|
] });
|
|
240
1570
|
case "assistant":
|
|
241
|
-
return /* @__PURE__ */ (0,
|
|
1571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
|
|
242
1572
|
"Robota:",
|
|
243
1573
|
" "
|
|
244
1574
|
] });
|
|
245
1575
|
case "system":
|
|
246
|
-
return /* @__PURE__ */ (0,
|
|
1576
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
|
|
247
1577
|
"System:",
|
|
248
1578
|
" "
|
|
249
1579
|
] });
|
|
250
1580
|
case "tool":
|
|
251
|
-
return /* @__PURE__ */ (0,
|
|
1581
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
252
1582
|
"Tool:",
|
|
253
1583
|
" "
|
|
254
1584
|
] });
|
|
255
1585
|
}
|
|
256
1586
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
/* @__PURE__ */ (0,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
1587
|
+
function ToolMessage({ message }) {
|
|
1588
|
+
if (!(0, import_agent_core4.isToolMessage)(message)) {
|
|
1589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
|
|
1590
|
+
}
|
|
1591
|
+
const toolName = message.name;
|
|
1592
|
+
const content = message.content;
|
|
1593
|
+
let summaries = null;
|
|
1594
|
+
try {
|
|
1595
|
+
const parsed = JSON.parse(content);
|
|
1596
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
1597
|
+
summaries = parsed;
|
|
1598
|
+
}
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
if (summaries) {
|
|
1602
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1604
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1605
|
+
"Tool:",
|
|
1606
|
+
" "
|
|
1607
|
+
] }),
|
|
1608
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1609
|
+
"[",
|
|
1610
|
+
toolName,
|
|
1611
|
+
"]"
|
|
1612
|
+
] })
|
|
1613
|
+
] }),
|
|
1614
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1615
|
+
summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1617
|
+
" ",
|
|
1618
|
+
"\u2713",
|
|
1619
|
+
" ",
|
|
1620
|
+
s.line
|
|
1621
|
+
] }),
|
|
1622
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
1623
|
+
] }, i))
|
|
1624
|
+
] });
|
|
1625
|
+
}
|
|
1626
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1630
|
+
"Tool:",
|
|
265
1631
|
" "
|
|
1632
|
+
] }),
|
|
1633
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1634
|
+
"[",
|
|
1635
|
+
toolName,
|
|
1636
|
+
"]"
|
|
266
1637
|
] })
|
|
267
1638
|
] }),
|
|
268
|
-
/* @__PURE__ */ (0,
|
|
269
|
-
|
|
1639
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1640
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1641
|
+
" ",
|
|
1642
|
+
"\u2713",
|
|
1643
|
+
" ",
|
|
1644
|
+
line
|
|
1645
|
+
] }, i))
|
|
270
1646
|
] });
|
|
271
1647
|
}
|
|
1648
|
+
var MessageItem = import_react7.default.memo(function MessageItem2({
|
|
1649
|
+
message
|
|
1650
|
+
}) {
|
|
1651
|
+
if ((0, import_agent_core4.isToolMessage)(message)) {
|
|
1652
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
|
|
1653
|
+
}
|
|
1654
|
+
const content = message.content ?? "";
|
|
1655
|
+
const isInterrupted = message.state === "interrupted";
|
|
1656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1657
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
|
|
1658
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1659
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: (0, import_agent_core4.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
|
|
1660
|
+
] });
|
|
1661
|
+
});
|
|
272
1662
|
function MessageList({ messages }) {
|
|
273
|
-
return /* @__PURE__ */ (0,
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message: msg }, msg.id)) });
|
|
274
1664
|
}
|
|
275
1665
|
|
|
276
1666
|
// src/ui/StatusBar.tsx
|
|
277
|
-
var
|
|
278
|
-
var
|
|
1667
|
+
var import_ink3 = require("ink");
|
|
1668
|
+
var import_agent_core5 = require("@robota-sdk/agent-core");
|
|
1669
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
279
1670
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
280
1671
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
281
1672
|
function getContextColor(percentage) {
|
|
@@ -294,8 +1685,8 @@ function StatusBar({
|
|
|
294
1685
|
contextMaxTokens
|
|
295
1686
|
}) {
|
|
296
1687
|
const contextColor = getContextColor(contextPercentage);
|
|
297
|
-
return /* @__PURE__ */ (0,
|
|
298
|
-
|
|
1688
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1689
|
+
import_ink3.Box,
|
|
299
1690
|
{
|
|
300
1691
|
borderStyle: "single",
|
|
301
1692
|
borderColor: "gray",
|
|
@@ -303,26 +1694,26 @@ function StatusBar({
|
|
|
303
1694
|
paddingRight: 1,
|
|
304
1695
|
justifyContent: "space-between",
|
|
305
1696
|
children: [
|
|
306
|
-
/* @__PURE__ */ (0,
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
1697
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
|
|
308
1699
|
" ",
|
|
309
|
-
/* @__PURE__ */ (0,
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
|
|
310
1701
|
" | ",
|
|
311
|
-
/* @__PURE__ */ (0,
|
|
1702
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
|
|
312
1703
|
" | ",
|
|
313
|
-
/* @__PURE__ */ (0,
|
|
1704
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
|
|
314
1705
|
"Context: ",
|
|
315
1706
|
Math.round(contextPercentage),
|
|
316
1707
|
"% (",
|
|
317
|
-
(
|
|
318
|
-
"
|
|
319
|
-
(
|
|
320
|
-
"
|
|
1708
|
+
(0, import_agent_core5.formatTokenCount)(contextUsedTokens),
|
|
1709
|
+
"/",
|
|
1710
|
+
(0, import_agent_core5.formatTokenCount)(contextMaxTokens),
|
|
1711
|
+
")"
|
|
321
1712
|
] })
|
|
322
1713
|
] }),
|
|
323
|
-
/* @__PURE__ */ (0,
|
|
324
|
-
isThinking && /* @__PURE__ */ (0,
|
|
325
|
-
/* @__PURE__ */ (0,
|
|
1714
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1715
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
|
|
1716
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
|
|
326
1717
|
"msgs: ",
|
|
327
1718
|
messageCount
|
|
328
1719
|
] })
|
|
@@ -333,146 +1724,224 @@ function StatusBar({
|
|
|
333
1724
|
}
|
|
334
1725
|
|
|
335
1726
|
// src/ui/InputArea.tsx
|
|
336
|
-
var
|
|
337
|
-
var
|
|
1727
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1728
|
+
var import_ink7 = require("ink");
|
|
338
1729
|
|
|
339
1730
|
// src/ui/CjkTextInput.tsx
|
|
340
|
-
var
|
|
341
|
-
var
|
|
1731
|
+
var import_react8 = require("react");
|
|
1732
|
+
var import_ink4 = require("ink");
|
|
1733
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
342
1734
|
var import_string_width = __toESM(require("string-width"), 1);
|
|
343
|
-
var
|
|
344
|
-
var
|
|
1735
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1736
|
+
var PASTE_START = "[200~";
|
|
1737
|
+
var PASTE_END = "[201~";
|
|
1738
|
+
function filterPrintable(input) {
|
|
1739
|
+
if (!input || input.length === 0) return "";
|
|
1740
|
+
return input.replace(/[\x00-\x1f\x7f]/g, "");
|
|
1741
|
+
}
|
|
1742
|
+
function insertAtCursor(value, cursor, input) {
|
|
1743
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
1744
|
+
return { value: next, cursor: cursor + input.length };
|
|
1745
|
+
}
|
|
1746
|
+
function displayOffset(chars, charIndex, width) {
|
|
1747
|
+
let offset = 0;
|
|
1748
|
+
for (let i = 0; i < charIndex && i < chars.length; i++) {
|
|
1749
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1750
|
+
const col = offset % width;
|
|
1751
|
+
if (col + w > width) offset += width - col;
|
|
1752
|
+
offset += w;
|
|
1753
|
+
}
|
|
1754
|
+
return offset;
|
|
1755
|
+
}
|
|
1756
|
+
function charIndexAtDisplayOffset(chars, target, width) {
|
|
1757
|
+
let offset = 0;
|
|
1758
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1759
|
+
if (offset >= target) return i;
|
|
1760
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1761
|
+
const col = offset % width;
|
|
1762
|
+
if (col + w > width) offset += width - col;
|
|
1763
|
+
offset += w;
|
|
1764
|
+
}
|
|
1765
|
+
return chars.length;
|
|
1766
|
+
}
|
|
345
1767
|
function CjkTextInput({
|
|
346
1768
|
value,
|
|
347
1769
|
onChange,
|
|
348
1770
|
onSubmit,
|
|
1771
|
+
onPaste,
|
|
349
1772
|
placeholder = "",
|
|
350
1773
|
focus = true,
|
|
351
|
-
showCursor = true
|
|
1774
|
+
showCursor = true,
|
|
1775
|
+
availableWidth
|
|
352
1776
|
}) {
|
|
353
|
-
const valueRef = (0,
|
|
354
|
-
const cursorRef = (0,
|
|
355
|
-
const [, forceRender] = (0,
|
|
356
|
-
const
|
|
1777
|
+
const valueRef = (0, import_react8.useRef)(value);
|
|
1778
|
+
const cursorRef = (0, import_react8.useRef)(value.length);
|
|
1779
|
+
const [, forceRender] = (0, import_react8.useState)(0);
|
|
1780
|
+
const isPastingRef = (0, import_react8.useRef)(false);
|
|
1781
|
+
const pasteBufferRef = (0, import_react8.useRef)("");
|
|
357
1782
|
if (value !== valueRef.current) {
|
|
358
1783
|
valueRef.current = value;
|
|
359
1784
|
if (cursorRef.current > value.length) {
|
|
360
1785
|
cursorRef.current = value.length;
|
|
361
1786
|
}
|
|
362
1787
|
}
|
|
363
|
-
(0,
|
|
1788
|
+
(0, import_ink4.useInput)(
|
|
364
1789
|
(input, key) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (cursorRef.current > 0) {
|
|
374
|
-
cursorRef.current -= 1;
|
|
375
|
-
forceRender((n) => n + 1);
|
|
1790
|
+
try {
|
|
1791
|
+
if (input === PASTE_START || input.startsWith(PASTE_START)) {
|
|
1792
|
+
isPastingRef.current = true;
|
|
1793
|
+
const afterMarker = input.slice(PASTE_START.length);
|
|
1794
|
+
if (afterMarker.length > 0) {
|
|
1795
|
+
pasteBufferRef.current += afterMarker;
|
|
1796
|
+
}
|
|
1797
|
+
return;
|
|
376
1798
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
1799
|
+
if (isPastingRef.current) {
|
|
1800
|
+
if (input === PASTE_END || input.includes(PASTE_END)) {
|
|
1801
|
+
const beforeMarker = input.split(PASTE_END)[0] ?? "";
|
|
1802
|
+
pasteBufferRef.current += beforeMarker;
|
|
1803
|
+
const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
|
|
1804
|
+
pasteBufferRef.current = "";
|
|
1805
|
+
isPastingRef.current = false;
|
|
1806
|
+
if (text.length > 0) {
|
|
1807
|
+
if (text.includes("\n") && onPaste) {
|
|
1808
|
+
onPaste(text);
|
|
1809
|
+
} else {
|
|
1810
|
+
const printable2 = filterPrintable(text);
|
|
1811
|
+
if (printable2.length > 0) {
|
|
1812
|
+
const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
|
|
1813
|
+
cursorRef.current = result2.cursor;
|
|
1814
|
+
valueRef.current = result2.value;
|
|
1815
|
+
onChange(result2.value);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} else {
|
|
1820
|
+
pasteBufferRef.current += input;
|
|
1821
|
+
}
|
|
1822
|
+
return;
|
|
383
1823
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (key.backspace || key.delete) {
|
|
387
|
-
if (cursorRef.current > 0) {
|
|
388
|
-
const v2 = valueRef.current;
|
|
389
|
-
const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
|
|
390
|
-
cursorRef.current -= 1;
|
|
391
|
-
valueRef.current = next2;
|
|
392
|
-
onChange(next2);
|
|
1824
|
+
if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
|
|
1825
|
+
return;
|
|
393
1826
|
}
|
|
394
|
-
|
|
1827
|
+
if (key.upArrow || key.downArrow) {
|
|
1828
|
+
if (availableWidth && availableWidth > 0) {
|
|
1829
|
+
const chars = [...valueRef.current];
|
|
1830
|
+
const offset = displayOffset(chars, cursorRef.current, availableWidth);
|
|
1831
|
+
const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
|
|
1832
|
+
if (target >= 0) {
|
|
1833
|
+
const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
|
|
1834
|
+
if (newCursor !== cursorRef.current) {
|
|
1835
|
+
cursorRef.current = newCursor;
|
|
1836
|
+
forceRender((n) => n + 1);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (key.return) {
|
|
1843
|
+
onSubmit?.(valueRef.current);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
|
|
1847
|
+
onPaste(input.replace(/\r\n?/g, "\n"));
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (key.leftArrow) {
|
|
1851
|
+
if (cursorRef.current > 0) {
|
|
1852
|
+
cursorRef.current -= 1;
|
|
1853
|
+
forceRender((n) => n + 1);
|
|
1854
|
+
}
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (key.rightArrow) {
|
|
1858
|
+
if (cursorRef.current < valueRef.current.length) {
|
|
1859
|
+
cursorRef.current += 1;
|
|
1860
|
+
forceRender((n) => n + 1);
|
|
1861
|
+
}
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (key.backspace || key.delete) {
|
|
1865
|
+
if (cursorRef.current > 0) {
|
|
1866
|
+
const v = valueRef.current;
|
|
1867
|
+
const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
|
|
1868
|
+
cursorRef.current -= 1;
|
|
1869
|
+
valueRef.current = next;
|
|
1870
|
+
onChange(next);
|
|
1871
|
+
}
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const printable = filterPrintable(input);
|
|
1875
|
+
if (printable.length === 0) return;
|
|
1876
|
+
const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
|
|
1877
|
+
cursorRef.current = result.cursor;
|
|
1878
|
+
valueRef.current = result.value;
|
|
1879
|
+
onChange(result.value);
|
|
1880
|
+
} catch {
|
|
395
1881
|
}
|
|
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
1882
|
},
|
|
403
1883
|
{ isActive: focus }
|
|
404
1884
|
);
|
|
405
|
-
|
|
406
|
-
const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
|
|
407
|
-
const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
|
|
408
|
-
setCursorPosition({ x: cursorX, y: 0 });
|
|
409
|
-
}
|
|
410
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
1885
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
411
1886
|
}
|
|
412
1887
|
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
413
1888
|
if (!showCursor) {
|
|
414
|
-
return value.length > 0 ? value : placeholder ?
|
|
1889
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
415
1890
|
}
|
|
416
1891
|
if (value.length === 0) {
|
|
417
1892
|
if (placeholder.length > 0) {
|
|
418
|
-
return
|
|
1893
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
419
1894
|
}
|
|
420
|
-
return
|
|
1895
|
+
return import_chalk.default.inverse(" ");
|
|
421
1896
|
}
|
|
422
1897
|
const chars = [...value];
|
|
423
1898
|
let rendered = "";
|
|
424
1899
|
for (let i = 0; i < chars.length; i++) {
|
|
425
1900
|
const char = chars[i] ?? "";
|
|
426
|
-
rendered += i === cursorOffset ?
|
|
1901
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
427
1902
|
}
|
|
428
1903
|
if (cursorOffset >= chars.length) {
|
|
429
|
-
rendered +=
|
|
1904
|
+
rendered += import_chalk.default.inverse(" ");
|
|
430
1905
|
}
|
|
431
1906
|
return rendered;
|
|
432
1907
|
}
|
|
433
1908
|
|
|
434
1909
|
// src/ui/WaveText.tsx
|
|
435
|
-
var
|
|
436
|
-
var
|
|
437
|
-
var
|
|
1910
|
+
var import_react9 = require("react");
|
|
1911
|
+
var import_ink5 = require("ink");
|
|
1912
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
438
1913
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
439
1914
|
var INTERVAL_MS = 400;
|
|
440
1915
|
var CHARS_PER_GROUP = 4;
|
|
441
1916
|
function WaveText({ text }) {
|
|
442
|
-
const [tick, setTick] = (0,
|
|
443
|
-
(0,
|
|
1917
|
+
const [tick, setTick] = (0, import_react9.useState)(0);
|
|
1918
|
+
(0, import_react9.useEffect)(() => {
|
|
444
1919
|
const timer = setInterval(() => {
|
|
445
1920
|
setTick((prev) => prev + 1);
|
|
446
1921
|
}, INTERVAL_MS);
|
|
447
1922
|
return () => clearInterval(timer);
|
|
448
1923
|
}, []);
|
|
449
1924
|
const chars = [...text];
|
|
450
|
-
return /* @__PURE__ */ (0,
|
|
1925
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
|
|
451
1926
|
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
452
1927
|
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
453
|
-
return /* @__PURE__ */ (0,
|
|
1928
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
454
1929
|
}) });
|
|
455
1930
|
}
|
|
456
1931
|
|
|
457
1932
|
// src/ui/SlashAutocomplete.tsx
|
|
458
|
-
var
|
|
459
|
-
var
|
|
1933
|
+
var import_ink6 = require("ink");
|
|
1934
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
460
1935
|
var MAX_VISIBLE = 8;
|
|
461
1936
|
function CommandRow(props) {
|
|
462
1937
|
const { cmd, isSelected, showSlash } = props;
|
|
463
|
-
const prefix = showSlash ? "/" : "";
|
|
464
1938
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
465
1939
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
466
1940
|
const dimmed = !isSelected;
|
|
467
|
-
return /* @__PURE__ */ (0,
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
cmd.name
|
|
472
|
-
] }),
|
|
473
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
474
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
475
|
-
] });
|
|
1941
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: nameColor, dimColor: dimmed, children: [
|
|
1942
|
+
indicator,
|
|
1943
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
1944
|
+
] }) });
|
|
476
1945
|
}
|
|
477
1946
|
function SlashAutocomplete({
|
|
478
1947
|
commands,
|
|
@@ -483,7 +1952,7 @@ function SlashAutocomplete({
|
|
|
483
1952
|
if (!visible || commands.length === 0) return null;
|
|
484
1953
|
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
485
1954
|
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
486
|
-
return /* @__PURE__ */ (0,
|
|
1955
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
487
1956
|
CommandRow,
|
|
488
1957
|
{
|
|
489
1958
|
cmd,
|
|
@@ -500,8 +1969,14 @@ function computeScrollOffset(selectedIndex, total) {
|
|
|
500
1969
|
return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
|
|
501
1970
|
}
|
|
502
1971
|
|
|
1972
|
+
// src/utils/paste-labels.ts
|
|
1973
|
+
var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
1974
|
+
function expandPasteLabels(text, store) {
|
|
1975
|
+
return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
|
|
1976
|
+
}
|
|
1977
|
+
|
|
503
1978
|
// src/ui/InputArea.tsx
|
|
504
|
-
var
|
|
1979
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
505
1980
|
function parseSlashInput(value) {
|
|
506
1981
|
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
507
1982
|
const afterSlash = value.slice(1);
|
|
@@ -512,16 +1987,16 @@ function parseSlashInput(value) {
|
|
|
512
1987
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
513
1988
|
}
|
|
514
1989
|
function useAutocomplete(value, registry) {
|
|
515
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
516
|
-
const [dismissed, setDismissed] = (0,
|
|
517
|
-
const prevValueRef =
|
|
1990
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
|
|
1991
|
+
const [dismissed, setDismissed] = (0, import_react10.useState)(false);
|
|
1992
|
+
const prevValueRef = import_react10.default.useRef(value);
|
|
518
1993
|
if (prevValueRef.current !== value) {
|
|
519
1994
|
prevValueRef.current = value;
|
|
520
1995
|
if (dismissed) setDismissed(false);
|
|
521
1996
|
}
|
|
522
1997
|
const parsed = parseSlashInput(value);
|
|
523
1998
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
524
|
-
const filteredCommands = (0,
|
|
1999
|
+
const filteredCommands = (0, import_react10.useMemo)(() => {
|
|
525
2000
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
526
2001
|
if (isSubcommandMode) {
|
|
527
2002
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -554,8 +2029,24 @@ function useAutocomplete(value, registry) {
|
|
|
554
2029
|
}
|
|
555
2030
|
};
|
|
556
2031
|
}
|
|
557
|
-
|
|
558
|
-
|
|
2032
|
+
var BORDER_HORIZONTAL = 2;
|
|
2033
|
+
var PADDING_LEFT = 1;
|
|
2034
|
+
var PROMPT_WIDTH = 2;
|
|
2035
|
+
var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
|
|
2036
|
+
function InputArea({
|
|
2037
|
+
onSubmit,
|
|
2038
|
+
onCancelQueue,
|
|
2039
|
+
isDisabled,
|
|
2040
|
+
isAborting,
|
|
2041
|
+
pendingPrompt,
|
|
2042
|
+
registry
|
|
2043
|
+
}) {
|
|
2044
|
+
const [value, setValue] = (0, import_react10.useState)("");
|
|
2045
|
+
const pasteStore = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
|
|
2046
|
+
const { stdout } = (0, import_ink7.useStdout)();
|
|
2047
|
+
const terminalColumns = stdout?.columns ?? 80;
|
|
2048
|
+
const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
|
|
2049
|
+
const pasteIdRef = (0, import_react10.useRef)(0);
|
|
559
2050
|
const {
|
|
560
2051
|
showPopup,
|
|
561
2052
|
filteredCommands,
|
|
@@ -564,7 +2055,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
564
2055
|
isSubcommandMode,
|
|
565
2056
|
setShowPopup
|
|
566
2057
|
} = useAutocomplete(value, registry);
|
|
567
|
-
const
|
|
2058
|
+
const handlePaste = (0, import_react10.useCallback)((text) => {
|
|
2059
|
+
pasteIdRef.current += 1;
|
|
2060
|
+
const id = pasteIdRef.current;
|
|
2061
|
+
pasteStore.current.set(id, text);
|
|
2062
|
+
const lineCount = text.split("\n").length;
|
|
2063
|
+
const label = `[Pasted text #${id} +${lineCount} lines]`;
|
|
2064
|
+
setValue((prev) => prev ? `${prev} ${label}` : label);
|
|
2065
|
+
}, []);
|
|
2066
|
+
const handleSubmit = (0, import_react10.useCallback)(
|
|
568
2067
|
(text) => {
|
|
569
2068
|
const trimmed = text.trim();
|
|
570
2069
|
if (trimmed.length === 0) return;
|
|
@@ -572,12 +2071,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
572
2071
|
selectCommand(filteredCommands[selectedIndex]);
|
|
573
2072
|
return;
|
|
574
2073
|
}
|
|
2074
|
+
const expanded = expandPasteLabels(trimmed, pasteStore.current);
|
|
575
2075
|
setValue("");
|
|
576
|
-
|
|
2076
|
+
pasteStore.current.clear();
|
|
2077
|
+
pasteIdRef.current = 0;
|
|
2078
|
+
onSubmit(expanded);
|
|
577
2079
|
},
|
|
578
2080
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
579
2081
|
);
|
|
580
|
-
const selectCommand = (0,
|
|
2082
|
+
const selectCommand = (0, import_react10.useCallback)(
|
|
581
2083
|
(cmd) => {
|
|
582
2084
|
const parsed = parseSlashInput(value);
|
|
583
2085
|
if (parsed.parentCommand) {
|
|
@@ -596,7 +2098,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
596
2098
|
},
|
|
597
2099
|
[value, onSubmit, setSelectedIndex]
|
|
598
2100
|
);
|
|
599
|
-
(0,
|
|
2101
|
+
(0, import_ink7.useInput)(
|
|
600
2102
|
(_input, key) => {
|
|
601
2103
|
if (!showPopup) return;
|
|
602
2104
|
if (key.upArrow) {
|
|
@@ -612,8 +2114,16 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
612
2114
|
},
|
|
613
2115
|
{ isActive: showPopup && !isDisabled }
|
|
614
2116
|
);
|
|
615
|
-
|
|
616
|
-
|
|
2117
|
+
(0, import_ink7.useInput)(
|
|
2118
|
+
(_input, key) => {
|
|
2119
|
+
if ((key.backspace || key.delete) && pendingPrompt) {
|
|
2120
|
+
onCancelQueue?.();
|
|
2121
|
+
}
|
|
2122
|
+
},
|
|
2123
|
+
{ isActive: !!pendingPrompt }
|
|
2124
|
+
);
|
|
2125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
|
|
2126
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
617
2127
|
SlashAutocomplete,
|
|
618
2128
|
{
|
|
619
2129
|
commands: filteredCommands,
|
|
@@ -622,41 +2132,100 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
622
2132
|
isSubcommandMode
|
|
623
2133
|
}
|
|
624
2134
|
),
|
|
625
|
-
/* @__PURE__ */ (0,
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
2135
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2136
|
+
import_ink7.Box,
|
|
2137
|
+
{
|
|
2138
|
+
borderStyle: "single",
|
|
2139
|
+
borderColor: isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green",
|
|
2140
|
+
paddingLeft: 1,
|
|
2141
|
+
children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: "cyan", children: [
|
|
2142
|
+
" ",
|
|
2143
|
+
"Queued: ",
|
|
2144
|
+
pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
|
|
2145
|
+
" ",
|
|
2146
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: "(Backspace to cancel)" })
|
|
2147
|
+
] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
|
|
2148
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
|
|
2149
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2150
|
+
CjkTextInput,
|
|
2151
|
+
{
|
|
2152
|
+
value,
|
|
2153
|
+
onChange: setValue,
|
|
2154
|
+
onSubmit: handleSubmit,
|
|
2155
|
+
onPaste: handlePaste,
|
|
2156
|
+
placeholder: "Type a message or /help",
|
|
2157
|
+
availableWidth
|
|
2158
|
+
}
|
|
2159
|
+
)
|
|
2160
|
+
] })
|
|
2161
|
+
}
|
|
2162
|
+
)
|
|
2163
|
+
] });
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// src/ui/ConfirmPrompt.tsx
|
|
2167
|
+
var import_react11 = require("react");
|
|
2168
|
+
var import_ink8 = require("ink");
|
|
2169
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2170
|
+
function ConfirmPrompt({
|
|
2171
|
+
message,
|
|
2172
|
+
options = ["Yes", "No"],
|
|
2173
|
+
onSelect
|
|
2174
|
+
}) {
|
|
2175
|
+
const [selected, setSelected] = (0, import_react11.useState)(0);
|
|
2176
|
+
const resolvedRef = (0, import_react11.useRef)(false);
|
|
2177
|
+
const doSelect = (0, import_react11.useCallback)(
|
|
2178
|
+
(index) => {
|
|
2179
|
+
if (resolvedRef.current) return;
|
|
2180
|
+
resolvedRef.current = true;
|
|
2181
|
+
onSelect(index);
|
|
2182
|
+
},
|
|
2183
|
+
[onSelect]
|
|
2184
|
+
);
|
|
2185
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
2186
|
+
if (resolvedRef.current) return;
|
|
2187
|
+
if (key.leftArrow || key.upArrow) {
|
|
2188
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
2189
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
2190
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
2191
|
+
} else if (key.return) {
|
|
2192
|
+
doSelect(selected);
|
|
2193
|
+
} else if (input === "y" && options.length === 2) {
|
|
2194
|
+
doSelect(0);
|
|
2195
|
+
} else if (input === "n" && options.length === 2) {
|
|
2196
|
+
doSelect(1);
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2200
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
|
|
2201
|
+
/* @__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: [
|
|
2202
|
+
i === selected ? "> " : " ",
|
|
2203
|
+
opt
|
|
2204
|
+
] }) }, opt)) }),
|
|
2205
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
637
2206
|
] });
|
|
638
2207
|
}
|
|
639
2208
|
|
|
640
2209
|
// src/ui/PermissionPrompt.tsx
|
|
641
|
-
var
|
|
642
|
-
var
|
|
643
|
-
var
|
|
2210
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
2211
|
+
var import_ink9 = require("ink");
|
|
2212
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
644
2213
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
645
|
-
function
|
|
2214
|
+
function formatArgs(args) {
|
|
646
2215
|
const entries = Object.entries(args);
|
|
647
2216
|
if (entries.length === 0) return "(no arguments)";
|
|
648
2217
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
649
2218
|
}
|
|
650
2219
|
function PermissionPrompt({ request }) {
|
|
651
|
-
const [selected, setSelected] =
|
|
652
|
-
const resolvedRef =
|
|
653
|
-
const prevRequestRef =
|
|
2220
|
+
const [selected, setSelected] = import_react12.default.useState(0);
|
|
2221
|
+
const resolvedRef = import_react12.default.useRef(false);
|
|
2222
|
+
const prevRequestRef = import_react12.default.useRef(request);
|
|
654
2223
|
if (prevRequestRef.current !== request) {
|
|
655
2224
|
prevRequestRef.current = request;
|
|
656
2225
|
resolvedRef.current = false;
|
|
657
2226
|
setSelected(0);
|
|
658
2227
|
}
|
|
659
|
-
const doResolve =
|
|
2228
|
+
const doResolve = import_react12.default.useCallback(
|
|
660
2229
|
(index) => {
|
|
661
2230
|
if (resolvedRef.current) return;
|
|
662
2231
|
resolvedRef.current = true;
|
|
@@ -666,7 +2235,7 @@ function PermissionPrompt({ request }) {
|
|
|
666
2235
|
},
|
|
667
2236
|
[request]
|
|
668
2237
|
);
|
|
669
|
-
(0,
|
|
2238
|
+
(0, import_ink9.useInput)((input, key) => {
|
|
670
2239
|
if (resolvedRef.current) return;
|
|
671
2240
|
if (key.upArrow || key.leftArrow) {
|
|
672
2241
|
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
@@ -682,364 +2251,709 @@ function PermissionPrompt({ request }) {
|
|
|
682
2251
|
doResolve(2);
|
|
683
2252
|
}
|
|
684
2253
|
});
|
|
685
|
-
return /* @__PURE__ */ (0,
|
|
686
|
-
/* @__PURE__ */ (0,
|
|
687
|
-
/* @__PURE__ */ (0,
|
|
2254
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2255
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
2256
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
|
|
688
2257
|
"Tool:",
|
|
689
2258
|
" ",
|
|
690
|
-
/* @__PURE__ */ (0,
|
|
2259
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
691
2260
|
] }),
|
|
692
|
-
/* @__PURE__ */ (0,
|
|
2261
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
|
|
693
2262
|
" ",
|
|
694
|
-
|
|
2263
|
+
formatArgs(request.toolArgs)
|
|
695
2264
|
] }),
|
|
696
|
-
/* @__PURE__ */ (0,
|
|
2265
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
697
2266
|
i === selected ? "> " : " ",
|
|
698
2267
|
opt
|
|
699
2268
|
] }) }, opt)) }),
|
|
700
|
-
/* @__PURE__ */ (0,
|
|
2269
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
701
2270
|
] });
|
|
702
2271
|
}
|
|
703
2272
|
|
|
704
|
-
// src/ui/
|
|
705
|
-
var
|
|
706
|
-
var
|
|
707
|
-
function
|
|
708
|
-
|
|
709
|
-
return
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
write: () => {
|
|
713
|
-
},
|
|
714
|
-
writeLine: () => {
|
|
715
|
-
},
|
|
716
|
-
writeMarkdown: () => {
|
|
717
|
-
},
|
|
718
|
-
writeError: () => {
|
|
719
|
-
},
|
|
720
|
-
prompt: () => Promise.resolve(""),
|
|
721
|
-
select: () => Promise.resolve(0),
|
|
722
|
-
spinner: () => ({ stop: () => {
|
|
723
|
-
}, update: () => {
|
|
724
|
-
} })
|
|
725
|
-
};
|
|
726
|
-
function useSession(props) {
|
|
727
|
-
const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
|
|
728
|
-
const [streamingText, setStreamingText] = (0, import_react5.useState)("");
|
|
729
|
-
const permissionQueueRef = (0, import_react5.useRef)([]);
|
|
730
|
-
const processingRef = (0, import_react5.useRef)(false);
|
|
731
|
-
const processNextPermission = (0, import_react5.useCallback)(() => {
|
|
732
|
-
if (processingRef.current) return;
|
|
733
|
-
const next = permissionQueueRef.current[0];
|
|
734
|
-
if (!next) {
|
|
735
|
-
setPermissionRequest(null);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
processingRef.current = true;
|
|
739
|
-
setPermissionRequest({
|
|
740
|
-
toolName: next.toolName,
|
|
741
|
-
toolArgs: next.toolArgs,
|
|
742
|
-
resolve: (result) => {
|
|
743
|
-
permissionQueueRef.current.shift();
|
|
744
|
-
processingRef.current = false;
|
|
745
|
-
setPermissionRequest(null);
|
|
746
|
-
next.resolve(result);
|
|
747
|
-
setTimeout(() => processNextPermission(), 0);
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
}, []);
|
|
751
|
-
const sessionRef = (0, import_react5.useRef)(null);
|
|
752
|
-
if (sessionRef.current === null) {
|
|
753
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
754
|
-
return new Promise((resolve) => {
|
|
755
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
756
|
-
processNextPermission();
|
|
757
|
-
});
|
|
758
|
-
};
|
|
759
|
-
const onTextDelta = (delta) => {
|
|
760
|
-
setStreamingText((prev) => prev + delta);
|
|
761
|
-
};
|
|
762
|
-
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
763
|
-
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
764
|
-
config: props.config,
|
|
765
|
-
context: props.context,
|
|
766
|
-
terminal: NOOP_TERMINAL,
|
|
767
|
-
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
768
|
-
projectInfo: props.projectInfo,
|
|
769
|
-
sessionStore: props.sessionStore,
|
|
770
|
-
permissionMode: props.permissionMode,
|
|
771
|
-
maxTurns: props.maxTurns,
|
|
772
|
-
permissionHandler,
|
|
773
|
-
onTextDelta
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
|
|
777
|
-
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
778
|
-
}
|
|
779
|
-
function useMessages() {
|
|
780
|
-
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
781
|
-
const addMessage = (0, import_react5.useCallback)((msg) => {
|
|
782
|
-
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
783
|
-
}, []);
|
|
784
|
-
return { messages, setMessages, addMessage };
|
|
2273
|
+
// src/ui/StreamingIndicator.tsx
|
|
2274
|
+
var import_ink10 = require("ink");
|
|
2275
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2276
|
+
function getToolStyle(t) {
|
|
2277
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
2278
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
2279
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
2280
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
785
2281
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
792
|
-
" /cost \u2014 Show session info",
|
|
793
|
-
" /exit \u2014 Exit CLI"
|
|
794
|
-
].join("\n");
|
|
795
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
796
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
797
|
-
if (!arg) {
|
|
798
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
799
|
-
} else if (validModes.includes(arg)) {
|
|
800
|
-
session.setPermissionMode(arg);
|
|
801
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
802
|
-
} else {
|
|
803
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
2282
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
2283
|
+
const hasTools = activeTools.length > 0;
|
|
2284
|
+
const hasText = text.length > 0;
|
|
2285
|
+
if (!hasTools && !hasText) {
|
|
2286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
|
|
804
2287
|
}
|
|
805
|
-
return
|
|
2288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
2289
|
+
hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2290
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
2291
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
2292
|
+
activeTools.map((t, i) => {
|
|
2293
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
2294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
2295
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
|
|
2296
|
+
" ",
|
|
2297
|
+
icon,
|
|
2298
|
+
" ",
|
|
2299
|
+
t.toolName,
|
|
2300
|
+
"(",
|
|
2301
|
+
t.firstArg,
|
|
2302
|
+
")"
|
|
2303
|
+
] }),
|
|
2304
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
2305
|
+
] }, `${t.toolName}-${i}`);
|
|
2306
|
+
})
|
|
2307
|
+
] }),
|
|
2308
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
2310
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
2311
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
2312
|
+
] })
|
|
2313
|
+
] });
|
|
806
2314
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
2315
|
+
|
|
2316
|
+
// src/ui/PluginTUI.tsx
|
|
2317
|
+
var import_react15 = require("react");
|
|
2318
|
+
|
|
2319
|
+
// src/ui/MenuSelect.tsx
|
|
2320
|
+
var import_react13 = require("react");
|
|
2321
|
+
var import_ink11 = require("ink");
|
|
2322
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2323
|
+
function MenuSelect({
|
|
2324
|
+
title,
|
|
2325
|
+
items,
|
|
2326
|
+
onSelect,
|
|
2327
|
+
onBack,
|
|
2328
|
+
loading,
|
|
2329
|
+
error
|
|
2330
|
+
}) {
|
|
2331
|
+
const [selected, setSelected] = (0, import_react13.useState)(0);
|
|
2332
|
+
const selectedRef = (0, import_react13.useRef)(0);
|
|
2333
|
+
const resolvedRef = (0, import_react13.useRef)(false);
|
|
2334
|
+
const doSelect = (0, import_react13.useCallback)(
|
|
2335
|
+
(index) => {
|
|
2336
|
+
if (resolvedRef.current || items.length === 0) return;
|
|
2337
|
+
resolvedRef.current = true;
|
|
2338
|
+
onSelect(items[index].value);
|
|
2339
|
+
},
|
|
2340
|
+
[items, onSelect]
|
|
2341
|
+
);
|
|
2342
|
+
(0, import_ink11.useInput)((input, key) => {
|
|
2343
|
+
if (resolvedRef.current) return;
|
|
2344
|
+
if (key.escape) {
|
|
2345
|
+
resolvedRef.current = true;
|
|
2346
|
+
onBack();
|
|
2347
|
+
return;
|
|
828
2348
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
2349
|
+
if (loading || error || items.length === 0) return;
|
|
2350
|
+
if (key.upArrow) {
|
|
2351
|
+
const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
|
|
2352
|
+
selectedRef.current = next;
|
|
2353
|
+
setSelected(next);
|
|
2354
|
+
} else if (key.downArrow) {
|
|
2355
|
+
const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
|
|
2356
|
+
selectedRef.current = next;
|
|
2357
|
+
setSelected(next);
|
|
2358
|
+
} else if (key.return) {
|
|
2359
|
+
doSelect(selectedRef.current);
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: title }),
|
|
2364
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Loading..." }) }),
|
|
2365
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { marginTop: 1, flexDirection: "column", children: [
|
|
2366
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "red", children: error }),
|
|
2367
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Press Esc to go back" })
|
|
2368
|
+
] }),
|
|
2369
|
+
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { children: [
|
|
2370
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
2371
|
+
i === selected ? "> " : " ",
|
|
2372
|
+
item.label
|
|
2373
|
+
] }),
|
|
2374
|
+
item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
|
|
2375
|
+
" ",
|
|
2376
|
+
item.hint
|
|
2377
|
+
] })
|
|
2378
|
+
] }, item.value)) }),
|
|
2379
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
|
|
2380
|
+
] });
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// src/ui/TextPrompt.tsx
|
|
2384
|
+
var import_react14 = require("react");
|
|
2385
|
+
var import_ink12 = require("ink");
|
|
2386
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2387
|
+
function TextPrompt({
|
|
2388
|
+
title,
|
|
2389
|
+
placeholder,
|
|
2390
|
+
onSubmit,
|
|
2391
|
+
onCancel,
|
|
2392
|
+
validate
|
|
2393
|
+
}) {
|
|
2394
|
+
const [value, setValue] = (0, import_react14.useState)("");
|
|
2395
|
+
const [error, setError] = (0, import_react14.useState)();
|
|
2396
|
+
const resolvedRef = (0, import_react14.useRef)(false);
|
|
2397
|
+
const valueRef = (0, import_react14.useRef)("");
|
|
2398
|
+
const handleSubmit = (0, import_react14.useCallback)(() => {
|
|
2399
|
+
if (resolvedRef.current) return;
|
|
2400
|
+
const trimmed = valueRef.current.trim();
|
|
2401
|
+
if (!trimmed) return;
|
|
2402
|
+
if (validate) {
|
|
2403
|
+
const err = validate(trimmed);
|
|
2404
|
+
if (err) {
|
|
2405
|
+
setError(err);
|
|
2406
|
+
return;
|
|
846
2407
|
}
|
|
847
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
848
|
-
return true;
|
|
849
2408
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
2409
|
+
resolvedRef.current = true;
|
|
2410
|
+
onSubmit(trimmed);
|
|
2411
|
+
}, [validate, onSubmit]);
|
|
2412
|
+
(0, import_ink12.useInput)((input, key) => {
|
|
2413
|
+
if (resolvedRef.current) return;
|
|
2414
|
+
if (key.escape) {
|
|
2415
|
+
resolvedRef.current = true;
|
|
2416
|
+
onCancel();
|
|
2417
|
+
return;
|
|
857
2418
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return
|
|
861
|
-
default: {
|
|
862
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
863
|
-
if (skillCmd) {
|
|
864
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
865
|
-
return false;
|
|
866
|
-
}
|
|
867
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
868
|
-
return true;
|
|
2419
|
+
if (key.return) {
|
|
2420
|
+
handleSubmit();
|
|
2421
|
+
return;
|
|
869
2422
|
}
|
|
2423
|
+
if (key.backspace || key.delete) {
|
|
2424
|
+
valueRef.current = valueRef.current.slice(0, -1);
|
|
2425
|
+
setValue(valueRef.current);
|
|
2426
|
+
setError(void 0);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (input && !key.ctrl && !key.meta) {
|
|
2430
|
+
valueRef.current = valueRef.current + input;
|
|
2431
|
+
setValue(valueRef.current);
|
|
2432
|
+
setError(void 0);
|
|
2433
|
+
}
|
|
2434
|
+
});
|
|
2435
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2436
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
|
|
2437
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { marginTop: 1, children: [
|
|
2438
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "> " }),
|
|
2439
|
+
value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: placeholder }) : null,
|
|
2440
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "\u2588" })
|
|
2441
|
+
] }),
|
|
2442
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "red", children: error }),
|
|
2443
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
|
|
2444
|
+
] });
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// src/ui/plugin-tui-handlers.ts
|
|
2448
|
+
function handleMainSelect(value, nav) {
|
|
2449
|
+
if (value === "marketplace") {
|
|
2450
|
+
nav.push({ screen: "marketplace-list" });
|
|
2451
|
+
} else if (value === "installed") {
|
|
2452
|
+
nav.push({ screen: "installed-list" });
|
|
870
2453
|
}
|
|
871
2454
|
}
|
|
872
|
-
function
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
},
|
|
879
|
-
[session, addMessage, setMessages, exit, registry]
|
|
880
|
-
);
|
|
2455
|
+
function handleMarketplaceListSelect(value, nav) {
|
|
2456
|
+
if (value === "__add__") {
|
|
2457
|
+
nav.push({ screen: "marketplace-add" });
|
|
2458
|
+
} else {
|
|
2459
|
+
nav.push({ screen: "marketplace-action", context: { marketplace: value } });
|
|
2460
|
+
}
|
|
881
2461
|
}
|
|
882
|
-
function
|
|
883
|
-
if (
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
2462
|
+
function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
|
|
2463
|
+
if (value === "browse") {
|
|
2464
|
+
nav.push({ screen: "marketplace-browse", context: { marketplace } });
|
|
2465
|
+
} else if (value === "update") {
|
|
2466
|
+
callbacks.marketplaceUpdate(marketplace).then(() => {
|
|
2467
|
+
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
2468
|
+
nav.pop();
|
|
2469
|
+
}).catch((err) => {
|
|
2470
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2471
|
+
});
|
|
2472
|
+
} else if (value === "remove") {
|
|
2473
|
+
nav.setConfirm({
|
|
2474
|
+
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
2475
|
+
onConfirm: () => {
|
|
2476
|
+
nav.setConfirm(void 0);
|
|
2477
|
+
callbacks.marketplaceRemove(marketplace).then(() => {
|
|
2478
|
+
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
2479
|
+
nav.popN(2);
|
|
2480
|
+
}).catch((err) => {
|
|
2481
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2482
|
+
});
|
|
2483
|
+
},
|
|
2484
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2485
|
+
});
|
|
892
2486
|
}
|
|
893
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
|
|
894
2487
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
903
|
-
} catch (err) {
|
|
904
|
-
clearStreamingText();
|
|
905
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
906
|
-
addMessage({ role: "system", content: "Cancelled." });
|
|
907
|
-
} else {
|
|
908
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
909
|
-
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
910
|
-
}
|
|
911
|
-
} finally {
|
|
912
|
-
setIsThinking(false);
|
|
2488
|
+
function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
|
|
2489
|
+
const fullId = `${value}@${marketplace}`;
|
|
2490
|
+
const item = items.find((i) => i.value === value);
|
|
2491
|
+
if (item?.hint === "installed") {
|
|
2492
|
+
nav.push({ screen: "installed-action", context: { pluginId: fullId } });
|
|
2493
|
+
} else {
|
|
2494
|
+
nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
|
|
913
2495
|
}
|
|
914
2496
|
}
|
|
915
|
-
function
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
2497
|
+
function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
|
|
2498
|
+
const scope = value;
|
|
2499
|
+
callbacks.install(pluginId, scope).then(() => {
|
|
2500
|
+
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
2501
|
+
nav.popN(2);
|
|
2502
|
+
}).catch((err) => {
|
|
2503
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2504
|
+
});
|
|
922
2505
|
}
|
|
923
|
-
function
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
2506
|
+
function handleInstalledListSelect(value, callbacks, nav) {
|
|
2507
|
+
nav.setConfirm({
|
|
2508
|
+
message: `Uninstall plugin "${value}"?`,
|
|
2509
|
+
onConfirm: () => {
|
|
2510
|
+
nav.setConfirm(void 0);
|
|
2511
|
+
callbacks.uninstall(value).then(() => {
|
|
2512
|
+
nav.notify(`Uninstalled plugin "${value}".`);
|
|
2513
|
+
nav.refresh();
|
|
2514
|
+
}).catch((err) => {
|
|
2515
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2516
|
+
});
|
|
2517
|
+
},
|
|
2518
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
|
|
2522
|
+
if (value === "uninstall") {
|
|
2523
|
+
nav.setConfirm({
|
|
2524
|
+
message: `Uninstall plugin "${pluginId}"?`,
|
|
2525
|
+
onConfirm: () => {
|
|
2526
|
+
nav.setConfirm(void 0);
|
|
2527
|
+
callbacks.uninstall(pluginId).then(() => {
|
|
2528
|
+
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
2529
|
+
nav.popN(2);
|
|
2530
|
+
}).catch((err) => {
|
|
2531
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2532
|
+
});
|
|
2533
|
+
},
|
|
2534
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// src/ui/PluginTUI.tsx
|
|
2540
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2541
|
+
function PluginTUI({ callbacks, onClose, addMessage }) {
|
|
2542
|
+
const [stack, setStack] = (0, import_react15.useState)([{ screen: "main" }]);
|
|
2543
|
+
const [items, setItems] = (0, import_react15.useState)([]);
|
|
2544
|
+
const [loading, setLoading] = (0, import_react15.useState)(false);
|
|
2545
|
+
const [error, setError] = (0, import_react15.useState)();
|
|
2546
|
+
const [confirm, setConfirm] = (0, import_react15.useState)();
|
|
2547
|
+
const [refreshCounter, setRefreshCounter] = (0, import_react15.useState)(0);
|
|
2548
|
+
const current = stack[stack.length - 1] ?? { screen: "main" };
|
|
2549
|
+
const push = (0, import_react15.useCallback)((state) => {
|
|
2550
|
+
setStack((prev) => [...prev, state]);
|
|
2551
|
+
setItems([]);
|
|
2552
|
+
setError(void 0);
|
|
2553
|
+
}, []);
|
|
2554
|
+
const pop = (0, import_react15.useCallback)(() => {
|
|
2555
|
+
setStack((prev) => {
|
|
2556
|
+
if (prev.length <= 1) {
|
|
2557
|
+
onClose();
|
|
2558
|
+
return prev;
|
|
2559
|
+
}
|
|
2560
|
+
return prev.slice(0, -1);
|
|
2561
|
+
});
|
|
2562
|
+
setItems([]);
|
|
2563
|
+
setError(void 0);
|
|
2564
|
+
}, [onClose]);
|
|
2565
|
+
const popN = (0, import_react15.useCallback)(
|
|
2566
|
+
(n) => {
|
|
2567
|
+
setStack((prev) => {
|
|
2568
|
+
const next = prev.slice(0, Math.max(1, prev.length - n));
|
|
2569
|
+
if (next.length === 0) {
|
|
2570
|
+
onClose();
|
|
2571
|
+
return prev;
|
|
931
2572
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
2573
|
+
return next;
|
|
2574
|
+
});
|
|
2575
|
+
setItems([]);
|
|
2576
|
+
setError(void 0);
|
|
2577
|
+
},
|
|
2578
|
+
[onClose]
|
|
2579
|
+
);
|
|
2580
|
+
const notify = (0, import_react15.useCallback)(
|
|
2581
|
+
(content) => {
|
|
2582
|
+
addMessage?.({ role: "system", content });
|
|
2583
|
+
},
|
|
2584
|
+
[addMessage]
|
|
2585
|
+
);
|
|
2586
|
+
const refresh = (0, import_react15.useCallback)(() => {
|
|
2587
|
+
setItems([]);
|
|
2588
|
+
setRefreshCounter((c) => c + 1);
|
|
2589
|
+
}, []);
|
|
2590
|
+
const nav = { push, pop, popN, notify, setConfirm, refresh };
|
|
2591
|
+
(0, import_react15.useEffect)(() => {
|
|
2592
|
+
const screen2 = current.screen;
|
|
2593
|
+
if (screen2 === "marketplace-list") {
|
|
2594
|
+
setLoading(true);
|
|
2595
|
+
callbacks.marketplaceList().then((sources) => {
|
|
2596
|
+
const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
|
|
2597
|
+
const sourceItems = sources.map((s) => ({
|
|
2598
|
+
label: s.name,
|
|
2599
|
+
value: s.name,
|
|
2600
|
+
hint: s.type
|
|
2601
|
+
}));
|
|
2602
|
+
setItems([...baseItems, ...sourceItems]);
|
|
2603
|
+
setLoading(false);
|
|
2604
|
+
}).catch((err) => {
|
|
2605
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2606
|
+
setLoading(false);
|
|
2607
|
+
});
|
|
2608
|
+
} else if (screen2 === "marketplace-browse") {
|
|
2609
|
+
const marketplace = current.context?.marketplace ?? "";
|
|
2610
|
+
setLoading(true);
|
|
2611
|
+
callbacks.listAvailablePlugins(marketplace).then((plugins) => {
|
|
2612
|
+
setItems(
|
|
2613
|
+
plugins.map((p) => ({
|
|
2614
|
+
label: p.name,
|
|
2615
|
+
value: p.name,
|
|
2616
|
+
hint: p.installed ? "installed" : p.description
|
|
2617
|
+
}))
|
|
2618
|
+
);
|
|
2619
|
+
setLoading(false);
|
|
2620
|
+
}).catch((err) => {
|
|
2621
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2622
|
+
setLoading(false);
|
|
2623
|
+
});
|
|
2624
|
+
} else if (screen2 === "installed-list") {
|
|
2625
|
+
setLoading(true);
|
|
2626
|
+
callbacks.listInstalled().then((plugins) => {
|
|
2627
|
+
setItems(
|
|
2628
|
+
plugins.map((p) => ({
|
|
2629
|
+
label: p.name,
|
|
2630
|
+
value: p.name,
|
|
2631
|
+
hint: p.description
|
|
2632
|
+
}))
|
|
941
2633
|
);
|
|
2634
|
+
setLoading(false);
|
|
2635
|
+
}).catch((err) => {
|
|
2636
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2637
|
+
setLoading(false);
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
}, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
|
|
2641
|
+
const handleSelect = (0, import_react15.useCallback)(
|
|
2642
|
+
(value) => {
|
|
2643
|
+
const screen2 = current.screen;
|
|
2644
|
+
const ctx = current.context;
|
|
2645
|
+
if (screen2 === "main") handleMainSelect(value, nav);
|
|
2646
|
+
else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
|
|
2647
|
+
else if (screen2 === "marketplace-action")
|
|
2648
|
+
handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
|
|
2649
|
+
else if (screen2 === "marketplace-browse")
|
|
2650
|
+
handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
|
|
2651
|
+
else if (screen2 === "marketplace-install-scope")
|
|
2652
|
+
handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
2653
|
+
else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
|
|
2654
|
+
else if (screen2 === "installed-action")
|
|
2655
|
+
handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
2656
|
+
},
|
|
2657
|
+
[current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
|
|
2658
|
+
);
|
|
2659
|
+
const handleTextSubmit = (0, import_react15.useCallback)(
|
|
2660
|
+
(value) => {
|
|
2661
|
+
if (current.screen === "marketplace-add") {
|
|
2662
|
+
callbacks.marketplaceAdd(value).then((name) => {
|
|
2663
|
+
notify(`Added marketplace "${name}" from ${value}.`);
|
|
2664
|
+
pop();
|
|
2665
|
+
}).catch((err) => {
|
|
2666
|
+
notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2667
|
+
pop();
|
|
2668
|
+
});
|
|
942
2669
|
}
|
|
943
|
-
addMessage({ role: "user", content: input });
|
|
944
|
-
return runSessionPrompt(
|
|
945
|
-
input,
|
|
946
|
-
session,
|
|
947
|
-
addMessage,
|
|
948
|
-
clearStreamingText,
|
|
949
|
-
setIsThinking,
|
|
950
|
-
setContextPercentage
|
|
951
|
-
);
|
|
952
2670
|
},
|
|
953
|
-
[
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
2671
|
+
[current.screen, callbacks, notify, pop]
|
|
2672
|
+
);
|
|
2673
|
+
if (confirm) {
|
|
2674
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2675
|
+
ConfirmPrompt,
|
|
2676
|
+
{
|
|
2677
|
+
message: confirm.message,
|
|
2678
|
+
onSelect: (index) => {
|
|
2679
|
+
if (index === 0) confirm.onConfirm();
|
|
2680
|
+
else confirm.onCancel();
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
const screen = current.screen;
|
|
2686
|
+
if (screen === "marketplace-add") {
|
|
2687
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2688
|
+
TextPrompt,
|
|
2689
|
+
{
|
|
2690
|
+
title: "Add Marketplace Source",
|
|
2691
|
+
placeholder: "owner/repo or git URL",
|
|
2692
|
+
onSubmit: handleTextSubmit,
|
|
2693
|
+
onCancel: pop,
|
|
2694
|
+
validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
|
|
2695
|
+
}
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
if (screen === "marketplace-action") {
|
|
2699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2700
|
+
MenuSelect,
|
|
2701
|
+
{
|
|
2702
|
+
title: `Marketplace: ${current.context?.marketplace ?? ""}`,
|
|
2703
|
+
items: [
|
|
2704
|
+
{ label: "Browse plugins", value: "browse" },
|
|
2705
|
+
{ label: "Update", value: "update" },
|
|
2706
|
+
{ label: "Remove", value: "remove" }
|
|
2707
|
+
],
|
|
2708
|
+
onSelect: handleSelect,
|
|
2709
|
+
onBack: pop
|
|
2710
|
+
},
|
|
2711
|
+
stack.length
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2714
|
+
if (screen === "marketplace-install-scope") {
|
|
2715
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2716
|
+
MenuSelect,
|
|
2717
|
+
{
|
|
2718
|
+
title: `Install scope for "${current.context?.pluginId ?? ""}"`,
|
|
2719
|
+
items: [
|
|
2720
|
+
{ label: "User scope", value: "user" },
|
|
2721
|
+
{ label: "Project scope", value: "project" }
|
|
2722
|
+
],
|
|
2723
|
+
onSelect: handleSelect,
|
|
2724
|
+
onBack: pop
|
|
2725
|
+
},
|
|
2726
|
+
stack.length
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
if (screen === "installed-action") {
|
|
2730
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2731
|
+
MenuSelect,
|
|
2732
|
+
{
|
|
2733
|
+
title: `Plugin: ${current.context?.pluginId ?? ""}`,
|
|
2734
|
+
items: [{ label: "Uninstall", value: "uninstall" }],
|
|
2735
|
+
onSelect: handleSelect,
|
|
2736
|
+
onBack: pop
|
|
2737
|
+
},
|
|
2738
|
+
stack.length
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
const titleMap = {
|
|
2742
|
+
main: "Plugin Management",
|
|
2743
|
+
"marketplace-list": "Marketplace",
|
|
2744
|
+
"marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
|
|
2745
|
+
"installed-list": "Installed Plugins"
|
|
2746
|
+
};
|
|
2747
|
+
const staticItemsMap = {
|
|
2748
|
+
main: [
|
|
2749
|
+
{ label: "Marketplace", value: "marketplace" },
|
|
2750
|
+
{ label: "Installed Plugins", value: "installed" }
|
|
961
2751
|
]
|
|
2752
|
+
};
|
|
2753
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2754
|
+
MenuSelect,
|
|
2755
|
+
{
|
|
2756
|
+
title: titleMap[screen] ?? "Plugin Management",
|
|
2757
|
+
items: staticItemsMap[screen] ?? items,
|
|
2758
|
+
onSelect: handleSelect,
|
|
2759
|
+
onBack: pop,
|
|
2760
|
+
loading,
|
|
2761
|
+
error
|
|
2762
|
+
},
|
|
2763
|
+
`${screen}-${stack.length}-${refreshCounter}`
|
|
962
2764
|
);
|
|
963
2765
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
2766
|
+
|
|
2767
|
+
// src/ui/App.tsx
|
|
2768
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2769
|
+
var EXIT_DELAY_MS2 = 500;
|
|
2770
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
2771
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
2772
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
2773
|
+
const merged = {};
|
|
2774
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
2775
|
+
merged[event] = [...groups];
|
|
2776
|
+
}
|
|
2777
|
+
if (configHooks) {
|
|
2778
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
2779
|
+
if (!Array.isArray(groups)) continue;
|
|
2780
|
+
if (!merged[event]) merged[event] = [];
|
|
2781
|
+
merged[event].push(...groups);
|
|
2782
|
+
}
|
|
971
2783
|
}
|
|
972
|
-
return
|
|
2784
|
+
return merged;
|
|
973
2785
|
}
|
|
974
2786
|
function App(props) {
|
|
975
|
-
const { exit } = (0,
|
|
976
|
-
const {
|
|
2787
|
+
const { exit } = (0, import_ink13.useApp)();
|
|
2788
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
2789
|
+
const configWithPluginHooks = {
|
|
2790
|
+
...props.config,
|
|
2791
|
+
hooks: mergeHooksIntoConfig(
|
|
2792
|
+
props.config.hooks,
|
|
2793
|
+
pluginHooks
|
|
2794
|
+
)
|
|
2795
|
+
};
|
|
2796
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
2797
|
+
{ ...props, config: configWithPluginHooks }
|
|
2798
|
+
);
|
|
977
2799
|
const { messages, setMessages, addMessage } = useMessages();
|
|
978
|
-
const [isThinking, setIsThinking] = (0,
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
2800
|
+
const [isThinking, setIsThinking] = (0, import_react16.useState)(false);
|
|
2801
|
+
const initialCtx = session.getContextState();
|
|
2802
|
+
const [contextState, setContextState] = (0, import_react16.useState)({
|
|
2803
|
+
percentage: initialCtx.usedPercentage,
|
|
2804
|
+
usedTokens: initialCtx.usedTokens,
|
|
2805
|
+
maxTokens: initialCtx.maxTokens
|
|
2806
|
+
});
|
|
2807
|
+
const pendingModelChangeRef = (0, import_react16.useRef)(null);
|
|
2808
|
+
const [pendingModelId, setPendingModelId] = (0, import_react16.useState)(null);
|
|
2809
|
+
const [showPluginTUI, setShowPluginTUI] = (0, import_react16.useState)(false);
|
|
2810
|
+
const [isAborting, setIsAborting] = (0, import_react16.useState)(false);
|
|
2811
|
+
const [pendingPrompt, setPendingPrompt] = (0, import_react16.useState)(null);
|
|
2812
|
+
const pendingPromptRef = (0, import_react16.useRef)(null);
|
|
2813
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
2814
|
+
const handleSlashCommand = useSlashCommands(
|
|
2815
|
+
session,
|
|
2816
|
+
addMessage,
|
|
2817
|
+
setMessages,
|
|
2818
|
+
exit,
|
|
2819
|
+
registry,
|
|
2820
|
+
pendingModelChangeRef,
|
|
2821
|
+
setPendingModelId,
|
|
2822
|
+
pluginCallbacks,
|
|
2823
|
+
setShowPluginTUI
|
|
2824
|
+
);
|
|
2825
|
+
const executePrompt = useSubmitHandler(
|
|
983
2826
|
session,
|
|
984
2827
|
addMessage,
|
|
985
2828
|
handleSlashCommand,
|
|
986
2829
|
clearStreamingText,
|
|
987
2830
|
setIsThinking,
|
|
988
|
-
|
|
2831
|
+
setContextState,
|
|
989
2832
|
registry
|
|
990
2833
|
);
|
|
991
|
-
(0,
|
|
2834
|
+
const handleSubmit = (0, import_react16.useCallback)(
|
|
2835
|
+
async (input) => {
|
|
2836
|
+
if (isThinking) {
|
|
2837
|
+
setPendingPrompt(input);
|
|
2838
|
+
pendingPromptRef.current = input;
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
await executePrompt(input);
|
|
2842
|
+
},
|
|
2843
|
+
[isThinking, executePrompt]
|
|
2844
|
+
);
|
|
2845
|
+
(0, import_ink13.useInput)(
|
|
992
2846
|
(_input, key) => {
|
|
993
|
-
if (key.
|
|
994
|
-
|
|
2847
|
+
if (key.escape && isThinking) {
|
|
2848
|
+
setIsAborting(true);
|
|
2849
|
+
setPendingPrompt(null);
|
|
2850
|
+
pendingPromptRef.current = null;
|
|
2851
|
+
session.abort();
|
|
2852
|
+
}
|
|
995
2853
|
},
|
|
996
|
-
{ isActive: !permissionRequest }
|
|
2854
|
+
{ isActive: !permissionRequest && !showPluginTUI }
|
|
997
2855
|
);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
2856
|
+
(0, import_react16.useEffect)(() => {
|
|
2857
|
+
if (!isThinking) {
|
|
2858
|
+
setIsAborting(false);
|
|
2859
|
+
if (pendingPromptRef.current) {
|
|
2860
|
+
const prompt = pendingPromptRef.current;
|
|
2861
|
+
setPendingPrompt(null);
|
|
2862
|
+
pendingPromptRef.current = null;
|
|
2863
|
+
setTimeout(() => executePrompt(prompt), 0);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}, [isThinking, pendingPrompt, executePrompt]);
|
|
2867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", children: [
|
|
2868
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
2869
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { color: "cyan", bold: true, children: `
|
|
1001
2870
|
____ ___ ____ ___ _____ _
|
|
1002
2871
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1003
2872
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1004
2873
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1005
2874
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1006
2875
|
` }),
|
|
1007
|
-
/* @__PURE__ */ (0,
|
|
2876
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
|
|
1008
2877
|
" v",
|
|
1009
2878
|
props.version ?? "0.0.0"
|
|
1010
2879
|
] })
|
|
1011
2880
|
] }),
|
|
1012
|
-
/* @__PURE__ */ (0,
|
|
1013
|
-
/* @__PURE__ */ (0,
|
|
1014
|
-
isThinking && /* @__PURE__ */ (0,
|
|
2881
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
2882
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageList, { messages }),
|
|
2883
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
1015
2884
|
] }),
|
|
1016
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
1017
|
-
/* @__PURE__ */ (0,
|
|
2885
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
2886
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2887
|
+
ConfirmPrompt,
|
|
2888
|
+
{
|
|
2889
|
+
message: `Change model to ${(0, import_agent_core6.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
2890
|
+
onSelect: (index) => {
|
|
2891
|
+
setPendingModelId(null);
|
|
2892
|
+
pendingModelChangeRef.current = null;
|
|
2893
|
+
if (index === 0) {
|
|
2894
|
+
try {
|
|
2895
|
+
const settingsPath = getUserSettingsPath();
|
|
2896
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
2897
|
+
addMessage(
|
|
2898
|
+
(0, import_agent_core7.createSystemMessage)(
|
|
2899
|
+
`Model changed to ${(0, import_agent_core6.getModelName)(pendingModelId)}. Restarting...`
|
|
2900
|
+
)
|
|
2901
|
+
);
|
|
2902
|
+
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
2903
|
+
} catch (err) {
|
|
2904
|
+
addMessage(
|
|
2905
|
+
(0, import_agent_core7.createSystemMessage)(
|
|
2906
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2907
|
+
)
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
} else {
|
|
2911
|
+
addMessage((0, import_agent_core7.createSystemMessage)("Model change cancelled."));
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
),
|
|
2916
|
+
showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2917
|
+
PluginTUI,
|
|
2918
|
+
{
|
|
2919
|
+
callbacks: pluginCallbacks,
|
|
2920
|
+
onClose: () => setShowPluginTUI(false),
|
|
2921
|
+
addMessage: (msg) => addMessage((0, import_agent_core7.createSystemMessage)(msg.content))
|
|
2922
|
+
}
|
|
2923
|
+
),
|
|
2924
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1018
2925
|
StatusBar,
|
|
1019
2926
|
{
|
|
1020
2927
|
permissionMode: session.getPermissionMode(),
|
|
1021
|
-
modelName: props.config.provider.model,
|
|
2928
|
+
modelName: (0, import_agent_core6.getModelName)(props.config.provider.model),
|
|
1022
2929
|
sessionId: session.getSessionId(),
|
|
1023
2930
|
messageCount: messages.length,
|
|
1024
2931
|
isThinking,
|
|
1025
|
-
contextPercentage,
|
|
1026
|
-
contextUsedTokens:
|
|
1027
|
-
contextMaxTokens:
|
|
2932
|
+
contextPercentage: contextState.percentage,
|
|
2933
|
+
contextUsedTokens: contextState.usedTokens,
|
|
2934
|
+
contextMaxTokens: contextState.maxTokens
|
|
1028
2935
|
}
|
|
1029
2936
|
),
|
|
1030
|
-
/* @__PURE__ */ (0,
|
|
2937
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1031
2938
|
InputArea,
|
|
1032
2939
|
{
|
|
1033
2940
|
onSubmit: handleSubmit,
|
|
1034
|
-
|
|
2941
|
+
onCancelQueue: () => {
|
|
2942
|
+
setPendingPrompt(null);
|
|
2943
|
+
pendingPromptRef.current = null;
|
|
2944
|
+
},
|
|
2945
|
+
isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
|
|
2946
|
+
isAborting,
|
|
2947
|
+
pendingPrompt,
|
|
1035
2948
|
registry
|
|
1036
2949
|
}
|
|
1037
|
-
)
|
|
2950
|
+
),
|
|
2951
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { children: " " })
|
|
1038
2952
|
] });
|
|
1039
2953
|
}
|
|
1040
2954
|
|
|
1041
2955
|
// src/ui/render.tsx
|
|
1042
|
-
var
|
|
2956
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1043
2957
|
function renderApp(options) {
|
|
1044
2958
|
process.on("unhandledRejection", (reason) => {
|
|
1045
2959
|
process.stderr.write(`
|
|
@@ -1050,29 +2964,50 @@ function renderApp(options) {
|
|
|
1050
2964
|
`);
|
|
1051
2965
|
}
|
|
1052
2966
|
});
|
|
1053
|
-
|
|
2967
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
2968
|
+
process.stdout.write("\x1B[?2004h");
|
|
2969
|
+
}
|
|
2970
|
+
const instance = (0, import_ink14.render)(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(App, { ...options }), {
|
|
1054
2971
|
exitOnCtrlC: true
|
|
1055
2972
|
});
|
|
1056
|
-
instance.waitUntilExit().
|
|
2973
|
+
instance.waitUntilExit().then(() => {
|
|
2974
|
+
if (process.stdout.isTTY) {
|
|
2975
|
+
process.stdout.write("\x1B[?2004l");
|
|
2976
|
+
}
|
|
2977
|
+
process.exit(0);
|
|
2978
|
+
}).catch((err) => {
|
|
1057
2979
|
if (err) {
|
|
1058
2980
|
process.stderr.write(`
|
|
1059
2981
|
[EXIT ERROR] ${err}
|
|
1060
2982
|
`);
|
|
1061
2983
|
}
|
|
2984
|
+
process.exit(1);
|
|
1062
2985
|
});
|
|
1063
2986
|
}
|
|
1064
2987
|
|
|
1065
2988
|
// src/cli.ts
|
|
1066
2989
|
var import_meta = {};
|
|
1067
|
-
|
|
2990
|
+
function checkSettingsFile(filePath) {
|
|
2991
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return "missing";
|
|
2992
|
+
try {
|
|
2993
|
+
const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8").trim();
|
|
2994
|
+
if (raw.length === 0) return "incomplete";
|
|
2995
|
+
const parsed = JSON.parse(raw);
|
|
2996
|
+
const provider = parsed.provider;
|
|
2997
|
+
if (!provider?.apiKey) return "incomplete";
|
|
2998
|
+
return "valid";
|
|
2999
|
+
} catch {
|
|
3000
|
+
return "corrupt";
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
1068
3003
|
function readVersion() {
|
|
1069
3004
|
try {
|
|
1070
3005
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1071
|
-
const dir = (0,
|
|
1072
|
-
const candidates = [(0,
|
|
3006
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
3007
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1073
3008
|
for (const pkgPath of candidates) {
|
|
1074
3009
|
try {
|
|
1075
|
-
const raw = (0,
|
|
3010
|
+
const raw = (0, import_node_fs4.readFileSync)(pkgPath, "utf-8");
|
|
1076
3011
|
const pkg = JSON.parse(raw);
|
|
1077
3012
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1078
3013
|
return pkg.version;
|
|
@@ -1085,97 +3020,107 @@ function readVersion() {
|
|
|
1085
3020
|
return "0.0.0";
|
|
1086
3021
|
}
|
|
1087
3022
|
}
|
|
1088
|
-
function
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
3023
|
+
function promptInput(label, masked = false) {
|
|
3024
|
+
return new Promise((resolve) => {
|
|
3025
|
+
process.stdout.write(label);
|
|
3026
|
+
let input = "";
|
|
3027
|
+
const stdin = process.stdin;
|
|
3028
|
+
const wasRaw = stdin.isRaw;
|
|
3029
|
+
stdin.setRawMode(true);
|
|
3030
|
+
stdin.resume();
|
|
3031
|
+
stdin.setEncoding("utf8");
|
|
3032
|
+
const onData = (data) => {
|
|
3033
|
+
for (const ch of data) {
|
|
3034
|
+
if (ch === "\r" || ch === "\n") {
|
|
3035
|
+
stdin.removeListener("data", onData);
|
|
3036
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
3037
|
+
stdin.pause();
|
|
3038
|
+
process.stdout.write("\n");
|
|
3039
|
+
resolve(input.trim());
|
|
3040
|
+
return;
|
|
3041
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
3042
|
+
if (input.length > 0) {
|
|
3043
|
+
input = input.slice(0, -1);
|
|
3044
|
+
process.stdout.write("\b \b");
|
|
3045
|
+
}
|
|
3046
|
+
} else if (ch === "") {
|
|
3047
|
+
process.stdout.write("\n");
|
|
3048
|
+
process.exit(0);
|
|
3049
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
3050
|
+
input += ch;
|
|
3051
|
+
process.stdout.write(masked ? "*" : ch);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
stdin.on("data", onData);
|
|
1119
3056
|
});
|
|
1120
|
-
return {
|
|
1121
|
-
positional: positionals,
|
|
1122
|
-
printMode: values["p"] ?? false,
|
|
1123
|
-
continueMode: values["c"] ?? false,
|
|
1124
|
-
resumeId: values["r"],
|
|
1125
|
-
model: values["model"],
|
|
1126
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1127
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1128
|
-
version: values["version"] ?? false
|
|
1129
|
-
};
|
|
1130
3057
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
3058
|
+
async function ensureConfig(cwd) {
|
|
3059
|
+
const userPath = getUserSettingsPath();
|
|
3060
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
3061
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
3062
|
+
const paths = [userPath, projectPath, localPath];
|
|
3063
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
3064
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
3065
|
+
return;
|
|
1134
3066
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
3067
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
3068
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
3069
|
+
process.stdout.write("\n");
|
|
3070
|
+
if (corrupt.length > 0) {
|
|
3071
|
+
for (const c of corrupt) {
|
|
3072
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
3073
|
+
`);
|
|
3074
|
+
}
|
|
3075
|
+
process.stdout.write("\n");
|
|
1137
3076
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
3077
|
+
if (incomplete.length > 0) {
|
|
3078
|
+
for (const c of incomplete) {
|
|
3079
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
3080
|
+
`);
|
|
3081
|
+
}
|
|
3082
|
+
process.stdout.write("\n");
|
|
1140
3083
|
}
|
|
1141
|
-
|
|
1142
|
-
process.
|
|
3084
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
3085
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
3086
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
3087
|
+
} else {
|
|
3088
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
1143
3089
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
terminal: false,
|
|
1150
|
-
historySize: 0
|
|
1151
|
-
});
|
|
1152
|
-
rl.question(question, (answer) => {
|
|
1153
|
-
rl.close();
|
|
1154
|
-
resolve(answer);
|
|
1155
|
-
});
|
|
1156
|
-
});
|
|
3090
|
+
process.stdout.write("\n");
|
|
3091
|
+
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
3092
|
+
if (!apiKey) {
|
|
3093
|
+
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
3094
|
+
process.exit(1);
|
|
1157
3095
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
3096
|
+
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
3097
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
3098
|
+
(0, import_node_fs4.mkdirSync)(settingsDir, { recursive: true });
|
|
3099
|
+
const settings = {
|
|
3100
|
+
provider: {
|
|
3101
|
+
name: "anthropic",
|
|
3102
|
+
model: "claude-sonnet-4-6",
|
|
3103
|
+
apiKey
|
|
1163
3104
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1168
|
-
if (trimmed === "") return initialIndex;
|
|
1169
|
-
const num = parseInt(trimmed, 10);
|
|
1170
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1171
|
-
return initialIndex;
|
|
3105
|
+
};
|
|
3106
|
+
if (language) {
|
|
3107
|
+
settings.language = language;
|
|
1172
3108
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
3109
|
+
(0, import_node_fs4.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
3110
|
+
process.stdout.write(`
|
|
3111
|
+
Config saved to ${userPath}
|
|
3112
|
+
|
|
3113
|
+
`);
|
|
3114
|
+
}
|
|
3115
|
+
function resetConfig() {
|
|
3116
|
+
const userPath = getUserSettingsPath();
|
|
3117
|
+
if (deleteSettings(userPath)) {
|
|
3118
|
+
process.stdout.write(`Deleted ${userPath}
|
|
3119
|
+
`);
|
|
3120
|
+
} else {
|
|
3121
|
+
process.stdout.write("No user settings found.\n");
|
|
1177
3122
|
}
|
|
1178
|
-
}
|
|
3123
|
+
}
|
|
1179
3124
|
async function startCli() {
|
|
1180
3125
|
const args = parseCliArgs();
|
|
1181
3126
|
if (args.version) {
|
|
@@ -1183,16 +3128,24 @@ async function startCli() {
|
|
|
1183
3128
|
`);
|
|
1184
3129
|
return;
|
|
1185
3130
|
}
|
|
3131
|
+
if (args.reset) {
|
|
3132
|
+
resetConfig();
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
1186
3135
|
const cwd = process.cwd();
|
|
3136
|
+
await ensureConfig(cwd);
|
|
1187
3137
|
const [config, context, projectInfo] = await Promise.all([
|
|
1188
|
-
(0,
|
|
1189
|
-
(0,
|
|
1190
|
-
(0,
|
|
3138
|
+
(0, import_agent_sdk5.loadConfig)(cwd),
|
|
3139
|
+
(0, import_agent_sdk5.loadContext)(cwd),
|
|
3140
|
+
(0, import_agent_sdk5.detectProject)(cwd)
|
|
1191
3141
|
]);
|
|
1192
3142
|
if (args.model !== void 0) {
|
|
1193
3143
|
config.provider.model = args.model;
|
|
1194
3144
|
}
|
|
1195
|
-
|
|
3145
|
+
if (args.language !== void 0) {
|
|
3146
|
+
config.language = args.language;
|
|
3147
|
+
}
|
|
3148
|
+
const sessionStore = new import_agent_sdk5.SessionStore();
|
|
1196
3149
|
if (args.printMode) {
|
|
1197
3150
|
const prompt = args.positional.join(" ").trim();
|
|
1198
3151
|
if (prompt.length === 0) {
|
|
@@ -1200,15 +3153,15 @@ async function startCli() {
|
|
|
1200
3153
|
process.exit(1);
|
|
1201
3154
|
}
|
|
1202
3155
|
const terminal = new PrintTerminal();
|
|
1203
|
-
const paths = (0,
|
|
1204
|
-
const session = (0,
|
|
3156
|
+
const paths = (0, import_agent_sdk5.projectPaths)(cwd);
|
|
3157
|
+
const session = (0, import_agent_sdk5.createSession)({
|
|
1205
3158
|
config,
|
|
1206
3159
|
context,
|
|
1207
3160
|
terminal,
|
|
1208
|
-
sessionLogger: new
|
|
3161
|
+
sessionLogger: new import_agent_sdk5.FileSessionLogger(paths.logs),
|
|
1209
3162
|
projectInfo,
|
|
1210
3163
|
permissionMode: args.permissionMode,
|
|
1211
|
-
promptForApproval
|
|
3164
|
+
promptForApproval: import_agent_sdk6.promptForApproval
|
|
1212
3165
|
});
|
|
1213
3166
|
const response = await session.run(prompt);
|
|
1214
3167
|
process.stdout.write(response + "\n");
|