@botconnector/bccli 0.4.0-beta.5 → 0.4.0-beta.51

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.
Files changed (58) hide show
  1. package/README.md +97 -2
  2. package/dist/acp-NWP4FFTG.js +182 -0
  3. package/dist/args-2R45YZ42.js +13 -0
  4. package/dist/{chunk-U7DLMVPZ.js → chunk-2QYHSESB.js} +2 -1
  5. package/dist/{chunk-UESQNU4A.js → chunk-4WVIPZFV.js} +14 -9
  6. package/dist/chunk-7F3IWBPQ.js +191 -0
  7. package/dist/chunk-DP7EBW53.js +341 -0
  8. package/dist/{chunk-SJH3YLW4.js → chunk-F6ACN47V.js} +60 -6
  9. package/dist/chunk-FTFVW7XD.js +235 -0
  10. package/dist/{chunk-ELML4KVQ.js → chunk-GAFJEQTJ.js} +32 -2
  11. package/dist/{chunk-NGLKANMD.js → chunk-H73FO5YK.js} +24 -2
  12. package/dist/chunk-KD2LMIS4.js +145 -0
  13. package/dist/chunk-KIZC5XV4.js +2141 -0
  14. package/dist/chunk-N7KP6RNP.js +76 -0
  15. package/dist/chunk-NDEJUL44.js +361 -0
  16. package/dist/chunk-PSO7R5AD.js +10 -0
  17. package/dist/chunk-QXOV4DUZ.js +56 -0
  18. package/dist/chunk-QZKLZELZ.js +64 -0
  19. package/dist/chunk-R4BJBTWC.js +940 -0
  20. package/dist/chunk-SNKVS26H.js +63 -0
  21. package/dist/chunk-TQWCM5RO.js +231 -0
  22. package/dist/chunk-VU5HNQHX.js +8 -0
  23. package/dist/chunk-XLC6T4SX.js +500 -0
  24. package/dist/chunk-YIYX3FFS.js +733 -0
  25. package/dist/cli.js +157 -1281
  26. package/dist/completion-OV3MW2N4.js +125 -0
  27. package/dist/{config-NPSYFAFD.js → config-AZZFOXKV.js} +5 -2
  28. package/dist/config-H5A7XHHK.js +30 -0
  29. package/dist/ghBotCli-7WDYF4L7.js +677 -0
  30. package/dist/integrationCli-ZMTSILYF.js +685 -0
  31. package/dist/login-W4Y6WHLV.js +16 -0
  32. package/dist/{mcpCli-Q2H63ISP.js → mcpCli-5QISTOFK.js} +42 -9
  33. package/dist/modelsCli-FZMBD4OE.js +36 -0
  34. package/dist/npmRunner-LU43GM3W.js +46 -0
  35. package/dist/oauthLogin-VESMXG4M.js +210 -0
  36. package/dist/pluginsCli-GEHR7FQR.js +284 -0
  37. package/dist/prCli-JXA7DJW4.js +72 -0
  38. package/dist/providerCli-EUZGKP5F.js +15 -0
  39. package/dist/sdk.js +5486 -0
  40. package/dist/serve-6KX4MXHC.js +178 -0
  41. package/dist/serve-YSUJD57D.js +515 -0
  42. package/dist/session-F6MG3UCU.js +12 -0
  43. package/dist/setup-LLWREJQH.js +24 -0
  44. package/dist/statsCli-H4HEY5ID.js +353 -0
  45. package/dist/transferCli-2XUKBG2L.js +71 -0
  46. package/dist/ui-6HJVYI46.js +2370 -0
  47. package/dist/update-O4WCLKOO.js +109 -0
  48. package/package.json +24 -3
  49. package/dist/args-FCLPQ7TO.js +0 -11
  50. package/dist/chunk-5O4HD7IU.js +0 -253
  51. package/dist/chunk-C5AFWX7H.js +0 -328
  52. package/dist/chunk-GW36UMHZ.js +0 -52
  53. package/dist/chunk-RZUJYAQV.js +0 -112
  54. package/dist/chunk-UN7QXPCB.js +0 -82
  55. package/dist/login-F4G5QXOU.js +0 -9
  56. package/dist/providerCli-MXV6PD6F.js +0 -11
  57. package/dist/session-7UB77CEG.js +0 -9
  58. package/dist/ui-RYTSKEIA.js +0 -1344
@@ -0,0 +1,2370 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CATALOG,
4
+ fillTemplate
5
+ } from "./chunk-4WVIPZFV.js";
6
+ import {
7
+ MAX_VERIFY_ROUNDS,
8
+ runVerify,
9
+ toolLabel,
10
+ verifyFollowup,
11
+ worktreeListText
12
+ } from "./chunk-TQWCM5RO.js";
13
+ import {
14
+ BUILTIN_AGENTS,
15
+ appendMemory,
16
+ ignorePatterns,
17
+ instructionFiles,
18
+ normalizeGlobPath,
19
+ parseFrontmatter,
20
+ parseMemoryArgs,
21
+ renderBackgroundTasks,
22
+ runHooks,
23
+ validateGlobPattern
24
+ } from "./chunk-YIYX3FFS.js";
25
+ import "./chunk-PSO7R5AD.js";
26
+ import {
27
+ nextMode
28
+ } from "./chunk-GAFJEQTJ.js";
29
+ import {
30
+ hasKey,
31
+ providerName,
32
+ writeGlobalConfig
33
+ } from "./chunk-2QYHSESB.js";
34
+ import {
35
+ Session
36
+ } from "./chunk-F6ACN47V.js";
37
+ import {
38
+ contentText
39
+ } from "./chunk-R4BJBTWC.js";
40
+ import {
41
+ REASONING_LEVELS,
42
+ assertReasoningSupported,
43
+ parseReasoningLevel,
44
+ supportedReasoningLevels
45
+ } from "./chunk-SNKVS26H.js";
46
+ import {
47
+ addGlobalServer,
48
+ globalMcpPath,
49
+ readMcpFile,
50
+ removeGlobalServer
51
+ } from "./chunk-H73FO5YK.js";
52
+ import {
53
+ ACCENT,
54
+ PRESETS,
55
+ color,
56
+ matchKeybind,
57
+ removeCredential
58
+ } from "./chunk-NDEJUL44.js";
59
+ import {
60
+ getLanguage,
61
+ locale,
62
+ parseLang,
63
+ setLanguage,
64
+ t
65
+ } from "./chunk-XLC6T4SX.js";
66
+
67
+ // src/ui/index.tsx
68
+ import { render } from "ink";
69
+
70
+ // src/ui/App.tsx
71
+ import { execFile as execFile3 } from "child_process";
72
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
73
+ import { homedir, tmpdir } from "os";
74
+ import { dirname, join as join3, relative, resolve } from "path";
75
+ import { Box as Box13, Static, Text as Text16, measureElement, useApp, useInput as useInput9, useStdin, useStdout as useStdout2 } from "ink";
76
+ import { useCallback, useEffect as useEffect3, useRef, useState as useState8 } from "react";
77
+
78
+ // src/commands.ts
79
+ var SLASH_COMMANDS = [
80
+ { name: "help", description: "list of commands and shortcuts" },
81
+ { name: "model", description: "change model" },
82
+ { name: "reasoning", description: "set reasoning: auto/off/low/medium/high/max" },
83
+ { name: "provider", description: "add/choose an AI provider" },
84
+ { name: "mcp", description: "install/manage MCP servers" },
85
+ { name: "new", description: "new session (the old one stays saved)" },
86
+ { name: "resume", description: "continue another session in this folder" },
87
+ { name: "session", description: "info about this session" },
88
+ { name: "status", description: "version, model, permission mode, MCP, context" },
89
+ { name: "permissions", description: "view/revoke permissions" },
90
+ { name: "undo", description: "undo the file edits of the last turn" },
91
+ { name: "redo", description: "re-apply the file edits that /undo just reverted" },
92
+ { name: "rewind", description: "step back turns: conversation and file edits (/rewind 2)" },
93
+ { name: "diff", description: "git diff of the project" },
94
+ { name: "worktree", description: "list git worktrees; create one with bccli -w <name>" },
95
+ { name: "pr", description: "review a pull request (/pr <n|url|branch>)" },
96
+ { name: "copy", description: "copy the last answer" },
97
+ { name: "export", description: "save the conversation as markdown" },
98
+ { name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
99
+ { name: "init", description: "create/update AGENTS.md for this project" },
100
+ { name: "agents", description: "list subagents" },
101
+ { name: "skills", description: "list skills and custom commands" },
102
+ { name: "doctor", description: "check installation health" },
103
+ { name: "login", description: "save the API key of the current provider" },
104
+ { name: "logout", description: "delete the API key of the current provider" },
105
+ { name: "language", description: "set the interface language: en/id" },
106
+ { name: "clear", description: "start a new conversation" },
107
+ { name: "compact", description: "summarize the conversation" },
108
+ { name: "cost", description: "token usage of this session" },
109
+ { name: "tasks", description: "list background tasks" },
110
+ { name: "exit", description: "quit" }
111
+ ];
112
+ function parseSlash(text) {
113
+ if (!text.startsWith("/")) return void 0;
114
+ const [name, ...rest] = text.slice(1).trim().split(/\s+/);
115
+ return { name: name.toLowerCase(), args: rest.join(" ") };
116
+ }
117
+ function expandCommand(def, args) {
118
+ if (def.body.includes("$ARGUMENTS")) return def.body.replaceAll("$ARGUMENTS", args);
119
+ return args ? `${def.body}
120
+
121
+ ${args}` : def.body;
122
+ }
123
+ function prReviewPrompt(selector) {
124
+ const target = selector.trim();
125
+ const refs = target ? `Use "${target}" as the PR selector: run gh pr view ${target} --json url,title,body,baseRefName,headRefName,files,additions,deletions and gh pr diff ${target}.` : "Start with `gh pr view --json url,title,baseRefName,headRefName` for the PR of the current branch, then fetch the change with `gh pr diff`. If the branch has no open PR, review its changes against the default branch instead: find the default branch (`gh repo view --json defaultBranchRef` or `git remote show origin`), then `git diff origin/<default-branch>...HEAD`.";
126
+ return `Review this pull request like a senior reviewer.
127
+ ${refs}
128
+ Read the changed files in the repository when the diff alone is not enough to judge the change.
129
+ Never change the repository state while reviewing: do not check out, switch, create, delete, merge or rebase branches, do not reset, stash, commit, pull, or fetch into the working tree, and do not write, edit or delete any file. The review is strictly read-only. To read a file as it exists in another revision, use read-only commands such as \`git show <ref>:<path>\` or \`gh pr diff\`.
130
+ Write the review as: a 2-3 sentence summary; concrete problems (bugs, security, race conditions, error handling), each with file:line and why it matters; missing or weak tests; smaller style/nit issues; then a final verdict \u2014 approve, request changes, or comment \u2014 with the single most important reason.`;
131
+ }
132
+
133
+ // src/slash/copy.ts
134
+ function osc52(text, inTmux) {
135
+ const seq = `\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`;
136
+ return inTmux ? `\x1BPtmux;${seq.replaceAll("\x1B", "\x1B\x1B")}\x1B\\` : seq;
137
+ }
138
+
139
+ // src/slash/diff.ts
140
+ import { execFile } from "child_process";
141
+ function git(cwd, args, bin = "git") {
142
+ return new Promise((resolve2) => {
143
+ execFile(
144
+ bin,
145
+ args,
146
+ { cwd, maxBuffer: 20 * 1024 * 1024 },
147
+ (error, stdout, stderr) => resolve2({ ok: !error, out: error ? stderr : stdout, missing: error?.code === "ENOENT" })
148
+ );
149
+ });
150
+ }
151
+ async function gitDiff(cwd, maxLines = 400, bin = "git") {
152
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
153
+ if (inside.missing) return t("git is not installed or not on PATH, so /diff cannot be used.");
154
+ if (!inside.ok || inside.out.trim() !== "true") return t("This folder is not a git repository, so there is no diff.");
155
+ const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
156
+ const [stat, diff, untracked] = await Promise.all([
157
+ git(cwd, ["diff", "--stat", base]),
158
+ git(cwd, ["diff", base]),
159
+ git(cwd, ["ls-files", "--others", "--exclude-standard"])
160
+ ]);
161
+ const fresh = untracked.out.split("\n").filter(Boolean);
162
+ if (!diff.out.trim() && !fresh.length) return t("No uncommitted changes.");
163
+ const lines = diff.out.trimEnd().split("\n");
164
+ const more = lines.length > maxLines ? `
165
+ ${t("\u2026 {n} more lines", { n: lines.length - maxLines })}` : "";
166
+ 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");
167
+ }
168
+
169
+ // src/slash/doctor.ts
170
+ async function doctorText(input, deps) {
171
+ const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
172
+ const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : t("\u2717 Node {version} (needs \u2265 22)", { version: deps.nodeVersion })];
173
+ for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : t("\u25CB {name} (no key yet)", { name: p.name }));
174
+ const cmds = ["rg", "git", "npx", "uvx"];
175
+ const [connection, found] = await Promise.all([
176
+ deps.listModels().then(
177
+ (models) => t("\u2713 Connection {provider} ({n} models)", { provider: input.activeProvider, n: models.length }),
178
+ (error) => t("\u2717 Connection {provider}: {error}", { provider: input.activeProvider, error: error.message })
179
+ ),
180
+ Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
181
+ ]);
182
+ lines.push(connection);
183
+ for (const m of input.mcp) lines.push(m.status === "error" ? `\u2717 MCP ${m.name}: ${m.error ?? ""}` : `\u2713 MCP ${m.name} (${m.status})`);
184
+ for (const [i, cmd] of cmds.entries()) lines.push(`${found[i] ? "\u2713" : "\u2717"} ${cmd}`);
185
+ return lines.join("\n");
186
+ }
187
+
188
+ // src/slash/export.ts
189
+ function exportMarkdown(messages, title) {
190
+ const parts = [`# ${title}`, ""];
191
+ for (const m of messages) {
192
+ if (m.role === "user") parts.push("## User", "", contentText(m.content), "");
193
+ else if (m.role === "assistant") {
194
+ for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
195
+ if (m.content) parts.push("## BCCLI", "", m.content, "");
196
+ }
197
+ }
198
+ return parts.join("\n");
199
+ }
200
+
201
+ // src/slash/info.ts
202
+ var k = (n) => n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
203
+ function statusText(s) {
204
+ const ready = s.mcp.filter((m) => m.status === "ready").length;
205
+ const errors = s.mcp.filter((m) => m.status === "error").length;
206
+ return [
207
+ `BCCLI ${s.version}`,
208
+ `Model: ${s.modelRef} (${s.providerLabel})`,
209
+ t("Permission mode: {mode}", { mode: s.mode }),
210
+ t("Folder: {cwd}", { cwd: s.cwd }),
211
+ t("MCP: {ready} active, {errors} errors", { ready, errors }),
212
+ t("Session tokens: {input} in \xB7 {output} out", { input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
213
+ `Cache: ${k(s.usage.cachedInputTokens ?? 0)} read \xB7 ${k(s.usage.cacheWriteTokens ?? 0)} write`,
214
+ t("Context: ~{pct}% of 128k", { pct: Math.round(s.lastInputTokens / 128e3 * 100) })
215
+ ].join("\n");
216
+ }
217
+ function agentsText(defs) {
218
+ 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");
219
+ }
220
+ function skillsText(skills, commands) {
221
+ const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : t("(no skills)");
222
+ const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : t("(no custom commands)");
223
+ return `${t("Skills")}:
224
+ ${s}
225
+
226
+ ${t("Custom commands")}:
227
+ ${c}`;
228
+ }
229
+ function sessionText(s) {
230
+ return [
231
+ t("Started: {when}", { when: s.started.toLocaleString(locale()) }),
232
+ t("{messages} messages \xB7 {input} tokens in \xB7 {output} out", { messages: s.messages, input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
233
+ `Model: ${s.modelRef}`,
234
+ t("File: {file}", { file: s.file })
235
+ ].join("\n");
236
+ }
237
+
238
+ // src/ui/Markdown.tsx
239
+ import { Box, Text, useStdout } from "ink";
240
+
241
+ // src/ui/table.ts
242
+ var WIDE = [
243
+ [4352, 4447],
244
+ [8986, 8987],
245
+ [9193, 9196],
246
+ [9200, 9200],
247
+ [9203, 9203],
248
+ [9725, 9726],
249
+ [9748, 9749],
250
+ [9800, 9811],
251
+ [9855, 9855],
252
+ [9875, 9875],
253
+ [9889, 9889],
254
+ [9898, 9899],
255
+ [9917, 9918],
256
+ [9924, 9925],
257
+ [9934, 9934],
258
+ [9940, 9940],
259
+ [9962, 9962],
260
+ [9970, 9971],
261
+ [9973, 9973],
262
+ [9978, 9978],
263
+ [9981, 9981],
264
+ [9989, 9989],
265
+ [9994, 9995],
266
+ [10024, 10024],
267
+ [10060, 10060],
268
+ [10062, 10062],
269
+ [10067, 10069],
270
+ [10071, 10071],
271
+ [10133, 10135],
272
+ [10160, 10160],
273
+ [10175, 10175],
274
+ [11035, 11036],
275
+ [11088, 11088],
276
+ [11093, 11093],
277
+ [11904, 42191],
278
+ [44032, 55203],
279
+ [63744, 64255],
280
+ [65072, 65135],
281
+ [65280, 65376],
282
+ [65504, 65510],
283
+ [127744, 128591],
284
+ [128640, 128767],
285
+ [129280, 129791],
286
+ [131072, 262141]
287
+ ];
288
+ var ZERO = [
289
+ [768, 879],
290
+ [6832, 6911],
291
+ [7616, 7679],
292
+ [8203, 8207],
293
+ [8400, 8447],
294
+ [65024, 65039],
295
+ [65279, 65279],
296
+ [917760, 917999]
297
+ ];
298
+ var inRanges = (cp, ranges) => ranges.some(([a, b]) => cp >= a && cp <= b);
299
+ function charWidth(cp) {
300
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
301
+ if (cp < 768) return 1;
302
+ if (inRanges(cp, ZERO)) return 0;
303
+ return inRanges(cp, WIDE) ? 2 : 1;
304
+ }
305
+ function displayWidth(text) {
306
+ let width = 0;
307
+ for (const ch of text) width += charWidth(ch.codePointAt(0));
308
+ return width;
309
+ }
310
+ var segsWidth = (segs) => segs.reduce((sum, s) => sum + displayWidth(s.text), 0);
311
+ var INLINE = /(`[^`]+`|\*\*[^*]+\*\*|~~[^~]+~~|!?\[[^\]\n]+\]\([^)\s]+\)|(?<![\w*])\*(?![\s*])[^*\n]+?(?<![\s*])\*(?![\w*])|(?<!\w)_(?![\s_])[^_\n]+?(?<![\s_])_(?!\w))/g;
312
+ function parseInline(text) {
313
+ const segs = [];
314
+ for (const part of text.split(INLINE)) {
315
+ if (part === "") continue;
316
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) segs.push({ text: part.slice(1, -1), style: "code" });
317
+ else if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) segs.push({ text: part.slice(2, -2), style: "bold" });
318
+ else if (part.length > 4 && part.startsWith("~~") && part.endsWith("~~")) segs.push({ text: part.slice(2, -2), style: "strike" });
319
+ else if (/^!?\[[^\]\n]+\]\([^)\s]+\)$/.test(part)) {
320
+ const m = /^(!?)\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
321
+ const [, image, label, url] = m;
322
+ segs.push({ text: label, style: image ? "plain" : "link" });
323
+ if (label !== url && !url.startsWith("#")) segs.push({ text: ` (${url})`, style: "dim" });
324
+ } else if (part.length > 2 && (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_"))) {
325
+ segs.push({ text: part.slice(1, -1), style: "italic" });
326
+ } else segs.push({ text: part, style: "plain" });
327
+ }
328
+ return segs;
329
+ }
330
+ function splitRow(line) {
331
+ let s = line.trim();
332
+ if (s.startsWith("|")) s = s.slice(1);
333
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
334
+ const cells = [];
335
+ let current = "";
336
+ for (let i = 0; i < s.length; i++) {
337
+ if (s[i] === "\\" && s[i + 1] === "|") {
338
+ current += "|";
339
+ i++;
340
+ } else if (s[i] === "|") {
341
+ cells.push(current);
342
+ current = "";
343
+ } else current += s[i];
344
+ }
345
+ cells.push(current);
346
+ return cells.map((c) => c.trim());
347
+ }
348
+ var SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
349
+ var isSeparator = (line) => line.includes("|") && SEPARATOR.test(line);
350
+ var cell = (raw) => parseInline(raw.replace(/<br\s*\/?>/gi, "\n"));
351
+ function parseTable(lines, start) {
352
+ if (start + 1 >= lines.length || !lines[start].includes("|") || !isSeparator(lines[start + 1])) return void 0;
353
+ const header = splitRow(lines[start]);
354
+ const sep = splitRow(lines[start + 1]);
355
+ if (sep.length !== header.length) return void 0;
356
+ const body = [];
357
+ let end = start + 2;
358
+ while (end < lines.length && lines[end].trim() && lines[end].includes("|")) body.push(splitRow(lines[end++]));
359
+ const columns = Math.max(header.length, ...body.map((r) => r.length));
360
+ const pad2 = (row, fill) => [...row, ...Array.from({ length: columns - row.length }, () => fill)];
361
+ const align = pad2(
362
+ sep.map((s) => s.startsWith(":") && s.endsWith(":") ? "center" : s.endsWith(":") ? "right" : "left"),
363
+ "left"
364
+ );
365
+ return { table: { header: pad2(header, "").map(cell), align, rows: body.map((r) => pad2(r, "").map(cell)) }, end };
366
+ }
367
+ function tokenize(segs) {
368
+ const tokens = [];
369
+ let word = [];
370
+ const endWord = () => {
371
+ if (word.length) tokens.push(word);
372
+ word = [];
373
+ };
374
+ for (const seg of segs) {
375
+ for (const piece of seg.text.match(/\n|[^\S\n]+|[^\s]+/g) ?? []) {
376
+ if (piece === "\n") {
377
+ endWord();
378
+ tokens.push("br");
379
+ } else if (/^\s+$/.test(piece)) endWord();
380
+ else word.push({ text: piece, style: seg.style });
381
+ }
382
+ }
383
+ endWord();
384
+ return tokens;
385
+ }
386
+ function splitWord(word, width) {
387
+ const pieces = [];
388
+ let current = [];
389
+ let used = 0;
390
+ for (const seg of word) {
391
+ let text = "";
392
+ for (const ch of seg.text) {
393
+ const w = charWidth(ch.codePointAt(0));
394
+ if (used + w > width && used > 0) {
395
+ if (text) current.push({ text, style: seg.style });
396
+ pieces.push(current);
397
+ current = [];
398
+ text = "";
399
+ used = 0;
400
+ }
401
+ text += ch;
402
+ used += w;
403
+ }
404
+ if (text) current.push({ text, style: seg.style });
405
+ }
406
+ if (current.length) pieces.push(current);
407
+ return pieces;
408
+ }
409
+ function wrap(tokens, width) {
410
+ const lines = [];
411
+ let line = [];
412
+ let used = 0;
413
+ const flush = () => {
414
+ lines.push(line);
415
+ line = [];
416
+ used = 0;
417
+ };
418
+ for (const token of tokens) {
419
+ if (token === "br") {
420
+ flush();
421
+ continue;
422
+ }
423
+ const w = segsWidth(token);
424
+ if (w > width) {
425
+ if (used > 0) flush();
426
+ const pieces = splitWord(token, width);
427
+ for (const piece of pieces.slice(0, -1)) {
428
+ line = piece;
429
+ flush();
430
+ }
431
+ line = pieces.at(-1) ?? [];
432
+ used = segsWidth(line);
433
+ } else if (used === 0) {
434
+ line = [...token];
435
+ used = w;
436
+ } else if (used + 1 + w <= width) {
437
+ line.push({ text: " ", style: "plain" }, ...token);
438
+ used += 1 + w;
439
+ } else {
440
+ flush();
441
+ line = [...token];
442
+ used = w;
443
+ }
444
+ }
445
+ flush();
446
+ return lines;
447
+ }
448
+ var SEP = " \u2502 ";
449
+ var RULE_JOIN = "\u2500\u253C\u2500";
450
+ var MAX_MIN_WIDTH = 12;
451
+ var dim = (text) => ({ text, style: "dim" });
452
+ function rtrim(line) {
453
+ const out = [...line];
454
+ while (out.length) {
455
+ const last = out[out.length - 1];
456
+ const text = last.text.trimEnd();
457
+ if (text) {
458
+ out[out.length - 1] = { ...last, text };
459
+ break;
460
+ }
461
+ out.pop();
462
+ }
463
+ return out;
464
+ }
465
+ var bold = (segs) => segs.map((s) => s.style === "plain" ? { ...s, style: "bold" } : s);
466
+ function pad(segs, width, align, last) {
467
+ const space = Math.max(0, width - segsWidth(segs));
468
+ const left = align === "right" ? space : align === "center" ? Math.floor(space / 2) : 0;
469
+ const right = last ? 0 : space - left;
470
+ return [...left ? [{ text: " ".repeat(left), style: "plain" }] : [], ...segs, ...right ? [{ text: " ".repeat(right), style: "plain" }] : []];
471
+ }
472
+ function columnWidths(table, available) {
473
+ const columns = table.align.length;
474
+ const all = [table.header, ...table.rows];
475
+ const natural = [];
476
+ const minimum = [];
477
+ for (let c = 0; c < columns; c++) {
478
+ let widest = 1;
479
+ let longestWord = 1;
480
+ for (const row of all) {
481
+ const tokens = tokenize(row[c]);
482
+ for (const line of wrap(tokens, Number.POSITIVE_INFINITY)) widest = Math.max(widest, segsWidth(line));
483
+ for (const token of tokens) if (token !== "br") longestWord = Math.max(longestWord, segsWidth(token));
484
+ }
485
+ natural.push(widest);
486
+ minimum.push(Math.min(longestWord, MAX_MIN_WIDTH, widest));
487
+ }
488
+ if (minimum.reduce((a, b) => a + b, 0) > available) return void 0;
489
+ const widths = [...natural];
490
+ let total = widths.reduce((a, b) => a + b, 0);
491
+ while (total > available) {
492
+ let pick = -1;
493
+ for (let c = 0; c < columns; c++) if (widths[c] > minimum[c] && (pick < 0 || widths[c] > widths[pick])) pick = c;
494
+ if (pick < 0) return void 0;
495
+ widths[pick]--;
496
+ total--;
497
+ }
498
+ return widths;
499
+ }
500
+ function stacked(table, width) {
501
+ const lines = [];
502
+ table.rows.forEach((row, r) => {
503
+ if (r > 0) lines.push([dim("\u2500".repeat(Math.min(width, 40)))]);
504
+ row.forEach((value, c) => {
505
+ const label = [...bold(table.header[c]), { text: ": ", style: "plain" }, ...value];
506
+ lines.push(...wrap(tokenize(label), width));
507
+ });
508
+ });
509
+ return lines.length ? lines : [bold(table.header.flat())];
510
+ }
511
+ function layoutTable(table, width) {
512
+ const columns = table.align.length;
513
+ const available = Math.max(columns, width - (columns - 1) * SEP.length);
514
+ const widths = columnWidths(table, available);
515
+ if (!widths) return stacked(table, Math.max(10, width));
516
+ const renderRow = (cells, header) => {
517
+ const wrapped = cells.map((segs, c) => wrap(tokenize(header ? bold(segs) : segs), widths[c]));
518
+ const height = Math.max(...wrapped.map((w) => w.length));
519
+ const lines = [];
520
+ for (let l = 0; l < height; l++) {
521
+ const line = [];
522
+ wrapped.forEach((w, c) => {
523
+ if (c > 0) line.push(dim(SEP));
524
+ line.push(...pad(w[l] ?? [], widths[c], table.align[c], c === columns - 1));
525
+ });
526
+ lines.push(rtrim(line));
527
+ }
528
+ return { lines, height };
529
+ };
530
+ const rule = [dim(widths.map((w) => "\u2500".repeat(w)).join(RULE_JOIN))];
531
+ const head = renderRow(table.header, true);
532
+ const body = table.rows.map((r) => renderRow(r, false));
533
+ const wrappedRows = body.some((b) => b.height > 1);
534
+ const out = [...head.lines, rule];
535
+ body.forEach((b, i) => {
536
+ if (wrappedRows && i > 0) out.push(rule);
537
+ out.push(...b.lines);
538
+ });
539
+ return out;
540
+ }
541
+
542
+ // src/ui/Markdown.tsx
543
+ import { jsx, jsxs } from "react/jsx-runtime";
544
+ function segment(seg, key) {
545
+ switch (seg.style) {
546
+ case "bold":
547
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: seg.text }, key);
548
+ case "italic":
549
+ return /* @__PURE__ */ jsx(Text, { italic: true, children: seg.text }, key);
550
+ case "strike":
551
+ return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: seg.text }, key);
552
+ case "link":
553
+ return /* @__PURE__ */ jsx(Text, { underline: true, color: color("cyan"), children: seg.text }, key);
554
+ case "code":
555
+ return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: seg.text }, key);
556
+ case "dim":
557
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: seg.text }, key);
558
+ default:
559
+ return seg.text;
560
+ }
561
+ }
562
+ var inline = (line) => parseInline(line).map(segment);
563
+ var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
564
+ var RULE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
565
+ var QUOTE = /^\s{0,3}((?:>\s?)+)(.*)$/;
566
+ var BULLET = /^(\s*)[-*+]\s+(.*)$/;
567
+ var NUMBERED = /^(\s*)(\d{1,9})[.)]\s+(.*)$/;
568
+ var BULLETS = ["\u2022", "\u25E6", "\u25AA"];
569
+ var bulletFor = (spaces) => BULLETS[spaces === 0 ? 0 : spaces <= 4 ? 1 : 2];
570
+ function hanging(key, marker, body, width, padding = 0) {
571
+ const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
572
+ return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
573
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
574
+ /* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
575
+ ] }, key);
576
+ }
577
+ function Markdown({ text, indent = 2 }) {
578
+ const { stdout } = useStdout();
579
+ const width = Math.max(20, (stdout?.columns ?? 80) - indent - 1);
580
+ const lines = text.split("\n");
581
+ const rows = [];
582
+ let inCode = false;
583
+ for (let i = 0; i < lines.length; i++) {
584
+ const line = lines[i];
585
+ if (line.trimStart().startsWith("```")) {
586
+ inCode = !inCode;
587
+ continue;
588
+ }
589
+ if (inCode) {
590
+ rows.push(
591
+ /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
592
+ );
593
+ continue;
594
+ }
595
+ const parsed = parseTable(lines, i);
596
+ if (parsed) {
597
+ layoutTable(parsed.table, width).forEach((tableLine, n) => {
598
+ rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
599
+ });
600
+ i = parsed.end - 1;
601
+ continue;
602
+ }
603
+ const heading = HEADING.exec(line);
604
+ if (heading) {
605
+ const level = heading[1].length;
606
+ rows.push(
607
+ /* @__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)
608
+ );
609
+ continue;
610
+ }
611
+ if (RULE.test(line)) {
612
+ rows.push(
613
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
614
+ );
615
+ continue;
616
+ }
617
+ const quote = QUOTE.exec(line);
618
+ if (quote) {
619
+ const depth = quote[1].replace(/\s/g, "").length;
620
+ rows.push(hanging(i, "\u258E".repeat(depth), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
621
+ continue;
622
+ }
623
+ const bullet = BULLET.exec(line);
624
+ if (bullet) {
625
+ const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
626
+ const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
627
+ rows.push(
628
+ task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, bulletFor(padding), inline(bullet[2]), width, padding)
629
+ );
630
+ continue;
631
+ }
632
+ const numbered = NUMBERED.exec(line);
633
+ if (numbered) {
634
+ const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
635
+ rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
636
+ continue;
637
+ }
638
+ rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
639
+ }
640
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
641
+ }
642
+
643
+ // src/ui/LinePrompt.tsx
644
+ import { Box as Box2, Text as Text3, useInput } from "ink";
645
+ import { useState } from "react";
646
+
647
+ // src/ui/EditableText.tsx
648
+ import { Text as Text2 } from "ink";
649
+
650
+ // src/ui/lineEdit.ts
651
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
652
+ function boundaries(text) {
653
+ const out = [];
654
+ for (const { index } of segmenter.segment(text)) out.push(index);
655
+ out.push(text.length);
656
+ return out;
657
+ }
658
+ function prevBoundary(text, pos) {
659
+ let previous = 0;
660
+ for (const b of boundaries(text)) {
661
+ if (b >= pos) return previous;
662
+ previous = b;
663
+ }
664
+ return previous;
665
+ }
666
+ function nextBoundary(text, pos) {
667
+ for (const b of boundaries(text)) if (b > pos) return b;
668
+ return text.length;
669
+ }
670
+ var isSpace = (ch) => /\s/.test(ch);
671
+ function wordLeft(text, pos) {
672
+ let i = pos;
673
+ while (i > 0 && isSpace(text[i - 1])) i--;
674
+ while (i > 0 && !isSpace(text[i - 1])) i--;
675
+ return i;
676
+ }
677
+ function wordRight(text, pos) {
678
+ let i = pos;
679
+ while (i < text.length && isSpace(text[i])) i++;
680
+ while (i < text.length && !isSpace(text[i])) i++;
681
+ return i;
682
+ }
683
+ var lineStart = (text, pos) => text.lastIndexOf("\n", pos - 1) + 1;
684
+ function lineEnd(text, pos) {
685
+ const i = text.indexOf("\n", pos);
686
+ return i === -1 ? text.length : i;
687
+ }
688
+ function position(text, pos) {
689
+ const before = text.slice(0, pos);
690
+ const start = lineStart(text, pos);
691
+ return { line: before.split("\n").length - 1, column: boundaries(text.slice(start, pos)).length - 1 };
692
+ }
693
+ function moveLine(text, pos, direction) {
694
+ const { line, column } = position(text, pos);
695
+ const lines = text.split("\n");
696
+ const target = line + direction;
697
+ if (target < 0 || target >= lines.length) return void 0;
698
+ let start = 0;
699
+ for (let i = 0; i < target; i++) start += lines[i].length + 1;
700
+ const chars = boundaries(lines[target]);
701
+ return start + chars[Math.min(column, chars.length - 1)];
702
+ }
703
+ var atEnd = (s) => s.cursor >= s.value.length;
704
+ function insert(s, text) {
705
+ return { value: s.value.slice(0, s.cursor) + text + s.value.slice(s.cursor), cursor: s.cursor + text.length };
706
+ }
707
+ function remove(s, from, to) {
708
+ return { value: s.value.slice(0, from) + s.value.slice(to), cursor: from };
709
+ }
710
+ function applyEditKey(s, input, key) {
711
+ const { value, cursor } = s;
712
+ const word = !!(key.ctrl || key.meta);
713
+ if (key.leftArrow) return { value, cursor: word ? wordLeft(value, cursor) : prevBoundary(value, cursor) };
714
+ if (key.rightArrow) return { value, cursor: word ? wordRight(value, cursor) : nextBoundary(value, cursor) };
715
+ if (key.home) return { value, cursor: lineStart(value, cursor) };
716
+ if (key.end) return { value, cursor: lineEnd(value, cursor) };
717
+ if (key.backspace) {
718
+ if (key.meta) return remove(s, wordLeft(value, cursor), cursor);
719
+ return cursor > 0 ? remove(s, prevBoundary(value, cursor), cursor) : s;
720
+ }
721
+ if (key.delete) {
722
+ if (atEnd(s)) return cursor > 0 ? remove(s, prevBoundary(value, cursor), cursor) : s;
723
+ return remove(s, cursor, nextBoundary(value, cursor));
724
+ }
725
+ if (key.ctrl) {
726
+ switch (input) {
727
+ case "a":
728
+ return { value, cursor: lineStart(value, cursor) };
729
+ case "e":
730
+ return { value, cursor: lineEnd(value, cursor) };
731
+ case "b":
732
+ return { value, cursor: prevBoundary(value, cursor) };
733
+ case "f":
734
+ return { value, cursor: nextBoundary(value, cursor) };
735
+ case "w":
736
+ return remove(s, wordLeft(value, cursor), cursor);
737
+ case "u":
738
+ return remove(s, lineStart(value, cursor), cursor);
739
+ case "k":
740
+ return remove(s, cursor, lineEnd(value, cursor));
741
+ }
742
+ return void 0;
743
+ }
744
+ if (key.meta) {
745
+ switch (input) {
746
+ case "b":
747
+ return { value, cursor: wordLeft(value, cursor) };
748
+ case "f":
749
+ return { value, cursor: wordRight(value, cursor) };
750
+ case "d":
751
+ return remove(s, cursor, wordRight(value, cursor));
752
+ }
753
+ }
754
+ return void 0;
755
+ }
756
+ function splitAtCursor(s) {
757
+ const before = s.value.slice(0, s.cursor);
758
+ if (s.cursor >= s.value.length) return { before, at: " ", after: "" };
759
+ const end = nextBoundary(s.value, s.cursor);
760
+ const char = s.value.slice(s.cursor, end);
761
+ if (char === "\n") return { before, at: " ", after: s.value.slice(s.cursor) };
762
+ return { before, at: char, after: s.value.slice(end) };
763
+ }
764
+ var graphemes = (text) => Array.from(segmenter.segment(text), (s) => s.segment);
765
+ function masked(s) {
766
+ return { value: "\u2022".repeat(graphemes(s.value).length), cursor: graphemes(s.value.slice(0, s.cursor)).length };
767
+ }
768
+
769
+ // src/ui/EditableText.tsx
770
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
771
+ function EditableText({ state, mask = false, showCursor = true }) {
772
+ const view = mask ? masked(state) : state;
773
+ if (!showCursor) return /* @__PURE__ */ jsx2(Fragment, { children: view.value });
774
+ const { before, at, after } = splitAtCursor(view);
775
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
776
+ before,
777
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: at }),
778
+ after
779
+ ] });
780
+ }
781
+
782
+ // src/ui/focusReport.ts
783
+ var isFocusReport = (input) => input === "[I" || input === "[O";
784
+
785
+ // src/ui/thinkingMouse.ts
786
+ function parseMousePress(value) {
787
+ const start = value.indexOf("\x1B[<");
788
+ if (start < 0) return void 0;
789
+ const match = /^<(\d+);(\d+);(\d+)M/.exec(value.slice(start + 2));
790
+ if (!match) return void 0;
791
+ const button = Number(match[1]);
792
+ if (button !== 0) return void 0;
793
+ const x = Number(match[2]);
794
+ const y = Number(match[3]);
795
+ if (x < 1 || y < 1) return void 0;
796
+ return { x, y };
797
+ }
798
+ function parseCursorPosition(value) {
799
+ const start = value.indexOf("\x1B[");
800
+ if (start < 0) return void 0;
801
+ const match = /^(\d+);(\d+)R/.exec(value.slice(start + 2));
802
+ if (!match) return void 0;
803
+ const row = Number(match[1]);
804
+ const col = Number(match[2]);
805
+ if (row < 1 || col < 1) return void 0;
806
+ return { row, col };
807
+ }
808
+ function clickedThinking(click, cursorRow, rootHeight, target) {
809
+ if (!rootHeight || !target.width || !target.height) return false;
810
+ const origin = cursorRow - rootHeight + 1;
811
+ const row = origin + target.y;
812
+ const col = target.x + 1;
813
+ return (click.y === row || click.y === row - 1) && click.x >= col && click.x < col + Math.min(target.width, 46);
814
+ }
815
+ function isMouseControlInput(input) {
816
+ return /^\[<(?:\d+;){2}\d+[mM]$/.test(input) || /^\[(?:\d+;)\d+R$/.test(input);
817
+ }
818
+ function parseThinkingReports(value) {
819
+ const events = [];
820
+ let offset = 0;
821
+ while (true) {
822
+ const start = value.indexOf("\x1B[", offset);
823
+ if (start < 0) break;
824
+ const finish = /[a-zA-Z~]/.exec(value.slice(start + 2));
825
+ if (!finish) return { events, rest: value.slice(start) };
826
+ const end = start + 2 + finish.index + 1;
827
+ const raw = value.slice(start, end);
828
+ const press = parseMousePress(raw);
829
+ const cursor = parseCursorPosition(raw);
830
+ if (press) events.push({ type: "mouse", press });
831
+ else if (cursor) events.push({ type: "cursor", cursor });
832
+ offset = end;
833
+ }
834
+ const remaining = value.lastIndexOf("\x1B[");
835
+ if (remaining >= offset) return { events, rest: value.slice(remaining) };
836
+ return { events, rest: value.endsWith("\x1B") ? "\x1B" : "" };
837
+ }
838
+
839
+ // src/ui/LinePrompt.tsx
840
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
841
+ function LinePrompt({ label, mask, onSubmit, onCancel }) {
842
+ const [line, setLine] = useState({ value: "", cursor: 0 });
843
+ useInput((input, key) => {
844
+ if (isMouseControlInput(input)) return;
845
+ if (key.return) onSubmit(line.value.trim());
846
+ else if (key.escape) onCancel();
847
+ else if (applyEditKey(line, input, key)) setLine((current) => applyEditKey(current, input, key) ?? current);
848
+ else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !isFocusReport(input)) {
849
+ const text = input.replace(/[\r\n]/g, "");
850
+ if (text) setLine((current) => insert(current, text));
851
+ }
852
+ });
853
+ return /* @__PURE__ */ jsx3(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
854
+ `${label}: `,
855
+ /* @__PURE__ */ jsx3(EditableText, { state: line, mask }),
856
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
857
+ ] }) });
858
+ }
859
+
860
+ // src/ui/ListPicker.tsx
861
+ import { Box as Box3, Text as Text4, useInput as useInput2 } from "ink";
862
+ import { useState as useState2 } from "react";
863
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
864
+ var VISIBLE = 12;
865
+ function ListPicker({ title, items, onPick }) {
866
+ const [filter, setFilter] = useState2("");
867
+ const [index, setIndex] = useState2(0);
868
+ const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
869
+ const cursor = Math.min(index, Math.max(0, shown.length - 1));
870
+ useInput2((input, key) => {
871
+ if (key.upArrow) setIndex(Math.max(0, cursor - 1));
872
+ else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
873
+ else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
874
+ else if (key.escape) onPick(void 0);
875
+ else if (key.backspace || key.delete) {
876
+ setFilter((f) => f.slice(0, -1));
877
+ setIndex(0);
878
+ } else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
879
+ setFilter((f) => f + input);
880
+ setIndex(0);
881
+ }
882
+ });
883
+ const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
884
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
885
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
886
+ shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs4(Text4, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
887
+ `${start + i === cursor ? "\u203A" : " "} ${item.label}`,
888
+ item.hint ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${item.hint}` }) : null
889
+ ] }, item.id)),
890
+ shown.length ? null : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matches.") })
891
+ ] });
892
+ }
893
+
894
+ // src/ui/ModelPicker.tsx
895
+ import { Box as Box4, Text as Text5, useInput as useInput3 } from "ink";
896
+ import { useState as useState3 } from "react";
897
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
898
+ var VISIBLE2 = 14;
899
+ function ModelPicker({ groups, current, onPick }) {
900
+ const [filter, setFilter] = useState3("");
901
+ const [index, setIndex] = useState3(0);
902
+ const needle = filter.toLowerCase();
903
+ const rows = [];
904
+ for (const g of groups) {
905
+ if (g.error) continue;
906
+ const models = g.models.filter((m) => m.toLowerCase().includes(needle));
907
+ if (!models.length) continue;
908
+ rows.push({ kind: "header", text: g.providerName });
909
+ for (const m of models) rows.push({ kind: "model", ref: `${g.providerId}/${m}`, label: m });
910
+ }
911
+ const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
912
+ const cursor = selectable[Math.min(index, selectable.length - 1)] ?? -1;
913
+ useInput3((input, key) => {
914
+ if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
915
+ else if (key.downArrow) setIndex((i) => Math.min(selectable.length - 1, i + 1));
916
+ else if (key.return) {
917
+ const row = rows[cursor];
918
+ if (row?.kind === "model") onPick(row.ref);
919
+ } else if (key.escape) onPick(void 0);
920
+ else if (key.backspace || key.delete) {
921
+ setFilter((f) => f.slice(0, -1));
922
+ setIndex(0);
923
+ } else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
924
+ setFilter((f) => f + input);
925
+ setIndex(0);
926
+ }
927
+ });
928
+ const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
929
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
930
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)", { filter: filter ? `: ${filter}` : "" }) }),
931
+ rows.slice(start, start + VISIBLE2).map(
932
+ (row, i) => row.kind === "header" ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `\u2500\u2500 ${row.text} ` }, `h${start + i}`) : /* @__PURE__ */ jsx5(Text5, { color: start + i === cursor ? color("green") : void 0, children: `${start + i === cursor ? "\u203A" : " "} ${row.label}${row.ref === current ? " (aktif)" : ""}` }, row.ref)
933
+ ),
934
+ !selectable.length ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("No matching models.") }) : null,
935
+ groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx5(Text5, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
936
+ ] });
937
+ }
938
+
939
+ // src/ui/ProviderMenu.tsx
940
+ import { Box as Box5, Text as Text6, useInput as useInput4 } from "ink";
941
+ import { useState as useState4 } from "react";
942
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
943
+ function ProviderMenu({ entries, onPick }) {
944
+ const rows = [...entries.map((e) => e.id), "custom"];
945
+ const [index, setIndex] = useState4(0);
946
+ useInput4((_input, key) => {
947
+ if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
948
+ else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
949
+ else if (key.return) onPick(rows[index]);
950
+ else if (key.escape) onPick(void 0);
951
+ });
952
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
953
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
954
+ entries.map((e, i) => /* @__PURE__ */ jsxs6(Text6, { color: i === index ? color("green") : void 0, children: [
955
+ `${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
956
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: ` ${e.baseURL}` })
957
+ ] }, e.id)),
958
+ /* @__PURE__ */ jsx6(Text6, { color: index === rows.length - 1 ? color("green") : void 0, children: `${index === rows.length - 1 ? "\u203A" : " "} Custom\u2026` })
959
+ ] });
960
+ }
961
+
962
+ // src/ui/PermissionPrompt.tsx
963
+ import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
964
+
965
+ // src/ui/DiffView.tsx
966
+ import { Box as Box6, Text as Text7 } from "ink";
967
+ import { jsx as jsx7 } from "react/jsx-runtime";
968
+ function DiffView({ diff }) {
969
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
970
+ const mark = line[6];
971
+ const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
972
+ return /* @__PURE__ */ jsx7(Text7, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
973
+ }) });
974
+ }
975
+
976
+ // src/ui/PermissionPrompt.tsx
977
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
978
+ var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
979
+ function PermissionPrompt({
980
+ request,
981
+ onAnswer
982
+ }) {
983
+ useInput5((input, key) => {
984
+ const k2 = input.toLowerCase();
985
+ if (k2 === "y") onAnswer("yes");
986
+ else if (k2 === "a" && request.sessionRules?.length) onAnswer("session");
987
+ else if (k2 === "s") onAnswer("all");
988
+ else if (k2 === "n" || key.escape) onAnswer("no");
989
+ });
990
+ const rules = request.sessionRules ?? [];
991
+ 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(", ") });
992
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
993
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
994
+ request.preview ? /* @__PURE__ */ jsx8(DiffView, { diff: request.preview }) : null,
995
+ /* @__PURE__ */ jsxs7(Text8, { children: [
996
+ /* @__PURE__ */ jsx8(Text8, { color: color("green"), children: t("[y] yes") }),
997
+ " ",
998
+ sessionLabel ? /* @__PURE__ */ jsx8(Text8, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
999
+ /* @__PURE__ */ jsx8(Text8, { color: color("yellow"), children: t("[s] yes to all ") }),
1000
+ /* @__PURE__ */ jsx8(Text8, { color: color("red"), children: t("[n] no") })
1001
+ ] })
1002
+ ] });
1003
+ }
1004
+
1005
+ // src/ui/PromptInput.tsx
1006
+ import { Box as Box8, Text as Text9, useInput as useInput6 } from "ink";
1007
+ import { useEffect, useState as useState5 } from "react";
1008
+
1009
+ // src/ui/complete.ts
1010
+ import { globSync } from "fs";
1011
+ import { statSync } from "fs";
1012
+ import { join } from "path";
1013
+ function completeFile(value, cwd) {
1014
+ const match = /(^|\s)@([^\s]*)$/.exec(value);
1015
+ if (!match) return value;
1016
+ const prefix = match[2];
1017
+ const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
1018
+ const pattern = `${escaped}*`;
1019
+ if (validateGlobPattern(pattern)) return value;
1020
+ const found = globSync(pattern, { cwd, exclude: ignorePatterns(cwd) }).map((entry2) => {
1021
+ const normalized = normalizeGlobPath(entry2);
1022
+ try {
1023
+ return statSync(join(cwd, entry2)).isDirectory() ? `${normalized}/` : normalized;
1024
+ } catch {
1025
+ return normalized;
1026
+ }
1027
+ }).sort();
1028
+ if (!found.length) return value;
1029
+ const pick = found[0];
1030
+ return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
1031
+ }
1032
+
1033
+ // src/ui/vim.ts
1034
+ var clamp = (s) => ({ value: s.value, cursor: Math.min(s.cursor, Math.max(0, s.value.length - 1)) });
1035
+ var wordStart = (text, pos) => {
1036
+ let i = wordRight(text, pos);
1037
+ while (i < text.length && /\s/.test(text[i])) i++;
1038
+ return i;
1039
+ };
1040
+ var deleteLine = (s) => {
1041
+ const from = lineStart(s.value, s.cursor);
1042
+ const nl = s.value.indexOf("\n", s.cursor);
1043
+ if (nl === -1) {
1044
+ const start = from > 0 ? from - 1 : 0;
1045
+ return { value: s.value.slice(0, start), cursor: start };
1046
+ }
1047
+ return { value: s.value.slice(0, from) + s.value.slice(nl + 1), cursor: from };
1048
+ };
1049
+ function applyVim(s, mode, input, key, pending) {
1050
+ if (key.escape) return { state: clamp(s), mode: "normal", handled: true };
1051
+ if (mode === "insert") return { state: s, mode, handled: false };
1052
+ if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.ctrl || key.meta || key.tab || key.return) {
1053
+ return { state: s, mode, pending, handled: false };
1054
+ }
1055
+ if (pending === "d") {
1056
+ if (input === "d") return { state: deleteLine(s), mode, handled: true };
1057
+ if (input === "w") {
1058
+ const to = wordStart(s.value, s.cursor);
1059
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(to), cursor: s.cursor }, mode, handled: true };
1060
+ }
1061
+ return { state: s, mode, handled: true };
1062
+ }
1063
+ switch (input) {
1064
+ case "h":
1065
+ return { state: { ...s, cursor: Math.max(lineStart(s.value, s.cursor), s.cursor - 1) }, mode, handled: true };
1066
+ case "l":
1067
+ return { state: { ...s, cursor: Math.min(lineEnd(s.value, s.cursor), s.cursor + 1) }, mode, handled: true };
1068
+ case "j": {
1069
+ const moved = moveLine(s.value, s.cursor, 1);
1070
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1071
+ }
1072
+ case "k": {
1073
+ const moved = moveLine(s.value, s.cursor, -1);
1074
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1075
+ }
1076
+ case "w":
1077
+ return { state: { ...s, cursor: wordStart(s.value, s.cursor) }, mode, handled: true };
1078
+ case "b":
1079
+ return { state: { ...s, cursor: wordLeft(s.value, s.cursor) }, mode, handled: true };
1080
+ case "0":
1081
+ return { state: { ...s, cursor: lineStart(s.value, s.cursor) }, mode, handled: true };
1082
+ case "$":
1083
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode, handled: true };
1084
+ case "x": {
1085
+ if (s.cursor >= s.value.length || s.value[s.cursor] === "\n") return { state: s, mode, handled: true };
1086
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(s.cursor + 1), cursor: s.cursor }, mode, handled: true };
1087
+ }
1088
+ case "i":
1089
+ return { state: s, mode: "insert", handled: true };
1090
+ case "a":
1091
+ return { state: { ...s, cursor: Math.min(s.cursor + 1, s.value.length) }, mode: "insert", handled: true };
1092
+ case "A":
1093
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode: "insert", handled: true };
1094
+ case "o": {
1095
+ const end = lineEnd(s.value, s.cursor);
1096
+ return { state: { value: `${s.value.slice(0, end)}
1097
+ ${s.value.slice(end)}`, cursor: end + 1 }, mode: "insert", handled: true };
1098
+ }
1099
+ case "O": {
1100
+ const start = lineStart(s.value, s.cursor);
1101
+ return { state: { value: `${s.value.slice(0, start)}
1102
+ ${s.value.slice(start)}`, cursor: start }, mode: "insert", handled: true };
1103
+ }
1104
+ case "d":
1105
+ return { state: s, mode, pending: "d", handled: true };
1106
+ default:
1107
+ return { state: s, mode, handled: true };
1108
+ }
1109
+ }
1110
+
1111
+ // src/ui/PromptInput.tsx
1112
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1113
+ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected, vim }) {
1114
+ const [line, setLine] = useState5({ value: "", cursor: 0 });
1115
+ const { value } = line;
1116
+ const [historyIndex, setHistoryIndex] = useState5(null);
1117
+ const [selected, setSelected] = useState5(0);
1118
+ const [vimMode, setVimMode] = useState5("insert");
1119
+ const [vimPending, setVimPending] = useState5(void 0);
1120
+ const injectedSeen = useState5(0);
1121
+ useEffect(() => {
1122
+ if (injected && injected.n !== injectedSeen[0]) {
1123
+ injectedSeen[1](injected.n);
1124
+ setLine((current) => insert(current, injected.text));
1125
+ setSelected(0);
1126
+ }
1127
+ }, [injected, injectedSeen]);
1128
+ const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
1129
+ const suggestions = value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
1130
+ const highlighted = Math.min(selected, Math.max(0, suggestions.length - 1));
1131
+ const typed = (text) => ({ value: text, cursor: text.length });
1132
+ const edit = (next) => {
1133
+ setLine(next);
1134
+ setSelected(0);
1135
+ };
1136
+ useInput6(
1137
+ (input, key) => {
1138
+ if (isMouseControlInput(input)) return;
1139
+ if (suggestions.length) {
1140
+ if (key.upArrow) {
1141
+ setSelected(Math.max(0, highlighted - 1));
1142
+ return;
1143
+ }
1144
+ if (key.downArrow) {
1145
+ setSelected(Math.min(suggestions.length - 1, highlighted + 1));
1146
+ return;
1147
+ }
1148
+ if (key.tab && !key.shift) {
1149
+ edit(typed(`/${suggestions[highlighted].name} `));
1150
+ return;
1151
+ }
1152
+ if (key.return) {
1153
+ edit(typed(""));
1154
+ setHistoryIndex(null);
1155
+ onSubmit(`/${suggestions[highlighted].name}`);
1156
+ return;
1157
+ }
1158
+ }
1159
+ if (vim) {
1160
+ const r = applyVim(line, vimMode, input, key, vimPending);
1161
+ if (r.handled) {
1162
+ setLine(r.state);
1163
+ setVimMode(r.mode);
1164
+ setVimPending(r.pending);
1165
+ setSelected(0);
1166
+ return;
1167
+ }
1168
+ }
1169
+ if (key.return) {
1170
+ const before = value.slice(0, line.cursor);
1171
+ if (before.endsWith("\\")) {
1172
+ edit({ value: `${before.slice(0, -1)}
1173
+ ${value.slice(line.cursor)}`, cursor: before.length });
1174
+ return;
1175
+ }
1176
+ const text2 = value.trim();
1177
+ if (!text2) return;
1178
+ edit(typed(""));
1179
+ setHistoryIndex(null);
1180
+ setVimMode("insert");
1181
+ setVimPending(void 0);
1182
+ onSubmit(text2);
1183
+ return;
1184
+ }
1185
+ const edited = applyEditKey(line, input, key);
1186
+ if (edited) {
1187
+ edit((current) => applyEditKey(current, input, key) ?? current);
1188
+ return;
1189
+ }
1190
+ if (key.upArrow || key.downArrow) {
1191
+ const moved = moveLine(value, line.cursor, key.upArrow ? -1 : 1);
1192
+ if (moved !== void 0) {
1193
+ setLine({ value, cursor: moved });
1194
+ return;
1195
+ }
1196
+ }
1197
+ if (key.upArrow) {
1198
+ if (!history.length) return;
1199
+ const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
1200
+ setHistoryIndex(i);
1201
+ setLine(typed(history[i]));
1202
+ return;
1203
+ }
1204
+ if (key.downArrow) {
1205
+ if (historyIndex === null) return;
1206
+ const i = historyIndex + 1;
1207
+ setHistoryIndex(i >= history.length ? null : i);
1208
+ setLine(typed(i >= history.length ? "" : history[i]));
1209
+ return;
1210
+ }
1211
+ if (key.tab && !key.shift) {
1212
+ const before = value.slice(0, line.cursor);
1213
+ const completed = completeFile(before, cwd);
1214
+ if (completed !== before) edit({ value: completed + value.slice(line.cursor), cursor: completed.length });
1215
+ return;
1216
+ }
1217
+ if (key.ctrl || key.meta || key.escape || key.tab || isFocusReport(input)) return;
1218
+ const text = input.replace(/\r\n?/g, "\n");
1219
+ if (text) edit((current) => insert(current, text));
1220
+ },
1221
+ { isActive: !disabled }
1222
+ );
1223
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1224
+ /* @__PURE__ */ jsx9(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
1225
+ /* @__PURE__ */ jsx9(Text9, { color: color("green"), children: "> " }),
1226
+ /* @__PURE__ */ jsx9(EditableText, { state: line, showCursor: !disabled }),
1227
+ vim && vimMode === "normal" && /* @__PURE__ */ jsxs8(Text9, { color: color("green"), children: [
1228
+ " ",
1229
+ t("NORMAL")
1230
+ ] })
1231
+ ] }) }),
1232
+ suggestions.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${t(s.description)}` }, s.name))
1233
+ ] });
1234
+ }
1235
+
1236
+ // src/ui/Spinner.tsx
1237
+ import { Text as Text10 } from "ink";
1238
+ import { useEffect as useEffect2, useState as useState6 } from "react";
1239
+ import { jsx as jsx10 } from "react/jsx-runtime";
1240
+ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
1241
+ function Spinner({ label, startedAt }) {
1242
+ const [tick, setTick] = useState6(0);
1243
+ useEffect2(() => {
1244
+ const id = setInterval(() => setTick((t2) => t2 + 1), 120);
1245
+ return () => clearInterval(id);
1246
+ }, []);
1247
+ const seconds = Math.floor((Date.now() - startedAt) / 1e3);
1248
+ return /* @__PURE__ */ jsx10(Text10, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
1249
+ }
1250
+
1251
+ // src/ui/StatusBar.tsx
1252
+ import { Text as Text11 } from "ink";
1253
+ import { jsx as jsx11 } from "react/jsx-runtime";
1254
+ var LABEL = {
1255
+ default: "\u23F5 default",
1256
+ acceptEdits: "\u23F5 accept edits",
1257
+ plan: "\u23F8 plan mode",
1258
+ allowAll: "\u23F5\u23F5 allow all"
1259
+ };
1260
+ function formatTokens(n) {
1261
+ return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
1262
+ }
1263
+ function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
1264
+ const danger = mode === "allowAll";
1265
+ return /* @__PURE__ */ jsx11(Text11, { 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")}` });
1266
+ }
1267
+
1268
+ // src/ui/McpMenu.tsx
1269
+ import { Box as Box9, Text as Text12, useInput as useInput7 } from "ink";
1270
+ import { useState as useState7 } from "react";
1271
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1272
+ var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
1273
+ function McpMenu({ items, onPick }) {
1274
+ const [index, setIndex] = useState7(0);
1275
+ useInput7((_input, key) => {
1276
+ if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
1277
+ else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
1278
+ else if (key.return) onPick(items[index]?.name);
1279
+ else if (key.escape) onPick(void 0);
1280
+ });
1281
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1282
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
1283
+ items.map((item, i) => /* @__PURE__ */ jsxs9(Text12, { color: i === index ? color("green") : void 0, children: [
1284
+ `${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
1285
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
1286
+ ] }, item.name))
1287
+ ] });
1288
+ }
1289
+
1290
+ // src/ui/PlanApproval.tsx
1291
+ import { Box as Box10, Text as Text13, useInput as useInput8 } from "ink";
1292
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1293
+ function PlanApproval({ plan, onAnswer }) {
1294
+ useInput8((input, key) => {
1295
+ const k2 = input.toLowerCase();
1296
+ if (k2 === "a") onAnswer("acceptEdits");
1297
+ else if (k2 === "y") onAnswer("default");
1298
+ else if (k2 === "n" || key.escape) onAnswer("no");
1299
+ });
1300
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1301
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: t("Plan from the agent") }),
1302
+ /* @__PURE__ */ jsx13(Markdown, { text: plan, indent: 5 }),
1303
+ /* @__PURE__ */ jsxs10(Text13, { children: [
1304
+ /* @__PURE__ */ jsx13(Text13, { color: color("green"), children: t("[a] yes, auto-edit") }),
1305
+ " ",
1306
+ /* @__PURE__ */ jsx13(Text13, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
1307
+ " ",
1308
+ /* @__PURE__ */ jsx13(Text13, { color: color("red"), children: t("[n] not yet, revise first") })
1309
+ ] })
1310
+ ] });
1311
+ }
1312
+
1313
+ // src/ui/TodoList.tsx
1314
+ import { Box as Box11, Text as Text14 } from "ink";
1315
+ import { jsx as jsx14 } from "react/jsx-runtime";
1316
+ var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
1317
+ function TodoList({ items }) {
1318
+ if (!items.length || items.every((t2) => t2.status === "completed")) return null;
1319
+ return /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t2, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: t2.status === "completed", color: t2.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t2.status]} ${t2.content}` }, i)) });
1320
+ }
1321
+
1322
+ // src/ui/ToolBlock.tsx
1323
+ import { Box as Box12, Text as Text15 } from "ink";
1324
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1325
+ var PREVIEW_LINES = 5;
1326
+ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub, moreBind = "ctrl+o" }) {
1327
+ const isTask = tool === "task";
1328
+ const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
1329
+ const lines = (output ?? "").split("\n");
1330
+ const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
1331
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 2, children: [
1332
+ /* @__PURE__ */ jsxs11(Text15, { children: [
1333
+ /* @__PURE__ */ jsx15(Text15, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
1334
+ /* @__PURE__ */ jsx15(Text15, { bold: true, children: toolLabel(tool) }),
1335
+ /* @__PURE__ */ jsx15(Text15, { children: ` ${target}` }),
1336
+ display && !isDiff && !isTask ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` (${display})` }) : null
1337
+ ] }),
1338
+ sub?.length ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1339
+ sub.slice(-6).map((s) => /* @__PURE__ */ jsx15(Text15, { dimColor: s.done && !s.isError, color: s.isError ? color("red") : void 0, children: `\u23BF ${toolLabel(s.tool)} ${s.target}` }, s.id)),
1340
+ sub.length > 6 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
1341
+ ] }) : null,
1342
+ isTask && done ? /* @__PURE__ */ jsx15(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx15(Text15, { color: isError ? color("red") : color("green"), children: `${isError ? t("\u2717 failed") : t("\u2713 done")}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
1343
+ isDiff ? /* @__PURE__ */ jsx15(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx15(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1344
+ shown.map((line, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
1345
+ lines.length > shown.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: lines.length - shown.length, bind: moreBind }) }) : null
1346
+ ] }) : null
1347
+ ] });
1348
+ }
1349
+
1350
+ // src/ui/transcript.ts
1351
+ var nextId = 0;
1352
+ var entry = (e) => ({ ...e, id: nextId++ });
1353
+ var NOTICES = {
1354
+ compacted: ["Conversation summarized to fit the model context.", "info"],
1355
+ stepLimit: ['Step limit of {n} reached. Type "continue" to keep going.', "warn"],
1356
+ aborted: ["Cancelled.", "warn"]
1357
+ };
1358
+ var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
1359
+ function endTurn(t2) {
1360
+ return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
1361
+ }
1362
+ var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
1363
+ function finishedCut(text) {
1364
+ let inFence = false;
1365
+ let pos = 0;
1366
+ let cut = -1;
1367
+ const lines = text.split("\n");
1368
+ for (const line of lines.slice(0, -1)) {
1369
+ if (TOOL_MARKUP.test(line)) break;
1370
+ if (line.trimStart().startsWith("```")) inFence = !inFence;
1371
+ const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
1372
+ pos += line.length + 1;
1373
+ if (!inFence && !tableRow) cut = pos - 1;
1374
+ }
1375
+ return cut;
1376
+ }
1377
+ function flushLines(t2) {
1378
+ const last = t2.live.at(-1);
1379
+ if (last?.kind !== "assistant") return t2;
1380
+ const cut = finishedCut(last.text);
1381
+ if (cut < 0) return t2;
1382
+ const chunk = last.text.slice(0, cut);
1383
+ if (!last.cont && !chunk.trim()) return t2;
1384
+ const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
1385
+ return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
1386
+ }
1387
+ function applyEvent(t2, event) {
1388
+ switch (event.type) {
1389
+ case "text": {
1390
+ const last = t2.live.at(-1);
1391
+ if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
1392
+ return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
1393
+ }
1394
+ case "thinking": {
1395
+ const last = t2.live.at(-1);
1396
+ if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
1397
+ return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
1398
+ }
1399
+ case "textReplace": {
1400
+ const last = t2.live.at(-1);
1401
+ if (last?.kind !== "assistant") return t2;
1402
+ return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
1403
+ }
1404
+ case "toolStart": {
1405
+ const running = t2.live.some((e) => e.kind === "tool" && !e.done);
1406
+ const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
1407
+ return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
1408
+ }
1409
+ case "toolEnd": {
1410
+ const live = t2.live.map(
1411
+ (e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
1412
+ );
1413
+ const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
1414
+ const cut = firstRunning === -1 ? live.length : firstRunning;
1415
+ return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
1416
+ }
1417
+ case "subagent": {
1418
+ const inner = event.event;
1419
+ if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
1420
+ const update = (e) => {
1421
+ if (e.kind !== "tool" || e.callId !== event.parentId) return e;
1422
+ const sub = e.sub ?? [];
1423
+ if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
1424
+ return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
1425
+ };
1426
+ return { ...t2, live: t2.live.map(update) };
1427
+ }
1428
+ case "error":
1429
+ return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
1430
+ case "stepLimit": {
1431
+ const limit = event.maxSteps;
1432
+ const notice = NOTICES.stepLimit;
1433
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0], { n: limit }), tone: notice[1] })] } : t2;
1434
+ }
1435
+ default: {
1436
+ const notice = NOTICES[event.type];
1437
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ // src/ui/replay.ts
1443
+ function replayEntries(history) {
1444
+ const chat = history.map((m) => m.role === "user" ? { role: "user", text: contentText(m.content) } : m.role === "assistant" && typeof m.content === "string" ? { role: "assistant", text: m.content } : void 0).filter((m) => !!m && !!m.text.trim());
1445
+ return [
1446
+ entry({ kind: "notice", text: t("Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).", { messages: history.length, chat: chat.length }), tone: "info" }),
1447
+ ...chat.map((m) => entry(m.role === "user" ? { kind: "user", text: m.text } : { kind: "assistant", text: m.text }))
1448
+ ];
1449
+ }
1450
+
1451
+ // src/clipboardImage.ts
1452
+ import { execFile as execFile2 } from "child_process";
1453
+ import { existsSync, statSync as statSync2 } from "fs";
1454
+ import { join as join2 } from "path";
1455
+ function clipboardTarget(tmpdir2, now) {
1456
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
1457
+ return join2(tmpdir2, `bccli-clipboard-${stamp}.png`);
1458
+ }
1459
+ function clipboardCommand(platform, target) {
1460
+ if (platform === "win32") {
1461
+ const escaped = target.replace(/'/g, "''");
1462
+ return {
1463
+ cmd: "powershell.exe",
1464
+ args: [
1465
+ "-NoProfile",
1466
+ "-NonInteractive",
1467
+ "-Command",
1468
+ `Add-Type -AssemblyName System.Drawing; $img = Get-Clipboard -Format Image -ErrorAction SilentlyContinue; if ($img) { $img.Save('${escaped}', [System.Drawing.Imaging.ImageFormat]::Png); 'OK' } else { 'EMPTY' }`
1469
+ ]
1470
+ };
1471
+ }
1472
+ if (platform === "darwin") {
1473
+ return {
1474
+ cmd: "osascript",
1475
+ args: [
1476
+ "-e",
1477
+ "try",
1478
+ "-e",
1479
+ "set d to the clipboard as \xABclass PNGf\xBB",
1480
+ "-e",
1481
+ `set f to open for access POSIX file "${target}" with write permission`,
1482
+ "-e",
1483
+ "set eof f to 0",
1484
+ "-e",
1485
+ "write d to f",
1486
+ "-e",
1487
+ "close access f",
1488
+ "-e",
1489
+ '"OK"',
1490
+ "-e",
1491
+ "on error",
1492
+ "-e",
1493
+ '"EMPTY"',
1494
+ "-e",
1495
+ "end try"
1496
+ ]
1497
+ };
1498
+ }
1499
+ if (platform === "linux") {
1500
+ return { cmd: "sh", args: ["-c", `xclip -selection clipboard -t image/png -o > '${target}' 2>/dev/null && echo OK || echo EMPTY`] };
1501
+ }
1502
+ return void 0;
1503
+ }
1504
+ var defaultRun = (cmd, args) => new Promise((resolve2) => {
1505
+ execFile2(cmd, args, { timeout: 15e3, windowsHide: true }, (error, stdout) => {
1506
+ const code = error ? error.code ?? 1 : 0;
1507
+ resolve2({ code: typeof code === "number" ? code : 1, output: String(stdout ?? "") });
1508
+ });
1509
+ });
1510
+ async function grabClipboardImage(deps) {
1511
+ const target = clipboardTarget(deps.tmpdir, deps.now);
1512
+ const command = clipboardCommand(deps.platform, target);
1513
+ if (!command) return void 0;
1514
+ const run = deps.run ?? defaultRun;
1515
+ const r = await run(command.cmd, command.args);
1516
+ if (r.code === 0 && r.output.includes("OK") && existsSync(target) && statSync2(target).size > 0) return target;
1517
+ return void 0;
1518
+ }
1519
+
1520
+ // src/ui/App.tsx
1521
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1522
+ function helpText(runtime) {
1523
+ const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
1524
+ const kb = runtime.config.keybinds;
1525
+ return [
1526
+ ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
1527
+ ...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
1528
+ ...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
1529
+ "",
1530
+ t("shift+tab switch permission mode esc cancel the turn"),
1531
+ t("{bind} full output of the last tool \\ + enter new line", { bind: kb.toolOutput.spec.padEnd(10) }),
1532
+ t("{bind} show/hide the model\u2019s thinking", { bind: kb.thinking.spec.padEnd(10) }),
1533
+ t("@file + tab complete file names \u2191\u2193 input history")
1534
+ ].join("\n");
1535
+ }
1536
+ 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.";
1537
+ var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
1538
+ function App({ runtime, initialPrompt, version }) {
1539
+ const { exit } = useApp();
1540
+ const [transcript, setTranscript] = useState8(() => ({
1541
+ done: [entry({ kind: "header" }), ...runtime.agent.messages.length ? replayEntries(runtime.session.loadHistory()) : []],
1542
+ live: []
1543
+ }));
1544
+ const [busy, setBusy] = useState8(false);
1545
+ const [cancelling, setCancelling] = useState8(false);
1546
+ const [startedAt, setStartedAt] = useState8(0);
1547
+ const [mode, setMode] = useState8(runtime.agent.permissions.mode);
1548
+ const [reasoning, setReasoning] = useState8(runtime.reasoning);
1549
+ const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
1550
+ const [asks, setAsks] = useState8([]);
1551
+ const pending = asks[0] ?? null;
1552
+ const askId = useRef(0);
1553
+ const [picker, setPicker] = useState8(null);
1554
+ const [providerMenu, setProviderMenu] = useState8(null);
1555
+ const [prompt, setPrompt] = useState8(null);
1556
+ const ask = useCallback((label, mask = false) => new Promise((resolve2) => setPrompt({ label, mask, resolve: resolve2 })), []);
1557
+ const [modelLabel, setModelLabel] = useState8(() => labelFor(runtime, runtime.modelRef));
1558
+ const [history, setHistory] = useState8([]);
1559
+ const controller = useRef(null);
1560
+ const [todos, setTodos] = useState8(runtime.todos.items);
1561
+ const [mcpMenu, setMcpMenu] = useState8(null);
1562
+ const [listPicker, setListPicker] = useState8(null);
1563
+ const [planAsk, setPlanAsk] = useState8(null);
1564
+ const [queued, setQueued] = useState8([]);
1565
+ const [showThinking, setShowThinking] = useState8(runtime.config.thinkingVisible === true);
1566
+ const showThinkingRef = useRef(showThinking);
1567
+ const [lastThinking, setLastThinking] = useState8("");
1568
+ const hasThinking = !!lastThinking.trim();
1569
+ const { stdin } = useStdin();
1570
+ const { stdout } = useStdout2();
1571
+ const liveRootRef = useRef(null);
1572
+ const thinkingTargetRef = useRef(null);
1573
+ const [injected, setInjected] = useState8();
1574
+ useEffect3(() => runtime.todos.subscribe(setTodos), [runtime]);
1575
+ const extraCommands = [
1576
+ ...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") })),
1577
+ ...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) }) }))
1578
+ ];
1579
+ const lastTool = useRef(null);
1580
+ const toolTargets = useRef(/* @__PURE__ */ new Map());
1581
+ const editedInTurn = useRef(false);
1582
+ const notice = useCallback((text, tone = "info") => {
1583
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
1584
+ }, []);
1585
+ const onEvent = useCallback((event) => {
1586
+ if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
1587
+ if (event.type === "toolEnd") {
1588
+ lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
1589
+ if ((event.tool === "edit" || event.tool === "write") && !event.isError) editedInTurn.current = true;
1590
+ }
1591
+ if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
1592
+ if (event.type === "thinking" && event.delta) setLastThinking((prev) => prev + event.delta);
1593
+ if (event.type === "budgetExceeded") {
1594
+ notice(
1595
+ t("Budget exceeded ({kind}): {used} of {limit}. Further model calls are blocked; raise usageCap in ~/.bccli/config.json.", {
1596
+ kind: event.kind,
1597
+ used: Math.round(event.used * 100) / 100,
1598
+ limit: event.limit
1599
+ }),
1600
+ "warn"
1601
+ );
1602
+ }
1603
+ setTranscript((t2) => applyEvent(t2, event));
1604
+ }, [notice]);
1605
+ useEffect3(() => {
1606
+ runtime.agent.onEvent = onEvent;
1607
+ runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
1608
+ runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
1609
+ runHooks(runtime.config.hooks, "SessionStart", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
1610
+ for (const w of outcome.warnings) notice(w, "warn");
1611
+ }).catch(() => {
1612
+ });
1613
+ runtime.emitPlugins("SessionStart").catch(() => {
1614
+ });
1615
+ }, [runtime, onEvent, notice]);
1616
+ const runTurn = useCallback(
1617
+ async (text) => {
1618
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
1619
+ setBusy(true);
1620
+ setLastThinking("");
1621
+ setStartedAt(Date.now());
1622
+ editedInTurn.current = false;
1623
+ controller.current = new AbortController();
1624
+ const signal = controller.current.signal;
1625
+ await runtime.agent.run(text, signal);
1626
+ const commands = runtime.config.verifyCommands;
1627
+ if (editedInTurn.current && commands.length && !signal.aborted) {
1628
+ for (let round = 0; round < MAX_VERIFY_ROUNDS; round++) {
1629
+ notice(t("Verifying edits: {cmds}", { cmds: commands.join(", ") }));
1630
+ const failures = await runVerify(commands, { cwd: runtime.cwd, env: runtime.env, signal });
1631
+ if (!failures.length) {
1632
+ notice(t("Verification passed."));
1633
+ break;
1634
+ }
1635
+ if (round === MAX_VERIFY_ROUNDS - 1 || signal.aborted) {
1636
+ notice(t("Verification still failing after {n} fix round(s). Run the commands manually to see why.", { n: MAX_VERIFY_ROUNDS }), "warn");
1637
+ break;
1638
+ }
1639
+ await runtime.agent.run(verifyFollowup(failures), signal);
1640
+ }
1641
+ }
1642
+ controller.current = null;
1643
+ runHooks(runtime.config.hooks, "Stop", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
1644
+ for (const w of outcome.warnings) notice(w, "warn");
1645
+ }).catch(() => {
1646
+ });
1647
+ runtime.emitPlugins("Stop").catch(() => {
1648
+ });
1649
+ setTranscript(endTurn);
1650
+ setBusy(false);
1651
+ setCancelling(false);
1652
+ },
1653
+ [runtime, notice]
1654
+ );
1655
+ const runSlashCommand = useCallback(
1656
+ async (name, args) => {
1657
+ switch (name) {
1658
+ case "help":
1659
+ notice(helpText(runtime));
1660
+ return;
1661
+ case "exit":
1662
+ exit();
1663
+ return;
1664
+ case "clear":
1665
+ runtime.agent.clear();
1666
+ setTokens(0);
1667
+ setLastThinking("");
1668
+ notice(t("Conversation cleared."));
1669
+ return;
1670
+ case "cost": {
1671
+ const u = runtime.agent.totalUsage;
1672
+ notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
1673
+ return;
1674
+ }
1675
+ case "tasks":
1676
+ notice(renderBackgroundTasks());
1677
+ return;
1678
+ case "compact": {
1679
+ setBusy(true);
1680
+ setStartedAt(Date.now());
1681
+ controller.current = new AbortController();
1682
+ try {
1683
+ await runtime.agent.compact(controller.current.signal);
1684
+ } catch (error) {
1685
+ notice(t("Could not summarize: {error}", { error: error.message }), "error");
1686
+ }
1687
+ controller.current = null;
1688
+ setBusy(false);
1689
+ setCancelling(false);
1690
+ return;
1691
+ }
1692
+ case "reasoning": {
1693
+ const apply = (value) => {
1694
+ try {
1695
+ const level = parseReasoningLevel(value, "/reasoning");
1696
+ const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1697
+ assertReasoningSupported(providerId2, level);
1698
+ runtime.setReasoning(level);
1699
+ writeGlobalConfig({ reasoning: level }, runtime.env);
1700
+ setReasoning(level);
1701
+ notice(t("Reasoning: {level} (saved as default)", { level }));
1702
+ } catch (error) {
1703
+ notice(error.message, "error");
1704
+ }
1705
+ };
1706
+ if (args) {
1707
+ apply(args);
1708
+ return;
1709
+ }
1710
+ const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1711
+ const supported = supportedReasoningLevels(providerId);
1712
+ setListPicker({
1713
+ title: "Reasoning",
1714
+ items: REASONING_LEVELS.map((level) => ({
1715
+ id: level,
1716
+ label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
1717
+ disabled: !supported.includes(level),
1718
+ hint: supported.includes(level) ? void 0 : t("not supported by this provider")
1719
+ })),
1720
+ onPick: (id) => {
1721
+ setListPicker(null);
1722
+ if (id) apply(id);
1723
+ }
1724
+ });
1725
+ return;
1726
+ }
1727
+ case "model": {
1728
+ if (args) {
1729
+ try {
1730
+ runtime.setModel(args);
1731
+ writeGlobalConfig({ model: args }, runtime.env);
1732
+ setModelLabel(labelFor(runtime, args));
1733
+ notice(t("Model: {model} (saved as default)", { model: args }));
1734
+ } catch (error) {
1735
+ notice(error.message, "error");
1736
+ }
1737
+ return;
1738
+ }
1739
+ try {
1740
+ setPicker(await runtime.listModels());
1741
+ } catch (error) {
1742
+ notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
1743
+ }
1744
+ return;
1745
+ }
1746
+ case "mcp": {
1747
+ const installed = readMcpFile(globalMcpPath(runtime.home));
1748
+ const states = runtime.mcp.states();
1749
+ const items = [
1750
+ ...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
1751
+ ...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
1752
+ ].map((i) => {
1753
+ const s = states.find((st) => st.name === i.name);
1754
+ return { ...i, status: s?.status ?? "off", error: s?.error };
1755
+ });
1756
+ setMcpMenu(items);
1757
+ return;
1758
+ }
1759
+ case "new":
1760
+ runtime.newSession();
1761
+ setTokens(0);
1762
+ setLastThinking("");
1763
+ notice(t("New session started. The previous session stays saved (/resume)."));
1764
+ return;
1765
+ case "session":
1766
+ notice(
1767
+ sessionText({
1768
+ file: runtime.session.file,
1769
+ started: runtime.startedAt,
1770
+ messages: runtime.agent.messages.length,
1771
+ usage: runtime.agent.totalUsage,
1772
+ modelRef: runtime.modelRef
1773
+ })
1774
+ );
1775
+ return;
1776
+ case "resume": {
1777
+ const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
1778
+ if (!others.length) {
1779
+ notice(t("No other sessions in this folder yet."));
1780
+ return;
1781
+ }
1782
+ setListPicker({
1783
+ title: t("Continue a session"),
1784
+ items: others.map((s) => ({
1785
+ id: s.session.file,
1786
+ label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
1787
+ hint: t("{n} messages", { n: s.session.load().length })
1788
+ })),
1789
+ onPick: (id) => {
1790
+ setListPicker(null);
1791
+ const chosen = others.find((s) => s.session.file === id);
1792
+ if (!chosen) return;
1793
+ runtime.resume(chosen.session);
1794
+ setTokens(0);
1795
+ setLastThinking("");
1796
+ setTranscript({ done: [entry({ kind: "header" }), ...replayEntries(chosen.session.loadHistory())], live: [] });
1797
+ }
1798
+ });
1799
+ return;
1800
+ }
1801
+ case "status":
1802
+ notice(
1803
+ statusText({
1804
+ version,
1805
+ modelRef: runtime.modelRef,
1806
+ providerLabel: runtime.providerLabel(),
1807
+ mode: runtime.agent.permissions.mode,
1808
+ cwd: runtime.cwd,
1809
+ mcp: runtime.mcp.states(),
1810
+ usage: runtime.agent.totalUsage,
1811
+ lastInputTokens: runtime.agent.lastInputTokens
1812
+ })
1813
+ );
1814
+ return;
1815
+ case "agents":
1816
+ notice(agentsText([...BUILTIN_AGENTS, ...runtime.agentDefs]));
1817
+ return;
1818
+ case "skills":
1819
+ notice(skillsText(runtime.skills, runtime.commands));
1820
+ return;
1821
+ case "doctor": {
1822
+ notice(t("Checking\u2026"));
1823
+ const c = runtime.config;
1824
+ notice(
1825
+ await doctorText(
1826
+ {
1827
+ providers: Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env) })),
1828
+ mcp: runtime.mcp.states(),
1829
+ activeProvider: runtime.providerLabel()
1830
+ },
1831
+ {
1832
+ which: (cmd) => new Promise((done) => execFile3(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
1833
+ nodeVersion: process.version,
1834
+ listModels: () => runtime.agent.provider.listModels()
1835
+ }
1836
+ )
1837
+ );
1838
+ return;
1839
+ }
1840
+ case "permissions": {
1841
+ const rules = runtime.agent.permissions.list();
1842
+ if (!rules.length) {
1843
+ notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
1844
+ return;
1845
+ }
1846
+ setListPicker({
1847
+ title: t("Active permissions (enter: revoke a session permission)"),
1848
+ items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
1849
+ onPick: (id) => {
1850
+ setListPicker(null);
1851
+ const rule = rules.find((r) => r.rule === id);
1852
+ if (!rule) return;
1853
+ 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");
1854
+ else {
1855
+ runtime.agent.permissions.revoke(rule.rule);
1856
+ notice(t("Permission {rule} revoked.", { rule: rule.rule }));
1857
+ }
1858
+ }
1859
+ });
1860
+ return;
1861
+ }
1862
+ case "memory": {
1863
+ if (!args) {
1864
+ const files = instructionFiles(runtime.cwd, runtime.home);
1865
+ 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>."));
1866
+ return;
1867
+ }
1868
+ const { global, text } = parseMemoryArgs(args);
1869
+ if (!text) {
1870
+ notice(t("Write the text: /memory global <text>."), "warn");
1871
+ return;
1872
+ }
1873
+ const file = global ? join3(runtime.home, "BCCLI.md") : join3(runtime.cwd, "AGENTS.md");
1874
+ try {
1875
+ mkdirSync(dirname(file), { recursive: true });
1876
+ appendMemory(file, text);
1877
+ } catch (error) {
1878
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
1879
+ return;
1880
+ }
1881
+ runtime.rebuildSystemPrompt();
1882
+ notice(t("Added to {file}.", { file }));
1883
+ return;
1884
+ }
1885
+ case "init":
1886
+ void runTurn(INIT_PROMPT);
1887
+ return;
1888
+ case "language": {
1889
+ if (!args) {
1890
+ notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
1891
+ return;
1892
+ }
1893
+ const wanted = parseLang(args);
1894
+ if (!wanted) {
1895
+ notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
1896
+ return;
1897
+ }
1898
+ setLanguage(wanted);
1899
+ try {
1900
+ writeGlobalConfig({ language: wanted }, runtime.env);
1901
+ } catch (error) {
1902
+ notice(t("Could not save the language: {error}", { error: error.message }), "error");
1903
+ }
1904
+ notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
1905
+ return;
1906
+ }
1907
+ case "login": {
1908
+ const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1909
+ const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
1910
+ if (!key) return;
1911
+ const result = await runtime.addProviderKey(id, key);
1912
+ if (!result.ok) {
1913
+ notice(result.error, "error");
1914
+ return;
1915
+ }
1916
+ runtime.setModel(runtime.modelRef);
1917
+ const envName = runtime.config.providers[id]?.apiKeyEnv;
1918
+ if (envName && runtime.env[envName]) {
1919
+ notice(t("Key for {provider} saved, but {env} in the environment takes precedence and is still used.", { provider: runtime.providerLabel(), env: envName }), "warn");
1920
+ return;
1921
+ }
1922
+ notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
1923
+ return;
1924
+ }
1925
+ case "logout": {
1926
+ const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1927
+ const removed = removeCredential(id, runtime.env);
1928
+ const envName = runtime.config.providers[id]?.apiKeyEnv;
1929
+ if (envName && runtime.env[envName]) {
1930
+ notice(
1931
+ 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 }),
1932
+ "warn"
1933
+ );
1934
+ return;
1935
+ }
1936
+ if (!removed) {
1937
+ notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
1938
+ return;
1939
+ }
1940
+ try {
1941
+ runtime.setModel(runtime.modelRef);
1942
+ } catch (error) {
1943
+ const fail = async () => {
1944
+ throw error;
1945
+ };
1946
+ runtime.agent.provider = { chat: fail, listModels: fail };
1947
+ }
1948
+ notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
1949
+ return;
1950
+ }
1951
+ case "diff":
1952
+ notice(await gitDiff(runtime.cwd));
1953
+ return;
1954
+ case "worktree":
1955
+ notice(await worktreeListText(runtime.cwd));
1956
+ return;
1957
+ case "pr":
1958
+ void runTurn(prReviewPrompt(args));
1959
+ return;
1960
+ case "undo": {
1961
+ const result = await runtime.checkpoints.undo();
1962
+ if (!result) {
1963
+ notice(t("No file edits to undo."));
1964
+ return;
1965
+ }
1966
+ const rel = (p) => relative(runtime.cwd, p) || p;
1967
+ notice(
1968
+ [
1969
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1970
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1971
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1972
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
1973
+ t("Note: changes made through bash commands cannot be undone.")
1974
+ ].filter(Boolean).join("\n")
1975
+ );
1976
+ return;
1977
+ }
1978
+ case "redo": {
1979
+ const result = await runtime.checkpoints.redo();
1980
+ if (!result) {
1981
+ notice(t("No file edits to redo."));
1982
+ return;
1983
+ }
1984
+ const rel = (p) => relative(runtime.cwd, p) || p;
1985
+ notice(
1986
+ [
1987
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1988
+ result.restored.length ? t("Re-applied: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1989
+ result.deleted.length ? t("Deleted again: {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1990
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : ""
1991
+ ].filter(Boolean).join("\n")
1992
+ );
1993
+ return;
1994
+ }
1995
+ case "rewind": {
1996
+ const parsed = Number.parseInt(args.trim(), 10);
1997
+ const result = await runtime.rewindTurns(Number.isFinite(parsed) && parsed > 0 ? parsed : 1);
1998
+ if (!result) {
1999
+ notice(t("Nothing to rewind."));
2000
+ return;
2001
+ }
2002
+ const rel = (p) => relative(runtime.cwd, p) || p;
2003
+ notice(
2004
+ [
2005
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
2006
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
2007
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
2008
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
2009
+ t("Rewound {n} turn(s): the conversation continues from before them.", { n: result.turns })
2010
+ ].filter(Boolean).join("\n")
2011
+ );
2012
+ return;
2013
+ }
2014
+ case "copy": {
2015
+ const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
2016
+ if (!last || typeof last.content !== "string") {
2017
+ notice(t("No answer to copy yet."));
2018
+ return;
2019
+ }
2020
+ process.stdout.write(osc52(last.content, !!process.env.TMUX));
2021
+ const file = join3(runtime.home, "last-answer.md");
2022
+ mkdirSync(runtime.home, { recursive: true });
2023
+ writeFileSync(file, last.content);
2024
+ notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
2025
+ return;
2026
+ }
2027
+ case "export": {
2028
+ const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
2029
+ const file = resolve(runtime.cwd, name2);
2030
+ if (existsSync2(file)) {
2031
+ const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
2032
+ if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
2033
+ notice(t("Export cancelled."));
2034
+ return;
2035
+ }
2036
+ }
2037
+ try {
2038
+ writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
2039
+ } catch (error) {
2040
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
2041
+ return;
2042
+ }
2043
+ notice(t("Conversation saved to {file}.", { file }));
2044
+ return;
2045
+ }
2046
+ case "provider": {
2047
+ const c = runtime.config;
2048
+ setProviderMenu(
2049
+ Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env), baseURL: c.providers[id].baseURL }))
2050
+ );
2051
+ return;
2052
+ }
2053
+ default:
2054
+ notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
2055
+ }
2056
+ },
2057
+ [runtime, notice, exit, version, runTurn, ask]
2058
+ );
2059
+ const runSlash = useCallback(
2060
+ (name, args) => runSlashCommand(name, args).catch((error) => notice(`/${name}: ${error.message}`, "error")),
2061
+ [runSlashCommand, notice]
2062
+ );
2063
+ const submit = useCallback(
2064
+ (text) => {
2065
+ setHistory((h) => [...h, text]);
2066
+ const slash = parseSlash(text);
2067
+ if (!slash) {
2068
+ void runTurn(text);
2069
+ return;
2070
+ }
2071
+ const command = runtime.commands.find((c) => c.name.toLowerCase() === slash.name);
2072
+ const skill = runtime.skills.find((s) => s.name.toLowerCase() === slash.name);
2073
+ if (SLASH_COMMANDS.some((b) => b.name === slash.name) || !command && !skill) void runSlash(slash.name, slash.args);
2074
+ else if (command) void runTurn(expandCommand(command, slash.args));
2075
+ else if (skill) {
2076
+ let body;
2077
+ try {
2078
+ body = parseFrontmatter(readFileSync(skill.file, "utf8")).body;
2079
+ } catch (error) {
2080
+ notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
2081
+ return;
2082
+ }
2083
+ void runTurn(`${body}
2084
+
2085
+ ARGUMENTS: ${slash.args}`);
2086
+ }
2087
+ },
2088
+ [runSlash, runTurn, runtime, notice]
2089
+ );
2090
+ const onPrompt = useCallback(
2091
+ (text) => {
2092
+ if (busy) setQueued((q) => [...q, text]);
2093
+ else submit(text);
2094
+ },
2095
+ [busy, submit]
2096
+ );
2097
+ useEffect3(() => {
2098
+ if (busy || pending || planAsk || !queued.length) return;
2099
+ const [next, ...rest] = queued;
2100
+ setQueued(rest);
2101
+ submit(next);
2102
+ }, [busy, pending, planAsk, queued, submit]);
2103
+ const initialSent = useRef(false);
2104
+ useEffect3(() => {
2105
+ if (initialPrompt && !initialSent.current) {
2106
+ initialSent.current = true;
2107
+ submit(initialPrompt);
2108
+ }
2109
+ }, [initialPrompt, submit]);
2110
+ const toggleThinking = useCallback(() => {
2111
+ const next = !showThinkingRef.current;
2112
+ showThinkingRef.current = next;
2113
+ setShowThinking(next);
2114
+ try {
2115
+ writeGlobalConfig({ thinkingVisible: next }, runtime.env);
2116
+ } catch (error) {
2117
+ notice(t("Could not save thinking visibility: {error}", { error: error.message }), "warn");
2118
+ }
2119
+ }, [runtime, notice]);
2120
+ useEffect3(() => {
2121
+ if (!runtime.config.thinkingClickCollapse || !busy && !hasThinking || pending || !stdin.isTTY || !stdout.isTTY || !thinkingTargetRef.current) return;
2122
+ let click;
2123
+ let mouseBuffer = "";
2124
+ const onData = (data) => {
2125
+ mouseBuffer += String(data);
2126
+ const reports = parseThinkingReports(mouseBuffer);
2127
+ mouseBuffer = reports.rest.slice(-80);
2128
+ for (const report of reports.events) {
2129
+ if (report.type === "mouse") {
2130
+ click = report.press;
2131
+ stdout.write("\x1B[6n");
2132
+ } else if (click && liveRootRef.current && thinkingTargetRef.current) {
2133
+ const current = click;
2134
+ click = void 0;
2135
+ const root = measureElement(liveRootRef.current);
2136
+ const target = measureElement(thinkingTargetRef.current);
2137
+ if (clickedThinking(current, report.cursor.row, root.height, target)) toggleThinking();
2138
+ }
2139
+ }
2140
+ };
2141
+ stdin.on("data", onData);
2142
+ stdout.write("\x1B[?1000h\x1B[?1006h");
2143
+ return () => {
2144
+ stdin.off("data", onData);
2145
+ stdout.write("\x1B[?1000l\x1B[?1006l");
2146
+ };
2147
+ }, [busy, pending, stdin, stdout, toggleThinking, hasThinking, runtime.config.thinkingClickCollapse]);
2148
+ useInput9((input, key) => {
2149
+ if (key.tab && key.shift) {
2150
+ const next = nextMode(runtime.agent.permissions.mode);
2151
+ runtime.agent.permissions.mode = next;
2152
+ setMode(next);
2153
+ } else if (key.escape && busy && !pending) {
2154
+ controller.current?.abort();
2155
+ setCancelling(true);
2156
+ if (queued.length) {
2157
+ setQueued([]);
2158
+ notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
2159
+ }
2160
+ } else if (matchKeybind(runtime.config.keybinds.pasteImage, input, key)) {
2161
+ void (async () => {
2162
+ const path = await grabClipboardImage({ platform: process.platform, tmpdir: tmpdir(), now: /* @__PURE__ */ new Date() });
2163
+ if (path) {
2164
+ setInjected((prev) => ({ text: `${path} `, n: (prev?.n ?? 0) + 1 }));
2165
+ notice(t("Clipboard image saved to {path} \u2014 the path is inserted in the input; the model sees it when you send.", { path }));
2166
+ } else {
2167
+ notice(t("No image on the clipboard."), "warn");
2168
+ }
2169
+ })();
2170
+ } else if (matchKeybind(runtime.config.keybinds.thinking, input, key)) {
2171
+ toggleThinking();
2172
+ } else if (matchKeybind(runtime.config.keybinds.toolOutput, input, key) && lastTool.current) {
2173
+ const t2 = lastTool.current;
2174
+ notice(`\u23BF ${t2.tool} ${t2.target}
2175
+ ${t2.output}`);
2176
+ }
2177
+ });
2178
+ const pickMcp = async (name) => {
2179
+ const item = mcpMenu?.find((i) => i.name === name);
2180
+ setMcpMenu(null);
2181
+ if (!item) return;
2182
+ if (item.installed) {
2183
+ removeGlobalServer(runtime.home, item.name);
2184
+ await runtime.mcp.remove(item.name);
2185
+ notice(t("MCP {name} removed.", { name: item.name }));
2186
+ return;
2187
+ }
2188
+ const entry2 = CATALOG.find((c) => c.name === item.name);
2189
+ if (!entry2) return;
2190
+ const values = {};
2191
+ for (const input of entry2.inputs ?? []) {
2192
+ const v = await ask(t(input.label), !!input.secret);
2193
+ if (!v) return;
2194
+ values[input.key] = v;
2195
+ }
2196
+ const config = fillTemplate(entry2.config, values);
2197
+ addGlobalServer(runtime.home, entry2.name, config);
2198
+ notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
2199
+ await runtime.mcp.add({ name: entry2.name, config, source: "global" });
2200
+ const state = runtime.mcp.states().find((s) => s.name === entry2.name);
2201
+ if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
2202
+ else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
2203
+ };
2204
+ const pickProvider = async (id) => {
2205
+ setProviderMenu(null);
2206
+ if (!id) return;
2207
+ let target = id;
2208
+ if (id === "custom") {
2209
+ const name = await ask("Id provider (huruf kecil, mis. corp)");
2210
+ const url = name ? await ask("Base URL OpenAI-compatible") : void 0;
2211
+ if (!name || !url) return;
2212
+ writeGlobalConfig({ providers: { [name]: { baseURL: url } } }, runtime.env);
2213
+ runtime.reloadConfig();
2214
+ target = name;
2215
+ }
2216
+ const c = runtime.config;
2217
+ const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
2218
+ if (!hasKey(c, target, runtime.env) || id === "custom") {
2219
+ const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
2220
+ if (key === void 0) return;
2221
+ const result = await runtime.addProviderKey(target, key);
2222
+ if (!result.ok) {
2223
+ notice(result.error, "error");
2224
+ return;
2225
+ }
2226
+ notice(`${providerName(c, target)} siap \xB7 ${result.models} model.`);
2227
+ }
2228
+ setPicker(await runtime.listModels(target));
2229
+ };
2230
+ const answer = (a) => {
2231
+ pending?.resolve(a);
2232
+ setAsks((q) => q.slice(1));
2233
+ if (a === "all") setMode("allowAll");
2234
+ };
2235
+ const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
2236
+ const renderEntry = (e) => {
2237
+ switch (e.kind) {
2238
+ case "header":
2239
+ return /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text16, { children: [
2240
+ /* @__PURE__ */ jsx16(Text16, { color: color(ACCENT), bold: true, children: " \u273B " }),
2241
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: `BCCLI ${version}` }),
2242
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
2243
+ ] }) }, e.id);
2244
+ case "user":
2245
+ return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `> ${e.text}` }) }, e.id);
2246
+ case "assistant":
2247
+ if (e.cont) {
2248
+ return e.text ? /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Markdown, { text: e.text }) }, e.id) : null;
2249
+ }
2250
+ if (!e.text.trim()) return null;
2251
+ return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, children: [
2252
+ /* @__PURE__ */ jsx16(Text16, { color: color(ACCENT), children: "\u25CF " }),
2253
+ /* @__PURE__ */ jsx16(Markdown, { text: e.text.replace(/^\s+/, "") })
2254
+ ] }, e.id);
2255
+ case "thinking":
2256
+ return null;
2257
+ case "tool":
2258
+ return /* @__PURE__ */ jsx16(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub, moreBind: runtime.config.keybinds.toolOutput.spec }, e.id);
2259
+ case "notice":
2260
+ return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
2261
+ }
2262
+ };
2263
+ return /* @__PURE__ */ jsxs12(Box13, { ref: liveRootRef, flexDirection: "column", children: [
2264
+ /* @__PURE__ */ jsx16(Static, { items: transcript.done, children: renderEntry }),
2265
+ transcript.live.map(renderEntry),
2266
+ busy && !pending && !planAsk || !busy && hasThinking ? /* @__PURE__ */ jsxs12(Box13, { ref: thinkingTargetRef, marginTop: 1, flexDirection: "column", children: [
2267
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", children: [
2268
+ busy ? /* @__PURE__ */ jsx16(Spinner, { label: cancelling ? t("Cancelling") : t("Thinking"), startedAt }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("\u273B Thinking") }),
2269
+ /* @__PURE__ */ jsxs12(Text16, { dimColor: true, children: [
2270
+ " \xB7 ",
2271
+ runtime.config.keybinds.thinking.spec,
2272
+ runtime.config.thinkingClickCollapse ? ` \xB7 ${t("click to toggle")}` : ""
2273
+ ] })
2274
+ ] }),
2275
+ showThinking ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2276
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("Model: {model} \xB7 reasoning: {level}", { model: runtime.modelRef, level: reasoning }) }),
2277
+ lastThinking.trim() ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, italic: true, children: lastThinking.trim() }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("No reasoning summary received yet; tool activity remains visible above.") })
2278
+ ] }) : null
2279
+ ] }) : null,
2280
+ pending ? /* @__PURE__ */ jsx16(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
2281
+ picker ? /* @__PURE__ */ jsx16(
2282
+ ModelPicker,
2283
+ {
2284
+ groups: picker,
2285
+ current: runtime.modelRef,
2286
+ onPick: (m) => {
2287
+ setPicker(null);
2288
+ if (m) void runSlash("model", m);
2289
+ }
2290
+ }
2291
+ ) : null,
2292
+ listPicker ? /* @__PURE__ */ jsx16(ListPicker, { title: listPicker.title, items: listPicker.items, onPick: listPicker.onPick }) : null,
2293
+ mcpMenu ? /* @__PURE__ */ jsx16(McpMenu, { items: mcpMenu, onPick: (n) => void pickMcp(n) }) : null,
2294
+ providerMenu ? /* @__PURE__ */ jsx16(ProviderMenu, { entries: providerMenu, onPick: (id) => void pickProvider(id) }) : null,
2295
+ prompt ? /* @__PURE__ */ jsx16(
2296
+ LinePrompt,
2297
+ {
2298
+ label: prompt.label,
2299
+ mask: prompt.mask,
2300
+ onSubmit: (v) => {
2301
+ setPrompt(null);
2302
+ prompt.resolve(v);
2303
+ },
2304
+ onCancel: () => {
2305
+ setPrompt(null);
2306
+ prompt.resolve(void 0);
2307
+ }
2308
+ }
2309
+ ) : null,
2310
+ planAsk ? /* @__PURE__ */ jsx16(
2311
+ PlanApproval,
2312
+ {
2313
+ plan: planAsk.plan,
2314
+ onAnswer: (d) => {
2315
+ planAsk.resolve(d);
2316
+ setPlanAsk(null);
2317
+ if (d !== "no") setMode(d);
2318
+ }
2319
+ }
2320
+ ) : null,
2321
+ /* @__PURE__ */ jsx16(TodoList, { items: todos }),
2322
+ queued.map((q, i) => /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
2323
+ /* @__PURE__ */ jsx16(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands, injected, vim: runtime.config.editor === "vim" }),
2324
+ /* @__PURE__ */ jsx16(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
2325
+ ] });
2326
+ }
2327
+
2328
+ // src/ui/index.tsx
2329
+ import { jsx as jsx17 } from "react/jsx-runtime";
2330
+ async function startInteractive(rt, opts) {
2331
+ if (opts.resume) {
2332
+ const { Session: Session2 } = await import("./session-F6MG3UCU.js");
2333
+ const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
2334
+ if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
2335
+ else {
2336
+ list.forEach((s, i) => {
2337
+ console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
2338
+ });
2339
+ const { createInterface } = await import("readline/promises");
2340
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2341
+ const answer = Number(await rl.question(t("Session number (enter = new): ")));
2342
+ rl.close();
2343
+ const chosen = list[answer - 1];
2344
+ if (chosen) rt.resume(chosen.session);
2345
+ }
2346
+ }
2347
+ const { serversToStart, setProjectTrust } = await import("./config-AZZFOXKV.js");
2348
+ const plan = serversToStart(rt.home, rt.cwd);
2349
+ if (plan.needTrust.length) {
2350
+ const { createInterface } = await import("readline/promises");
2351
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2352
+ for (const s of plan.needTrust) {
2353
+ const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
2354
+ 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();
2355
+ setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
2356
+ if (answer === "y") plan.start.push(s);
2357
+ }
2358
+ rl.close();
2359
+ }
2360
+ void rt.startMcp(plan.start).catch((error) => {
2361
+ console.error(t("Could not start MCP servers: {error}", { error: error instanceof Error ? error.message : String(error) }));
2362
+ });
2363
+ const instance = render(/* @__PURE__ */ jsx17(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
2364
+ await instance.waitUntilExit();
2365
+ await rt.mcp.stop();
2366
+ return 0;
2367
+ }
2368
+ export {
2369
+ startInteractive
2370
+ };