@botconnector/bccli 0.4.0-beta.2 → 0.4.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -1
- package/dist/args-XQYEBOKB.js +12 -0
- package/dist/{chunk-NGLKANMD.js → chunk-44MXWCFN.js} +4 -3
- package/dist/{chunk-UESQNU4A.js → chunk-BEXQSEBI.js} +11 -10
- package/dist/chunk-EO7IVVX4.js +443 -0
- package/dist/{chunk-OSWQPSA4.js → chunk-HP3J6JEY.js} +13 -4
- package/dist/{chunk-L7L4RGKG.js → chunk-IEUNV3XI.js} +2 -2
- package/dist/{chunk-U7DLMVPZ.js → chunk-K5JNR3HL.js} +2 -1
- package/dist/chunk-ON6JYPE7.js +103 -0
- package/dist/chunk-SN3ODJZN.js +63 -0
- package/dist/chunk-ZHB44IEA.js +784 -0
- package/dist/cli.js +569 -146
- package/dist/{config-NPSYFAFD.js → config-PBEZ3IGD.js} +2 -2
- package/dist/integrationCli-ZTZSZLO7.js +586 -0
- package/dist/{login-KHU7ONSR.js → login-QPH44MM5.js} +1 -1
- package/dist/{mcpCli-Q2H63ISP.js → mcpCli-J43FHF2H.js} +11 -10
- package/dist/providerCli-GQCLV6X2.js +12 -0
- package/dist/{ui-A32PTF4F.js → ui-SRXPL7KU.js} +773 -232
- package/package.json +5 -1
- 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
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
instructionPaths,
|
|
6
6
|
parseFrontmatter,
|
|
7
7
|
toolLabel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HP3J6JEY.js";
|
|
9
9
|
import {
|
|
10
10
|
nextMode
|
|
11
11
|
} from "./chunk-ELML4KVQ.js";
|
|
@@ -13,24 +13,35 @@ import {
|
|
|
13
13
|
hasKey,
|
|
14
14
|
providerName,
|
|
15
15
|
writeGlobalConfig
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-K5JNR3HL.js";
|
|
17
|
+
import {
|
|
18
|
+
REASONING_LEVELS,
|
|
19
|
+
assertReasoningSupported,
|
|
20
|
+
parseReasoningLevel,
|
|
21
|
+
supportedReasoningLevels
|
|
22
|
+
} from "./chunk-SN3ODJZN.js";
|
|
17
23
|
import {
|
|
18
24
|
Session
|
|
19
25
|
} from "./chunk-SJH3YLW4.js";
|
|
20
26
|
import {
|
|
21
27
|
CATALOG,
|
|
22
28
|
fillTemplate
|
|
23
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-BEXQSEBI.js";
|
|
24
30
|
import {
|
|
25
31
|
addGlobalServer,
|
|
26
32
|
globalMcpPath,
|
|
27
33
|
readMcpFile,
|
|
28
34
|
removeGlobalServer
|
|
29
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-44MXWCFN.js";
|
|
30
36
|
import {
|
|
31
37
|
PRESETS,
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
getLanguage,
|
|
39
|
+
locale,
|
|
40
|
+
parseLang,
|
|
41
|
+
removeCredential,
|
|
42
|
+
setLanguage,
|
|
43
|
+
t
|
|
44
|
+
} from "./chunk-EO7IVVX4.js";
|
|
34
45
|
|
|
35
46
|
// src/ui/index.tsx
|
|
36
47
|
import { render } from "ink";
|
|
@@ -45,30 +56,32 @@ import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } f
|
|
|
45
56
|
|
|
46
57
|
// src/commands.ts
|
|
47
58
|
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: "
|
|
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" }
|
|
72
85
|
];
|
|
73
86
|
function parseSlash(text) {
|
|
74
87
|
if (!text.startsWith("/")) return void 0;
|
|
@@ -102,8 +115,8 @@ function git(cwd, args, bin = "git") {
|
|
|
102
115
|
}
|
|
103
116
|
async function gitDiff(cwd, maxLines = 400, bin = "git") {
|
|
104
117
|
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 "
|
|
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.");
|
|
107
120
|
const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
108
121
|
const [stat, diff, untracked] = await Promise.all([
|
|
109
122
|
git(cwd, ["diff", "--stat", base]),
|
|
@@ -111,23 +124,23 @@ async function gitDiff(cwd, maxLines = 400, bin = "git") {
|
|
|
111
124
|
git(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
112
125
|
]);
|
|
113
126
|
const fresh = untracked.out.split("\n").filter(Boolean);
|
|
114
|
-
if (!diff.out.trim() && !fresh.length) return "
|
|
127
|
+
if (!diff.out.trim() && !fresh.length) return t("No uncommitted changes.");
|
|
115
128
|
const lines = diff.out.trimEnd().split("\n");
|
|
116
129
|
const more = lines.length > maxLines ? `
|
|
117
|
-
\u2026
|
|
118
|
-
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");
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
// src/slash/doctor.ts
|
|
122
135
|
async function doctorText(input, deps) {
|
|
123
136
|
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}` :
|
|
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 }));
|
|
126
139
|
const cmds = ["rg", "git", "npx", "uvx"];
|
|
127
140
|
const [connection, found] = await Promise.all([
|
|
128
141
|
deps.listModels().then(
|
|
129
|
-
(models) =>
|
|
130
|
-
(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 })
|
|
131
144
|
),
|
|
132
145
|
Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
|
|
133
146
|
]);
|
|
@@ -158,31 +171,31 @@ function statusText(s) {
|
|
|
158
171
|
return [
|
|
159
172
|
`BCCLI ${s.version}`,
|
|
160
173
|
`Model: ${s.modelRef} (${s.providerLabel})`,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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) })
|
|
166
179
|
].join("\n");
|
|
167
180
|
}
|
|
168
181
|
function agentsText(defs) {
|
|
169
|
-
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");
|
|
170
183
|
}
|
|
171
184
|
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
|
|
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")}:
|
|
175
188
|
${s}
|
|
176
189
|
|
|
177
|
-
|
|
190
|
+
${t("Custom commands")}:
|
|
178
191
|
${c}`;
|
|
179
192
|
}
|
|
180
193
|
function sessionText(s) {
|
|
181
194
|
return [
|
|
182
|
-
|
|
183
|
-
|
|
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) }),
|
|
184
197
|
`Model: ${s.modelRef}`,
|
|
185
|
-
|
|
198
|
+
t("File: {file}", { file: s.file })
|
|
186
199
|
].join("\n");
|
|
187
200
|
}
|
|
188
201
|
|
|
@@ -202,7 +215,308 @@ function parseMemoryArgs(args) {
|
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
// src/ui/Markdown.tsx
|
|
205
|
-
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
|
+
}
|
|
206
520
|
|
|
207
521
|
// src/ui/theme.ts
|
|
208
522
|
function color(name) {
|
|
@@ -211,58 +525,136 @@ function color(name) {
|
|
|
211
525
|
var ACCENT = "green";
|
|
212
526
|
|
|
213
527
|
// 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
|
-
|
|
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
|
+
}
|
|
225
546
|
}
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
var bulletFor = (spaces) => BULLETS[spaces === 0 ? 0 : spaces <= 4 ? 1 : 2];
|
|
555
|
+
function hanging(key, marker, body, width, padding = 0) {
|
|
556
|
+
const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
|
|
557
|
+
return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
|
|
558
|
+
/* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
|
|
559
|
+
/* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
|
|
560
|
+
] }, key);
|
|
561
|
+
}
|
|
562
|
+
function Markdown({ text, indent = 2 }) {
|
|
563
|
+
const { stdout } = useStdout();
|
|
564
|
+
const width = Math.max(20, (stdout?.columns ?? 80) - indent - 1);
|
|
565
|
+
const lines = text.split("\n");
|
|
228
566
|
const rows = [];
|
|
229
|
-
|
|
567
|
+
let inCode = false;
|
|
568
|
+
for (let i = 0; i < lines.length; i++) {
|
|
569
|
+
const line = lines[i];
|
|
230
570
|
if (line.trimStart().startsWith("```")) {
|
|
231
571
|
inCode = !inCode;
|
|
232
|
-
|
|
572
|
+
continue;
|
|
233
573
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
574
|
+
if (inCode) {
|
|
575
|
+
rows.push(
|
|
576
|
+
/* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
|
|
577
|
+
);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const parsed = parseTable(lines, i);
|
|
581
|
+
if (parsed) {
|
|
582
|
+
layoutTable(parsed.table, width).forEach((tableLine, n) => {
|
|
583
|
+
rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
|
|
584
|
+
});
|
|
585
|
+
i = parsed.end - 1;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const heading = HEADING.exec(line);
|
|
589
|
+
if (heading) {
|
|
590
|
+
const level = heading[1].length;
|
|
591
|
+
rows.push(
|
|
592
|
+
/* @__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)
|
|
593
|
+
);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (RULE.test(line)) {
|
|
597
|
+
rows.push(
|
|
598
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
|
|
599
|
+
);
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
const quote = QUOTE.exec(line);
|
|
603
|
+
if (quote) {
|
|
604
|
+
const depth = quote[1].replace(/\s/g, "").length;
|
|
605
|
+
rows.push(hanging(i, "\u258E".repeat(depth), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const bullet = BULLET.exec(line);
|
|
609
|
+
if (bullet) {
|
|
610
|
+
const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
|
|
611
|
+
const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
|
|
612
|
+
rows.push(
|
|
613
|
+
task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, bulletFor(padding), inline(bullet[2]), width, padding)
|
|
614
|
+
);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const numbered = NUMBERED.exec(line);
|
|
618
|
+
if (numbered) {
|
|
619
|
+
const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
|
|
620
|
+
rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
|
|
624
|
+
}
|
|
238
625
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
|
|
239
626
|
}
|
|
240
627
|
|
|
241
628
|
// src/ui/LinePrompt.tsx
|
|
242
629
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
243
630
|
import { useState } from "react";
|
|
244
|
-
|
|
631
|
+
|
|
632
|
+
// src/ui/focusReport.ts
|
|
633
|
+
var isFocusReport = (input) => input === "[I" || input === "[O";
|
|
634
|
+
|
|
635
|
+
// src/ui/LinePrompt.tsx
|
|
636
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
245
637
|
function LinePrompt({ label, mask, onSubmit, onCancel }) {
|
|
246
638
|
const [value, setValue] = useState("");
|
|
247
639
|
useInput((input, key) => {
|
|
248
640
|
if (key.return) onSubmit(value.trim());
|
|
249
641
|
else if (key.escape) onCancel();
|
|
250
642
|
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, ""));
|
|
643
|
+
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !isFocusReport(input)) setValue((v) => v + input.replace(/[\r\n]/g, ""));
|
|
252
644
|
});
|
|
253
645
|
const shown = mask ? "\u2022".repeat(value.length) : value;
|
|
254
|
-
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */
|
|
646
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
255
647
|
`${label}: `,
|
|
256
648
|
shown,
|
|
257
649
|
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
|
|
258
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter
|
|
650
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
|
|
259
651
|
] }) });
|
|
260
652
|
}
|
|
261
653
|
|
|
262
654
|
// src/ui/ListPicker.tsx
|
|
263
655
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
264
656
|
import { useState as useState2 } from "react";
|
|
265
|
-
import { jsx as jsx3, jsxs as
|
|
657
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
266
658
|
var VISIBLE = 12;
|
|
267
659
|
function ListPicker({ title, items, onPick }) {
|
|
268
660
|
const [filter, setFilter] = useState2("");
|
|
@@ -272,31 +664,31 @@ function ListPicker({ title, items, onPick }) {
|
|
|
272
664
|
useInput2((input, key) => {
|
|
273
665
|
if (key.upArrow) setIndex(Math.max(0, cursor - 1));
|
|
274
666
|
else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
|
|
275
|
-
else if (key.return) onPick(shown[cursor]?.id);
|
|
667
|
+
else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
|
|
276
668
|
else if (key.escape) onPick(void 0);
|
|
277
669
|
else if (key.backspace || key.delete) {
|
|
278
670
|
setFilter((f) => f.slice(0, -1));
|
|
279
671
|
setIndex(0);
|
|
280
|
-
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
672
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
|
|
281
673
|
setFilter((f) => f + input);
|
|
282
674
|
setIndex(0);
|
|
283
675
|
}
|
|
284
676
|
});
|
|
285
677
|
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__ */
|
|
678
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
679
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
|
|
680
|
+
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
681
|
`${start + i === cursor ? "\u203A" : " "} ${item.label}`,
|
|
290
682
|
item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
|
|
291
683
|
] }, item.id)),
|
|
292
|
-
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "
|
|
684
|
+
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("No matches.") })
|
|
293
685
|
] });
|
|
294
686
|
}
|
|
295
687
|
|
|
296
688
|
// src/ui/ModelPicker.tsx
|
|
297
689
|
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
298
690
|
import { useState as useState3 } from "react";
|
|
299
|
-
import { jsx as jsx4, jsxs as
|
|
691
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
300
692
|
var VISIBLE2 = 14;
|
|
301
693
|
function ModelPicker({ groups, current, onPick }) {
|
|
302
694
|
const [filter, setFilter] = useState3("");
|
|
@@ -322,18 +714,18 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
322
714
|
else if (key.backspace || key.delete) {
|
|
323
715
|
setFilter((f) => f.slice(0, -1));
|
|
324
716
|
setIndex(0);
|
|
325
|
-
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
717
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
|
|
326
718
|
setFilter((f) => f + input);
|
|
327
719
|
setIndex(0);
|
|
328
720
|
}
|
|
329
721
|
});
|
|
330
722
|
const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
|
|
331
|
-
return /* @__PURE__ */
|
|
332
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children:
|
|
723
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
724
|
+
/* @__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
725
|
rows.slice(start, start + VISIBLE2).map(
|
|
334
726
|
(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
727
|
),
|
|
336
|
-
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "
|
|
728
|
+
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matching models.") }) : null,
|
|
337
729
|
groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
|
|
338
730
|
] });
|
|
339
731
|
}
|
|
@@ -341,7 +733,7 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
341
733
|
// src/ui/ProviderMenu.tsx
|
|
342
734
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
343
735
|
import { useState as useState4 } from "react";
|
|
344
|
-
import { jsx as jsx5, jsxs as
|
|
736
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
345
737
|
function ProviderMenu({ entries, onPick }) {
|
|
346
738
|
const rows = [...entries.map((e) => e.id), "custom"];
|
|
347
739
|
const [index, setIndex] = useState4(0);
|
|
@@ -351,9 +743,9 @@ function ProviderMenu({ entries, onPick }) {
|
|
|
351
743
|
else if (key.return) onPick(rows[index]);
|
|
352
744
|
else if (key.escape) onPick(void 0);
|
|
353
745
|
});
|
|
354
|
-
return /* @__PURE__ */
|
|
355
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc
|
|
356
|
-
entries.map((e, i) => /* @__PURE__ */
|
|
746
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
747
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
|
|
748
|
+
entries.map((e, i) => /* @__PURE__ */ jsxs5(Text5, { color: i === index ? color("green") : void 0, children: [
|
|
357
749
|
`${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
|
|
358
750
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
|
|
359
751
|
] }, e.id)),
|
|
@@ -376,8 +768,8 @@ function DiffView({ diff }) {
|
|
|
376
768
|
}
|
|
377
769
|
|
|
378
770
|
// src/ui/PermissionPrompt.tsx
|
|
379
|
-
import { jsx as jsx7, jsxs as
|
|
380
|
-
var VERB = { edit: "edit", write: "
|
|
771
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
772
|
+
var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
|
|
381
773
|
function PermissionPrompt({
|
|
382
774
|
request,
|
|
383
775
|
onAnswer
|
|
@@ -390,16 +782,16 @@ function PermissionPrompt({
|
|
|
390
782
|
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
391
783
|
});
|
|
392
784
|
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}] ` : ""}
|
|
785
|
+
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(", ") });
|
|
786
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
787
|
+
/* @__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
788
|
request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
|
|
397
|
-
/* @__PURE__ */
|
|
398
|
-
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y]
|
|
789
|
+
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
790
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: t("[y] yes") }),
|
|
399
791
|
" ",
|
|
400
792
|
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]
|
|
793
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: t("[s] yes to all ") }),
|
|
794
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("red"), children: t("[n] no") })
|
|
403
795
|
] })
|
|
404
796
|
] });
|
|
405
797
|
}
|
|
@@ -422,7 +814,7 @@ function completeFile(value, cwd) {
|
|
|
422
814
|
}
|
|
423
815
|
|
|
424
816
|
// src/ui/PromptInput.tsx
|
|
425
|
-
import { jsx as jsx8, jsxs as
|
|
817
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
426
818
|
function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
427
819
|
const [value, setValue] = useState5("");
|
|
428
820
|
const [historyIndex, setHistoryIndex] = useState5(null);
|
|
@@ -491,18 +883,18 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
|
491
883
|
edit((v) => completeFile(v, cwd));
|
|
492
884
|
return;
|
|
493
885
|
}
|
|
494
|
-
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
|
|
886
|
+
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow || isFocusReport(input)) return;
|
|
495
887
|
edit((v) => v + input.replace(/\r/g, "\n"));
|
|
496
888
|
},
|
|
497
889
|
{ isActive: !disabled }
|
|
498
890
|
);
|
|
499
|
-
return /* @__PURE__ */
|
|
500
|
-
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */
|
|
891
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
892
|
+
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
501
893
|
/* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
|
|
502
894
|
value,
|
|
503
895
|
disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
|
|
504
896
|
] }) }),
|
|
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))
|
|
897
|
+
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
898
|
] });
|
|
507
899
|
}
|
|
508
900
|
|
|
@@ -514,11 +906,11 @@ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
|
514
906
|
function Spinner({ label, startedAt }) {
|
|
515
907
|
const [tick, setTick] = useState6(0);
|
|
516
908
|
useEffect(() => {
|
|
517
|
-
const id = setInterval(() => setTick((
|
|
909
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), 120);
|
|
518
910
|
return () => clearInterval(id);
|
|
519
911
|
}, []);
|
|
520
912
|
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
521
|
-
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (
|
|
913
|
+
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
|
|
522
914
|
}
|
|
523
915
|
|
|
524
916
|
// src/ui/StatusBar.tsx
|
|
@@ -533,15 +925,15 @@ var LABEL = {
|
|
|
533
925
|
function formatTokens(n) {
|
|
534
926
|
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
535
927
|
}
|
|
536
|
-
function StatusBar({ mode, tokens, busy, model }) {
|
|
928
|
+
function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
|
|
537
929
|
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
|
|
930
|
+
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
931
|
}
|
|
540
932
|
|
|
541
933
|
// src/ui/McpMenu.tsx
|
|
542
934
|
import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
|
|
543
935
|
import { useState as useState7 } from "react";
|
|
544
|
-
import { jsx as jsx11, jsxs as
|
|
936
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
545
937
|
var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
|
|
546
938
|
function McpMenu({ items, onPick }) {
|
|
547
939
|
const [index, setIndex] = useState7(0);
|
|
@@ -551,9 +943,9 @@ function McpMenu({ items, onPick }) {
|
|
|
551
943
|
else if (key.return) onPick(items[index]?.name);
|
|
552
944
|
else if (key.escape) onPick(void 0);
|
|
553
945
|
});
|
|
554
|
-
return /* @__PURE__ */
|
|
555
|
-
/* @__PURE__ */ jsx11(Text11, { bold: true, children: "
|
|
556
|
-
items.map((item, i) => /* @__PURE__ */
|
|
946
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
947
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
|
|
948
|
+
items.map((item, i) => /* @__PURE__ */ jsxs8(Text11, { color: i === index ? color("green") : void 0, children: [
|
|
557
949
|
`${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
|
|
558
950
|
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
|
|
559
951
|
] }, item.name))
|
|
@@ -562,7 +954,7 @@ function McpMenu({ items, onPick }) {
|
|
|
562
954
|
|
|
563
955
|
// src/ui/PlanApproval.tsx
|
|
564
956
|
import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
|
|
565
|
-
import { jsx as jsx12, jsxs as
|
|
957
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
566
958
|
function PlanApproval({ plan, onAnswer }) {
|
|
567
959
|
useInput8((input, key) => {
|
|
568
960
|
const k2 = input.toLowerCase();
|
|
@@ -570,15 +962,15 @@ function PlanApproval({ plan, onAnswer }) {
|
|
|
570
962
|
else if (k2 === "y") onAnswer("default");
|
|
571
963
|
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
572
964
|
});
|
|
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]
|
|
965
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
966
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: t("Plan from the agent") }),
|
|
967
|
+
/* @__PURE__ */ jsx12(Markdown, { text: plan, indent: 5 }),
|
|
968
|
+
/* @__PURE__ */ jsxs9(Text12, { children: [
|
|
969
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("green"), children: t("[a] yes, auto-edit") }),
|
|
578
970
|
" ",
|
|
579
|
-
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y]
|
|
971
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
|
|
580
972
|
" ",
|
|
581
|
-
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n]
|
|
973
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: t("[n] not yet, revise first") })
|
|
582
974
|
] })
|
|
583
975
|
] });
|
|
584
976
|
}
|
|
@@ -588,34 +980,34 @@ import { Box as Box11, Text as Text13 } from "ink";
|
|
|
588
980
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
589
981
|
var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
|
|
590
982
|
function TodoList({ items }) {
|
|
591
|
-
if (!items.length || items.every((
|
|
592
|
-
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((
|
|
983
|
+
if (!items.length || items.every((t2) => t2.status === "completed")) return null;
|
|
984
|
+
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
985
|
}
|
|
594
986
|
|
|
595
987
|
// src/ui/ToolBlock.tsx
|
|
596
988
|
import { Box as Box12, Text as Text14 } from "ink";
|
|
597
|
-
import { jsx as jsx14, jsxs as
|
|
989
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
598
990
|
var PREVIEW_LINES = 5;
|
|
599
991
|
function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
|
|
600
992
|
const isTask = tool === "task";
|
|
601
993
|
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
602
994
|
const lines = (output ?? "").split("\n");
|
|
603
995
|
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
/* @__PURE__ */
|
|
996
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 2, children: [
|
|
997
|
+
/* @__PURE__ */ jsxs10(Text14, { children: [
|
|
606
998
|
/* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
607
999
|
/* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
|
|
608
1000
|
/* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
|
|
609
1001
|
display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
|
|
610
1002
|
] }),
|
|
611
|
-
sub?.length ? /* @__PURE__ */
|
|
1003
|
+
sub?.length ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
612
1004
|
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:
|
|
1005
|
+
sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
|
|
614
1006
|
] }) : 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__ */
|
|
1007
|
+
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,
|
|
1008
|
+
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
1009
|
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:
|
|
1010
|
+
lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} more lines (ctrl+o)", { n: lines.length - shown.length }) }) : null
|
|
619
1011
|
] }) : null
|
|
620
1012
|
] });
|
|
621
1013
|
}
|
|
@@ -624,73 +1016,105 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
|
|
|
624
1016
|
var nextId = 0;
|
|
625
1017
|
var entry = (e) => ({ ...e, id: nextId++ });
|
|
626
1018
|
var NOTICES = {
|
|
627
|
-
compacted: ["
|
|
628
|
-
stepLimit: ['
|
|
629
|
-
aborted: ["
|
|
1019
|
+
compacted: ["Conversation summarized to fit the model context.", "info"],
|
|
1020
|
+
stepLimit: ['Step limit of 50 reached. Type "continue" to keep going.', "warn"],
|
|
1021
|
+
aborted: ["Cancelled.", "warn"]
|
|
630
1022
|
};
|
|
631
|
-
|
|
632
|
-
|
|
1023
|
+
var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
|
|
1024
|
+
function endTurn(t2) {
|
|
1025
|
+
return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
|
|
633
1026
|
}
|
|
634
|
-
|
|
1027
|
+
var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
|
|
1028
|
+
function finishedCut(text) {
|
|
1029
|
+
let inFence = false;
|
|
1030
|
+
let pos = 0;
|
|
1031
|
+
let cut = -1;
|
|
1032
|
+
const lines = text.split("\n");
|
|
1033
|
+
for (const line of lines.slice(0, -1)) {
|
|
1034
|
+
if (TOOL_MARKUP.test(line)) break;
|
|
1035
|
+
if (line.trimStart().startsWith("```")) inFence = !inFence;
|
|
1036
|
+
const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
|
|
1037
|
+
pos += line.length + 1;
|
|
1038
|
+
if (!inFence && !tableRow) cut = pos - 1;
|
|
1039
|
+
}
|
|
1040
|
+
return cut;
|
|
1041
|
+
}
|
|
1042
|
+
function flushLines(t2) {
|
|
1043
|
+
const last = t2.live.at(-1);
|
|
1044
|
+
if (last?.kind !== "assistant") return t2;
|
|
1045
|
+
const cut = finishedCut(last.text);
|
|
1046
|
+
if (cut < 0) return t2;
|
|
1047
|
+
const chunk = last.text.slice(0, cut);
|
|
1048
|
+
if (!last.cont && !chunk.trim()) return t2;
|
|
1049
|
+
const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
|
|
1050
|
+
return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
|
|
1051
|
+
}
|
|
1052
|
+
function applyEvent(t2, event) {
|
|
635
1053
|
switch (event.type) {
|
|
636
1054
|
case "text": {
|
|
637
|
-
const last =
|
|
638
|
-
if (last?.kind === "assistant") return { ...
|
|
639
|
-
return { ...
|
|
1055
|
+
const last = t2.live.at(-1);
|
|
1056
|
+
if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
|
|
1057
|
+
return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
|
|
1058
|
+
}
|
|
1059
|
+
case "thinking": {
|
|
1060
|
+
const last = t2.live.at(-1);
|
|
1061
|
+
if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
1062
|
+
return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
|
|
640
1063
|
}
|
|
641
1064
|
case "textReplace": {
|
|
642
|
-
const last =
|
|
643
|
-
if (last?.kind !== "assistant") return
|
|
644
|
-
return { ...
|
|
1065
|
+
const last = t2.live.at(-1);
|
|
1066
|
+
if (last?.kind !== "assistant") return t2;
|
|
1067
|
+
return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
|
|
645
1068
|
}
|
|
646
1069
|
case "toolStart": {
|
|
647
|
-
const running =
|
|
1070
|
+
const running = t2.live.some((e) => e.kind === "tool" && !e.done);
|
|
648
1071
|
const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
|
|
649
|
-
return running ? { ...
|
|
1072
|
+
return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
|
|
650
1073
|
}
|
|
651
1074
|
case "toolEnd": {
|
|
652
|
-
const live =
|
|
1075
|
+
const live = t2.live.map(
|
|
653
1076
|
(e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
|
|
654
1077
|
);
|
|
655
1078
|
const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
|
|
656
1079
|
const cut = firstRunning === -1 ? live.length : firstRunning;
|
|
657
|
-
return { done: [...
|
|
1080
|
+
return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
|
|
658
1081
|
}
|
|
659
1082
|
case "subagent": {
|
|
660
1083
|
const inner = event.event;
|
|
661
|
-
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return
|
|
1084
|
+
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
|
|
662
1085
|
const update = (e) => {
|
|
663
1086
|
if (e.kind !== "tool" || e.callId !== event.parentId) return e;
|
|
664
1087
|
const sub = e.sub ?? [];
|
|
665
1088
|
if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
|
|
666
1089
|
return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
|
|
667
1090
|
};
|
|
668
|
-
return { ...
|
|
1091
|
+
return { ...t2, live: t2.live.map(update) };
|
|
669
1092
|
}
|
|
670
1093
|
case "error":
|
|
671
|
-
return { ...
|
|
1094
|
+
return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
672
1095
|
default: {
|
|
673
1096
|
const notice = NOTICES[event.type];
|
|
674
|
-
return notice ? { ...
|
|
1097
|
+
return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
|
|
675
1098
|
}
|
|
676
1099
|
}
|
|
677
1100
|
}
|
|
678
1101
|
|
|
679
1102
|
// src/ui/App.tsx
|
|
680
|
-
import { jsx as jsx15, jsxs as
|
|
1103
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
681
1104
|
function helpText(runtime) {
|
|
682
1105
|
const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
|
|
683
1106
|
return [
|
|
684
|
-
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
|
|
685
|
-
...custom.length ? ["", "
|
|
686
|
-
...runtime.skills.length ? ["", "
|
|
1107
|
+
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
|
|
1108
|
+
...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
|
|
1109
|
+
...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
|
|
687
1110
|
"",
|
|
688
|
-
"shift+tab
|
|
689
|
-
"ctrl+o output
|
|
690
|
-
"
|
|
1111
|
+
t("shift+tab switch permission mode esc cancel the turn"),
|
|
1112
|
+
t("ctrl+o full output of the last tool \\ + enter new line"),
|
|
1113
|
+
t("ctrl+t show/hide the model\u2019s thinking"),
|
|
1114
|
+
t("@file + tab complete file names \u2191\u2193 input history")
|
|
691
1115
|
].join("\n");
|
|
692
1116
|
}
|
|
693
|
-
var INIT_PROMPT = "
|
|
1117
|
+
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
1118
|
var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
|
|
695
1119
|
function App({ runtime, initialPrompt, version }) {
|
|
696
1120
|
const { exit } = useApp();
|
|
@@ -698,6 +1122,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
698
1122
|
const [busy, setBusy] = useState8(false);
|
|
699
1123
|
const [startedAt, setStartedAt] = useState8(0);
|
|
700
1124
|
const [mode, setMode] = useState8(runtime.agent.permissions.mode);
|
|
1125
|
+
const [reasoning, setReasoning] = useState8(runtime.reasoning);
|
|
701
1126
|
const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
702
1127
|
const [asks, setAsks] = useState8([]);
|
|
703
1128
|
const pending = asks[0] ?? null;
|
|
@@ -713,15 +1138,17 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
713
1138
|
const [mcpMenu, setMcpMenu] = useState8(null);
|
|
714
1139
|
const [listPicker, setListPicker] = useState8(null);
|
|
715
1140
|
const [planAsk, setPlanAsk] = useState8(null);
|
|
1141
|
+
const [queued, setQueued] = useState8([]);
|
|
1142
|
+
const [showThinking, setShowThinking] = useState8(false);
|
|
716
1143
|
useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
|
|
717
1144
|
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:
|
|
1145
|
+
...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") })),
|
|
1146
|
+
...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
1147
|
];
|
|
721
1148
|
const lastTool = useRef(null);
|
|
722
1149
|
const toolTargets = useRef(/* @__PURE__ */ new Map());
|
|
723
1150
|
const notice = useCallback((text, tone = "info") => {
|
|
724
|
-
setTranscript((
|
|
1151
|
+
setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
|
|
725
1152
|
}, []);
|
|
726
1153
|
const onEvent = useCallback((event) => {
|
|
727
1154
|
if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
|
|
@@ -729,7 +1156,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
729
1156
|
lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
|
|
730
1157
|
}
|
|
731
1158
|
if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
|
|
732
|
-
setTranscript((
|
|
1159
|
+
setTranscript((t2) => applyEvent(t2, event));
|
|
733
1160
|
}, []);
|
|
734
1161
|
useEffect2(() => {
|
|
735
1162
|
runtime.agent.onEvent = onEvent;
|
|
@@ -738,7 +1165,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
738
1165
|
}, [runtime, onEvent]);
|
|
739
1166
|
const runTurn = useCallback(
|
|
740
1167
|
async (text) => {
|
|
741
|
-
setTranscript((
|
|
1168
|
+
setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
|
|
742
1169
|
setBusy(true);
|
|
743
1170
|
setStartedAt(Date.now());
|
|
744
1171
|
controller.current = new AbortController();
|
|
@@ -761,11 +1188,11 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
761
1188
|
case "clear":
|
|
762
1189
|
runtime.agent.clear();
|
|
763
1190
|
setTokens(0);
|
|
764
|
-
notice("
|
|
1191
|
+
notice(t("Conversation cleared."));
|
|
765
1192
|
return;
|
|
766
1193
|
case "cost": {
|
|
767
1194
|
const u = runtime.agent.totalUsage;
|
|
768
|
-
notice(
|
|
1195
|
+
notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
|
|
769
1196
|
return;
|
|
770
1197
|
}
|
|
771
1198
|
case "compact": {
|
|
@@ -775,19 +1202,54 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
775
1202
|
try {
|
|
776
1203
|
await runtime.agent.compact(controller.current.signal);
|
|
777
1204
|
} catch (error) {
|
|
778
|
-
notice(
|
|
1205
|
+
notice(t("Could not summarize: {error}", { error: error.message }), "error");
|
|
779
1206
|
}
|
|
780
1207
|
controller.current = null;
|
|
781
1208
|
setBusy(false);
|
|
782
1209
|
return;
|
|
783
1210
|
}
|
|
1211
|
+
case "reasoning": {
|
|
1212
|
+
const apply = (value) => {
|
|
1213
|
+
try {
|
|
1214
|
+
const level = parseReasoningLevel(value, "/reasoning");
|
|
1215
|
+
const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1216
|
+
assertReasoningSupported(providerId2, level);
|
|
1217
|
+
runtime.setReasoning(level);
|
|
1218
|
+
writeGlobalConfig({ reasoning: level }, runtime.env);
|
|
1219
|
+
setReasoning(level);
|
|
1220
|
+
notice(t("Reasoning: {level} (saved as default)", { level }));
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
notice(error.message, "error");
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
if (args) {
|
|
1226
|
+
apply(args);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1230
|
+
const supported = supportedReasoningLevels(providerId);
|
|
1231
|
+
setListPicker({
|
|
1232
|
+
title: "Reasoning",
|
|
1233
|
+
items: REASONING_LEVELS.map((level) => ({
|
|
1234
|
+
id: level,
|
|
1235
|
+
label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
|
|
1236
|
+
disabled: !supported.includes(level),
|
|
1237
|
+
hint: supported.includes(level) ? void 0 : t("not supported by this provider")
|
|
1238
|
+
})),
|
|
1239
|
+
onPick: (id) => {
|
|
1240
|
+
setListPicker(null);
|
|
1241
|
+
if (id) apply(id);
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
784
1246
|
case "model": {
|
|
785
1247
|
if (args) {
|
|
786
1248
|
try {
|
|
787
1249
|
runtime.setModel(args);
|
|
788
1250
|
writeGlobalConfig({ model: args }, runtime.env);
|
|
789
1251
|
setModelLabel(labelFor(runtime, args));
|
|
790
|
-
notice(
|
|
1252
|
+
notice(t("Model: {model} (saved as default)", { model: args }));
|
|
791
1253
|
} catch (error) {
|
|
792
1254
|
notice(error.message, "error");
|
|
793
1255
|
}
|
|
@@ -796,7 +1258,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
796
1258
|
try {
|
|
797
1259
|
setPicker(await runtime.listModels());
|
|
798
1260
|
} catch (error) {
|
|
799
|
-
notice(
|
|
1261
|
+
notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
|
|
800
1262
|
}
|
|
801
1263
|
return;
|
|
802
1264
|
}
|
|
@@ -804,7 +1266,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
804
1266
|
const installed = readMcpFile(globalMcpPath(runtime.home));
|
|
805
1267
|
const states = runtime.mcp.states();
|
|
806
1268
|
const items = [
|
|
807
|
-
...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
|
|
1269
|
+
...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
|
|
808
1270
|
...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
|
|
809
1271
|
].map((i) => {
|
|
810
1272
|
const s = states.find((st) => st.name === i.name);
|
|
@@ -816,7 +1278,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
816
1278
|
case "new":
|
|
817
1279
|
runtime.newSession();
|
|
818
1280
|
setTokens(0);
|
|
819
|
-
notice("
|
|
1281
|
+
notice(t("New session started. The previous session stays saved (/resume)."));
|
|
820
1282
|
return;
|
|
821
1283
|
case "session":
|
|
822
1284
|
notice(
|
|
@@ -832,15 +1294,15 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
832
1294
|
case "resume": {
|
|
833
1295
|
const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
|
|
834
1296
|
if (!others.length) {
|
|
835
|
-
notice("
|
|
1297
|
+
notice(t("No other sessions in this folder yet."));
|
|
836
1298
|
return;
|
|
837
1299
|
}
|
|
838
1300
|
setListPicker({
|
|
839
|
-
title: "
|
|
1301
|
+
title: t("Continue a session"),
|
|
840
1302
|
items: others.map((s) => ({
|
|
841
1303
|
id: s.session.file,
|
|
842
|
-
label: `${s.mtime.toLocaleString(
|
|
843
|
-
hint:
|
|
1304
|
+
label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
|
|
1305
|
+
hint: t("{n} messages", { n: s.session.load().length })
|
|
844
1306
|
})),
|
|
845
1307
|
onPick: (id) => {
|
|
846
1308
|
setListPicker(null);
|
|
@@ -848,12 +1310,23 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
848
1310
|
if (!chosen) return;
|
|
849
1311
|
runtime.resume(chosen.session);
|
|
850
1312
|
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
|
-
)
|
|
1313
|
+
const chat = runtime.agent.messages.filter(
|
|
1314
|
+
(m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
|
|
856
1315
|
);
|
|
1316
|
+
setTranscript({
|
|
1317
|
+
done: [
|
|
1318
|
+
entry({ kind: "header" }),
|
|
1319
|
+
entry({
|
|
1320
|
+
kind: "notice",
|
|
1321
|
+
text: t("Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).", { messages: runtime.agent.messages.length, chat: chat.length }),
|
|
1322
|
+
tone: "info"
|
|
1323
|
+
}),
|
|
1324
|
+
...chat.map(
|
|
1325
|
+
(m) => entry(m.role === "user" ? { kind: "user", text: m.content } : { kind: "assistant", text: m.content })
|
|
1326
|
+
)
|
|
1327
|
+
],
|
|
1328
|
+
live: []
|
|
1329
|
+
});
|
|
857
1330
|
}
|
|
858
1331
|
});
|
|
859
1332
|
return;
|
|
@@ -879,7 +1352,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
879
1352
|
notice(skillsText(runtime.skills, runtime.commands));
|
|
880
1353
|
return;
|
|
881
1354
|
case "doctor": {
|
|
882
|
-
notice("
|
|
1355
|
+
notice(t("Checking\u2026"));
|
|
883
1356
|
const c = runtime.config;
|
|
884
1357
|
notice(
|
|
885
1358
|
await doctorText(
|
|
@@ -900,20 +1373,20 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
900
1373
|
case "permissions": {
|
|
901
1374
|
const rules = runtime.agent.permissions.list();
|
|
902
1375
|
if (!rules.length) {
|
|
903
|
-
notice("
|
|
1376
|
+
notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
|
|
904
1377
|
return;
|
|
905
1378
|
}
|
|
906
1379
|
setListPicker({
|
|
907
|
-
title: "
|
|
908
|
-
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "
|
|
1380
|
+
title: t("Active permissions (enter: revoke a session permission)"),
|
|
1381
|
+
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
|
|
909
1382
|
onPick: (id) => {
|
|
910
1383
|
setListPicker(null);
|
|
911
1384
|
const rule = rules.find((r) => r.rule === id);
|
|
912
1385
|
if (!rule) return;
|
|
913
|
-
if (rule.source === "config") notice(
|
|
1386
|
+
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
1387
|
else {
|
|
915
1388
|
runtime.agent.permissions.revoke(rule.rule);
|
|
916
|
-
notice(
|
|
1389
|
+
notice(t("Permission {rule} revoked.", { rule: rule.rule }));
|
|
917
1390
|
}
|
|
918
1391
|
}
|
|
919
1392
|
});
|
|
@@ -922,12 +1395,12 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
922
1395
|
case "memory": {
|
|
923
1396
|
if (!args) {
|
|
924
1397
|
const files = instructionFiles(runtime.cwd, runtime.home);
|
|
925
|
-
notice(files.length ? files.map((f) =>
|
|
1398
|
+
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
1399
|
return;
|
|
927
1400
|
}
|
|
928
1401
|
const { global, text } = parseMemoryArgs(args);
|
|
929
1402
|
if (!text) {
|
|
930
|
-
notice("
|
|
1403
|
+
notice(t("Write the text: /memory global <text>."), "warn");
|
|
931
1404
|
return;
|
|
932
1405
|
}
|
|
933
1406
|
const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
|
|
@@ -935,19 +1408,38 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
935
1408
|
mkdirSync(dirname(file), { recursive: true });
|
|
936
1409
|
appendMemory(file, text);
|
|
937
1410
|
} catch (error) {
|
|
938
|
-
notice(
|
|
1411
|
+
notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
|
|
939
1412
|
return;
|
|
940
1413
|
}
|
|
941
1414
|
runtime.rebuildSystemPrompt();
|
|
942
|
-
notice(
|
|
1415
|
+
notice(t("Added to {file}.", { file }));
|
|
943
1416
|
return;
|
|
944
1417
|
}
|
|
945
1418
|
case "init":
|
|
946
1419
|
void runTurn(INIT_PROMPT);
|
|
947
1420
|
return;
|
|
1421
|
+
case "language": {
|
|
1422
|
+
if (!args) {
|
|
1423
|
+
notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const wanted = parseLang(args);
|
|
1427
|
+
if (!wanted) {
|
|
1428
|
+
notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
setLanguage(wanted);
|
|
1432
|
+
try {
|
|
1433
|
+
writeGlobalConfig({ language: wanted }, runtime.env);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
notice(t("Could not save the language: {error}", { error: error.message }), "error");
|
|
1436
|
+
}
|
|
1437
|
+
notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
948
1440
|
case "login": {
|
|
949
1441
|
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
950
|
-
const key = await ask(
|
|
1442
|
+
const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
|
|
951
1443
|
if (!key) return;
|
|
952
1444
|
const result = await runtime.addProviderKey(id, key);
|
|
953
1445
|
if (!result.ok) {
|
|
@@ -957,10 +1449,10 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
957
1449
|
runtime.setModel(runtime.modelRef);
|
|
958
1450
|
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
959
1451
|
if (envName && runtime.env[envName]) {
|
|
960
|
-
notice(
|
|
1452
|
+
notice(t("Key for {provider} saved, but {env} in the environment takes precedence and is still used.", { provider: runtime.providerLabel(), env: envName }), "warn");
|
|
961
1453
|
return;
|
|
962
1454
|
}
|
|
963
|
-
notice(
|
|
1455
|
+
notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
|
|
964
1456
|
return;
|
|
965
1457
|
}
|
|
966
1458
|
case "logout": {
|
|
@@ -969,13 +1461,13 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
969
1461
|
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
970
1462
|
if (envName && runtime.env[envName]) {
|
|
971
1463
|
notice(
|
|
972
|
-
|
|
1464
|
+
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
1465
|
"warn"
|
|
974
1466
|
);
|
|
975
1467
|
return;
|
|
976
1468
|
}
|
|
977
1469
|
if (!removed) {
|
|
978
|
-
notice(
|
|
1470
|
+
notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
|
|
979
1471
|
return;
|
|
980
1472
|
}
|
|
981
1473
|
try {
|
|
@@ -986,7 +1478,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
986
1478
|
};
|
|
987
1479
|
runtime.agent.provider = { chat: fail, listModels: fail };
|
|
988
1480
|
}
|
|
989
|
-
notice(
|
|
1481
|
+
notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
|
|
990
1482
|
return;
|
|
991
1483
|
}
|
|
992
1484
|
case "diff":
|
|
@@ -995,17 +1487,17 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
995
1487
|
case "undo": {
|
|
996
1488
|
const result = await runtime.checkpoints.undo();
|
|
997
1489
|
if (!result) {
|
|
998
|
-
notice("
|
|
1490
|
+
notice(t("No file edits to undo."));
|
|
999
1491
|
return;
|
|
1000
1492
|
}
|
|
1001
1493
|
const rel = (p) => relative(runtime.cwd, p) || p;
|
|
1002
1494
|
notice(
|
|
1003
1495
|
[
|
|
1004
|
-
result.failed.length ?
|
|
1005
|
-
result.restored.length ?
|
|
1006
|
-
result.deleted.length ?
|
|
1007
|
-
result.skipped.length ?
|
|
1008
|
-
"
|
|
1496
|
+
result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
|
|
1497
|
+
result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
|
|
1498
|
+
result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
|
|
1499
|
+
result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
|
|
1500
|
+
t("Note: changes made through bash commands cannot be undone.")
|
|
1009
1501
|
].filter(Boolean).join("\n")
|
|
1010
1502
|
);
|
|
1011
1503
|
return;
|
|
@@ -1013,33 +1505,33 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1013
1505
|
case "copy": {
|
|
1014
1506
|
const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
|
|
1015
1507
|
if (!last || typeof last.content !== "string") {
|
|
1016
|
-
notice("
|
|
1508
|
+
notice(t("No answer to copy yet."));
|
|
1017
1509
|
return;
|
|
1018
1510
|
}
|
|
1019
1511
|
process.stdout.write(osc52(last.content, !!process.env.TMUX));
|
|
1020
1512
|
const file = join(runtime.home, "last-answer.md");
|
|
1021
1513
|
mkdirSync(runtime.home, { recursive: true });
|
|
1022
1514
|
writeFileSync(file, last.content);
|
|
1023
|
-
notice(
|
|
1515
|
+
notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
|
|
1024
1516
|
return;
|
|
1025
1517
|
}
|
|
1026
1518
|
case "export": {
|
|
1027
1519
|
const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
|
|
1028
1520
|
const file = resolve(runtime.cwd, name2);
|
|
1029
1521
|
if (existsSync2(file)) {
|
|
1030
|
-
const answer2 = await ask(
|
|
1522
|
+
const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
|
|
1031
1523
|
if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
|
|
1032
|
-
notice("
|
|
1524
|
+
notice(t("Export cancelled."));
|
|
1033
1525
|
return;
|
|
1034
1526
|
}
|
|
1035
1527
|
}
|
|
1036
1528
|
try {
|
|
1037
|
-
writeFileSync(file, exportMarkdown(runtime.agent.messages,
|
|
1529
|
+
writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
|
|
1038
1530
|
} catch (error) {
|
|
1039
|
-
notice(
|
|
1531
|
+
notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
|
|
1040
1532
|
return;
|
|
1041
1533
|
}
|
|
1042
|
-
notice(
|
|
1534
|
+
notice(t("Conversation saved to {file}.", { file }));
|
|
1043
1535
|
return;
|
|
1044
1536
|
}
|
|
1045
1537
|
case "provider": {
|
|
@@ -1050,7 +1542,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1050
1542
|
return;
|
|
1051
1543
|
}
|
|
1052
1544
|
default:
|
|
1053
|
-
notice(
|
|
1545
|
+
notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
|
|
1054
1546
|
}
|
|
1055
1547
|
},
|
|
1056
1548
|
[runtime, notice, exit, version, runTurn, ask]
|
|
@@ -1076,7 +1568,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1076
1568
|
try {
|
|
1077
1569
|
body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
|
|
1078
1570
|
} catch (error) {
|
|
1079
|
-
notice(
|
|
1571
|
+
notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
|
|
1080
1572
|
return;
|
|
1081
1573
|
}
|
|
1082
1574
|
void runTurn(`${body}
|
|
@@ -1086,6 +1578,19 @@ ARGUMENTS: ${slash.args}`);
|
|
|
1086
1578
|
},
|
|
1087
1579
|
[runSlash, runTurn, runtime, notice]
|
|
1088
1580
|
);
|
|
1581
|
+
const onPrompt = useCallback(
|
|
1582
|
+
(text) => {
|
|
1583
|
+
if (busy) setQueued((q) => [...q, text]);
|
|
1584
|
+
else submit(text);
|
|
1585
|
+
},
|
|
1586
|
+
[busy, submit]
|
|
1587
|
+
);
|
|
1588
|
+
useEffect2(() => {
|
|
1589
|
+
if (busy || pending || planAsk || !queued.length) return;
|
|
1590
|
+
const [next, ...rest] = queued;
|
|
1591
|
+
setQueued(rest);
|
|
1592
|
+
submit(next);
|
|
1593
|
+
}, [busy, pending, planAsk, queued, submit]);
|
|
1089
1594
|
const initialSent = useRef(false);
|
|
1090
1595
|
useEffect2(() => {
|
|
1091
1596
|
if (initialPrompt && !initialSent.current) {
|
|
@@ -1100,10 +1605,30 @@ ARGUMENTS: ${slash.args}`);
|
|
|
1100
1605
|
setMode(next);
|
|
1101
1606
|
} else if (key.escape && busy && !pending) {
|
|
1102
1607
|
controller.current?.abort();
|
|
1608
|
+
if (queued.length) {
|
|
1609
|
+
setQueued([]);
|
|
1610
|
+
notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
|
|
1611
|
+
}
|
|
1612
|
+
} else if (key.ctrl && input === "t") {
|
|
1613
|
+
const next = !showThinking;
|
|
1614
|
+
const toggled = next ? t("Thinking shown (ctrl+t to hide).") : t("Thinking hidden (ctrl+t to show).");
|
|
1615
|
+
setShowThinking(next);
|
|
1616
|
+
setTranscript((t2) => {
|
|
1617
|
+
const last = [...t2.done, ...t2.live].findLast((e) => e.kind === "thinking");
|
|
1618
|
+
const reprint = next && last?.kind === "thinking" && t2.done.includes(last);
|
|
1619
|
+
return {
|
|
1620
|
+
...t2,
|
|
1621
|
+
done: [
|
|
1622
|
+
...t2.done,
|
|
1623
|
+
...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
|
|
1624
|
+
entry({ kind: "notice", text: toggled, tone: "info" })
|
|
1625
|
+
]
|
|
1626
|
+
};
|
|
1627
|
+
});
|
|
1103
1628
|
} else if (key.ctrl && input === "o" && lastTool.current) {
|
|
1104
|
-
const
|
|
1105
|
-
notice(`\u23BF ${
|
|
1106
|
-
${
|
|
1629
|
+
const t2 = lastTool.current;
|
|
1630
|
+
notice(`\u23BF ${t2.tool} ${t2.target}
|
|
1631
|
+
${t2.output}`);
|
|
1107
1632
|
}
|
|
1108
1633
|
});
|
|
1109
1634
|
const pickMcp = async (name) => {
|
|
@@ -1113,24 +1638,24 @@ ${t.output}`);
|
|
|
1113
1638
|
if (item.installed) {
|
|
1114
1639
|
removeGlobalServer(runtime.home, item.name);
|
|
1115
1640
|
await runtime.mcp.remove(item.name);
|
|
1116
|
-
notice(
|
|
1641
|
+
notice(t("MCP {name} removed.", { name: item.name }));
|
|
1117
1642
|
return;
|
|
1118
1643
|
}
|
|
1119
1644
|
const entry2 = CATALOG.find((c) => c.name === item.name);
|
|
1120
1645
|
if (!entry2) return;
|
|
1121
1646
|
const values = {};
|
|
1122
1647
|
for (const input of entry2.inputs ?? []) {
|
|
1123
|
-
const v = await ask(input.label, !!input.secret);
|
|
1648
|
+
const v = await ask(t(input.label), !!input.secret);
|
|
1124
1649
|
if (!v) return;
|
|
1125
1650
|
values[input.key] = v;
|
|
1126
1651
|
}
|
|
1127
1652
|
const config = fillTemplate(entry2.config, values);
|
|
1128
1653
|
addGlobalServer(runtime.home, entry2.name, config);
|
|
1129
|
-
notice(
|
|
1654
|
+
notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
|
|
1130
1655
|
await runtime.mcp.add({ name: entry2.name, config, source: "global" });
|
|
1131
1656
|
const state = runtime.mcp.states().find((s) => s.name === entry2.name);
|
|
1132
|
-
if (state?.status === "ready") notice(
|
|
1133
|
-
else notice(
|
|
1657
|
+
if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
|
|
1658
|
+
else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
|
|
1134
1659
|
};
|
|
1135
1660
|
const pickProvider = async (id) => {
|
|
1136
1661
|
setProviderMenu(null);
|
|
@@ -1147,7 +1672,7 @@ ${t.output}`);
|
|
|
1147
1672
|
const c = runtime.config;
|
|
1148
1673
|
const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
|
|
1149
1674
|
if (!hasKey(c, target, runtime.env) || id === "custom") {
|
|
1150
|
-
const key = keyless ? "" : await ask(
|
|
1675
|
+
const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
|
|
1151
1676
|
if (key === void 0) return;
|
|
1152
1677
|
const result = await runtime.addProviderKey(target, key);
|
|
1153
1678
|
if (!result.ok) {
|
|
@@ -1167,7 +1692,7 @@ ${t.output}`);
|
|
|
1167
1692
|
const renderEntry = (e) => {
|
|
1168
1693
|
switch (e.kind) {
|
|
1169
1694
|
case "header":
|
|
1170
|
-
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */
|
|
1695
|
+
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Text15, { children: [
|
|
1171
1696
|
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
1172
1697
|
/* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
|
|
1173
1698
|
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
|
|
@@ -1175,21 +1700,36 @@ ${t.output}`);
|
|
|
1175
1700
|
case "user":
|
|
1176
1701
|
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
1177
1702
|
case "assistant":
|
|
1703
|
+
if (e.cont) {
|
|
1704
|
+
return e.text ? /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Markdown, { text: e.text }) }, e.id) : null;
|
|
1705
|
+
}
|
|
1178
1706
|
if (!e.text.trim()) return null;
|
|
1179
|
-
return /* @__PURE__ */
|
|
1707
|
+
return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, children: [
|
|
1180
1708
|
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
|
|
1181
1709
|
/* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
1182
1710
|
] }, e.id);
|
|
1711
|
+
case "thinking": {
|
|
1712
|
+
const text = e.text.trim();
|
|
1713
|
+
if (!text) return null;
|
|
1714
|
+
if (!(e.open ?? showThinking)) {
|
|
1715
|
+
const lines = text.split("\n").length;
|
|
1716
|
+
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);
|
|
1717
|
+
}
|
|
1718
|
+
return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1719
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking (ctrl+t to hide)") }),
|
|
1720
|
+
/* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
|
|
1721
|
+
] }, e.id);
|
|
1722
|
+
}
|
|
1183
1723
|
case "tool":
|
|
1184
1724
|
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
1725
|
case "notice":
|
|
1186
1726
|
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
1727
|
}
|
|
1188
1728
|
};
|
|
1189
|
-
return /* @__PURE__ */
|
|
1729
|
+
return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", children: [
|
|
1190
1730
|
/* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
|
|
1191
1731
|
transcript.live.map(renderEntry),
|
|
1192
|
-
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "
|
|
1732
|
+
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: t("Thinking"), startedAt }) }) : null,
|
|
1193
1733
|
pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
|
|
1194
1734
|
picker ? /* @__PURE__ */ jsx15(
|
|
1195
1735
|
ModelPicker,
|
|
@@ -1232,8 +1772,9 @@ ${t.output}`);
|
|
|
1232
1772
|
}
|
|
1233
1773
|
) : null,
|
|
1234
1774
|
/* @__PURE__ */ jsx15(TodoList, { items: todos }),
|
|
1235
|
-
/* @__PURE__ */ jsx15(
|
|
1236
|
-
/* @__PURE__ */ jsx15(
|
|
1775
|
+
queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
|
|
1776
|
+
/* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
|
|
1777
|
+
/* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
|
|
1237
1778
|
] });
|
|
1238
1779
|
}
|
|
1239
1780
|
|
|
@@ -1243,27 +1784,27 @@ async function startInteractive(rt, opts) {
|
|
|
1243
1784
|
if (opts.resume) {
|
|
1244
1785
|
const { Session: Session2 } = await import("./session-7UB77CEG.js");
|
|
1245
1786
|
const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
|
|
1246
|
-
if (!list.length) console.log("
|
|
1787
|
+
if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
|
|
1247
1788
|
else {
|
|
1248
1789
|
list.forEach((s, i) => {
|
|
1249
|
-
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(
|
|
1790
|
+
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
|
|
1250
1791
|
});
|
|
1251
1792
|
const { createInterface } = await import("readline/promises");
|
|
1252
1793
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1253
|
-
const answer = Number(await rl.question("
|
|
1794
|
+
const answer = Number(await rl.question(t("Session number (enter = new): ")));
|
|
1254
1795
|
rl.close();
|
|
1255
1796
|
const chosen = list[answer - 1];
|
|
1256
1797
|
if (chosen) rt.resume(chosen.session);
|
|
1257
1798
|
}
|
|
1258
1799
|
}
|
|
1259
|
-
const { serversToStart, setProjectTrust } = await import("./config-
|
|
1800
|
+
const { serversToStart, setProjectTrust } = await import("./config-PBEZ3IGD.js");
|
|
1260
1801
|
const plan = serversToStart(rt.home, rt.cwd);
|
|
1261
1802
|
if (plan.needTrust.length) {
|
|
1262
1803
|
const { createInterface } = await import("readline/promises");
|
|
1263
1804
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1264
1805
|
for (const s of plan.needTrust) {
|
|
1265
1806
|
const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
|
|
1266
|
-
const answer = (await rl.question(
|
|
1807
|
+
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
1808
|
setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
|
|
1268
1809
|
if (answer === "y") plan.start.push(s);
|
|
1269
1810
|
}
|