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