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