@botconnector/bccli 0.4.0-beta.3 → 0.4.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -1
- package/dist/args-EFFR44JW.js +12 -0
- package/dist/{chunk-U7DLMVPZ.js → chunk-AOVIQXXO.js} +2 -1
- package/dist/{chunk-NGLKANMD.js → chunk-CDUPYHNL.js} +4 -3
- package/dist/chunk-CX5U4EHX.js +952 -0
- package/dist/{chunk-UESQNU4A.js → chunk-GEG6QT5H.js} +11 -10
- package/dist/chunk-IJGTX7RC.js +444 -0
- package/dist/chunk-NM3C75BT.js +103 -0
- package/dist/{chunk-OSWQPSA4.js → chunk-NWVFCTTO.js} +44 -4
- package/dist/chunk-UJH2JT6H.js +63 -0
- package/dist/{chunk-L7L4RGKG.js → chunk-Z37BR5H4.js} +2 -2
- package/dist/cli.js +743 -244
- package/dist/{config-NPSYFAFD.js → config-DMF4AO7E.js} +2 -2
- package/dist/integrationCli-G4C6XQUE.js +586 -0
- package/dist/{login-KHU7ONSR.js → login-AP2ODLJX.js} +1 -1
- package/dist/{mcpCli-Q2H63ISP.js → mcpCli-UGNEDBMU.js} +11 -10
- package/dist/providerCli-J4Y7D6PF.js +12 -0
- package/dist/{ui-A32PTF4F.js → ui-EFY7SNIW.js} +796 -238
- package/package.json +20 -3
- package/dist/args-FCLPQ7TO.js +0 -11
- package/dist/chunk-2JBRLGKD.js +0 -266
- package/dist/chunk-RZUJYAQV.js +0 -112
- package/dist/chunk-UN7QXPCB.js +0 -82
- package/dist/providerCli-G3X7KENO.js +0 -11
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
BUILTIN_AGENTS,
|
|
4
4
|
ignorePatterns,
|
|
5
5
|
instructionPaths,
|
|
6
|
+
normalizeGlobPath,
|
|
6
7
|
parseFrontmatter,
|
|
7
|
-
toolLabel
|
|
8
|
-
|
|
8
|
+
toolLabel,
|
|
9
|
+
validateGlobPattern
|
|
10
|
+
} from "./chunk-NWVFCTTO.js";
|
|
9
11
|
import {
|
|
10
12
|
nextMode
|
|
11
13
|
} from "./chunk-ELML4KVQ.js";
|
|
@@ -13,24 +15,35 @@ import {
|
|
|
13
15
|
hasKey,
|
|
14
16
|
providerName,
|
|
15
17
|
writeGlobalConfig
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-AOVIQXXO.js";
|
|
19
|
+
import {
|
|
20
|
+
REASONING_LEVELS,
|
|
21
|
+
assertReasoningSupported,
|
|
22
|
+
parseReasoningLevel,
|
|
23
|
+
supportedReasoningLevels
|
|
24
|
+
} from "./chunk-UJH2JT6H.js";
|
|
17
25
|
import {
|
|
18
26
|
Session
|
|
19
27
|
} from "./chunk-SJH3YLW4.js";
|
|
20
28
|
import {
|
|
21
29
|
CATALOG,
|
|
22
30
|
fillTemplate
|
|
23
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GEG6QT5H.js";
|
|
24
32
|
import {
|
|
25
33
|
addGlobalServer,
|
|
26
34
|
globalMcpPath,
|
|
27
35
|
readMcpFile,
|
|
28
36
|
removeGlobalServer
|
|
29
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-CDUPYHNL.js";
|
|
30
38
|
import {
|
|
31
39
|
PRESETS,
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
getLanguage,
|
|
41
|
+
locale,
|
|
42
|
+
parseLang,
|
|
43
|
+
removeCredential,
|
|
44
|
+
setLanguage,
|
|
45
|
+
t
|
|
46
|
+
} from "./chunk-IJGTX7RC.js";
|
|
34
47
|
|
|
35
48
|
// src/ui/index.tsx
|
|
36
49
|
import { render } from "ink";
|
|
@@ -39,36 +52,38 @@ import { render } from "ink";
|
|
|
39
52
|
import { execFile as execFile2 } from "child_process";
|
|
40
53
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
41
54
|
import { homedir } from "os";
|
|
42
|
-
import { dirname, join, relative, resolve } from "path";
|
|
55
|
+
import { dirname, join as join2, relative, resolve } from "path";
|
|
43
56
|
import { Box as Box13, Static, Text as Text15, useApp, useInput as useInput9 } from "ink";
|
|
44
57
|
import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } from "react";
|
|
45
58
|
|
|
46
59
|
// src/commands.ts
|
|
47
60
|
var SLASH_COMMANDS = [
|
|
48
|
-
{ name: "help", description: "
|
|
49
|
-
{ name: "model", description: "
|
|
50
|
-
{ name: "
|
|
51
|
-
{ name: "
|
|
52
|
-
{ name: "
|
|
53
|
-
{ name: "
|
|
54
|
-
{ name: "
|
|
55
|
-
{ name: "
|
|
56
|
-
{ name: "
|
|
57
|
-
{ name: "
|
|
58
|
-
{ name: "
|
|
59
|
-
{ name: "
|
|
60
|
-
{ name: "
|
|
61
|
-
{ name: "
|
|
62
|
-
{ name: "
|
|
63
|
-
{ name: "
|
|
64
|
-
{ name: "
|
|
65
|
-
{ name: "
|
|
66
|
-
{ name: "
|
|
67
|
-
{ name: "
|
|
68
|
-
{ name: "
|
|
69
|
-
{ name: "
|
|
70
|
-
{ name: "
|
|
71
|
-
{ name: "
|
|
61
|
+
{ name: "help", description: "list of commands and shortcuts" },
|
|
62
|
+
{ name: "model", description: "change model" },
|
|
63
|
+
{ name: "reasoning", description: "set reasoning: auto/off/low/medium/high/max" },
|
|
64
|
+
{ name: "provider", description: "add/choose an AI provider" },
|
|
65
|
+
{ name: "mcp", description: "install/manage MCP servers" },
|
|
66
|
+
{ name: "new", description: "new session (the old one stays saved)" },
|
|
67
|
+
{ name: "resume", description: "continue another session in this folder" },
|
|
68
|
+
{ name: "session", description: "info about this session" },
|
|
69
|
+
{ name: "status", description: "version, model, permission mode, MCP, context" },
|
|
70
|
+
{ name: "permissions", description: "view/revoke permissions" },
|
|
71
|
+
{ name: "undo", description: "undo the file edits of the last turn" },
|
|
72
|
+
{ name: "diff", description: "git diff of the project" },
|
|
73
|
+
{ name: "copy", description: "copy the last answer" },
|
|
74
|
+
{ name: "export", description: "save the conversation as markdown" },
|
|
75
|
+
{ name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
|
|
76
|
+
{ name: "init", description: "create/update AGENTS.md for this project" },
|
|
77
|
+
{ name: "agents", description: "list subagents" },
|
|
78
|
+
{ name: "skills", description: "list skills and custom commands" },
|
|
79
|
+
{ name: "doctor", description: "check installation health" },
|
|
80
|
+
{ name: "login", description: "save the API key of the current provider" },
|
|
81
|
+
{ name: "logout", description: "delete the API key of the current provider" },
|
|
82
|
+
{ name: "language", description: "set the interface language: en/id" },
|
|
83
|
+
{ name: "clear", description: "start a new conversation" },
|
|
84
|
+
{ name: "compact", description: "summarize the conversation" },
|
|
85
|
+
{ name: "cost", description: "token usage of this session" },
|
|
86
|
+
{ name: "exit", description: "quit" }
|
|
72
87
|
];
|
|
73
88
|
function parseSlash(text) {
|
|
74
89
|
if (!text.startsWith("/")) return void 0;
|
|
@@ -102,8 +117,8 @@ function git(cwd, args, bin = "git") {
|
|
|
102
117
|
}
|
|
103
118
|
async function gitDiff(cwd, maxLines = 400, bin = "git") {
|
|
104
119
|
const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
|
|
105
|
-
if (inside.missing) return "git
|
|
106
|
-
if (!inside.ok || inside.out.trim() !== "true") return "
|
|
120
|
+
if (inside.missing) return t("git is not installed or not on PATH, so /diff cannot be used.");
|
|
121
|
+
if (!inside.ok || inside.out.trim() !== "true") return t("This folder is not a git repository, so there is no diff.");
|
|
107
122
|
const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
108
123
|
const [stat, diff, untracked] = await Promise.all([
|
|
109
124
|
git(cwd, ["diff", "--stat", base]),
|
|
@@ -111,23 +126,23 @@ async function gitDiff(cwd, maxLines = 400, bin = "git") {
|
|
|
111
126
|
git(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
112
127
|
]);
|
|
113
128
|
const fresh = untracked.out.split("\n").filter(Boolean);
|
|
114
|
-
if (!diff.out.trim() && !fresh.length) return "
|
|
129
|
+
if (!diff.out.trim() && !fresh.length) return t("No uncommitted changes.");
|
|
115
130
|
const lines = diff.out.trimEnd().split("\n");
|
|
116
131
|
const more = lines.length > maxLines ? `
|
|
117
|
-
\u2026
|
|
118
|
-
return [stat.out.trim(), lines.slice(0, maxLines).join("\n") + more, fresh.length ?
|
|
132
|
+
${t("\u2026 {n} more lines", { n: lines.length - maxLines })}` : "";
|
|
133
|
+
return [stat.out.trim(), lines.slice(0, maxLines).join("\n") + more, fresh.length ? t("New files (untracked): {files}", { files: fresh.join(", ") }) : ""].filter(Boolean).join("\n\n");
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
// src/slash/doctor.ts
|
|
122
137
|
async function doctorText(input, deps) {
|
|
123
138
|
const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
|
|
124
|
-
const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` :
|
|
125
|
-
for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` :
|
|
139
|
+
const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : t("\u2717 Node {version} (needs \u2265 22)", { version: deps.nodeVersion })];
|
|
140
|
+
for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : t("\u25CB {name} (no key yet)", { name: p.name }));
|
|
126
141
|
const cmds = ["rg", "git", "npx", "uvx"];
|
|
127
142
|
const [connection, found] = await Promise.all([
|
|
128
143
|
deps.listModels().then(
|
|
129
|
-
(models) =>
|
|
130
|
-
(error) =>
|
|
144
|
+
(models) => t("\u2713 Connection {provider} ({n} models)", { provider: input.activeProvider, n: models.length }),
|
|
145
|
+
(error) => t("\u2717 Connection {provider}: {error}", { provider: input.activeProvider, error: error.message })
|
|
131
146
|
),
|
|
132
147
|
Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
|
|
133
148
|
]);
|
|
@@ -158,31 +173,31 @@ function statusText(s) {
|
|
|
158
173
|
return [
|
|
159
174
|
`BCCLI ${s.version}`,
|
|
160
175
|
`Model: ${s.modelRef} (${s.providerLabel})`,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
t("Permission mode: {mode}", { mode: s.mode }),
|
|
177
|
+
t("Folder: {cwd}", { cwd: s.cwd }),
|
|
178
|
+
t("MCP: {ready} active, {errors} errors", { ready, errors }),
|
|
179
|
+
t("Session tokens: {input} in \xB7 {output} out", { input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
|
|
180
|
+
t("Context: ~{pct}% of 128k", { pct: Math.round(s.lastInputTokens / 128e3 * 100) })
|
|
166
181
|
].join("\n");
|
|
167
182
|
}
|
|
168
183
|
function agentsText(defs) {
|
|
169
|
-
return defs.map((d) =>
|
|
184
|
+
return defs.map((d) => t("{name} \u2014 {description} \xB7 tools: {tools} \xB7 model: {model}", { name: d.name, description: d.description, tools: d.tools ? d.tools.join(", ") : t("all"), model: d.model ?? t("same as main") })).join("\n");
|
|
170
185
|
}
|
|
171
186
|
function skillsText(skills, commands) {
|
|
172
|
-
const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : "(
|
|
173
|
-
const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : "(
|
|
174
|
-
return
|
|
187
|
+
const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : t("(no skills)");
|
|
188
|
+
const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : t("(no custom commands)");
|
|
189
|
+
return `${t("Skills")}:
|
|
175
190
|
${s}
|
|
176
191
|
|
|
177
|
-
|
|
192
|
+
${t("Custom commands")}:
|
|
178
193
|
${c}`;
|
|
179
194
|
}
|
|
180
195
|
function sessionText(s) {
|
|
181
196
|
return [
|
|
182
|
-
|
|
183
|
-
|
|
197
|
+
t("Started: {when}", { when: s.started.toLocaleString(locale()) }),
|
|
198
|
+
t("{messages} messages \xB7 {input} tokens in \xB7 {output} out", { messages: s.messages, input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
|
|
184
199
|
`Model: ${s.modelRef}`,
|
|
185
|
-
|
|
200
|
+
t("File: {file}", { file: s.file })
|
|
186
201
|
].join("\n");
|
|
187
202
|
}
|
|
188
203
|
|
|
@@ -202,7 +217,308 @@ function parseMemoryArgs(args) {
|
|
|
202
217
|
}
|
|
203
218
|
|
|
204
219
|
// src/ui/Markdown.tsx
|
|
205
|
-
import { Box, Text } from "ink";
|
|
220
|
+
import { Box, Text, useStdout } from "ink";
|
|
221
|
+
|
|
222
|
+
// src/ui/table.ts
|
|
223
|
+
var WIDE = [
|
|
224
|
+
[4352, 4447],
|
|
225
|
+
[8986, 8987],
|
|
226
|
+
[9193, 9196],
|
|
227
|
+
[9200, 9200],
|
|
228
|
+
[9203, 9203],
|
|
229
|
+
[9725, 9726],
|
|
230
|
+
[9748, 9749],
|
|
231
|
+
[9800, 9811],
|
|
232
|
+
[9855, 9855],
|
|
233
|
+
[9875, 9875],
|
|
234
|
+
[9889, 9889],
|
|
235
|
+
[9898, 9899],
|
|
236
|
+
[9917, 9918],
|
|
237
|
+
[9924, 9925],
|
|
238
|
+
[9934, 9934],
|
|
239
|
+
[9940, 9940],
|
|
240
|
+
[9962, 9962],
|
|
241
|
+
[9970, 9971],
|
|
242
|
+
[9973, 9973],
|
|
243
|
+
[9978, 9978],
|
|
244
|
+
[9981, 9981],
|
|
245
|
+
[9989, 9989],
|
|
246
|
+
[9994, 9995],
|
|
247
|
+
[10024, 10024],
|
|
248
|
+
[10060, 10060],
|
|
249
|
+
[10062, 10062],
|
|
250
|
+
[10067, 10069],
|
|
251
|
+
[10071, 10071],
|
|
252
|
+
[10133, 10135],
|
|
253
|
+
[10160, 10160],
|
|
254
|
+
[10175, 10175],
|
|
255
|
+
[11035, 11036],
|
|
256
|
+
[11088, 11088],
|
|
257
|
+
[11093, 11093],
|
|
258
|
+
[11904, 42191],
|
|
259
|
+
[44032, 55203],
|
|
260
|
+
[63744, 64255],
|
|
261
|
+
[65072, 65135],
|
|
262
|
+
[65280, 65376],
|
|
263
|
+
[65504, 65510],
|
|
264
|
+
[127744, 128591],
|
|
265
|
+
[128640, 128767],
|
|
266
|
+
[129280, 129791],
|
|
267
|
+
[131072, 262141]
|
|
268
|
+
];
|
|
269
|
+
var ZERO = [
|
|
270
|
+
[768, 879],
|
|
271
|
+
[6832, 6911],
|
|
272
|
+
[7616, 7679],
|
|
273
|
+
[8203, 8207],
|
|
274
|
+
[8400, 8447],
|
|
275
|
+
[65024, 65039],
|
|
276
|
+
[65279, 65279],
|
|
277
|
+
[917760, 917999]
|
|
278
|
+
];
|
|
279
|
+
var inRanges = (cp, ranges) => ranges.some(([a, b]) => cp >= a && cp <= b);
|
|
280
|
+
function charWidth(cp) {
|
|
281
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
282
|
+
if (cp < 768) return 1;
|
|
283
|
+
if (inRanges(cp, ZERO)) return 0;
|
|
284
|
+
return inRanges(cp, WIDE) ? 2 : 1;
|
|
285
|
+
}
|
|
286
|
+
function displayWidth(text) {
|
|
287
|
+
let width = 0;
|
|
288
|
+
for (const ch of text) width += charWidth(ch.codePointAt(0));
|
|
289
|
+
return width;
|
|
290
|
+
}
|
|
291
|
+
var segsWidth = (segs) => segs.reduce((sum, s) => sum + displayWidth(s.text), 0);
|
|
292
|
+
var INLINE = /(`[^`]+`|\*\*[^*]+\*\*|~~[^~]+~~|!?\[[^\]\n]+\]\([^)\s]+\)|(?<![\w*])\*(?![\s*])[^*\n]+?(?<![\s*])\*(?![\w*])|(?<!\w)_(?![\s_])[^_\n]+?(?<![\s_])_(?!\w))/g;
|
|
293
|
+
function parseInline(text) {
|
|
294
|
+
const segs = [];
|
|
295
|
+
for (const part of text.split(INLINE)) {
|
|
296
|
+
if (part === "") continue;
|
|
297
|
+
if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) segs.push({ text: part.slice(1, -1), style: "code" });
|
|
298
|
+
else if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) segs.push({ text: part.slice(2, -2), style: "bold" });
|
|
299
|
+
else if (part.length > 4 && part.startsWith("~~") && part.endsWith("~~")) segs.push({ text: part.slice(2, -2), style: "strike" });
|
|
300
|
+
else if (/^!?\[[^\]\n]+\]\([^)\s]+\)$/.test(part)) {
|
|
301
|
+
const m = /^(!?)\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
|
|
302
|
+
const [, image, label, url] = m;
|
|
303
|
+
segs.push({ text: label, style: image ? "plain" : "link" });
|
|
304
|
+
if (label !== url && !url.startsWith("#")) segs.push({ text: ` (${url})`, style: "dim" });
|
|
305
|
+
} else if (part.length > 2 && (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_"))) {
|
|
306
|
+
segs.push({ text: part.slice(1, -1), style: "italic" });
|
|
307
|
+
} else segs.push({ text: part, style: "plain" });
|
|
308
|
+
}
|
|
309
|
+
return segs;
|
|
310
|
+
}
|
|
311
|
+
function splitRow(line) {
|
|
312
|
+
let s = line.trim();
|
|
313
|
+
if (s.startsWith("|")) s = s.slice(1);
|
|
314
|
+
if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
|
|
315
|
+
const cells = [];
|
|
316
|
+
let current = "";
|
|
317
|
+
for (let i = 0; i < s.length; i++) {
|
|
318
|
+
if (s[i] === "\\" && s[i + 1] === "|") {
|
|
319
|
+
current += "|";
|
|
320
|
+
i++;
|
|
321
|
+
} else if (s[i] === "|") {
|
|
322
|
+
cells.push(current);
|
|
323
|
+
current = "";
|
|
324
|
+
} else current += s[i];
|
|
325
|
+
}
|
|
326
|
+
cells.push(current);
|
|
327
|
+
return cells.map((c) => c.trim());
|
|
328
|
+
}
|
|
329
|
+
var SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
|
|
330
|
+
var isSeparator = (line) => line.includes("|") && SEPARATOR.test(line);
|
|
331
|
+
var cell = (raw) => parseInline(raw.replace(/<br\s*\/?>/gi, "\n"));
|
|
332
|
+
function parseTable(lines, start) {
|
|
333
|
+
if (start + 1 >= lines.length || !lines[start].includes("|") || !isSeparator(lines[start + 1])) return void 0;
|
|
334
|
+
const header = splitRow(lines[start]);
|
|
335
|
+
const sep = splitRow(lines[start + 1]);
|
|
336
|
+
if (sep.length !== header.length) return void 0;
|
|
337
|
+
const body = [];
|
|
338
|
+
let end = start + 2;
|
|
339
|
+
while (end < lines.length && lines[end].trim() && lines[end].includes("|")) body.push(splitRow(lines[end++]));
|
|
340
|
+
const columns = Math.max(header.length, ...body.map((r) => r.length));
|
|
341
|
+
const pad2 = (row, fill) => [...row, ...Array.from({ length: columns - row.length }, () => fill)];
|
|
342
|
+
const align = pad2(
|
|
343
|
+
sep.map((s) => s.startsWith(":") && s.endsWith(":") ? "center" : s.endsWith(":") ? "right" : "left"),
|
|
344
|
+
"left"
|
|
345
|
+
);
|
|
346
|
+
return { table: { header: pad2(header, "").map(cell), align, rows: body.map((r) => pad2(r, "").map(cell)) }, end };
|
|
347
|
+
}
|
|
348
|
+
function tokenize(segs) {
|
|
349
|
+
const tokens = [];
|
|
350
|
+
let word = [];
|
|
351
|
+
const endWord = () => {
|
|
352
|
+
if (word.length) tokens.push(word);
|
|
353
|
+
word = [];
|
|
354
|
+
};
|
|
355
|
+
for (const seg of segs) {
|
|
356
|
+
for (const piece of seg.text.match(/\n|[^\S\n]+|[^\s]+/g) ?? []) {
|
|
357
|
+
if (piece === "\n") {
|
|
358
|
+
endWord();
|
|
359
|
+
tokens.push("br");
|
|
360
|
+
} else if (/^\s+$/.test(piece)) endWord();
|
|
361
|
+
else word.push({ text: piece, style: seg.style });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
endWord();
|
|
365
|
+
return tokens;
|
|
366
|
+
}
|
|
367
|
+
function splitWord(word, width) {
|
|
368
|
+
const pieces = [];
|
|
369
|
+
let current = [];
|
|
370
|
+
let used = 0;
|
|
371
|
+
for (const seg of word) {
|
|
372
|
+
let text = "";
|
|
373
|
+
for (const ch of seg.text) {
|
|
374
|
+
const w = charWidth(ch.codePointAt(0));
|
|
375
|
+
if (used + w > width && used > 0) {
|
|
376
|
+
if (text) current.push({ text, style: seg.style });
|
|
377
|
+
pieces.push(current);
|
|
378
|
+
current = [];
|
|
379
|
+
text = "";
|
|
380
|
+
used = 0;
|
|
381
|
+
}
|
|
382
|
+
text += ch;
|
|
383
|
+
used += w;
|
|
384
|
+
}
|
|
385
|
+
if (text) current.push({ text, style: seg.style });
|
|
386
|
+
}
|
|
387
|
+
if (current.length) pieces.push(current);
|
|
388
|
+
return pieces;
|
|
389
|
+
}
|
|
390
|
+
function wrap(tokens, width) {
|
|
391
|
+
const lines = [];
|
|
392
|
+
let line = [];
|
|
393
|
+
let used = 0;
|
|
394
|
+
const flush = () => {
|
|
395
|
+
lines.push(line);
|
|
396
|
+
line = [];
|
|
397
|
+
used = 0;
|
|
398
|
+
};
|
|
399
|
+
for (const token of tokens) {
|
|
400
|
+
if (token === "br") {
|
|
401
|
+
flush();
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
const w = segsWidth(token);
|
|
405
|
+
if (w > width) {
|
|
406
|
+
if (used > 0) flush();
|
|
407
|
+
const pieces = splitWord(token, width);
|
|
408
|
+
for (const piece of pieces.slice(0, -1)) {
|
|
409
|
+
line = piece;
|
|
410
|
+
flush();
|
|
411
|
+
}
|
|
412
|
+
line = pieces.at(-1) ?? [];
|
|
413
|
+
used = segsWidth(line);
|
|
414
|
+
} else if (used === 0) {
|
|
415
|
+
line = [...token];
|
|
416
|
+
used = w;
|
|
417
|
+
} else if (used + 1 + w <= width) {
|
|
418
|
+
line.push({ text: " ", style: "plain" }, ...token);
|
|
419
|
+
used += 1 + w;
|
|
420
|
+
} else {
|
|
421
|
+
flush();
|
|
422
|
+
line = [...token];
|
|
423
|
+
used = w;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
flush();
|
|
427
|
+
return lines;
|
|
428
|
+
}
|
|
429
|
+
var SEP = " \u2502 ";
|
|
430
|
+
var RULE_JOIN = "\u2500\u253C\u2500";
|
|
431
|
+
var MAX_MIN_WIDTH = 12;
|
|
432
|
+
var dim = (text) => ({ text, style: "dim" });
|
|
433
|
+
function rtrim(line) {
|
|
434
|
+
const out = [...line];
|
|
435
|
+
while (out.length) {
|
|
436
|
+
const last = out[out.length - 1];
|
|
437
|
+
const text = last.text.trimEnd();
|
|
438
|
+
if (text) {
|
|
439
|
+
out[out.length - 1] = { ...last, text };
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
out.pop();
|
|
443
|
+
}
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
var bold = (segs) => segs.map((s) => s.style === "plain" ? { ...s, style: "bold" } : s);
|
|
447
|
+
function pad(segs, width, align, last) {
|
|
448
|
+
const space = Math.max(0, width - segsWidth(segs));
|
|
449
|
+
const left = align === "right" ? space : align === "center" ? Math.floor(space / 2) : 0;
|
|
450
|
+
const right = last ? 0 : space - left;
|
|
451
|
+
return [...left ? [{ text: " ".repeat(left), style: "plain" }] : [], ...segs, ...right ? [{ text: " ".repeat(right), style: "plain" }] : []];
|
|
452
|
+
}
|
|
453
|
+
function columnWidths(table, available) {
|
|
454
|
+
const columns = table.align.length;
|
|
455
|
+
const all = [table.header, ...table.rows];
|
|
456
|
+
const natural = [];
|
|
457
|
+
const minimum = [];
|
|
458
|
+
for (let c = 0; c < columns; c++) {
|
|
459
|
+
let widest = 1;
|
|
460
|
+
let longestWord = 1;
|
|
461
|
+
for (const row of all) {
|
|
462
|
+
const tokens = tokenize(row[c]);
|
|
463
|
+
for (const line of wrap(tokens, Number.POSITIVE_INFINITY)) widest = Math.max(widest, segsWidth(line));
|
|
464
|
+
for (const token of tokens) if (token !== "br") longestWord = Math.max(longestWord, segsWidth(token));
|
|
465
|
+
}
|
|
466
|
+
natural.push(widest);
|
|
467
|
+
minimum.push(Math.min(longestWord, MAX_MIN_WIDTH, widest));
|
|
468
|
+
}
|
|
469
|
+
if (minimum.reduce((a, b) => a + b, 0) > available) return void 0;
|
|
470
|
+
const widths = [...natural];
|
|
471
|
+
let total = widths.reduce((a, b) => a + b, 0);
|
|
472
|
+
while (total > available) {
|
|
473
|
+
let pick = -1;
|
|
474
|
+
for (let c = 0; c < columns; c++) if (widths[c] > minimum[c] && (pick < 0 || widths[c] > widths[pick])) pick = c;
|
|
475
|
+
if (pick < 0) return void 0;
|
|
476
|
+
widths[pick]--;
|
|
477
|
+
total--;
|
|
478
|
+
}
|
|
479
|
+
return widths;
|
|
480
|
+
}
|
|
481
|
+
function stacked(table, width) {
|
|
482
|
+
const lines = [];
|
|
483
|
+
table.rows.forEach((row, r) => {
|
|
484
|
+
if (r > 0) lines.push([dim("\u2500".repeat(Math.min(width, 40)))]);
|
|
485
|
+
row.forEach((value, c) => {
|
|
486
|
+
const label = [...bold(table.header[c]), { text: ": ", style: "plain" }, ...value];
|
|
487
|
+
lines.push(...wrap(tokenize(label), width));
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
return lines.length ? lines : [bold(table.header.flat())];
|
|
491
|
+
}
|
|
492
|
+
function layoutTable(table, width) {
|
|
493
|
+
const columns = table.align.length;
|
|
494
|
+
const available = Math.max(columns, width - (columns - 1) * SEP.length);
|
|
495
|
+
const widths = columnWidths(table, available);
|
|
496
|
+
if (!widths) return stacked(table, Math.max(10, width));
|
|
497
|
+
const renderRow = (cells, header) => {
|
|
498
|
+
const wrapped = cells.map((segs, c) => wrap(tokenize(header ? bold(segs) : segs), widths[c]));
|
|
499
|
+
const height = Math.max(...wrapped.map((w) => w.length));
|
|
500
|
+
const lines = [];
|
|
501
|
+
for (let l = 0; l < height; l++) {
|
|
502
|
+
const line = [];
|
|
503
|
+
wrapped.forEach((w, c) => {
|
|
504
|
+
if (c > 0) line.push(dim(SEP));
|
|
505
|
+
line.push(...pad(w[l] ?? [], widths[c], table.align[c], c === columns - 1));
|
|
506
|
+
});
|
|
507
|
+
lines.push(rtrim(line));
|
|
508
|
+
}
|
|
509
|
+
return { lines, height };
|
|
510
|
+
};
|
|
511
|
+
const rule = [dim(widths.map((w) => "\u2500".repeat(w)).join(RULE_JOIN))];
|
|
512
|
+
const head = renderRow(table.header, true);
|
|
513
|
+
const body = table.rows.map((r) => renderRow(r, false));
|
|
514
|
+
const wrappedRows = body.some((b) => b.height > 1);
|
|
515
|
+
const out = [...head.lines, rule];
|
|
516
|
+
body.forEach((b, i) => {
|
|
517
|
+
if (wrappedRows && i > 0) out.push(rule);
|
|
518
|
+
out.push(...b.lines);
|
|
519
|
+
});
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
206
522
|
|
|
207
523
|
// src/ui/theme.ts
|
|
208
524
|
function color(name) {
|
|
@@ -211,58 +527,136 @@ function color(name) {
|
|
|
211
527
|
var ACCENT = "green";
|
|
212
528
|
|
|
213
529
|
// src/ui/Markdown.tsx
|
|
214
|
-
import { jsx } from "react/jsx-runtime";
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return /* @__PURE__ */ jsx(Text, { bold: true, children:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
530
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
531
|
+
function segment(seg, key) {
|
|
532
|
+
switch (seg.style) {
|
|
533
|
+
case "bold":
|
|
534
|
+
return /* @__PURE__ */ jsx(Text, { bold: true, children: seg.text }, key);
|
|
535
|
+
case "italic":
|
|
536
|
+
return /* @__PURE__ */ jsx(Text, { italic: true, children: seg.text }, key);
|
|
537
|
+
case "strike":
|
|
538
|
+
return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: seg.text }, key);
|
|
539
|
+
case "link":
|
|
540
|
+
return /* @__PURE__ */ jsx(Text, { underline: true, color: color("cyan"), children: seg.text }, key);
|
|
541
|
+
case "code":
|
|
542
|
+
return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: seg.text }, key);
|
|
543
|
+
case "dim":
|
|
544
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: seg.text }, key);
|
|
545
|
+
default:
|
|
546
|
+
return seg.text;
|
|
547
|
+
}
|
|
225
548
|
}
|
|
226
|
-
|
|
227
|
-
|
|
549
|
+
var inline = (line) => parseInline(line).map(segment);
|
|
550
|
+
var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
551
|
+
var RULE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
|
|
552
|
+
var QUOTE = /^\s{0,3}((?:>\s?)+)(.*)$/;
|
|
553
|
+
var BULLET = /^(\s*)[-*+]\s+(.*)$/;
|
|
554
|
+
var NUMBERED = /^(\s*)(\d{1,9})[.)]\s+(.*)$/;
|
|
555
|
+
var BULLETS = ["\u2022", "\u25E6", "\u25AA"];
|
|
556
|
+
var bulletFor = (spaces) => BULLETS[spaces === 0 ? 0 : spaces <= 4 ? 1 : 2];
|
|
557
|
+
function hanging(key, marker, body, width, padding = 0) {
|
|
558
|
+
const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
|
|
559
|
+
return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
|
|
560
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
|
|
561
|
+
/* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
|
|
562
|
+
] }, key);
|
|
563
|
+
}
|
|
564
|
+
function Markdown({ text, indent = 2 }) {
|
|
565
|
+
const { stdout } = useStdout();
|
|
566
|
+
const width = Math.max(20, (stdout?.columns ?? 80) - indent - 1);
|
|
567
|
+
const lines = text.split("\n");
|
|
228
568
|
const rows = [];
|
|
229
|
-
|
|
569
|
+
let inCode = false;
|
|
570
|
+
for (let i = 0; i < lines.length; i++) {
|
|
571
|
+
const line = lines[i];
|
|
230
572
|
if (line.trimStart().startsWith("```")) {
|
|
231
573
|
inCode = !inCode;
|
|
232
|
-
|
|
574
|
+
continue;
|
|
233
575
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
576
|
+
if (inCode) {
|
|
577
|
+
rows.push(
|
|
578
|
+
/* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
|
|
579
|
+
);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const parsed = parseTable(lines, i);
|
|
583
|
+
if (parsed) {
|
|
584
|
+
layoutTable(parsed.table, width).forEach((tableLine, n) => {
|
|
585
|
+
rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
|
|
586
|
+
});
|
|
587
|
+
i = parsed.end - 1;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const heading = HEADING.exec(line);
|
|
591
|
+
if (heading) {
|
|
592
|
+
const level = heading[1].length;
|
|
593
|
+
rows.push(
|
|
594
|
+
/* @__PURE__ */ jsx(Text, { bold: true, underline: level === 1, color: level <= 2 ? color(ACCENT) : void 0, children: parseInline(heading[2]).map((s) => s.text).join("") }, i)
|
|
595
|
+
);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (RULE.test(line)) {
|
|
599
|
+
rows.push(
|
|
600
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
|
|
601
|
+
);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const quote = QUOTE.exec(line);
|
|
605
|
+
if (quote) {
|
|
606
|
+
const depth = quote[1].replace(/\s/g, "").length;
|
|
607
|
+
rows.push(hanging(i, "\u258E".repeat(depth), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const bullet = BULLET.exec(line);
|
|
611
|
+
if (bullet) {
|
|
612
|
+
const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
|
|
613
|
+
const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
|
|
614
|
+
rows.push(
|
|
615
|
+
task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, bulletFor(padding), inline(bullet[2]), width, padding)
|
|
616
|
+
);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const numbered = NUMBERED.exec(line);
|
|
620
|
+
if (numbered) {
|
|
621
|
+
const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
|
|
622
|
+
rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
|
|
626
|
+
}
|
|
238
627
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
|
|
239
628
|
}
|
|
240
629
|
|
|
241
630
|
// src/ui/LinePrompt.tsx
|
|
242
631
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
243
632
|
import { useState } from "react";
|
|
244
|
-
|
|
633
|
+
|
|
634
|
+
// src/ui/focusReport.ts
|
|
635
|
+
var isFocusReport = (input) => input === "[I" || input === "[O";
|
|
636
|
+
|
|
637
|
+
// src/ui/LinePrompt.tsx
|
|
638
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
245
639
|
function LinePrompt({ label, mask, onSubmit, onCancel }) {
|
|
246
640
|
const [value, setValue] = useState("");
|
|
247
641
|
useInput((input, key) => {
|
|
248
642
|
if (key.return) onSubmit(value.trim());
|
|
249
643
|
else if (key.escape) onCancel();
|
|
250
644
|
else if (key.backspace || key.delete) setValue((v) => v.slice(0, -1));
|
|
251
|
-
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow) setValue((v) => v + input.replace(/[\r\n]/g, ""));
|
|
645
|
+
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !isFocusReport(input)) setValue((v) => v + input.replace(/[\r\n]/g, ""));
|
|
252
646
|
});
|
|
253
647
|
const shown = mask ? "\u2022".repeat(value.length) : value;
|
|
254
|
-
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */
|
|
648
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
255
649
|
`${label}: `,
|
|
256
650
|
shown,
|
|
257
651
|
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
|
|
258
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter
|
|
652
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
|
|
259
653
|
] }) });
|
|
260
654
|
}
|
|
261
655
|
|
|
262
656
|
// src/ui/ListPicker.tsx
|
|
263
657
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
264
658
|
import { useState as useState2 } from "react";
|
|
265
|
-
import { jsx as jsx3, jsxs as
|
|
659
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
266
660
|
var VISIBLE = 12;
|
|
267
661
|
function ListPicker({ title, items, onPick }) {
|
|
268
662
|
const [filter, setFilter] = useState2("");
|
|
@@ -272,31 +666,31 @@ function ListPicker({ title, items, onPick }) {
|
|
|
272
666
|
useInput2((input, key) => {
|
|
273
667
|
if (key.upArrow) setIndex(Math.max(0, cursor - 1));
|
|
274
668
|
else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
|
|
275
|
-
else if (key.return) onPick(shown[cursor]?.id);
|
|
669
|
+
else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
|
|
276
670
|
else if (key.escape) onPick(void 0);
|
|
277
671
|
else if (key.backspace || key.delete) {
|
|
278
672
|
setFilter((f) => f.slice(0, -1));
|
|
279
673
|
setIndex(0);
|
|
280
|
-
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
674
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
|
|
281
675
|
setFilter((f) => f + input);
|
|
282
676
|
setIndex(0);
|
|
283
677
|
}
|
|
284
678
|
});
|
|
285
679
|
const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
|
|
286
|
-
return /* @__PURE__ */
|
|
287
|
-
/* @__PURE__ */ jsx3(Text3, { bold: true, children:
|
|
288
|
-
shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */
|
|
680
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
681
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
|
|
682
|
+
shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs3(Text3, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
|
|
289
683
|
`${start + i === cursor ? "\u203A" : " "} ${item.label}`,
|
|
290
684
|
item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
|
|
291
685
|
] }, item.id)),
|
|
292
|
-
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "
|
|
686
|
+
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("No matches.") })
|
|
293
687
|
] });
|
|
294
688
|
}
|
|
295
689
|
|
|
296
690
|
// src/ui/ModelPicker.tsx
|
|
297
691
|
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
298
692
|
import { useState as useState3 } from "react";
|
|
299
|
-
import { jsx as jsx4, jsxs as
|
|
693
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
300
694
|
var VISIBLE2 = 14;
|
|
301
695
|
function ModelPicker({ groups, current, onPick }) {
|
|
302
696
|
const [filter, setFilter] = useState3("");
|
|
@@ -322,18 +716,18 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
322
716
|
else if (key.backspace || key.delete) {
|
|
323
717
|
setFilter((f) => f.slice(0, -1));
|
|
324
718
|
setIndex(0);
|
|
325
|
-
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
719
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
|
|
326
720
|
setFilter((f) => f + input);
|
|
327
721
|
setIndex(0);
|
|
328
722
|
}
|
|
329
723
|
});
|
|
330
724
|
const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
|
|
331
|
-
return /* @__PURE__ */
|
|
332
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children:
|
|
725
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
726
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: t("Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)", { filter: filter ? `: ${filter}` : "" }) }),
|
|
333
727
|
rows.slice(start, start + VISIBLE2).map(
|
|
334
728
|
(row, i) => row.kind === "header" ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `\u2500\u2500 ${row.text} ` }, `h${start + i}`) : /* @__PURE__ */ jsx4(Text4, { color: start + i === cursor ? color("green") : void 0, children: `${start + i === cursor ? "\u203A" : " "} ${row.label}${row.ref === current ? " (aktif)" : ""}` }, row.ref)
|
|
335
729
|
),
|
|
336
|
-
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "
|
|
730
|
+
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matching models.") }) : null,
|
|
337
731
|
groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
|
|
338
732
|
] });
|
|
339
733
|
}
|
|
@@ -341,7 +735,7 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
341
735
|
// src/ui/ProviderMenu.tsx
|
|
342
736
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
343
737
|
import { useState as useState4 } from "react";
|
|
344
|
-
import { jsx as jsx5, jsxs as
|
|
738
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
345
739
|
function ProviderMenu({ entries, onPick }) {
|
|
346
740
|
const rows = [...entries.map((e) => e.id), "custom"];
|
|
347
741
|
const [index, setIndex] = useState4(0);
|
|
@@ -351,9 +745,9 @@ function ProviderMenu({ entries, onPick }) {
|
|
|
351
745
|
else if (key.return) onPick(rows[index]);
|
|
352
746
|
else if (key.escape) onPick(void 0);
|
|
353
747
|
});
|
|
354
|
-
return /* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc
|
|
356
|
-
entries.map((e, i) => /* @__PURE__ */
|
|
748
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
749
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
|
|
750
|
+
entries.map((e, i) => /* @__PURE__ */ jsxs5(Text5, { color: i === index ? color("green") : void 0, children: [
|
|
357
751
|
`${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
|
|
358
752
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
|
|
359
753
|
] }, e.id)),
|
|
@@ -376,8 +770,8 @@ function DiffView({ diff }) {
|
|
|
376
770
|
}
|
|
377
771
|
|
|
378
772
|
// src/ui/PermissionPrompt.tsx
|
|
379
|
-
import { jsx as jsx7, jsxs as
|
|
380
|
-
var VERB = { edit: "edit", write: "
|
|
773
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
774
|
+
var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
|
|
381
775
|
function PermissionPrompt({
|
|
382
776
|
request,
|
|
383
777
|
onAnswer
|
|
@@ -390,16 +784,16 @@ function PermissionPrompt({
|
|
|
390
784
|
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
391
785
|
});
|
|
392
786
|
const rules = request.sessionRules ?? [];
|
|
393
|
-
const sessionLabel = !rules.length ? null : request.kind === "edit" ? "
|
|
394
|
-
return /* @__PURE__ */
|
|
395
|
-
/* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}
|
|
787
|
+
const sessionLabel = !rules.length ? null : request.kind === "edit" ? t("yes for all edits in this project, this session") : t("yes this session for {rules}", { rules: rules.join(", ") });
|
|
788
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
789
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
|
|
396
790
|
request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
|
|
397
|
-
/* @__PURE__ */
|
|
398
|
-
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y]
|
|
791
|
+
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
792
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: t("[y] yes") }),
|
|
399
793
|
" ",
|
|
400
794
|
sessionLabel ? /* @__PURE__ */ jsx7(Text7, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
|
|
401
|
-
/* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: "[s]
|
|
402
|
-
/* @__PURE__ */ jsx7(Text7, { color: color("red"), children: "[n]
|
|
795
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: t("[s] yes to all ") }),
|
|
796
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("red"), children: t("[n] no") })
|
|
403
797
|
] })
|
|
404
798
|
] });
|
|
405
799
|
}
|
|
@@ -409,20 +803,31 @@ import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
|
409
803
|
import { useState as useState5 } from "react";
|
|
410
804
|
|
|
411
805
|
// src/ui/complete.ts
|
|
412
|
-
import
|
|
806
|
+
import { globSync } from "fs";
|
|
807
|
+
import { statSync } from "fs";
|
|
808
|
+
import { join } from "path";
|
|
413
809
|
function completeFile(value, cwd) {
|
|
414
810
|
const match = /(^|\s)@([^\s]*)$/.exec(value);
|
|
415
811
|
if (!match) return value;
|
|
416
812
|
const prefix = match[2];
|
|
417
813
|
const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
|
|
418
|
-
const
|
|
814
|
+
const pattern = `${escaped}*`;
|
|
815
|
+
if (validateGlobPattern(pattern)) return value;
|
|
816
|
+
const found = globSync(pattern, { cwd, exclude: ignorePatterns(cwd) }).map((entry2) => {
|
|
817
|
+
const normalized = normalizeGlobPath(entry2);
|
|
818
|
+
try {
|
|
819
|
+
return statSync(join(cwd, entry2)).isDirectory() ? `${normalized}/` : normalized;
|
|
820
|
+
} catch {
|
|
821
|
+
return normalized;
|
|
822
|
+
}
|
|
823
|
+
}).sort();
|
|
419
824
|
if (!found.length) return value;
|
|
420
825
|
const pick = found[0];
|
|
421
826
|
return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
|
|
422
827
|
}
|
|
423
828
|
|
|
424
829
|
// src/ui/PromptInput.tsx
|
|
425
|
-
import { jsx as jsx8, jsxs as
|
|
830
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
426
831
|
function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
427
832
|
const [value, setValue] = useState5("");
|
|
428
833
|
const [historyIndex, setHistoryIndex] = useState5(null);
|
|
@@ -491,18 +896,18 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
|
491
896
|
edit((v) => completeFile(v, cwd));
|
|
492
897
|
return;
|
|
493
898
|
}
|
|
494
|
-
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
|
|
899
|
+
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow || isFocusReport(input)) return;
|
|
495
900
|
edit((v) => v + input.replace(/\r/g, "\n"));
|
|
496
901
|
},
|
|
497
902
|
{ isActive: !disabled }
|
|
498
903
|
);
|
|
499
|
-
return /* @__PURE__ */
|
|
500
|
-
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */
|
|
904
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
905
|
+
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
501
906
|
/* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
|
|
502
907
|
value,
|
|
503
908
|
disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
|
|
504
909
|
] }) }),
|
|
505
|
-
suggestions.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${s.description}` }, s.name))
|
|
910
|
+
suggestions.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${t(s.description)}` }, s.name))
|
|
506
911
|
] });
|
|
507
912
|
}
|
|
508
913
|
|
|
@@ -514,11 +919,11 @@ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
|
514
919
|
function Spinner({ label, startedAt }) {
|
|
515
920
|
const [tick, setTick] = useState6(0);
|
|
516
921
|
useEffect(() => {
|
|
517
|
-
const id = setInterval(() => setTick((
|
|
922
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), 120);
|
|
518
923
|
return () => clearInterval(id);
|
|
519
924
|
}, []);
|
|
520
925
|
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
521
|
-
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (
|
|
926
|
+
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
|
|
522
927
|
}
|
|
523
928
|
|
|
524
929
|
// src/ui/StatusBar.tsx
|
|
@@ -533,15 +938,15 @@ var LABEL = {
|
|
|
533
938
|
function formatTokens(n) {
|
|
534
939
|
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
535
940
|
}
|
|
536
|
-
function StatusBar({ mode, tokens, busy, model }) {
|
|
941
|
+
function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
|
|
537
942
|
const danger = mode === "allowAll";
|
|
538
|
-
return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 shift+tab
|
|
943
|
+
return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 ${t("shift+tab switch mode")} \xB7 ${t("{n} tokens", { n: formatTokens(tokens) })} \xB7 ${busy ? t("esc to cancel") : t("/ for commands")}` });
|
|
539
944
|
}
|
|
540
945
|
|
|
541
946
|
// src/ui/McpMenu.tsx
|
|
542
947
|
import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
|
|
543
948
|
import { useState as useState7 } from "react";
|
|
544
|
-
import { jsx as jsx11, jsxs as
|
|
949
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
545
950
|
var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
|
|
546
951
|
function McpMenu({ items, onPick }) {
|
|
547
952
|
const [index, setIndex] = useState7(0);
|
|
@@ -551,9 +956,9 @@ function McpMenu({ items, onPick }) {
|
|
|
551
956
|
else if (key.return) onPick(items[index]?.name);
|
|
552
957
|
else if (key.escape) onPick(void 0);
|
|
553
958
|
});
|
|
554
|
-
return /* @__PURE__ */
|
|
555
|
-
/* @__PURE__ */ jsx11(Text11, { bold: true, children: "
|
|
556
|
-
items.map((item, i) => /* @__PURE__ */
|
|
959
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
960
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
|
|
961
|
+
items.map((item, i) => /* @__PURE__ */ jsxs8(Text11, { color: i === index ? color("green") : void 0, children: [
|
|
557
962
|
`${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
|
|
558
963
|
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
|
|
559
964
|
] }, item.name))
|
|
@@ -562,7 +967,7 @@ function McpMenu({ items, onPick }) {
|
|
|
562
967
|
|
|
563
968
|
// src/ui/PlanApproval.tsx
|
|
564
969
|
import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
|
|
565
|
-
import { jsx as jsx12, jsxs as
|
|
970
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
566
971
|
function PlanApproval({ plan, onAnswer }) {
|
|
567
972
|
useInput8((input, key) => {
|
|
568
973
|
const k2 = input.toLowerCase();
|
|
@@ -570,15 +975,15 @@ function PlanApproval({ plan, onAnswer }) {
|
|
|
570
975
|
else if (k2 === "y") onAnswer("default");
|
|
571
976
|
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
572
977
|
});
|
|
573
|
-
return /* @__PURE__ */
|
|
574
|
-
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "
|
|
575
|
-
/* @__PURE__ */ jsx12(Markdown, { text: plan }),
|
|
576
|
-
/* @__PURE__ */
|
|
577
|
-
/* @__PURE__ */ jsx12(Text12, { color: color("green"), children: "[a]
|
|
978
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
979
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: t("Plan from the agent") }),
|
|
980
|
+
/* @__PURE__ */ jsx12(Markdown, { text: plan, indent: 5 }),
|
|
981
|
+
/* @__PURE__ */ jsxs9(Text12, { children: [
|
|
982
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("green"), children: t("[a] yes, auto-edit") }),
|
|
578
983
|
" ",
|
|
579
|
-
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y]
|
|
984
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
|
|
580
985
|
" ",
|
|
581
|
-
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n]
|
|
986
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: t("[n] not yet, revise first") })
|
|
582
987
|
] })
|
|
583
988
|
] });
|
|
584
989
|
}
|
|
@@ -588,34 +993,34 @@ import { Box as Box11, Text as Text13 } from "ink";
|
|
|
588
993
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
589
994
|
var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
|
|
590
995
|
function TodoList({ items }) {
|
|
591
|
-
if (!items.length || items.every((
|
|
592
|
-
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((
|
|
996
|
+
if (!items.length || items.every((t2) => t2.status === "completed")) return null;
|
|
997
|
+
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t2, i) => /* @__PURE__ */ jsx13(Text13, { dimColor: t2.status === "completed", color: t2.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t2.status]} ${t2.content}` }, i)) });
|
|
593
998
|
}
|
|
594
999
|
|
|
595
1000
|
// src/ui/ToolBlock.tsx
|
|
596
1001
|
import { Box as Box12, Text as Text14 } from "ink";
|
|
597
|
-
import { jsx as jsx14, jsxs as
|
|
1002
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
598
1003
|
var PREVIEW_LINES = 5;
|
|
599
1004
|
function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
|
|
600
1005
|
const isTask = tool === "task";
|
|
601
1006
|
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
602
1007
|
const lines = (output ?? "").split("\n");
|
|
603
1008
|
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
/* @__PURE__ */
|
|
1009
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 2, children: [
|
|
1010
|
+
/* @__PURE__ */ jsxs10(Text14, { children: [
|
|
606
1011
|
/* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
607
1012
|
/* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
|
|
608
1013
|
/* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
|
|
609
1014
|
display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
|
|
610
1015
|
] }),
|
|
611
|
-
sub?.length ? /* @__PURE__ */
|
|
1016
|
+
sub?.length ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
612
1017
|
sub.slice(-6).map((s) => /* @__PURE__ */ jsx14(Text14, { dimColor: s.done && !s.isError, color: s.isError ? color("red") : void 0, children: `\u23BF ${toolLabel(s.tool)} ${s.target}` }, s.id)),
|
|
613
|
-
sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children:
|
|
1018
|
+
sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
|
|
614
1019
|
] }) : null,
|
|
615
|
-
isTask && done ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : color("green"), children: `${isError ? "\u2717
|
|
616
|
-
isDiff ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */
|
|
1020
|
+
isTask && done ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : color("green"), children: `${isError ? t("\u2717 failed") : t("\u2713 done")}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
|
|
1021
|
+
isDiff ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
617
1022
|
shown.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
|
|
618
|
-
lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children:
|
|
1023
|
+
lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} more lines (ctrl+o)", { n: lines.length - shown.length }) }) : null
|
|
619
1024
|
] }) : null
|
|
620
1025
|
] });
|
|
621
1026
|
}
|
|
@@ -624,80 +1029,114 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
|
|
|
624
1029
|
var nextId = 0;
|
|
625
1030
|
var entry = (e) => ({ ...e, id: nextId++ });
|
|
626
1031
|
var NOTICES = {
|
|
627
|
-
compacted: ["
|
|
628
|
-
stepLimit: ['
|
|
629
|
-
aborted: ["
|
|
1032
|
+
compacted: ["Conversation summarized to fit the model context.", "info"],
|
|
1033
|
+
stepLimit: ['Step limit of 50 reached. Type "continue" to keep going.', "warn"],
|
|
1034
|
+
aborted: ["Cancelled.", "warn"]
|
|
630
1035
|
};
|
|
631
|
-
|
|
632
|
-
|
|
1036
|
+
var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
|
|
1037
|
+
function endTurn(t2) {
|
|
1038
|
+
return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
|
|
633
1039
|
}
|
|
634
|
-
|
|
1040
|
+
var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
|
|
1041
|
+
function finishedCut(text) {
|
|
1042
|
+
let inFence = false;
|
|
1043
|
+
let pos = 0;
|
|
1044
|
+
let cut = -1;
|
|
1045
|
+
const lines = text.split("\n");
|
|
1046
|
+
for (const line of lines.slice(0, -1)) {
|
|
1047
|
+
if (TOOL_MARKUP.test(line)) break;
|
|
1048
|
+
if (line.trimStart().startsWith("```")) inFence = !inFence;
|
|
1049
|
+
const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
|
|
1050
|
+
pos += line.length + 1;
|
|
1051
|
+
if (!inFence && !tableRow) cut = pos - 1;
|
|
1052
|
+
}
|
|
1053
|
+
return cut;
|
|
1054
|
+
}
|
|
1055
|
+
function flushLines(t2) {
|
|
1056
|
+
const last = t2.live.at(-1);
|
|
1057
|
+
if (last?.kind !== "assistant") return t2;
|
|
1058
|
+
const cut = finishedCut(last.text);
|
|
1059
|
+
if (cut < 0) return t2;
|
|
1060
|
+
const chunk = last.text.slice(0, cut);
|
|
1061
|
+
if (!last.cont && !chunk.trim()) return t2;
|
|
1062
|
+
const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
|
|
1063
|
+
return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
|
|
1064
|
+
}
|
|
1065
|
+
function applyEvent(t2, event) {
|
|
635
1066
|
switch (event.type) {
|
|
636
1067
|
case "text": {
|
|
637
|
-
const last =
|
|
638
|
-
if (last?.kind === "assistant") return { ...
|
|
639
|
-
return { ...
|
|
1068
|
+
const last = t2.live.at(-1);
|
|
1069
|
+
if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
|
|
1070
|
+
return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
|
|
1071
|
+
}
|
|
1072
|
+
case "thinking": {
|
|
1073
|
+
const last = t2.live.at(-1);
|
|
1074
|
+
if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
1075
|
+
return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
|
|
640
1076
|
}
|
|
641
1077
|
case "textReplace": {
|
|
642
|
-
const last =
|
|
643
|
-
if (last?.kind !== "assistant") return
|
|
644
|
-
return { ...
|
|
1078
|
+
const last = t2.live.at(-1);
|
|
1079
|
+
if (last?.kind !== "assistant") return t2;
|
|
1080
|
+
return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
|
|
645
1081
|
}
|
|
646
1082
|
case "toolStart": {
|
|
647
|
-
const running =
|
|
1083
|
+
const running = t2.live.some((e) => e.kind === "tool" && !e.done);
|
|
648
1084
|
const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
|
|
649
|
-
return running ? { ...
|
|
1085
|
+
return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
|
|
650
1086
|
}
|
|
651
1087
|
case "toolEnd": {
|
|
652
|
-
const live =
|
|
1088
|
+
const live = t2.live.map(
|
|
653
1089
|
(e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
|
|
654
1090
|
);
|
|
655
1091
|
const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
|
|
656
1092
|
const cut = firstRunning === -1 ? live.length : firstRunning;
|
|
657
|
-
return { done: [...
|
|
1093
|
+
return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
|
|
658
1094
|
}
|
|
659
1095
|
case "subagent": {
|
|
660
1096
|
const inner = event.event;
|
|
661
|
-
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return
|
|
1097
|
+
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
|
|
662
1098
|
const update = (e) => {
|
|
663
1099
|
if (e.kind !== "tool" || e.callId !== event.parentId) return e;
|
|
664
1100
|
const sub = e.sub ?? [];
|
|
665
1101
|
if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
|
|
666
1102
|
return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
|
|
667
1103
|
};
|
|
668
|
-
return { ...
|
|
1104
|
+
return { ...t2, live: t2.live.map(update) };
|
|
669
1105
|
}
|
|
670
1106
|
case "error":
|
|
671
|
-
return { ...
|
|
1107
|
+
return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
672
1108
|
default: {
|
|
673
1109
|
const notice = NOTICES[event.type];
|
|
674
|
-
return notice ? { ...
|
|
1110
|
+
return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
|
|
675
1111
|
}
|
|
676
1112
|
}
|
|
677
1113
|
}
|
|
678
1114
|
|
|
679
1115
|
// src/ui/App.tsx
|
|
680
|
-
import { jsx as jsx15, jsxs as
|
|
1116
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
681
1117
|
function helpText(runtime) {
|
|
682
1118
|
const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
|
|
683
1119
|
return [
|
|
684
|
-
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
|
|
685
|
-
...custom.length ? ["", "
|
|
686
|
-
...runtime.skills.length ? ["", "
|
|
1120
|
+
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
|
|
1121
|
+
...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
|
|
1122
|
+
...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
|
|
687
1123
|
"",
|
|
688
|
-
"shift+tab
|
|
689
|
-
"ctrl+o output
|
|
690
|
-
"
|
|
1124
|
+
t("shift+tab switch permission mode esc cancel the turn"),
|
|
1125
|
+
t("ctrl+o full output of the last tool \\ + enter new line"),
|
|
1126
|
+
t("ctrl+t show/hide the model\u2019s thinking"),
|
|
1127
|
+
t("@file + tab complete file names \u2191\u2193 input history")
|
|
691
1128
|
].join("\n");
|
|
692
1129
|
}
|
|
693
|
-
var INIT_PROMPT = "
|
|
1130
|
+
var INIT_PROMPT = "Study this project (folder structure, package/build files, test and lint commands, visible code conventions). Then create AGENTS.md in the project root, or update it if it already exists, covering: how to build, test, and lint; a short structure overview; important conventions. Keep it concise and factual, only what actually exists in the project.";
|
|
694
1131
|
var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
|
|
695
1132
|
function App({ runtime, initialPrompt, version }) {
|
|
696
1133
|
const { exit } = useApp();
|
|
697
1134
|
const [transcript, setTranscript] = useState8(() => ({ done: [entry({ kind: "header" })], live: [] }));
|
|
698
1135
|
const [busy, setBusy] = useState8(false);
|
|
1136
|
+
const [cancelling, setCancelling] = useState8(false);
|
|
699
1137
|
const [startedAt, setStartedAt] = useState8(0);
|
|
700
1138
|
const [mode, setMode] = useState8(runtime.agent.permissions.mode);
|
|
1139
|
+
const [reasoning, setReasoning] = useState8(runtime.reasoning);
|
|
701
1140
|
const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
702
1141
|
const [asks, setAsks] = useState8([]);
|
|
703
1142
|
const pending = asks[0] ?? null;
|
|
@@ -713,15 +1152,17 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
713
1152
|
const [mcpMenu, setMcpMenu] = useState8(null);
|
|
714
1153
|
const [listPicker, setListPicker] = useState8(null);
|
|
715
1154
|
const [planAsk, setPlanAsk] = useState8(null);
|
|
1155
|
+
const [queued, setQueued] = useState8([]);
|
|
1156
|
+
const [showThinking, setShowThinking] = useState8(false);
|
|
716
1157
|
useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
|
|
717
1158
|
const extraCommands = [
|
|
718
|
-
...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "
|
|
719
|
-
...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description:
|
|
1159
|
+
...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? t("custom command") })),
|
|
1160
|
+
...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description: t("skill \xB7 {description}", { description: s.description.slice(0, 50) }) }))
|
|
720
1161
|
];
|
|
721
1162
|
const lastTool = useRef(null);
|
|
722
1163
|
const toolTargets = useRef(/* @__PURE__ */ new Map());
|
|
723
1164
|
const notice = useCallback((text, tone = "info") => {
|
|
724
|
-
setTranscript((
|
|
1165
|
+
setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
|
|
725
1166
|
}, []);
|
|
726
1167
|
const onEvent = useCallback((event) => {
|
|
727
1168
|
if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
|
|
@@ -729,7 +1170,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
729
1170
|
lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
|
|
730
1171
|
}
|
|
731
1172
|
if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
|
|
732
|
-
setTranscript((
|
|
1173
|
+
setTranscript((t2) => applyEvent(t2, event));
|
|
733
1174
|
}, []);
|
|
734
1175
|
useEffect2(() => {
|
|
735
1176
|
runtime.agent.onEvent = onEvent;
|
|
@@ -738,7 +1179,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
738
1179
|
}, [runtime, onEvent]);
|
|
739
1180
|
const runTurn = useCallback(
|
|
740
1181
|
async (text) => {
|
|
741
|
-
setTranscript((
|
|
1182
|
+
setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
|
|
742
1183
|
setBusy(true);
|
|
743
1184
|
setStartedAt(Date.now());
|
|
744
1185
|
controller.current = new AbortController();
|
|
@@ -746,6 +1187,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
746
1187
|
controller.current = null;
|
|
747
1188
|
setTranscript(endTurn);
|
|
748
1189
|
setBusy(false);
|
|
1190
|
+
setCancelling(false);
|
|
749
1191
|
},
|
|
750
1192
|
[runtime]
|
|
751
1193
|
);
|
|
@@ -761,11 +1203,11 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
761
1203
|
case "clear":
|
|
762
1204
|
runtime.agent.clear();
|
|
763
1205
|
setTokens(0);
|
|
764
|
-
notice("
|
|
1206
|
+
notice(t("Conversation cleared."));
|
|
765
1207
|
return;
|
|
766
1208
|
case "cost": {
|
|
767
1209
|
const u = runtime.agent.totalUsage;
|
|
768
|
-
notice(
|
|
1210
|
+
notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
|
|
769
1211
|
return;
|
|
770
1212
|
}
|
|
771
1213
|
case "compact": {
|
|
@@ -775,10 +1217,46 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
775
1217
|
try {
|
|
776
1218
|
await runtime.agent.compact(controller.current.signal);
|
|
777
1219
|
} catch (error) {
|
|
778
|
-
notice(
|
|
1220
|
+
notice(t("Could not summarize: {error}", { error: error.message }), "error");
|
|
779
1221
|
}
|
|
780
1222
|
controller.current = null;
|
|
781
1223
|
setBusy(false);
|
|
1224
|
+
setCancelling(false);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
case "reasoning": {
|
|
1228
|
+
const apply = (value) => {
|
|
1229
|
+
try {
|
|
1230
|
+
const level = parseReasoningLevel(value, "/reasoning");
|
|
1231
|
+
const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1232
|
+
assertReasoningSupported(providerId2, level);
|
|
1233
|
+
runtime.setReasoning(level);
|
|
1234
|
+
writeGlobalConfig({ reasoning: level }, runtime.env);
|
|
1235
|
+
setReasoning(level);
|
|
1236
|
+
notice(t("Reasoning: {level} (saved as default)", { level }));
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
notice(error.message, "error");
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
if (args) {
|
|
1242
|
+
apply(args);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1246
|
+
const supported = supportedReasoningLevels(providerId);
|
|
1247
|
+
setListPicker({
|
|
1248
|
+
title: "Reasoning",
|
|
1249
|
+
items: REASONING_LEVELS.map((level) => ({
|
|
1250
|
+
id: level,
|
|
1251
|
+
label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
|
|
1252
|
+
disabled: !supported.includes(level),
|
|
1253
|
+
hint: supported.includes(level) ? void 0 : t("not supported by this provider")
|
|
1254
|
+
})),
|
|
1255
|
+
onPick: (id) => {
|
|
1256
|
+
setListPicker(null);
|
|
1257
|
+
if (id) apply(id);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
782
1260
|
return;
|
|
783
1261
|
}
|
|
784
1262
|
case "model": {
|
|
@@ -787,7 +1265,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
787
1265
|
runtime.setModel(args);
|
|
788
1266
|
writeGlobalConfig({ model: args }, runtime.env);
|
|
789
1267
|
setModelLabel(labelFor(runtime, args));
|
|
790
|
-
notice(
|
|
1268
|
+
notice(t("Model: {model} (saved as default)", { model: args }));
|
|
791
1269
|
} catch (error) {
|
|
792
1270
|
notice(error.message, "error");
|
|
793
1271
|
}
|
|
@@ -796,7 +1274,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
796
1274
|
try {
|
|
797
1275
|
setPicker(await runtime.listModels());
|
|
798
1276
|
} catch (error) {
|
|
799
|
-
notice(
|
|
1277
|
+
notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
|
|
800
1278
|
}
|
|
801
1279
|
return;
|
|
802
1280
|
}
|
|
@@ -804,7 +1282,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
804
1282
|
const installed = readMcpFile(globalMcpPath(runtime.home));
|
|
805
1283
|
const states = runtime.mcp.states();
|
|
806
1284
|
const items = [
|
|
807
|
-
...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
|
|
1285
|
+
...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
|
|
808
1286
|
...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
|
|
809
1287
|
].map((i) => {
|
|
810
1288
|
const s = states.find((st) => st.name === i.name);
|
|
@@ -816,7 +1294,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
816
1294
|
case "new":
|
|
817
1295
|
runtime.newSession();
|
|
818
1296
|
setTokens(0);
|
|
819
|
-
notice("
|
|
1297
|
+
notice(t("New session started. The previous session stays saved (/resume)."));
|
|
820
1298
|
return;
|
|
821
1299
|
case "session":
|
|
822
1300
|
notice(
|
|
@@ -832,15 +1310,15 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
832
1310
|
case "resume": {
|
|
833
1311
|
const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
|
|
834
1312
|
if (!others.length) {
|
|
835
|
-
notice("
|
|
1313
|
+
notice(t("No other sessions in this folder yet."));
|
|
836
1314
|
return;
|
|
837
1315
|
}
|
|
838
1316
|
setListPicker({
|
|
839
|
-
title: "
|
|
1317
|
+
title: t("Continue a session"),
|
|
840
1318
|
items: others.map((s) => ({
|
|
841
1319
|
id: s.session.file,
|
|
842
|
-
label: `${s.mtime.toLocaleString(
|
|
843
|
-
hint:
|
|
1320
|
+
label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
|
|
1321
|
+
hint: t("{n} messages", { n: s.session.load().length })
|
|
844
1322
|
})),
|
|
845
1323
|
onPick: (id) => {
|
|
846
1324
|
setListPicker(null);
|
|
@@ -848,12 +1326,23 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
848
1326
|
if (!chosen) return;
|
|
849
1327
|
runtime.resume(chosen.session);
|
|
850
1328
|
setTokens(0);
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
[`Melanjutkan sesi (${runtime.agent.messages.length} pesan):`, ...tail.map((m) => `${m.role === "user" ? ">" : "\u25CF"} ${String(m.content).slice(0, 200)}`)].join(
|
|
854
|
-
"\n"
|
|
855
|
-
)
|
|
1329
|
+
const chat = runtime.agent.messages.filter(
|
|
1330
|
+
(m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
|
|
856
1331
|
);
|
|
1332
|
+
setTranscript({
|
|
1333
|
+
done: [
|
|
1334
|
+
entry({ kind: "header" }),
|
|
1335
|
+
entry({
|
|
1336
|
+
kind: "notice",
|
|
1337
|
+
text: t("Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).", { messages: runtime.agent.messages.length, chat: chat.length }),
|
|
1338
|
+
tone: "info"
|
|
1339
|
+
}),
|
|
1340
|
+
...chat.map(
|
|
1341
|
+
(m) => entry(m.role === "user" ? { kind: "user", text: m.content } : { kind: "assistant", text: m.content })
|
|
1342
|
+
)
|
|
1343
|
+
],
|
|
1344
|
+
live: []
|
|
1345
|
+
});
|
|
857
1346
|
}
|
|
858
1347
|
});
|
|
859
1348
|
return;
|
|
@@ -879,7 +1368,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
879
1368
|
notice(skillsText(runtime.skills, runtime.commands));
|
|
880
1369
|
return;
|
|
881
1370
|
case "doctor": {
|
|
882
|
-
notice("
|
|
1371
|
+
notice(t("Checking\u2026"));
|
|
883
1372
|
const c = runtime.config;
|
|
884
1373
|
notice(
|
|
885
1374
|
await doctorText(
|
|
@@ -900,20 +1389,20 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
900
1389
|
case "permissions": {
|
|
901
1390
|
const rules = runtime.agent.permissions.list();
|
|
902
1391
|
if (!rules.length) {
|
|
903
|
-
notice("
|
|
1392
|
+
notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
|
|
904
1393
|
return;
|
|
905
1394
|
}
|
|
906
1395
|
setListPicker({
|
|
907
|
-
title: "
|
|
908
|
-
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "
|
|
1396
|
+
title: t("Active permissions (enter: revoke a session permission)"),
|
|
1397
|
+
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
|
|
909
1398
|
onPick: (id) => {
|
|
910
1399
|
setListPicker(null);
|
|
911
1400
|
const rule = rules.find((r) => r.rule === id);
|
|
912
1401
|
if (!rule) return;
|
|
913
|
-
if (rule.source === "config") notice(
|
|
1402
|
+
if (rule.source === "config") notice(t("{rule} comes from the config/flag; remove it from ~/.bccli/config.json to revoke it.", { rule: rule.rule }), "warn");
|
|
914
1403
|
else {
|
|
915
1404
|
runtime.agent.permissions.revoke(rule.rule);
|
|
916
|
-
notice(
|
|
1405
|
+
notice(t("Permission {rule} revoked.", { rule: rule.rule }));
|
|
917
1406
|
}
|
|
918
1407
|
}
|
|
919
1408
|
});
|
|
@@ -922,32 +1411,51 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
922
1411
|
case "memory": {
|
|
923
1412
|
if (!args) {
|
|
924
1413
|
const files = instructionFiles(runtime.cwd, runtime.home);
|
|
925
|
-
notice(files.length ? files.map((f) =>
|
|
1414
|
+
notice(files.length ? files.map((f) => t("{path} ({lines} lines)", { path: f.path, lines: f.lines })).join("\n") : t("No AGENTS.md / BCCLI.md yet. Add one with /memory <text>."));
|
|
926
1415
|
return;
|
|
927
1416
|
}
|
|
928
1417
|
const { global, text } = parseMemoryArgs(args);
|
|
929
1418
|
if (!text) {
|
|
930
|
-
notice("
|
|
1419
|
+
notice(t("Write the text: /memory global <text>."), "warn");
|
|
931
1420
|
return;
|
|
932
1421
|
}
|
|
933
|
-
const file = global ?
|
|
1422
|
+
const file = global ? join2(runtime.home, "BCCLI.md") : join2(runtime.cwd, "AGENTS.md");
|
|
934
1423
|
try {
|
|
935
1424
|
mkdirSync(dirname(file), { recursive: true });
|
|
936
1425
|
appendMemory(file, text);
|
|
937
1426
|
} catch (error) {
|
|
938
|
-
notice(
|
|
1427
|
+
notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
|
|
939
1428
|
return;
|
|
940
1429
|
}
|
|
941
1430
|
runtime.rebuildSystemPrompt();
|
|
942
|
-
notice(
|
|
1431
|
+
notice(t("Added to {file}.", { file }));
|
|
943
1432
|
return;
|
|
944
1433
|
}
|
|
945
1434
|
case "init":
|
|
946
1435
|
void runTurn(INIT_PROMPT);
|
|
947
1436
|
return;
|
|
1437
|
+
case "language": {
|
|
1438
|
+
if (!args) {
|
|
1439
|
+
notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const wanted = parseLang(args);
|
|
1443
|
+
if (!wanted) {
|
|
1444
|
+
notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
setLanguage(wanted);
|
|
1448
|
+
try {
|
|
1449
|
+
writeGlobalConfig({ language: wanted }, runtime.env);
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
notice(t("Could not save the language: {error}", { error: error.message }), "error");
|
|
1452
|
+
}
|
|
1453
|
+
notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
948
1456
|
case "login": {
|
|
949
1457
|
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
950
|
-
const key = await ask(
|
|
1458
|
+
const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
|
|
951
1459
|
if (!key) return;
|
|
952
1460
|
const result = await runtime.addProviderKey(id, key);
|
|
953
1461
|
if (!result.ok) {
|
|
@@ -957,10 +1465,10 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
957
1465
|
runtime.setModel(runtime.modelRef);
|
|
958
1466
|
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
959
1467
|
if (envName && runtime.env[envName]) {
|
|
960
|
-
notice(
|
|
1468
|
+
notice(t("Key for {provider} saved, but {env} in the environment takes precedence and is still used.", { provider: runtime.providerLabel(), env: envName }), "warn");
|
|
961
1469
|
return;
|
|
962
1470
|
}
|
|
963
|
-
notice(
|
|
1471
|
+
notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
|
|
964
1472
|
return;
|
|
965
1473
|
}
|
|
966
1474
|
case "logout": {
|
|
@@ -969,13 +1477,13 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
969
1477
|
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
970
1478
|
if (envName && runtime.env[envName]) {
|
|
971
1479
|
notice(
|
|
972
|
-
|
|
1480
|
+
removed ? t("Stored key deleted, but {env} is still set in the environment and is still used.", { env: envName }) : t("No stored key; {env} is still set in the environment and is still used.", { env: envName }),
|
|
973
1481
|
"warn"
|
|
974
1482
|
);
|
|
975
1483
|
return;
|
|
976
1484
|
}
|
|
977
1485
|
if (!removed) {
|
|
978
|
-
notice(
|
|
1486
|
+
notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
|
|
979
1487
|
return;
|
|
980
1488
|
}
|
|
981
1489
|
try {
|
|
@@ -986,7 +1494,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
986
1494
|
};
|
|
987
1495
|
runtime.agent.provider = { chat: fail, listModels: fail };
|
|
988
1496
|
}
|
|
989
|
-
notice(
|
|
1497
|
+
notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
|
|
990
1498
|
return;
|
|
991
1499
|
}
|
|
992
1500
|
case "diff":
|
|
@@ -995,17 +1503,17 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
995
1503
|
case "undo": {
|
|
996
1504
|
const result = await runtime.checkpoints.undo();
|
|
997
1505
|
if (!result) {
|
|
998
|
-
notice("
|
|
1506
|
+
notice(t("No file edits to undo."));
|
|
999
1507
|
return;
|
|
1000
1508
|
}
|
|
1001
1509
|
const rel = (p) => relative(runtime.cwd, p) || p;
|
|
1002
1510
|
notice(
|
|
1003
1511
|
[
|
|
1004
|
-
result.failed.length ?
|
|
1005
|
-
result.restored.length ?
|
|
1006
|
-
result.deleted.length ?
|
|
1007
|
-
result.skipped.length ?
|
|
1008
|
-
"
|
|
1512
|
+
result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
|
|
1513
|
+
result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
|
|
1514
|
+
result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
|
|
1515
|
+
result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
|
|
1516
|
+
t("Note: changes made through bash commands cannot be undone.")
|
|
1009
1517
|
].filter(Boolean).join("\n")
|
|
1010
1518
|
);
|
|
1011
1519
|
return;
|
|
@@ -1013,33 +1521,33 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1013
1521
|
case "copy": {
|
|
1014
1522
|
const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
|
|
1015
1523
|
if (!last || typeof last.content !== "string") {
|
|
1016
|
-
notice("
|
|
1524
|
+
notice(t("No answer to copy yet."));
|
|
1017
1525
|
return;
|
|
1018
1526
|
}
|
|
1019
1527
|
process.stdout.write(osc52(last.content, !!process.env.TMUX));
|
|
1020
|
-
const file =
|
|
1528
|
+
const file = join2(runtime.home, "last-answer.md");
|
|
1021
1529
|
mkdirSync(runtime.home, { recursive: true });
|
|
1022
1530
|
writeFileSync(file, last.content);
|
|
1023
|
-
notice(
|
|
1531
|
+
notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
|
|
1024
1532
|
return;
|
|
1025
1533
|
}
|
|
1026
1534
|
case "export": {
|
|
1027
1535
|
const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
|
|
1028
1536
|
const file = resolve(runtime.cwd, name2);
|
|
1029
1537
|
if (existsSync2(file)) {
|
|
1030
|
-
const answer2 = await ask(
|
|
1538
|
+
const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
|
|
1031
1539
|
if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
|
|
1032
|
-
notice("
|
|
1540
|
+
notice(t("Export cancelled."));
|
|
1033
1541
|
return;
|
|
1034
1542
|
}
|
|
1035
1543
|
}
|
|
1036
1544
|
try {
|
|
1037
|
-
writeFileSync(file, exportMarkdown(runtime.agent.messages,
|
|
1545
|
+
writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
|
|
1038
1546
|
} catch (error) {
|
|
1039
|
-
notice(
|
|
1547
|
+
notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
|
|
1040
1548
|
return;
|
|
1041
1549
|
}
|
|
1042
|
-
notice(
|
|
1550
|
+
notice(t("Conversation saved to {file}.", { file }));
|
|
1043
1551
|
return;
|
|
1044
1552
|
}
|
|
1045
1553
|
case "provider": {
|
|
@@ -1050,7 +1558,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1050
1558
|
return;
|
|
1051
1559
|
}
|
|
1052
1560
|
default:
|
|
1053
|
-
notice(
|
|
1561
|
+
notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
|
|
1054
1562
|
}
|
|
1055
1563
|
},
|
|
1056
1564
|
[runtime, notice, exit, version, runTurn, ask]
|
|
@@ -1076,7 +1584,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1076
1584
|
try {
|
|
1077
1585
|
body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
|
|
1078
1586
|
} catch (error) {
|
|
1079
|
-
notice(
|
|
1587
|
+
notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
|
|
1080
1588
|
return;
|
|
1081
1589
|
}
|
|
1082
1590
|
void runTurn(`${body}
|
|
@@ -1086,6 +1594,19 @@ ARGUMENTS: ${slash.args}`);
|
|
|
1086
1594
|
},
|
|
1087
1595
|
[runSlash, runTurn, runtime, notice]
|
|
1088
1596
|
);
|
|
1597
|
+
const onPrompt = useCallback(
|
|
1598
|
+
(text) => {
|
|
1599
|
+
if (busy) setQueued((q) => [...q, text]);
|
|
1600
|
+
else submit(text);
|
|
1601
|
+
},
|
|
1602
|
+
[busy, submit]
|
|
1603
|
+
);
|
|
1604
|
+
useEffect2(() => {
|
|
1605
|
+
if (busy || pending || planAsk || !queued.length) return;
|
|
1606
|
+
const [next, ...rest] = queued;
|
|
1607
|
+
setQueued(rest);
|
|
1608
|
+
submit(next);
|
|
1609
|
+
}, [busy, pending, planAsk, queued, submit]);
|
|
1089
1610
|
const initialSent = useRef(false);
|
|
1090
1611
|
useEffect2(() => {
|
|
1091
1612
|
if (initialPrompt && !initialSent.current) {
|
|
@@ -1100,10 +1621,31 @@ ARGUMENTS: ${slash.args}`);
|
|
|
1100
1621
|
setMode(next);
|
|
1101
1622
|
} else if (key.escape && busy && !pending) {
|
|
1102
1623
|
controller.current?.abort();
|
|
1624
|
+
setCancelling(true);
|
|
1625
|
+
if (queued.length) {
|
|
1626
|
+
setQueued([]);
|
|
1627
|
+
notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
|
|
1628
|
+
}
|
|
1629
|
+
} else if (key.ctrl && input === "t") {
|
|
1630
|
+
const next = !showThinking;
|
|
1631
|
+
const toggled = next ? t("Thinking shown (ctrl+t to hide).") : t("Thinking hidden (ctrl+t to show).");
|
|
1632
|
+
setShowThinking(next);
|
|
1633
|
+
setTranscript((t2) => {
|
|
1634
|
+
const last = [...t2.done, ...t2.live].findLast((e) => e.kind === "thinking");
|
|
1635
|
+
const reprint = next && last?.kind === "thinking" && t2.done.includes(last);
|
|
1636
|
+
return {
|
|
1637
|
+
...t2,
|
|
1638
|
+
done: [
|
|
1639
|
+
...t2.done,
|
|
1640
|
+
...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
|
|
1641
|
+
entry({ kind: "notice", text: toggled, tone: "info" })
|
|
1642
|
+
]
|
|
1643
|
+
};
|
|
1644
|
+
});
|
|
1103
1645
|
} else if (key.ctrl && input === "o" && lastTool.current) {
|
|
1104
|
-
const
|
|
1105
|
-
notice(`\u23BF ${
|
|
1106
|
-
${
|
|
1646
|
+
const t2 = lastTool.current;
|
|
1647
|
+
notice(`\u23BF ${t2.tool} ${t2.target}
|
|
1648
|
+
${t2.output}`);
|
|
1107
1649
|
}
|
|
1108
1650
|
});
|
|
1109
1651
|
const pickMcp = async (name) => {
|
|
@@ -1113,24 +1655,24 @@ ${t.output}`);
|
|
|
1113
1655
|
if (item.installed) {
|
|
1114
1656
|
removeGlobalServer(runtime.home, item.name);
|
|
1115
1657
|
await runtime.mcp.remove(item.name);
|
|
1116
|
-
notice(
|
|
1658
|
+
notice(t("MCP {name} removed.", { name: item.name }));
|
|
1117
1659
|
return;
|
|
1118
1660
|
}
|
|
1119
1661
|
const entry2 = CATALOG.find((c) => c.name === item.name);
|
|
1120
1662
|
if (!entry2) return;
|
|
1121
1663
|
const values = {};
|
|
1122
1664
|
for (const input of entry2.inputs ?? []) {
|
|
1123
|
-
const v = await ask(input.label, !!input.secret);
|
|
1665
|
+
const v = await ask(t(input.label), !!input.secret);
|
|
1124
1666
|
if (!v) return;
|
|
1125
1667
|
values[input.key] = v;
|
|
1126
1668
|
}
|
|
1127
1669
|
const config = fillTemplate(entry2.config, values);
|
|
1128
1670
|
addGlobalServer(runtime.home, entry2.name, config);
|
|
1129
|
-
notice(
|
|
1671
|
+
notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
|
|
1130
1672
|
await runtime.mcp.add({ name: entry2.name, config, source: "global" });
|
|
1131
1673
|
const state = runtime.mcp.states().find((s) => s.name === entry2.name);
|
|
1132
|
-
if (state?.status === "ready") notice(
|
|
1133
|
-
else notice(
|
|
1674
|
+
if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
|
|
1675
|
+
else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
|
|
1134
1676
|
};
|
|
1135
1677
|
const pickProvider = async (id) => {
|
|
1136
1678
|
setProviderMenu(null);
|
|
@@ -1147,7 +1689,7 @@ ${t.output}`);
|
|
|
1147
1689
|
const c = runtime.config;
|
|
1148
1690
|
const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
|
|
1149
1691
|
if (!hasKey(c, target, runtime.env) || id === "custom") {
|
|
1150
|
-
const key = keyless ? "" : await ask(
|
|
1692
|
+
const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
|
|
1151
1693
|
if (key === void 0) return;
|
|
1152
1694
|
const result = await runtime.addProviderKey(target, key);
|
|
1153
1695
|
if (!result.ok) {
|
|
@@ -1167,7 +1709,7 @@ ${t.output}`);
|
|
|
1167
1709
|
const renderEntry = (e) => {
|
|
1168
1710
|
switch (e.kind) {
|
|
1169
1711
|
case "header":
|
|
1170
|
-
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */
|
|
1712
|
+
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Text15, { children: [
|
|
1171
1713
|
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
1172
1714
|
/* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
|
|
1173
1715
|
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
|
|
@@ -1175,21 +1717,36 @@ ${t.output}`);
|
|
|
1175
1717
|
case "user":
|
|
1176
1718
|
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
1177
1719
|
case "assistant":
|
|
1720
|
+
if (e.cont) {
|
|
1721
|
+
return e.text ? /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Markdown, { text: e.text }) }, e.id) : null;
|
|
1722
|
+
}
|
|
1178
1723
|
if (!e.text.trim()) return null;
|
|
1179
|
-
return /* @__PURE__ */
|
|
1724
|
+
return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, children: [
|
|
1180
1725
|
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
|
|
1181
1726
|
/* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
1182
1727
|
] }, e.id);
|
|
1728
|
+
case "thinking": {
|
|
1729
|
+
const text = e.text.trim();
|
|
1730
|
+
if (!text) return null;
|
|
1731
|
+
if (!(e.open ?? showThinking)) {
|
|
1732
|
+
const lines = text.split("\n").length;
|
|
1733
|
+
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking \xB7 {n} lines \xB7 ctrl+t to show", { n: lines }) }) }, e.id);
|
|
1734
|
+
}
|
|
1735
|
+
return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1736
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking (ctrl+t to hide)") }),
|
|
1737
|
+
/* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
|
|
1738
|
+
] }, e.id);
|
|
1739
|
+
}
|
|
1183
1740
|
case "tool":
|
|
1184
1741
|
return /* @__PURE__ */ jsx15(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
|
|
1185
1742
|
case "notice":
|
|
1186
1743
|
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
|
|
1187
1744
|
}
|
|
1188
1745
|
};
|
|
1189
|
-
return /* @__PURE__ */
|
|
1746
|
+
return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", children: [
|
|
1190
1747
|
/* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
|
|
1191
1748
|
transcript.live.map(renderEntry),
|
|
1192
|
-
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "
|
|
1749
|
+
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: cancelling ? t("Cancelling") : t("Thinking"), startedAt }) }) : null,
|
|
1193
1750
|
pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
|
|
1194
1751
|
picker ? /* @__PURE__ */ jsx15(
|
|
1195
1752
|
ModelPicker,
|
|
@@ -1232,8 +1789,9 @@ ${t.output}`);
|
|
|
1232
1789
|
}
|
|
1233
1790
|
) : null,
|
|
1234
1791
|
/* @__PURE__ */ jsx15(TodoList, { items: todos }),
|
|
1235
|
-
/* @__PURE__ */ jsx15(
|
|
1236
|
-
/* @__PURE__ */ jsx15(
|
|
1792
|
+
queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
|
|
1793
|
+
/* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
|
|
1794
|
+
/* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
|
|
1237
1795
|
] });
|
|
1238
1796
|
}
|
|
1239
1797
|
|
|
@@ -1243,27 +1801,27 @@ async function startInteractive(rt, opts) {
|
|
|
1243
1801
|
if (opts.resume) {
|
|
1244
1802
|
const { Session: Session2 } = await import("./session-7UB77CEG.js");
|
|
1245
1803
|
const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
|
|
1246
|
-
if (!list.length) console.log("
|
|
1804
|
+
if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
|
|
1247
1805
|
else {
|
|
1248
1806
|
list.forEach((s, i) => {
|
|
1249
|
-
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(
|
|
1807
|
+
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
|
|
1250
1808
|
});
|
|
1251
1809
|
const { createInterface } = await import("readline/promises");
|
|
1252
1810
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1253
|
-
const answer = Number(await rl.question("
|
|
1811
|
+
const answer = Number(await rl.question(t("Session number (enter = new): ")));
|
|
1254
1812
|
rl.close();
|
|
1255
1813
|
const chosen = list[answer - 1];
|
|
1256
1814
|
if (chosen) rt.resume(chosen.session);
|
|
1257
1815
|
}
|
|
1258
1816
|
}
|
|
1259
|
-
const { serversToStart, setProjectTrust } = await import("./config-
|
|
1817
|
+
const { serversToStart, setProjectTrust } = await import("./config-DMF4AO7E.js");
|
|
1260
1818
|
const plan = serversToStart(rt.home, rt.cwd);
|
|
1261
1819
|
if (plan.needTrust.length) {
|
|
1262
1820
|
const { createInterface } = await import("readline/promises");
|
|
1263
1821
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1264
1822
|
for (const s of plan.needTrust) {
|
|
1265
1823
|
const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
|
|
1266
|
-
const answer = (await rl.question(
|
|
1824
|
+
const answer = (await rl.question(t('This repo wants to run the MCP server "{name}" ({what}). Allow? [y/N] ', { name: s.name, what }))).trim().toLowerCase();
|
|
1267
1825
|
setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
|
|
1268
1826
|
if (answer === "y") plan.start.push(s);
|
|
1269
1827
|
}
|