@botconnector/bccli 0.5.0-rc.4 → 0.5.1

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 (187) hide show
  1. package/LICENSE +21 -203
  2. package/README.en.md +253 -0
  3. package/README.md +250 -0
  4. package/dist/abort.d.ts +5 -0
  5. package/dist/acp-HWHF25KU.js +221 -0
  6. package/dist/acp.d.ts +44 -0
  7. package/dist/agent.d.ts +158 -0
  8. package/dist/agents.d.ts +4 -0
  9. package/dist/args-XA77XUMD.js +13 -0
  10. package/dist/args.d.ts +59 -0
  11. package/dist/budget.d.ts +26 -0
  12. package/dist/checkpoints.d.ts +35 -0
  13. package/dist/chunk-2IP5F3EK.js +56 -0
  14. package/dist/chunk-5CVHTYBQ.js +270 -0
  15. package/dist/chunk-6LO6YRDY.js +216 -0
  16. package/dist/chunk-6PJRWKVI.js +50 -0
  17. package/dist/chunk-6YRTPXGY.js +8 -0
  18. package/dist/chunk-7OTFZK4B.js +159 -0
  19. package/dist/chunk-7U7CMKKI.js +861 -0
  20. package/dist/chunk-A3NBWIEQ.js +2706 -0
  21. package/dist/chunk-FY543EQW.js +50 -0
  22. package/dist/chunk-IDZLHEVL.js +524 -0
  23. package/dist/chunk-IF5RNXBI.js +341 -0
  24. package/dist/chunk-M5T4AM4I.js +256 -0
  25. package/dist/chunk-NGFWBVHD.js +71 -0
  26. package/dist/chunk-OCEQ2TRJ.js +1075 -0
  27. package/dist/chunk-OK2USAXX.js +88 -0
  28. package/dist/chunk-PSO7R5AD.js +10 -0
  29. package/dist/chunk-RLSQAZBT.js +78 -0
  30. package/dist/chunk-VPFCIUC6.js +91 -0
  31. package/dist/chunk-YGBTNDPK.js +202 -0
  32. package/dist/chunk-YYTFJUO2.js +148 -0
  33. package/dist/chunk-ZXWRLN7E.js +378 -0
  34. package/dist/cli.d.ts +1 -0
  35. package/dist/cli.js +251 -0
  36. package/dist/clipboardImage.d.ts +17 -0
  37. package/dist/commands.d.ts +16 -0
  38. package/dist/completion-IRVEVW6H.js +125 -0
  39. package/dist/completion.d.ts +14 -0
  40. package/dist/config-OVWCUVY7.js +25 -0
  41. package/dist/config-S3CZUIBB.js +34 -0
  42. package/dist/config.d.ts +99 -0
  43. package/dist/context.d.ts +13 -0
  44. package/dist/contextWindow.d.ts +3 -0
  45. package/dist/diff.d.ts +7 -0
  46. package/dist/exit.d.ts +9 -0
  47. package/dist/extensions.d.ts +34 -0
  48. package/dist/ghBotCli-FYKJDJOD.js +680 -0
  49. package/dist/ghBotCli.d.ts +9 -0
  50. package/dist/ghBotDispatch.d.ts +68 -0
  51. package/dist/ghBotRunner.d.ts +82 -0
  52. package/dist/ghBotServe.d.ts +21 -0
  53. package/dist/ghBotWebhook.d.ts +45 -0
  54. package/dist/hooks.d.ts +27 -0
  55. package/dist/i18n/id.d.ts +1 -0
  56. package/dist/i18n.d.ts +16 -0
  57. package/dist/integrationCli-GY73S2K7.js +876 -0
  58. package/dist/integrationCli.d.ts +9 -0
  59. package/dist/keybinds.d.ts +22 -0
  60. package/dist/login-KSN63EM4.js +18 -0
  61. package/dist/login.d.ts +21 -0
  62. package/dist/lsp.d.ts +73 -0
  63. package/dist/mcp/catalog.d.ts +14 -0
  64. package/dist/mcp/config.d.ts +36 -0
  65. package/dist/mcp/manager.d.ts +52 -0
  66. package/dist/mcp/oauth.d.ts +82 -0
  67. package/dist/mcpCli-VWSCCDWT.js +113 -0
  68. package/dist/mcpCli.d.ts +3 -0
  69. package/dist/mcpServer/serve.d.ts +8 -0
  70. package/dist/mcpServer/server.d.ts +19 -0
  71. package/dist/models.d.ts +13 -0
  72. package/dist/modelsCli-4Q6KFWV2.js +36 -0
  73. package/dist/modelsCli.d.ts +15 -0
  74. package/dist/npmRunner-LU43GM3W.js +46 -0
  75. package/dist/npmRunner.d.ts +21 -0
  76. package/dist/oauthLogin-JWXQZTMZ.js +210 -0
  77. package/dist/oauthLogin.d.ts +47 -0
  78. package/dist/onboarding.d.ts +6 -0
  79. package/dist/permissions.d.ts +30 -0
  80. package/dist/pluginSpec.d.ts +25 -0
  81. package/dist/plugins.d.ts +45 -0
  82. package/dist/pluginsCli-7XWDLJEL.js +288 -0
  83. package/dist/pluginsCli.d.ts +16 -0
  84. package/dist/prCli-FH7N26D4.js +94 -0
  85. package/dist/prCli.d.ts +46 -0
  86. package/dist/presets.d.ts +8 -0
  87. package/dist/print.d.ts +17 -0
  88. package/dist/provider.d.ts +145 -0
  89. package/dist/providerCli-YMG4G56R.js +15 -0
  90. package/dist/providerCli.d.ts +17 -0
  91. package/dist/providers.d.ts +6 -0
  92. package/dist/reasoning.d.ts +12 -0
  93. package/dist/repomap.d.ts +10 -0
  94. package/dist/sdk.d.ts +61 -0
  95. package/dist/sdk.js +6532 -0
  96. package/dist/serve-HSFF6MGB.js +178 -0
  97. package/dist/serve-R2IHP26A.js +532 -0
  98. package/dist/serve.d.ts +37 -0
  99. package/dist/serveHeartbeat.d.ts +14 -0
  100. package/dist/serveProtocol.d.ts +47 -0
  101. package/dist/session-YM36TGU7.js +12 -0
  102. package/dist/session.d.ts +33 -0
  103. package/dist/setup-3YB6EKYX.js +24 -0
  104. package/dist/setup.d.ts +79 -0
  105. package/dist/slash/copy.d.ts +1 -0
  106. package/dist/slash/diff.d.ts +1 -0
  107. package/dist/slash/doctor.d.ts +18 -0
  108. package/dist/slash/export.d.ts +2 -0
  109. package/dist/slash/info.d.ts +46 -0
  110. package/dist/slash/memory.d.ts +10 -0
  111. package/dist/statsCli-I4TSDU3X.js +353 -0
  112. package/dist/statsCli.d.ts +62 -0
  113. package/dist/textToolCalls.d.ts +6 -0
  114. package/dist/thinking.d.ts +17 -0
  115. package/dist/tokenBudget.d.ts +22 -0
  116. package/dist/tools/bash.d.ts +54 -0
  117. package/dist/tools/diagnostics.d.ts +13 -0
  118. package/dist/tools/edit.d.ts +12 -0
  119. package/dist/tools/fetch.d.ts +41 -0
  120. package/dist/tools/glob.d.ts +5 -0
  121. package/dist/tools/globSafety.d.ts +7 -0
  122. package/dist/tools/grep.d.ts +25 -0
  123. package/dist/tools/ignore.d.ts +2 -0
  124. package/dist/tools/index.d.ts +12 -0
  125. package/dist/tools/memory.d.ts +13 -0
  126. package/dist/tools/paths.d.ts +4 -0
  127. package/dist/tools/plan.d.ts +10 -0
  128. package/dist/tools/read.d.ts +16 -0
  129. package/dist/tools/repomap.d.ts +2 -0
  130. package/dist/tools/sandbox.d.ts +15 -0
  131. package/dist/tools/shell.d.ts +18 -0
  132. package/dist/tools/skill.d.ts +3 -0
  133. package/dist/tools/task.d.ts +28 -0
  134. package/dist/tools/todo.d.ts +13 -0
  135. package/dist/tools/types.d.ts +52 -0
  136. package/dist/tools/websearch.d.ts +9 -0
  137. package/dist/tools/write.d.ts +5 -0
  138. package/dist/transferCli-3B4LMOYI.js +73 -0
  139. package/dist/transferCli.d.ts +23 -0
  140. package/dist/ui/App.d.ts +7 -0
  141. package/dist/ui/DiffView.d.ts +3 -0
  142. package/dist/ui/EditableText.d.ts +7 -0
  143. package/dist/ui/LinePrompt.d.ts +6 -0
  144. package/dist/ui/ListPicker.d.ts +11 -0
  145. package/dist/ui/Markdown.d.ts +8 -0
  146. package/dist/ui/McpMenu.d.ts +11 -0
  147. package/dist/ui/ModelPicker.d.ts +6 -0
  148. package/dist/ui/PermissionPrompt.d.ts +5 -0
  149. package/dist/ui/PlanApproval.d.ts +5 -0
  150. package/dist/ui/PromptInput.d.ts +20 -0
  151. package/dist/ui/ProviderMenu.d.ts +10 -0
  152. package/dist/ui/Spinner.d.ts +4 -0
  153. package/dist/ui/StatusBar.d.ts +14 -0
  154. package/dist/ui/TodoList.d.ts +4 -0
  155. package/dist/ui/ToolBlock.d.ts +14 -0
  156. package/dist/ui/complete.d.ts +8 -0
  157. package/dist/ui/index.d.ts +7 -0
  158. package/dist/ui/lineEdit.d.ts +42 -0
  159. package/dist/ui/liveCap.d.ts +29 -0
  160. package/dist/ui/markdownLayout.d.ts +23 -0
  161. package/dist/ui/replay.d.ts +4 -0
  162. package/dist/ui/stdinFilter.d.ts +18 -0
  163. package/dist/ui/table.d.ts +20 -0
  164. package/dist/ui/theme.d.ts +5 -0
  165. package/dist/ui/thinkingMouse.d.ts +38 -0
  166. package/dist/ui/transcript.d.ts +55 -0
  167. package/dist/ui/tty.d.ts +11 -0
  168. package/dist/ui/vim.d.ts +26 -0
  169. package/dist/ui-TAZMRXRI.js +3044 -0
  170. package/dist/update-XC3B6SJC.js +145 -0
  171. package/dist/update.d.ts +30 -0
  172. package/dist/verify.d.ts +16 -0
  173. package/dist/version.d.ts +1 -0
  174. package/dist/webBudget.d.ts +25 -0
  175. package/dist/worktree.d.ts +12 -0
  176. package/package.json +59 -35
  177. package/THIRD_PARTY_NOTICES.md +0 -9
  178. package/branding.mjs +0 -347
  179. package/cli.mjs +0 -120
  180. package/core.mjs +0 -260
  181. package/credentials.mjs +0 -52
  182. package/gateway.mjs +0 -2
  183. package/model-command.mjs +0 -61
  184. package/provider-cli.mjs +0 -77
  185. package/providers.mjs +0 -166
  186. package/proxy.mjs +0 -242
  187. package/startup-menu.mjs +0 -120
@@ -0,0 +1,3044 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CATALOG,
4
+ fillTemplate
5
+ } from "./chunk-6PJRWKVI.js";
6
+ import {
7
+ MAX_VERIFY_ROUNDS,
8
+ runVerify,
9
+ toolLabel,
10
+ verifyFollowup,
11
+ worktreeListText
12
+ } from "./chunk-5CVHTYBQ.js";
13
+ import {
14
+ BUILTIN_AGENTS,
15
+ appendMemory,
16
+ ignorePatterns,
17
+ instructionFiles,
18
+ normalizeGlobPath,
19
+ parseFrontmatter,
20
+ parseMemoryArgs,
21
+ recordFileMtime,
22
+ renderBackgroundTasks,
23
+ resolveShell,
24
+ runCommand,
25
+ runHooks,
26
+ validateGlobPattern
27
+ } from "./chunk-7U7CMKKI.js";
28
+ import "./chunk-PSO7R5AD.js";
29
+ import {
30
+ nextMode,
31
+ requiresSensitiveToolApproval
32
+ } from "./chunk-YYTFJUO2.js";
33
+ import {
34
+ hasKey,
35
+ providerName,
36
+ writeGlobalConfig
37
+ } from "./chunk-FY543EQW.js";
38
+ import {
39
+ Session
40
+ } from "./chunk-6LO6YRDY.js";
41
+ import {
42
+ contentText
43
+ } from "./chunk-OCEQ2TRJ.js";
44
+ import {
45
+ REASONING_LEVELS,
46
+ assertReasoningSupported,
47
+ parseReasoningLevel,
48
+ supportedReasoningLevels
49
+ } from "./chunk-NGFWBVHD.js";
50
+ import {
51
+ addGlobalServer,
52
+ globalMcpPath,
53
+ readMcpFile,
54
+ removeGlobalServer
55
+ } from "./chunk-VPFCIUC6.js";
56
+ import {
57
+ ACCENT,
58
+ PRESETS,
59
+ color,
60
+ matchKeybind,
61
+ removeCredential
62
+ } from "./chunk-ZXWRLN7E.js";
63
+ import {
64
+ getLanguage,
65
+ locale,
66
+ parseLang,
67
+ setLanguage,
68
+ t
69
+ } from "./chunk-IDZLHEVL.js";
70
+
71
+ // src/ui/index.tsx
72
+ import { render } from "ink";
73
+
74
+ // src/ui/App.tsx
75
+ import { execFile as execFile3 } from "child_process";
76
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
77
+ import { homedir, tmpdir } from "os";
78
+ import { dirname, join as join3, relative as relative2, resolve as resolve2 } from "path";
79
+ import { Box as Box13, Static, Text as Text16, measureElement, useApp, useInput as useInput9, useStdin, useStdout as useStdout2 } from "ink";
80
+ import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState10 } from "react";
81
+
82
+ // src/commands.ts
83
+ var SLASH_COMMANDS = [
84
+ { name: "help", description: "list of commands and shortcuts" },
85
+ { name: "thinking", description: "show/hide the model\u2019s thinking" },
86
+ { name: "model", description: "change model" },
87
+ { name: "reasoning", description: "set reasoning: auto/off/low/medium/high/max" },
88
+ { name: "provider", description: "add/choose an AI provider" },
89
+ { name: "mcp", description: "install/manage MCP servers" },
90
+ { name: "new", description: "new session (the old one stays saved)" },
91
+ { name: "resume", description: "continue another session in this folder" },
92
+ { name: "session", description: "info about this session" },
93
+ { name: "status", description: "version, model, permission mode, MCP, context" },
94
+ { name: "permissions", description: "view/revoke permissions" },
95
+ { name: "undo", description: "undo the file edits of the last turn" },
96
+ { name: "redo", description: "re-apply the file edits that /undo just reverted" },
97
+ { name: "rewind", description: "step back turns: conversation and file edits (/rewind 2)" },
98
+ { name: "diff", description: "git diff of the project" },
99
+ { name: "worktree", description: "list git worktrees; create one with bccli -w <name>" },
100
+ { name: "pr", description: "review a pull request (/pr <n|url|branch>)" },
101
+ { name: "copy", description: "copy the last answer" },
102
+ { name: "export", description: "save the conversation as markdown" },
103
+ { name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
104
+ { name: "init", description: "create/update AGENTS.md for this project" },
105
+ { name: "agents", description: "list subagents" },
106
+ { name: "skills", description: "list skills and custom commands" },
107
+ { name: "doctor", description: "check installation health" },
108
+ { name: "login", description: "save the API key of the current provider" },
109
+ { name: "logout", description: "delete the API key of the current provider" },
110
+ { name: "language", description: "set the interface language: en/id" },
111
+ { name: "clear", description: "start a new conversation" },
112
+ { name: "compact", description: "summarize the conversation" },
113
+ { name: "cost", description: "token usage of this session" },
114
+ { name: "config", description: "view or change configuration settings" },
115
+ { name: "tasks", description: "list background tasks" },
116
+ { name: "exit", description: "quit" }
117
+ ];
118
+ function parseSlash(text) {
119
+ if (!text.startsWith("/")) return void 0;
120
+ const [name, ...rest] = text.slice(1).trim().split(/\s+/);
121
+ return { name: name.toLowerCase(), args: rest.join(" ") };
122
+ }
123
+ function expandCommand(def, args) {
124
+ if (def.body.includes("$ARGUMENTS")) return def.body.replaceAll("$ARGUMENTS", args);
125
+ return args ? `${def.body}
126
+
127
+ ${args}` : def.body;
128
+ }
129
+ function prReviewPrompt(selector) {
130
+ const target = selector.trim();
131
+ 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`.";
132
+ return `Review this pull request like a senior reviewer.
133
+ ${refs}
134
+ Read the changed files in the repository when the diff alone is not enough to judge the change.
135
+ 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\`.
136
+ 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.`;
137
+ }
138
+
139
+ // src/slash/copy.ts
140
+ function osc52(text, inTmux) {
141
+ const seq = `\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`;
142
+ return inTmux ? `\x1BPtmux;${seq.replaceAll("\x1B", "\x1B\x1B")}\x1B\\` : seq;
143
+ }
144
+
145
+ // src/slash/diff.ts
146
+ import { execFile } from "child_process";
147
+ function git(cwd, args, bin = "git") {
148
+ return new Promise((resolve3) => {
149
+ execFile(
150
+ bin,
151
+ args,
152
+ { cwd, maxBuffer: 20 * 1024 * 1024 },
153
+ (error, stdout, stderr) => resolve3({ ok: !error, out: error ? stderr : stdout, missing: error?.code === "ENOENT" })
154
+ );
155
+ });
156
+ }
157
+ async function gitDiff(cwd, maxLines = 400, bin = "git") {
158
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
159
+ if (inside.missing) return t("git is not installed or not on PATH, so /diff cannot be used.");
160
+ if (!inside.ok || inside.out.trim() !== "true") return t("This folder is not a git repository, so there is no diff.");
161
+ const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
162
+ const [stat, diff, untracked] = await Promise.all([
163
+ git(cwd, ["diff", "--stat", base]),
164
+ git(cwd, ["diff", base]),
165
+ git(cwd, ["ls-files", "--others", "--exclude-standard"])
166
+ ]);
167
+ const fresh = untracked.out.split("\n").filter(Boolean);
168
+ if (!diff.out.trim() && !fresh.length) return t("No uncommitted changes.");
169
+ const lines = diff.out.trimEnd().split("\n");
170
+ const more = lines.length > maxLines ? `
171
+ ${t("\u2026 {n} more lines", { n: lines.length - maxLines })}` : "";
172
+ 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");
173
+ }
174
+
175
+ // src/slash/doctor.ts
176
+ async function doctorText(input, deps) {
177
+ const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
178
+ const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : t("\u2717 Node {version} (needs \u2265 22)", { version: deps.nodeVersion })];
179
+ for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : t("\u25CB {name} (no key yet)", { name: p.name }));
180
+ const cmds = ["rg", "git", "npx", "uvx"];
181
+ const [connection, found] = await Promise.all([
182
+ deps.listModels().then(
183
+ (models) => t("\u2713 Connection {provider} ({n} models)", { provider: input.activeProvider, n: models.length }),
184
+ (error) => t("\u2717 Connection {provider}: {error}", { provider: input.activeProvider, error: error.message })
185
+ ),
186
+ Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
187
+ ]);
188
+ lines.push(connection);
189
+ for (const m of input.mcp) lines.push(m.status === "error" ? `\u2717 MCP ${m.name}: ${m.error ?? ""}` : `\u2713 MCP ${m.name} (${m.status})`);
190
+ for (const [i, cmd] of cmds.entries()) lines.push(`${found[i] ? "\u2713" : "\u2717"} ${cmd}`);
191
+ return lines.join("\n");
192
+ }
193
+
194
+ // src/slash/export.ts
195
+ function exportMarkdown(messages, title) {
196
+ const parts = [`# ${title}`, ""];
197
+ for (const m of messages) {
198
+ if (m.role === "user") parts.push("## User", "", contentText(m.content), "");
199
+ else if (m.role === "assistant") {
200
+ for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
201
+ if (m.content) parts.push("## BCCLI", "", m.content, "");
202
+ }
203
+ }
204
+ return parts.join("\n");
205
+ }
206
+
207
+ // src/slash/info.ts
208
+ var k = (n) => n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
209
+ function statusText(s) {
210
+ const ready = s.mcp.filter((m) => m.status === "ready").length;
211
+ const errors = s.mcp.filter((m) => m.status === "error").length;
212
+ const win = s.contextWindow && s.contextWindow > 0 ? s.contextWindow : 128e3;
213
+ const ctxLine = t("Context: ~{pct}% of 128k", { pct: Math.round(s.lastInputTokens / win * 100) }).replace("128k", k(win));
214
+ return [
215
+ `BCCLI ${s.version}`,
216
+ `Model: ${s.modelRef} (${s.providerLabel})`,
217
+ t("Permission mode: {mode}", { mode: s.mode }),
218
+ t("Folder: {cwd}", { cwd: s.cwd }),
219
+ t("MCP: {ready} active, {errors} errors", { ready, errors }),
220
+ t("Session tokens: {input} in \xB7 {output} out", { input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
221
+ `Cache: ${k(s.usage.cachedInputTokens ?? 0)} read \xB7 ${k(s.usage.cacheWriteTokens ?? 0)} write`,
222
+ ctxLine
223
+ ].join("\n");
224
+ }
225
+ function agentsText(defs) {
226
+ 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");
227
+ }
228
+ function skillsText(skills, commands) {
229
+ const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : t("(no skills)");
230
+ const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : t("(no custom commands)");
231
+ return `${t("Skills")}:
232
+ ${s}
233
+
234
+ ${t("Custom commands")}:
235
+ ${c}`;
236
+ }
237
+ function sessionText(s) {
238
+ return [
239
+ t("Started: {when}", { when: s.started.toLocaleString(locale()) }),
240
+ t("{messages} messages \xB7 {input} tokens in \xB7 {output} out", { messages: s.messages, input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
241
+ `Model: ${s.modelRef}`,
242
+ t("File: {file}", { file: s.file })
243
+ ].join("\n");
244
+ }
245
+
246
+ // src/ui/Markdown.tsx
247
+ import { Box, Text, useStdout } from "ink";
248
+
249
+ // src/ui/table.ts
250
+ var WIDE = [
251
+ [4352, 4447],
252
+ [8986, 8987],
253
+ [9193, 9196],
254
+ [9200, 9200],
255
+ [9203, 9203],
256
+ [9725, 9726],
257
+ [9748, 9749],
258
+ [9800, 9811],
259
+ [9855, 9855],
260
+ [9875, 9875],
261
+ [9889, 9889],
262
+ [9898, 9899],
263
+ [9917, 9918],
264
+ [9924, 9925],
265
+ [9934, 9934],
266
+ [9940, 9940],
267
+ [9962, 9962],
268
+ [9970, 9971],
269
+ [9973, 9973],
270
+ [9978, 9978],
271
+ [9981, 9981],
272
+ [9989, 9989],
273
+ [9994, 9995],
274
+ [10024, 10024],
275
+ [10060, 10060],
276
+ [10062, 10062],
277
+ [10067, 10069],
278
+ [10071, 10071],
279
+ [10133, 10135],
280
+ [10160, 10160],
281
+ [10175, 10175],
282
+ [11035, 11036],
283
+ [11088, 11088],
284
+ [11093, 11093],
285
+ [11904, 42191],
286
+ [44032, 55203],
287
+ [63744, 64255],
288
+ [65072, 65135],
289
+ [65280, 65376],
290
+ [65504, 65510],
291
+ [127744, 128591],
292
+ [128640, 128767],
293
+ [129280, 129791],
294
+ [131072, 262141]
295
+ ];
296
+ var ZERO = [
297
+ [768, 879],
298
+ [6832, 6911],
299
+ [7616, 7679],
300
+ [8203, 8207],
301
+ [8400, 8447],
302
+ [65024, 65039],
303
+ [65279, 65279],
304
+ [917760, 917999]
305
+ ];
306
+ var inRanges = (cp, ranges) => ranges.some(([a, b]) => cp >= a && cp <= b);
307
+ function charWidth(cp) {
308
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
309
+ if (cp < 768) return 1;
310
+ if (inRanges(cp, ZERO)) return 0;
311
+ return inRanges(cp, WIDE) ? 2 : 1;
312
+ }
313
+ function displayWidth(text) {
314
+ let width = 0;
315
+ for (const ch of text) width += charWidth(ch.codePointAt(0));
316
+ return width;
317
+ }
318
+ var segsWidth = (segs) => segs.reduce((sum, s) => sum + displayWidth(s.text), 0);
319
+ var INLINE = /(`[^`]+`|\*\*[^*]+\*\*|~~[^~]+~~|!?\[[^\]\n]+\]\([^)\s]+\)|(?<![\w*])\*(?![\s*])[^*\n]+?(?<![\s*])\*(?![\w*])|(?<!\w)_(?![\s_])[^_\n]+?(?<![\s_])_(?!\w))/g;
320
+ function parseInline(text) {
321
+ const segs = [];
322
+ for (const part of text.split(INLINE)) {
323
+ if (part === "") continue;
324
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) segs.push({ text: part.slice(1, -1), style: "code" });
325
+ else if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) segs.push({ text: part.slice(2, -2), style: "bold" });
326
+ else if (part.length > 4 && part.startsWith("~~") && part.endsWith("~~")) segs.push({ text: part.slice(2, -2), style: "strike" });
327
+ else if (/^!?\[[^\]\n]+\]\([^)\s]+\)$/.test(part)) {
328
+ const m = /^(!?)\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
329
+ const [, image, label, url] = m;
330
+ segs.push({ text: label, style: image ? "plain" : "link" });
331
+ if (label !== url && !url.startsWith("#")) segs.push({ text: ` (${url})`, style: "dim" });
332
+ } else if (part.length > 2 && (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_"))) {
333
+ segs.push({ text: part.slice(1, -1), style: "italic" });
334
+ } else segs.push({ text: part, style: "plain" });
335
+ }
336
+ return segs;
337
+ }
338
+ function splitRow(line) {
339
+ let s = line.trim();
340
+ if (s.startsWith("|")) s = s.slice(1);
341
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
342
+ const cells = [];
343
+ let current = "";
344
+ for (let i = 0; i < s.length; i++) {
345
+ if (s[i] === "\\" && s[i + 1] === "|") {
346
+ current += "|";
347
+ i++;
348
+ } else if (s[i] === "|") {
349
+ cells.push(current);
350
+ current = "";
351
+ } else current += s[i];
352
+ }
353
+ cells.push(current);
354
+ return cells.map((c) => c.trim());
355
+ }
356
+ var SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
357
+ var isSeparator = (line) => line.includes("|") && SEPARATOR.test(line);
358
+ var cell = (raw) => parseInline(raw.replace(/<br\s*\/?>/gi, "\n"));
359
+ function parseTable(lines, start) {
360
+ if (start + 1 >= lines.length || !lines[start].includes("|") || !isSeparator(lines[start + 1])) return void 0;
361
+ const header = splitRow(lines[start]);
362
+ const sep = splitRow(lines[start + 1]);
363
+ if (sep.length !== header.length) return void 0;
364
+ const body = [];
365
+ let end = start + 2;
366
+ while (end < lines.length && lines[end].trim() && lines[end].includes("|")) body.push(splitRow(lines[end++]));
367
+ const columns = Math.max(header.length, ...body.map((r) => r.length));
368
+ const pad2 = (row, fill) => [...row, ...Array.from({ length: columns - row.length }, () => fill)];
369
+ const align = pad2(
370
+ sep.map((s) => s.startsWith(":") && s.endsWith(":") ? "center" : s.endsWith(":") ? "right" : "left"),
371
+ "left"
372
+ );
373
+ return { table: { header: pad2(header, "").map(cell), align, rows: body.map((r) => pad2(r, "").map(cell)) }, end };
374
+ }
375
+ function tokenize(segs) {
376
+ const tokens = [];
377
+ let word = [];
378
+ const endWord = () => {
379
+ if (word.length) tokens.push(word);
380
+ word = [];
381
+ };
382
+ for (const seg of segs) {
383
+ for (const piece of seg.text.match(/\n|[^\S\n]+|[^\s]+/g) ?? []) {
384
+ if (piece === "\n") {
385
+ endWord();
386
+ tokens.push("br");
387
+ } else if (/^\s+$/.test(piece)) endWord();
388
+ else word.push({ text: piece, style: seg.style });
389
+ }
390
+ }
391
+ endWord();
392
+ return tokens;
393
+ }
394
+ function splitWord(word, width) {
395
+ const pieces = [];
396
+ let current = [];
397
+ let used = 0;
398
+ for (const seg of word) {
399
+ let text = "";
400
+ for (const ch of seg.text) {
401
+ const w = charWidth(ch.codePointAt(0));
402
+ if (used + w > width && used > 0) {
403
+ if (text) current.push({ text, style: seg.style });
404
+ pieces.push(current);
405
+ current = [];
406
+ text = "";
407
+ used = 0;
408
+ }
409
+ text += ch;
410
+ used += w;
411
+ }
412
+ if (text) current.push({ text, style: seg.style });
413
+ }
414
+ if (current.length) pieces.push(current);
415
+ return pieces;
416
+ }
417
+ function wrap(tokens, width) {
418
+ const lines = [];
419
+ let line = [];
420
+ let used = 0;
421
+ const flush = () => {
422
+ lines.push(line);
423
+ line = [];
424
+ used = 0;
425
+ };
426
+ for (const token of tokens) {
427
+ if (token === "br") {
428
+ flush();
429
+ continue;
430
+ }
431
+ const w = segsWidth(token);
432
+ if (w > width) {
433
+ if (used > 0) flush();
434
+ const pieces = splitWord(token, width);
435
+ for (const piece of pieces.slice(0, -1)) {
436
+ line = piece;
437
+ flush();
438
+ }
439
+ line = pieces.at(-1) ?? [];
440
+ used = segsWidth(line);
441
+ } else if (used === 0) {
442
+ line = [...token];
443
+ used = w;
444
+ } else if (used + 1 + w <= width) {
445
+ line.push({ text: " ", style: "plain" }, ...token);
446
+ used += 1 + w;
447
+ } else {
448
+ flush();
449
+ line = [...token];
450
+ used = w;
451
+ }
452
+ }
453
+ flush();
454
+ return lines;
455
+ }
456
+ var SEP = " \u2502 ";
457
+ var RULE_JOIN = "\u2500\u253C\u2500";
458
+ var MAX_MIN_WIDTH = 12;
459
+ var dim = (text) => ({ text, style: "dim" });
460
+ function rtrim(line) {
461
+ const out = [...line];
462
+ while (out.length) {
463
+ const last = out[out.length - 1];
464
+ const text = last.text.trimEnd();
465
+ if (text) {
466
+ out[out.length - 1] = { ...last, text };
467
+ break;
468
+ }
469
+ out.pop();
470
+ }
471
+ return out;
472
+ }
473
+ var bold = (segs) => segs.map((s) => s.style === "plain" ? { ...s, style: "bold" } : s);
474
+ function pad(segs, width, align, last) {
475
+ const space = Math.max(0, width - segsWidth(segs));
476
+ const left = align === "right" ? space : align === "center" ? Math.floor(space / 2) : 0;
477
+ const right = last ? 0 : space - left;
478
+ return [...left ? [{ text: " ".repeat(left), style: "plain" }] : [], ...segs, ...right ? [{ text: " ".repeat(right), style: "plain" }] : []];
479
+ }
480
+ function columnWidths(table, available) {
481
+ const columns = table.align.length;
482
+ const all = [table.header, ...table.rows];
483
+ const natural = [];
484
+ const minimum = [];
485
+ for (let c = 0; c < columns; c++) {
486
+ let widest = 1;
487
+ let longestWord = 1;
488
+ for (const row of all) {
489
+ const tokens = tokenize(row[c]);
490
+ for (const line of wrap(tokens, Number.POSITIVE_INFINITY)) widest = Math.max(widest, segsWidth(line));
491
+ for (const token of tokens) if (token !== "br") longestWord = Math.max(longestWord, segsWidth(token));
492
+ }
493
+ natural.push(widest);
494
+ minimum.push(Math.min(longestWord, MAX_MIN_WIDTH, widest));
495
+ }
496
+ if (minimum.reduce((a, b) => a + b, 0) > available) return void 0;
497
+ const widths = [...natural];
498
+ let total = widths.reduce((a, b) => a + b, 0);
499
+ while (total > available) {
500
+ let pick = -1;
501
+ for (let c = 0; c < columns; c++) if (widths[c] > minimum[c] && (pick < 0 || widths[c] > widths[pick])) pick = c;
502
+ if (pick < 0) return void 0;
503
+ widths[pick]--;
504
+ total--;
505
+ }
506
+ return widths;
507
+ }
508
+ function stacked(table, width) {
509
+ const lines = [];
510
+ table.rows.forEach((row, r) => {
511
+ if (r > 0) lines.push([dim("\u2500".repeat(Math.min(width, 40)))]);
512
+ row.forEach((value, c) => {
513
+ const label = [...bold(table.header[c]), { text: ": ", style: "plain" }, ...value];
514
+ lines.push(...wrap(tokenize(label), width));
515
+ });
516
+ });
517
+ return lines.length ? lines : [bold(table.header.flat())];
518
+ }
519
+ function layoutTable(table, width) {
520
+ const columns = table.align.length;
521
+ const available = Math.max(columns, width - (columns - 1) * SEP.length);
522
+ const widths = columnWidths(table, available);
523
+ if (!widths) return stacked(table, Math.max(10, width));
524
+ const renderRow = (cells, header) => {
525
+ const wrapped = cells.map((segs, c) => wrap(tokenize(header ? bold(segs) : segs), widths[c]));
526
+ const height = Math.max(...wrapped.map((w) => w.length));
527
+ const lines = [];
528
+ for (let l = 0; l < height; l++) {
529
+ const line = [];
530
+ wrapped.forEach((w, c) => {
531
+ if (c > 0) line.push(dim(SEP));
532
+ line.push(...pad(w[l] ?? [], widths[c], table.align[c], c === columns - 1));
533
+ });
534
+ lines.push(rtrim(line));
535
+ }
536
+ return { lines, height };
537
+ };
538
+ const rule = [dim(widths.map((w) => "\u2500".repeat(w)).join(RULE_JOIN))];
539
+ const head = renderRow(table.header, true);
540
+ const body = table.rows.map((r) => renderRow(r, false));
541
+ const wrappedRows2 = body.some((b) => b.height > 1);
542
+ const out = [...head.lines, rule];
543
+ body.forEach((b, i) => {
544
+ if (wrappedRows2 && i > 0) out.push(rule);
545
+ out.push(...b.lines);
546
+ });
547
+ return out;
548
+ }
549
+
550
+ // src/ui/markdownLayout.ts
551
+ var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
552
+ var RULE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
553
+ var QUOTE = /^\s{0,3}((?:>\s?)+)(.*)$/;
554
+ var BULLET = /^(\s*)[-*+]\s+(.*)$/;
555
+ var NUMBERED = /^(\s*)(\d{1,9})[.)]\s+(.*)$/;
556
+ var BULLETS = ["\u2022", "\u25E6", "\u25AA"];
557
+ var bulletFor = (spaces) => BULLETS[spaces === 0 ? 0 : spaces <= 4 ? 1 : 2];
558
+ function wrappedRows(text, width) {
559
+ const usable = Math.max(1, width);
560
+ const lines = text.split("\n");
561
+ return lines.reduce((sum, line) => sum + Math.max(1, Math.ceil(displayWidth(line) / usable)), 0);
562
+ }
563
+ var plain = (line) => parseInline(line).map((seg) => seg.text).join("");
564
+ var hangingRows = (marker, body, width, padding = 0) => {
565
+ const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
566
+ return wrappedRows(plain(body), textWidth);
567
+ };
568
+ function markdownRowCount(text, width) {
569
+ const lines = text.split("\n");
570
+ let rows = 0;
571
+ let inCode = false;
572
+ for (let i = 0; i < lines.length; i++) {
573
+ const line = lines[i];
574
+ if (line.trimStart().startsWith("```")) {
575
+ inCode = !inCode;
576
+ continue;
577
+ }
578
+ if (inCode) {
579
+ rows += wrappedRows(` ${line}`, width);
580
+ continue;
581
+ }
582
+ const table = parseTable(lines, i);
583
+ if (table) {
584
+ rows += layoutTable(table.table, width).length;
585
+ i = table.end - 1;
586
+ continue;
587
+ }
588
+ const heading = HEADING.exec(line);
589
+ if (heading) {
590
+ rows += wrappedRows(plain(heading[2]), width);
591
+ continue;
592
+ }
593
+ if (RULE.test(line)) {
594
+ rows += 1;
595
+ continue;
596
+ }
597
+ const quote = QUOTE.exec(line);
598
+ if (quote) {
599
+ rows += hangingRows("\u258E".repeat(quote[1].replace(/\s/g, "").length), quote[2], width);
600
+ continue;
601
+ }
602
+ const bullet = BULLET.exec(line);
603
+ if (bullet) {
604
+ const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
605
+ const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
606
+ rows += task ? hangingRows(task[1] === " " ? "\u2610" : "\u2611", task[2], width, padding) : hangingRows(bulletFor(padding), bullet[2], width, padding);
607
+ continue;
608
+ }
609
+ const numbered = NUMBERED.exec(line);
610
+ if (numbered) {
611
+ const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
612
+ rows += hangingRows(`${numbered[2]}.`, numbered[3], width, padding);
613
+ continue;
614
+ }
615
+ rows += line ? wrappedRows(plain(line), width) : 1;
616
+ }
617
+ return rows;
618
+ }
619
+
620
+ // src/ui/tty.ts
621
+ function isTtyStream(stream) {
622
+ return typeof stream === "object" && stream !== null && stream.isTTY === true;
623
+ }
624
+ function isRealTty(stream) {
625
+ return isTtyStream(stream) && typeof stream.fd === "number";
626
+ }
627
+ function streamColumns(stream) {
628
+ const columns = typeof stream === "object" && stream !== null ? stream.columns : void 0;
629
+ return typeof columns === "number" && columns > 0 ? columns : 80;
630
+ }
631
+ function streamRows(stream) {
632
+ const rows = typeof stream === "object" && stream !== null ? stream.rows : void 0;
633
+ return typeof rows === "number" && rows > 0 ? rows : 24;
634
+ }
635
+
636
+ // src/ui/Markdown.tsx
637
+ import { jsx, jsxs } from "react/jsx-runtime";
638
+ function segment(seg, key) {
639
+ switch (seg.style) {
640
+ case "bold":
641
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: seg.text }, key);
642
+ case "italic":
643
+ return /* @__PURE__ */ jsx(Text, { italic: true, children: seg.text }, key);
644
+ case "strike":
645
+ return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: seg.text }, key);
646
+ case "link":
647
+ return /* @__PURE__ */ jsx(Text, { underline: true, color: color("cyan"), children: seg.text }, key);
648
+ case "code":
649
+ return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: seg.text }, key);
650
+ case "dim":
651
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: seg.text }, key);
652
+ default:
653
+ return seg.text;
654
+ }
655
+ }
656
+ var inline = (line) => parseInline(line).map(segment);
657
+ function hanging(key, marker, body, width, padding = 0) {
658
+ const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
659
+ return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
660
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
661
+ /* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
662
+ ] }, key);
663
+ }
664
+ function Markdown({ text, indent = 2 }) {
665
+ const { stdout } = useStdout();
666
+ const width = Math.max(20, streamColumns(stdout) - indent - 1);
667
+ const lines = text.split("\n");
668
+ const rows = [];
669
+ let inCode = false;
670
+ for (let i = 0; i < lines.length; i++) {
671
+ const line = lines[i];
672
+ if (line.trimStart().startsWith("```")) {
673
+ inCode = !inCode;
674
+ continue;
675
+ }
676
+ if (inCode) {
677
+ rows.push(
678
+ /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
679
+ );
680
+ continue;
681
+ }
682
+ const parsed = parseTable(lines, i);
683
+ if (parsed) {
684
+ layoutTable(parsed.table, width).forEach((tableLine, n) => {
685
+ rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
686
+ });
687
+ i = parsed.end - 1;
688
+ continue;
689
+ }
690
+ const heading = HEADING.exec(line);
691
+ if (heading) {
692
+ const level = heading[1].length;
693
+ rows.push(
694
+ /* @__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)
695
+ );
696
+ continue;
697
+ }
698
+ if (RULE.test(line)) {
699
+ rows.push(
700
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
701
+ );
702
+ continue;
703
+ }
704
+ const quote = QUOTE.exec(line);
705
+ if (quote) {
706
+ const depth = quote[1].replace(/\s/g, "").length;
707
+ rows.push(hanging(i, "\u258E".repeat(depth), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
708
+ continue;
709
+ }
710
+ const bullet = BULLET.exec(line);
711
+ if (bullet) {
712
+ const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
713
+ const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
714
+ rows.push(
715
+ task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, bulletFor(padding), inline(bullet[2]), width, padding)
716
+ );
717
+ continue;
718
+ }
719
+ const numbered = NUMBERED.exec(line);
720
+ if (numbered) {
721
+ const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
722
+ rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
723
+ continue;
724
+ }
725
+ rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
726
+ }
727
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
728
+ }
729
+
730
+ // src/ui/LinePrompt.tsx
731
+ import { Box as Box2, Text as Text3, useInput } from "ink";
732
+ import { useState } from "react";
733
+
734
+ // src/ui/EditableText.tsx
735
+ import { Text as Text2 } from "ink";
736
+
737
+ // src/ui/lineEdit.ts
738
+ var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
739
+ function boundaries(text) {
740
+ const out = [];
741
+ for (const { index } of segmenter.segment(text)) out.push(index);
742
+ out.push(text.length);
743
+ return out;
744
+ }
745
+ function prevBoundary(text, pos) {
746
+ let previous = 0;
747
+ for (const b of boundaries(text)) {
748
+ if (b >= pos) return previous;
749
+ previous = b;
750
+ }
751
+ return previous;
752
+ }
753
+ function nextBoundary(text, pos) {
754
+ for (const b of boundaries(text)) if (b > pos) return b;
755
+ return text.length;
756
+ }
757
+ var isSpace = (ch) => /\s/.test(ch);
758
+ function wordLeft(text, pos) {
759
+ let i = pos;
760
+ while (i > 0 && isSpace(text[i - 1])) i--;
761
+ while (i > 0 && !isSpace(text[i - 1])) i--;
762
+ return i;
763
+ }
764
+ function wordRight(text, pos) {
765
+ let i = pos;
766
+ while (i < text.length && isSpace(text[i])) i++;
767
+ while (i < text.length && !isSpace(text[i])) i++;
768
+ return i;
769
+ }
770
+ var lineStart = (text, pos) => text.lastIndexOf("\n", pos - 1) + 1;
771
+ function lineEnd(text, pos) {
772
+ const i = text.indexOf("\n", pos);
773
+ return i === -1 ? text.length : i;
774
+ }
775
+ function position(text, pos) {
776
+ const before = text.slice(0, pos);
777
+ const start = lineStart(text, pos);
778
+ return { line: before.split("\n").length - 1, column: boundaries(text.slice(start, pos)).length - 1 };
779
+ }
780
+ function moveLine(text, pos, direction) {
781
+ const { line, column } = position(text, pos);
782
+ const lines = text.split("\n");
783
+ const target = line + direction;
784
+ if (target < 0 || target >= lines.length) return void 0;
785
+ let start = 0;
786
+ for (let i = 0; i < target; i++) start += lines[i].length + 1;
787
+ const chars = boundaries(lines[target]);
788
+ return start + chars[Math.min(column, chars.length - 1)];
789
+ }
790
+ var atEnd = (s) => s.cursor >= s.value.length;
791
+ function insert(s, text) {
792
+ return { value: s.value.slice(0, s.cursor) + text + s.value.slice(s.cursor), cursor: s.cursor + text.length };
793
+ }
794
+ function remove(s, from, to) {
795
+ return { value: s.value.slice(0, from) + s.value.slice(to), cursor: from };
796
+ }
797
+ function applyEditKey(s, input, key) {
798
+ const { value, cursor } = s;
799
+ const word = !!(key.ctrl || key.meta);
800
+ if (key.leftArrow) return { value, cursor: word ? wordLeft(value, cursor) : prevBoundary(value, cursor) };
801
+ if (key.rightArrow) return { value, cursor: word ? wordRight(value, cursor) : nextBoundary(value, cursor) };
802
+ if (key.home) return { value, cursor: lineStart(value, cursor) };
803
+ if (key.end) return { value, cursor: lineEnd(value, cursor) };
804
+ if (key.backspace) {
805
+ if (key.meta) return remove(s, wordLeft(value, cursor), cursor);
806
+ return cursor > 0 ? remove(s, prevBoundary(value, cursor), cursor) : s;
807
+ }
808
+ if (key.delete) {
809
+ if (atEnd(s)) return cursor > 0 ? remove(s, prevBoundary(value, cursor), cursor) : s;
810
+ return remove(s, cursor, nextBoundary(value, cursor));
811
+ }
812
+ if (key.ctrl) {
813
+ switch (input) {
814
+ case "a":
815
+ return { value, cursor: lineStart(value, cursor) };
816
+ case "e":
817
+ return { value, cursor: lineEnd(value, cursor) };
818
+ case "b":
819
+ return { value, cursor: prevBoundary(value, cursor) };
820
+ case "f":
821
+ return { value, cursor: nextBoundary(value, cursor) };
822
+ case "w":
823
+ return remove(s, wordLeft(value, cursor), cursor);
824
+ case "u":
825
+ return remove(s, lineStart(value, cursor), cursor);
826
+ case "k":
827
+ return remove(s, cursor, lineEnd(value, cursor));
828
+ }
829
+ return void 0;
830
+ }
831
+ if (key.meta) {
832
+ switch (input) {
833
+ case "b":
834
+ return { value, cursor: wordLeft(value, cursor) };
835
+ case "f":
836
+ return { value, cursor: wordRight(value, cursor) };
837
+ case "d":
838
+ return remove(s, cursor, wordRight(value, cursor));
839
+ }
840
+ }
841
+ return void 0;
842
+ }
843
+ function splitAtCursor(s) {
844
+ const before = s.value.slice(0, s.cursor);
845
+ if (s.cursor >= s.value.length) return { before, at: " ", after: "" };
846
+ const end = nextBoundary(s.value, s.cursor);
847
+ const char = s.value.slice(s.cursor, end);
848
+ if (char === "\n") return { before, at: " ", after: s.value.slice(s.cursor) };
849
+ return { before, at: char, after: s.value.slice(end) };
850
+ }
851
+ var graphemes = (text) => Array.from(segmenter.segment(text), (s) => s.segment);
852
+ function masked(s) {
853
+ return { value: "\u2022".repeat(graphemes(s.value).length), cursor: graphemes(s.value.slice(0, s.cursor)).length };
854
+ }
855
+
856
+ // src/ui/EditableText.tsx
857
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
858
+ var MAX_VISIBLE_LINES = 10;
859
+ function windowedState(s) {
860
+ const lines = s.value.split("\n");
861
+ if (lines.length <= MAX_VISIBLE_LINES) return { view: s, hiddenAbove: 0, hiddenBelow: 0 };
862
+ const cursorLine = s.value.slice(0, s.cursor).split("\n").length - 1;
863
+ const startLine = Math.max(0, Math.min(cursorLine - Math.floor(MAX_VISIBLE_LINES / 2), lines.length - MAX_VISIBLE_LINES));
864
+ const endLine = Math.min(lines.length, startLine + MAX_VISIBLE_LINES);
865
+ let charStart = 0;
866
+ for (let i = 0; i < startLine; i++) charStart += lines[i].length + 1;
867
+ const sliced = lines.slice(startLine, endLine).join("\n");
868
+ const relCursor = Math.max(0, Math.min(sliced.length, s.cursor - charStart));
869
+ return {
870
+ view: { value: sliced, cursor: relCursor },
871
+ hiddenAbove: startLine,
872
+ hiddenBelow: lines.length - endLine
873
+ };
874
+ }
875
+ function EditableText({ state, mask = false, showCursor = true }) {
876
+ const raw = mask ? masked(state) : state;
877
+ const { view, hiddenAbove, hiddenBelow } = windowedState(raw);
878
+ const prefix = hiddenAbove > 0 ? `\u2026 (+${hiddenAbove} lines above)
879
+ ` : "";
880
+ const suffix = hiddenBelow > 0 ? `
881
+ \u2026 (+${hiddenBelow} lines below)` : "";
882
+ if (!showCursor) return /* @__PURE__ */ jsx2(Fragment, { children: `${prefix}${view.value}${suffix}` });
883
+ const { before, at, after } = splitAtCursor(view);
884
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
885
+ prefix ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: prefix }) : null,
886
+ before,
887
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: at }),
888
+ after,
889
+ suffix ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: suffix }) : null
890
+ ] });
891
+ }
892
+
893
+ // src/ui/thinkingMouse.ts
894
+ function parseMousePress(value) {
895
+ const start = value.indexOf("\x1B[<");
896
+ if (start < 0) return void 0;
897
+ const match = /^<(\d+);(\d+);(\d+)M/.exec(value.slice(start + 2));
898
+ if (!match) return void 0;
899
+ const button = Number(match[1]);
900
+ if (button !== 0) return void 0;
901
+ const x = Number(match[2]);
902
+ const y = Number(match[3]);
903
+ if (x < 1 || y < 1) return void 0;
904
+ return { x, y };
905
+ }
906
+ function parseCursorPosition(value) {
907
+ const start = value.indexOf("\x1B[");
908
+ if (start < 0) return void 0;
909
+ const match = /^(\d+);(\d+)R/.exec(value.slice(start + 2));
910
+ if (!match) return void 0;
911
+ const row = Number(match[1]);
912
+ const col = Number(match[2]);
913
+ if (row < 1 || col < 1) return void 0;
914
+ return { row, col };
915
+ }
916
+ function clickedThinking(click, cursorRow, rootHeight, target) {
917
+ if (!rootHeight || !target.width || !target.height) return false;
918
+ const origin = cursorRow - rootHeight + 1;
919
+ const row = origin + target.y;
920
+ const col = target.x + 1;
921
+ return (click.y === row || click.y === row - 1) && click.x >= col && click.x < col + Math.min(target.width, 72);
922
+ }
923
+ function hasMouseWheelSequence(chunk) {
924
+ return chunk.includes("\x1B[") && /\[<?6[4567];\d+;\d+[mM]/.test(chunk);
925
+ }
926
+ function isControlReportFragment(input) {
927
+ return /^\[<[\d;]*[mM]?$/.test(input) || /^\[(?:\d+;){2}\d+[mM]$/.test(input) || /^\[(?:\d+;)+\d*R?$/.test(input) || /^\[M[\s\S]{0,3}$/.test(input);
928
+ }
929
+ function parseThinkingReports(value) {
930
+ const events = [];
931
+ let offset = 0;
932
+ while (true) {
933
+ const start = value.indexOf("\x1B[", offset);
934
+ if (start < 0) break;
935
+ const finish = /[a-zA-Z~]/.exec(value.slice(start + 2));
936
+ if (!finish) return { events, rest: value.slice(start) };
937
+ const end = start + 2 + finish.index + 1;
938
+ const raw = value.slice(start, end);
939
+ const press = parseMousePress(raw);
940
+ const cursor = parseCursorPosition(raw);
941
+ if (press) events.push({ type: "mouse", press });
942
+ else if (cursor) events.push({ type: "cursor", cursor });
943
+ offset = end;
944
+ }
945
+ const remaining = value.lastIndexOf("\x1B[");
946
+ if (remaining >= offset) return { events, rest: value.slice(remaining) };
947
+ return { events, rest: value.endsWith("\x1B") ? "\x1B" : "" };
948
+ }
949
+
950
+ // src/ui/LinePrompt.tsx
951
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
952
+ function LinePrompt({ label, mask, onSubmit, onCancel }) {
953
+ const [line, setLine] = useState({ value: "", cursor: 0 });
954
+ useInput((input, key) => {
955
+ if (isControlReportFragment(input)) return;
956
+ if (key.return) onSubmit(line.value.trim());
957
+ else if (key.escape) onCancel();
958
+ else if (applyEditKey(line, input, key)) setLine((current) => applyEditKey(current, input, key) ?? current);
959
+ else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow) {
960
+ const text = input.replace(/[\r\n]/g, "");
961
+ if (text) setLine((current) => insert(current, text));
962
+ }
963
+ });
964
+ return /* @__PURE__ */ jsx3(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
965
+ `${label}: `,
966
+ /* @__PURE__ */ jsx3(EditableText, { state: line, mask }),
967
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
968
+ ] }) });
969
+ }
970
+
971
+ // src/ui/ListPicker.tsx
972
+ import { Box as Box3, Text as Text4, useInput as useInput2 } from "ink";
973
+ import { useState as useState2 } from "react";
974
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
975
+ var VISIBLE = 12;
976
+ function ListPicker({ title, items, onPick }) {
977
+ const [filter, setFilter] = useState2("");
978
+ const [index, setIndex] = useState2(0);
979
+ const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
980
+ const cursor = Math.min(index, Math.max(0, shown.length - 1));
981
+ useInput2((input, key) => {
982
+ if (isControlReportFragment(input)) return;
983
+ if (key.upArrow) setIndex(Math.max(0, cursor - 1));
984
+ else if (key.downArrow) setIndex(Math.min(Math.max(0, shown.length - 1), cursor + 1));
985
+ else if (key.pageUp) setIndex(Math.max(0, cursor - VISIBLE));
986
+ else if (key.pageDown) setIndex(Math.min(Math.max(0, shown.length - 1), cursor + VISIBLE));
987
+ else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
988
+ else if (key.escape) onPick(void 0);
989
+ else if (key.backspace || key.delete) {
990
+ setFilter((f) => f.slice(0, -1));
991
+ setIndex(0);
992
+ } else if (!key.ctrl && !key.meta && !key.tab && input) {
993
+ setFilter((f) => f + input);
994
+ setIndex(0);
995
+ }
996
+ });
997
+ const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
998
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
999
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
1000
+ 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: [
1001
+ `${start + i === cursor ? "\u203A" : " "} ${item.label}`,
1002
+ item.hint ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${item.hint}` }) : null
1003
+ ] }, item.id)),
1004
+ shown.length ? null : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matches.") })
1005
+ ] });
1006
+ }
1007
+
1008
+ // src/ui/ModelPicker.tsx
1009
+ import { Box as Box4, Text as Text5, useInput as useInput3 } from "ink";
1010
+ import { useState as useState3 } from "react";
1011
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1012
+ var VISIBLE2 = 14;
1013
+ function ModelPicker({ groups, current, onPick }) {
1014
+ const [filter, setFilter] = useState3("");
1015
+ const [index, setIndex] = useState3(0);
1016
+ const needle = filter.toLowerCase();
1017
+ const rows = [];
1018
+ for (const g of groups) {
1019
+ if (g.error) continue;
1020
+ const models = g.models.filter((m) => m.toLowerCase().includes(needle));
1021
+ if (!models.length) continue;
1022
+ rows.push({ kind: "header", text: g.providerName });
1023
+ for (const m of models) rows.push({ kind: "model", ref: `${g.providerId}/${m}`, label: m });
1024
+ }
1025
+ const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
1026
+ const clampedIndex = Math.min(index, Math.max(0, selectable.length - 1));
1027
+ const cursor = selectable[clampedIndex] ?? -1;
1028
+ useInput3((input, key) => {
1029
+ if (isControlReportFragment(input)) return;
1030
+ if (key.upArrow) setIndex(Math.max(0, clampedIndex - 1));
1031
+ else if (key.downArrow) setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + 1));
1032
+ else if (key.pageUp) setIndex(Math.max(0, clampedIndex - VISIBLE2));
1033
+ else if (key.pageDown) setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + VISIBLE2));
1034
+ else if (key.return) {
1035
+ const row = rows[cursor];
1036
+ if (row?.kind === "model") onPick(row.ref);
1037
+ } else if (key.escape) onPick(void 0);
1038
+ else if (key.backspace || key.delete) {
1039
+ setFilter((f) => f.slice(0, -1));
1040
+ setIndex(0);
1041
+ } else if (!key.ctrl && !key.meta && !key.tab && input) {
1042
+ setFilter((f) => f + input);
1043
+ setIndex(0);
1044
+ }
1045
+ });
1046
+ const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
1047
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1048
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)", { filter: filter ? `: ${filter}` : "" }) }),
1049
+ rows.slice(start, start + VISIBLE2).map(
1050
+ (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)
1051
+ ),
1052
+ !selectable.length ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("No matching models.") }) : null,
1053
+ groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx5(Text5, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
1054
+ ] });
1055
+ }
1056
+
1057
+ // src/ui/ProviderMenu.tsx
1058
+ import { Box as Box5, Text as Text6, useInput as useInput4 } from "ink";
1059
+ import { useState as useState4 } from "react";
1060
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1061
+ function ProviderMenu({ entries, onPick }) {
1062
+ const rows = [...entries.map((e) => e.id), "custom"];
1063
+ const [index, setIndex] = useState4(0);
1064
+ useInput4((_input, key) => {
1065
+ if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
1066
+ else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
1067
+ else if (key.pageUp) setIndex((i) => Math.max(0, i - 10));
1068
+ else if (key.pageDown) setIndex((i) => Math.min(rows.length - 1, i + 10));
1069
+ else if (key.return) onPick(rows[index]);
1070
+ else if (key.escape) onPick(void 0);
1071
+ });
1072
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1073
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
1074
+ entries.map((e, i) => /* @__PURE__ */ jsxs6(Text6, { color: i === index ? color("green") : void 0, children: [
1075
+ `${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
1076
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: ` ${e.baseURL}` })
1077
+ ] }, e.id)),
1078
+ /* @__PURE__ */ jsx6(Text6, { color: index === rows.length - 1 ? color("green") : void 0, children: `${index === rows.length - 1 ? "\u203A" : " "} Custom\u2026` })
1079
+ ] });
1080
+ }
1081
+
1082
+ // src/ui/PermissionPrompt.tsx
1083
+ import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
1084
+ import { useState as useState5 } from "react";
1085
+
1086
+ // src/ui/DiffView.tsx
1087
+ import { Box as Box6, Text as Text7 } from "ink";
1088
+ import { jsx as jsx7 } from "react/jsx-runtime";
1089
+ function DiffView({ diff }) {
1090
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
1091
+ const mark = line[6];
1092
+ const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
1093
+ return /* @__PURE__ */ jsx7(Text7, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
1094
+ }) });
1095
+ }
1096
+
1097
+ // src/ui/PermissionPrompt.tsx
1098
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1099
+ var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
1100
+ var MAX_DIFF_LINES = 16;
1101
+ function PermissionPrompt({
1102
+ request,
1103
+ onAnswer
1104
+ }) {
1105
+ const diffLines = request.preview ? request.preview.split("\n") : [];
1106
+ const maxOffset = Math.max(0, diffLines.length - MAX_DIFF_LINES);
1107
+ const [offset, setOffset] = useState5(0);
1108
+ useInput5((input, key) => {
1109
+ if (key.upArrow) {
1110
+ setOffset((o) => Math.max(0, o - 1));
1111
+ return;
1112
+ }
1113
+ if (key.downArrow) {
1114
+ setOffset((o) => Math.min(maxOffset, o + 1));
1115
+ return;
1116
+ }
1117
+ if (key.pageUp) {
1118
+ setOffset((o) => Math.max(0, o - MAX_DIFF_LINES));
1119
+ return;
1120
+ }
1121
+ if (key.pageDown) {
1122
+ setOffset((o) => Math.min(maxOffset, o + MAX_DIFF_LINES));
1123
+ return;
1124
+ }
1125
+ const k2 = input.toLowerCase();
1126
+ if (k2 === "y") onAnswer("yes");
1127
+ else if (k2 === "a" && request.sessionRules?.length) onAnswer("session");
1128
+ else if (k2 === "s") onAnswer("all");
1129
+ else if (k2 === "n" || key.escape) onAnswer("no");
1130
+ });
1131
+ const rules = request.sessionRules ?? [];
1132
+ 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(", ") });
1133
+ const visibleDiff = diffLines.length > MAX_DIFF_LINES ? diffLines.slice(offset, offset + MAX_DIFF_LINES).join("\n") : request.preview;
1134
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
1135
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
1136
+ visibleDiff ? /* @__PURE__ */ jsx8(DiffView, { diff: visibleDiff }) : null,
1137
+ diffLines.length > MAX_DIFF_LINES ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: `\u2191\u2193 scroll (${offset + 1}\u2013${Math.min(diffLines.length, offset + MAX_DIFF_LINES)} / ${diffLines.length})` }) : null,
1138
+ /* @__PURE__ */ jsxs7(Text8, { children: [
1139
+ /* @__PURE__ */ jsx8(Text8, { color: color("green"), children: t("[y] yes") }),
1140
+ " ",
1141
+ sessionLabel ? /* @__PURE__ */ jsx8(Text8, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
1142
+ /* @__PURE__ */ jsx8(Text8, { color: color("yellow"), children: t("[s] yes to all ") }),
1143
+ /* @__PURE__ */ jsx8(Text8, { color: color("red"), children: t("[n] no") })
1144
+ ] })
1145
+ ] });
1146
+ }
1147
+
1148
+ // src/ui/PromptInput.tsx
1149
+ import { Box as Box8, Text as Text9, useInput as useInput6 } from "ink";
1150
+ import { useEffect, useRef, useState as useState6 } from "react";
1151
+
1152
+ // src/ui/complete.ts
1153
+ import { existsSync, globSync, readFileSync, statSync } from "fs";
1154
+ import { isAbsolute, join, relative, resolve } from "path";
1155
+ var MAX_ATTACH_BYTES = 32768;
1156
+ function listFileSuggestions(value, cwd, limit = 8) {
1157
+ const match = /(^|\s)@([^\s]*)$/.exec(value);
1158
+ if (!match) return [];
1159
+ const prefix = match[2];
1160
+ const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
1161
+ const pattern = `${escaped}*`;
1162
+ if (validateGlobPattern(pattern)) return [];
1163
+ const exclude = ignorePatterns(cwd);
1164
+ const formatEntry = (entry2) => {
1165
+ const normalized = normalizeGlobPath(entry2);
1166
+ try {
1167
+ return statSync(join(cwd, entry2)).isDirectory() ? `${normalized}/` : normalized;
1168
+ } catch {
1169
+ return normalized;
1170
+ }
1171
+ };
1172
+ try {
1173
+ let found = globSync(pattern, { cwd, exclude }).map(formatEntry).sort();
1174
+ if (!found.length && prefix && !prefix.includes("/")) {
1175
+ const deepPattern = `**/*${escaped}*`;
1176
+ if (!validateGlobPattern(deepPattern)) {
1177
+ found = globSync(deepPattern, { cwd, exclude }).map(formatEntry).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
1178
+ }
1179
+ }
1180
+ return found.slice(0, limit);
1181
+ } catch {
1182
+ return [];
1183
+ }
1184
+ }
1185
+ function applyFileSuggestion(value, pick) {
1186
+ const match = /(^|\s)@([^\s]*)$/.exec(value);
1187
+ if (!match) return value;
1188
+ const prefix = match[2];
1189
+ return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
1190
+ }
1191
+ function completeFile(value, cwd) {
1192
+ const found = listFileSuggestions(value, cwd, 1);
1193
+ if (!found.length) return value;
1194
+ return applyFileSuggestion(value, found[0]);
1195
+ }
1196
+ function expandAtFileMentions(prompt, cwd, readFiles) {
1197
+ const matches = [...prompt.matchAll(/(?:^|\s)@([^\s]+)/g)];
1198
+ if (!matches.length) return prompt;
1199
+ const blocks = [];
1200
+ const seen = /* @__PURE__ */ new Set();
1201
+ for (const m of matches) {
1202
+ const rawPath = m[1];
1203
+ if (!rawPath || seen.has(rawPath)) continue;
1204
+ seen.add(rawPath);
1205
+ if (requiresSensitiveToolApproval({ tool: "read", kind: "read", target: rawPath })) continue;
1206
+ const abs = resolve(cwd, rawPath);
1207
+ const rel = relative(cwd, abs);
1208
+ if (!rel || rel.startsWith("..") || isAbsolute(rel)) continue;
1209
+ try {
1210
+ if (!existsSync(abs) || !statSync(abs).isFile() || statSync(abs).size > MAX_ATTACH_BYTES) continue;
1211
+ const buf = readFileSync(abs);
1212
+ if (buf.subarray(0, 4e3).includes(0)) continue;
1213
+ if (readFiles) recordFileMtime(readFiles, abs);
1214
+ blocks.push(`<file path="${rawPath}">
1215
+ ${buf.toString("utf8")}
1216
+ </file>`);
1217
+ } catch {
1218
+ }
1219
+ }
1220
+ return blocks.length ? `${prompt}
1221
+
1222
+ ${blocks.join("\n\n")}` : prompt;
1223
+ }
1224
+
1225
+ // src/ui/vim.ts
1226
+ var clamp = (s) => {
1227
+ const start = lineStart(s.value, s.cursor);
1228
+ const end = lineEnd(s.value, s.cursor);
1229
+ if (s.cursor < end) return s;
1230
+ return { value: s.value, cursor: Math.max(start, prevBoundary(s.value, end)) };
1231
+ };
1232
+ var wordStart = (text, pos) => {
1233
+ let i = wordRight(text, pos);
1234
+ while (i < text.length && /\s/.test(text[i])) i++;
1235
+ return i;
1236
+ };
1237
+ var deleteLine = (s) => {
1238
+ const from = lineStart(s.value, s.cursor);
1239
+ const nl = s.value.indexOf("\n", s.cursor);
1240
+ if (nl === -1) {
1241
+ const start = from > 0 ? from - 1 : 0;
1242
+ return { value: s.value.slice(0, start), cursor: start };
1243
+ }
1244
+ return { value: s.value.slice(0, from) + s.value.slice(nl + 1), cursor: from };
1245
+ };
1246
+ function applyVim(s, mode, input, key, pending) {
1247
+ if (key.escape) return { state: clamp(s), mode: "normal", handled: true };
1248
+ if (mode === "insert") return { state: s, mode, handled: false };
1249
+ if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.ctrl || key.meta || key.tab || key.return) {
1250
+ return { state: s, mode, pending, handled: false };
1251
+ }
1252
+ if (pending === "d" || pending === "c") {
1253
+ const nextMode2 = pending === "c" ? "insert" : mode;
1254
+ if (input === pending) return { state: deleteLine(s), mode: nextMode2, handled: true };
1255
+ if (input === "w") {
1256
+ const to = wordStart(s.value, s.cursor);
1257
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(to), cursor: s.cursor }, mode: nextMode2, handled: true };
1258
+ }
1259
+ return { state: s, mode, handled: true };
1260
+ }
1261
+ switch (input) {
1262
+ case "h":
1263
+ return { state: { ...s, cursor: Math.max(lineStart(s.value, s.cursor), prevBoundary(s.value, s.cursor)) }, mode, handled: true };
1264
+ case "l":
1265
+ return { state: { ...s, cursor: Math.min(lineEnd(s.value, s.cursor), nextBoundary(s.value, s.cursor)) }, mode, handled: true };
1266
+ case "j": {
1267
+ const moved = moveLine(s.value, s.cursor, 1);
1268
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1269
+ }
1270
+ case "k": {
1271
+ const moved = moveLine(s.value, s.cursor, -1);
1272
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1273
+ }
1274
+ case "w":
1275
+ return { state: { ...s, cursor: wordStart(s.value, s.cursor) }, mode, handled: true };
1276
+ case "e":
1277
+ return { state: { ...s, cursor: Math.max(s.cursor, prevBoundary(s.value, wordRight(s.value, s.cursor))) }, mode, handled: true };
1278
+ case "b":
1279
+ return { state: { ...s, cursor: wordLeft(s.value, s.cursor) }, mode, handled: true };
1280
+ case "0":
1281
+ return { state: { ...s, cursor: lineStart(s.value, s.cursor) }, mode, handled: true };
1282
+ case "$":
1283
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode, handled: true };
1284
+ case "x": {
1285
+ if (s.cursor >= s.value.length || s.value[s.cursor] === "\n") return { state: s, mode, handled: true };
1286
+ const next = nextBoundary(s.value, s.cursor);
1287
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(next), cursor: s.cursor }, mode, handled: true };
1288
+ }
1289
+ case "D": {
1290
+ const end = lineEnd(s.value, s.cursor);
1291
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(end), cursor: s.cursor }, mode, handled: true };
1292
+ }
1293
+ case "C": {
1294
+ const end = lineEnd(s.value, s.cursor);
1295
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(end), cursor: s.cursor }, mode: "insert", handled: true };
1296
+ }
1297
+ case "i":
1298
+ return { state: s, mode: "insert", handled: true };
1299
+ case "I": {
1300
+ const start = lineStart(s.value, s.cursor);
1301
+ const end = lineEnd(s.value, s.cursor);
1302
+ let pos = start;
1303
+ while (pos < end && (s.value[pos] === " " || s.value[pos] === " ")) pos++;
1304
+ return { state: { ...s, cursor: pos }, mode: "insert", handled: true };
1305
+ }
1306
+ case "a":
1307
+ return { state: { ...s, cursor: Math.min(lineEnd(s.value, s.cursor), nextBoundary(s.value, s.cursor)) }, mode: "insert", handled: true };
1308
+ case "A":
1309
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode: "insert", handled: true };
1310
+ case "o": {
1311
+ const end = lineEnd(s.value, s.cursor);
1312
+ return { state: { value: `${s.value.slice(0, end)}
1313
+ ${s.value.slice(end)}`, cursor: end + 1 }, mode: "insert", handled: true };
1314
+ }
1315
+ case "O": {
1316
+ const start = lineStart(s.value, s.cursor);
1317
+ return { state: { value: `${s.value.slice(0, start)}
1318
+ ${s.value.slice(start)}`, cursor: start }, mode: "insert", handled: true };
1319
+ }
1320
+ case "d":
1321
+ case "c":
1322
+ return { state: s, mode, pending: input, handled: true };
1323
+ default:
1324
+ return { state: s, mode, handled: true };
1325
+ }
1326
+ }
1327
+
1328
+ // src/ui/PromptInput.tsx
1329
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1330
+ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected, vim, onEmptyTab }) {
1331
+ const [line, setLine] = useState6({ value: "", cursor: 0 });
1332
+ const { value } = line;
1333
+ const [historyIndex, setHistoryIndex] = useState6(null);
1334
+ const draftRef = useRef("");
1335
+ const [selected, setSelected] = useState6(0);
1336
+ const [dismissed, setDismissed] = useState6(false);
1337
+ const [vimMode, setVimMode] = useState6("insert");
1338
+ const [vimPending, setVimPending] = useState6(void 0);
1339
+ const injectedSeen = useState6(0);
1340
+ useEffect(() => {
1341
+ if (injected && injected.n !== injectedSeen[0]) {
1342
+ injectedSeen[1](injected.n);
1343
+ setLine((current) => insert(current, injected.text));
1344
+ setSelected(0);
1345
+ }
1346
+ }, [injected, injectedSeen]);
1347
+ const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
1348
+ const suggestions = historyIndex === null && !dismissed && value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
1349
+ const beforeCursor = value.slice(0, line.cursor);
1350
+ const fileSuggestions = historyIndex === null && !dismissed && !suggestions.length && /(^|\s)@([^\s]*)$/.test(beforeCursor) ? listFileSuggestions(beforeCursor, cwd, 8) : [];
1351
+ const activeCount = suggestions.length || fileSuggestions.length;
1352
+ const highlighted = Math.min(selected, Math.max(0, activeCount - 1));
1353
+ const typed = (text) => ({ value: text, cursor: text.length });
1354
+ const edit = (next) => {
1355
+ setLine(next);
1356
+ setSelected(0);
1357
+ setDismissed(false);
1358
+ setHistoryIndex(null);
1359
+ };
1360
+ useInput6(
1361
+ (input, key) => {
1362
+ if (isControlReportFragment(input)) return;
1363
+ if (suggestions.length) {
1364
+ if (key.escape) {
1365
+ setDismissed(true);
1366
+ return;
1367
+ }
1368
+ if (key.upArrow) {
1369
+ setSelected(Math.max(0, highlighted - 1));
1370
+ return;
1371
+ }
1372
+ if (key.downArrow) {
1373
+ setSelected(Math.min(suggestions.length - 1, highlighted + 1));
1374
+ return;
1375
+ }
1376
+ if (key.tab && !key.shift) {
1377
+ edit(typed(`/${suggestions[highlighted].name} `));
1378
+ return;
1379
+ }
1380
+ if (key.return && !key.meta && !key.shift) {
1381
+ edit(typed(""));
1382
+ setHistoryIndex(null);
1383
+ draftRef.current = "";
1384
+ onSubmit(`/${suggestions[highlighted].name}`);
1385
+ return;
1386
+ }
1387
+ }
1388
+ if (fileSuggestions.length) {
1389
+ if (key.escape) {
1390
+ setDismissed(true);
1391
+ return;
1392
+ }
1393
+ if (key.upArrow) {
1394
+ setSelected(Math.max(0, highlighted - 1));
1395
+ return;
1396
+ }
1397
+ if (key.downArrow) {
1398
+ setSelected(Math.min(fileSuggestions.length - 1, highlighted + 1));
1399
+ return;
1400
+ }
1401
+ if (key.tab && !key.shift) {
1402
+ const completed = applyFileSuggestion(beforeCursor, fileSuggestions[highlighted]);
1403
+ edit({ value: completed + value.slice(line.cursor), cursor: completed.length });
1404
+ return;
1405
+ }
1406
+ }
1407
+ if (vim) {
1408
+ const r = applyVim(line, vimMode, input, key, vimPending);
1409
+ if (r.handled) {
1410
+ setLine(r.state);
1411
+ setVimMode(r.mode);
1412
+ setVimPending(r.pending);
1413
+ setSelected(0);
1414
+ return;
1415
+ }
1416
+ }
1417
+ if (key.return && (key.meta || key.shift) || key.ctrl && input.toLowerCase() === "j" || input === "\n") {
1418
+ edit((current) => insert(current, "\n"));
1419
+ return;
1420
+ }
1421
+ if (key.return) {
1422
+ const before = value.slice(0, line.cursor);
1423
+ if (before.endsWith("\\")) {
1424
+ edit({ value: `${before.slice(0, -1)}
1425
+ ${value.slice(line.cursor)}`, cursor: before.length });
1426
+ return;
1427
+ }
1428
+ const text2 = value.trim();
1429
+ if (!text2) return;
1430
+ edit(typed(""));
1431
+ setHistoryIndex(null);
1432
+ draftRef.current = "";
1433
+ setVimMode("insert");
1434
+ setVimPending(void 0);
1435
+ onSubmit(text2);
1436
+ return;
1437
+ }
1438
+ const edited = applyEditKey(line, input, key);
1439
+ if (edited) {
1440
+ edit((current) => applyEditKey(current, input, key) ?? current);
1441
+ return;
1442
+ }
1443
+ if (key.upArrow || key.downArrow) {
1444
+ const moved = moveLine(value, line.cursor, key.upArrow ? -1 : 1);
1445
+ if (moved !== void 0) {
1446
+ setLine({ value, cursor: moved });
1447
+ return;
1448
+ }
1449
+ }
1450
+ if (key.upArrow) {
1451
+ if (!history.length) return;
1452
+ if (historyIndex === null) draftRef.current = value;
1453
+ const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
1454
+ setHistoryIndex(i);
1455
+ setLine(typed(history[i]));
1456
+ return;
1457
+ }
1458
+ if (key.downArrow) {
1459
+ if (historyIndex === null) return;
1460
+ const i = historyIndex + 1;
1461
+ setHistoryIndex(i >= history.length ? null : i);
1462
+ setLine(typed(i >= history.length ? draftRef.current : history[i]));
1463
+ return;
1464
+ }
1465
+ if (key.tab && !key.shift) {
1466
+ if (!value.trim() && onEmptyTab) {
1467
+ onEmptyTab();
1468
+ return;
1469
+ }
1470
+ const before = value.slice(0, line.cursor);
1471
+ const completed = completeFile(before, cwd);
1472
+ if (completed !== before) edit({ value: completed + value.slice(line.cursor), cursor: completed.length });
1473
+ return;
1474
+ }
1475
+ if (key.ctrl || key.meta || key.escape || key.tab) return;
1476
+ const text = input.replace(/\r\n?/g, "\n");
1477
+ if (text) edit((current) => insert(current, text));
1478
+ },
1479
+ { isActive: !disabled }
1480
+ );
1481
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1482
+ /* @__PURE__ */ jsx9(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
1483
+ /* @__PURE__ */ jsx9(Text9, { color: color("green"), children: "> " }),
1484
+ /* @__PURE__ */ jsx9(EditableText, { state: line, showCursor: !disabled }),
1485
+ vim && vimMode === "normal" && /* @__PURE__ */ jsxs8(Text9, { color: color("green"), children: [
1486
+ " ",
1487
+ t("NORMAL")
1488
+ ] })
1489
+ ] }) }),
1490
+ 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)),
1491
+ fileSuggestions.map((f, i) => /* @__PURE__ */ jsx9(Text9, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} @${f}` }, f))
1492
+ ] });
1493
+ }
1494
+
1495
+ // src/ui/Spinner.tsx
1496
+ import { Text as Text10 } from "ink";
1497
+ import { useEffect as useEffect2, useState as useState7 } from "react";
1498
+ import { jsx as jsx10 } from "react/jsx-runtime";
1499
+ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
1500
+ function Spinner({ label, startedAt }) {
1501
+ const [tick, setTick] = useState7(0);
1502
+ useEffect2(() => {
1503
+ const id = setInterval(() => setTick((t2) => t2 + 1), 120);
1504
+ return () => clearInterval(id);
1505
+ }, []);
1506
+ const seconds = Math.floor((Date.now() - startedAt) / 1e3);
1507
+ return /* @__PURE__ */ jsx10(Text10, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
1508
+ }
1509
+
1510
+ // src/ui/StatusBar.tsx
1511
+ import { Text as Text11 } from "ink";
1512
+ import { jsx as jsx11 } from "react/jsx-runtime";
1513
+ var LABEL = {
1514
+ default: "\u23F5 default",
1515
+ acceptEdits: "\u23F5 accept edits",
1516
+ plan: "\u23F8 plan mode",
1517
+ allowAll: "\u23F5\u23F5 allow all"
1518
+ };
1519
+ function formatTokens(n) {
1520
+ return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
1521
+ }
1522
+ function StatusBar({
1523
+ mode,
1524
+ tokens,
1525
+ cached = 0,
1526
+ estimated = false,
1527
+ busy,
1528
+ model,
1529
+ reasoning = "auto"
1530
+ }) {
1531
+ const danger = mode === "allowAll";
1532
+ const cacheText = cached > 0 ? ` \xB7 ${t("{n} cached", { n: formatTokens(cached) })}` : "";
1533
+ 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 ${estimated ? "~" : ""}${t("{n} tokens in context", { n: formatTokens(tokens) })}${cacheText} \xB7 ${busy ? t("esc to cancel") : t("/ for commands")}` });
1534
+ }
1535
+
1536
+ // src/ui/liveCap.ts
1537
+ function liveRowCaps(wanted, budget) {
1538
+ const caps = new Array(wanted.length).fill(0);
1539
+ let room = Math.max(0, budget);
1540
+ for (let i = wanted.length - 1; i >= 0; i--) {
1541
+ const take = Math.max(0, Math.min(wanted[i], room));
1542
+ caps[i] = take;
1543
+ room -= take;
1544
+ }
1545
+ return caps;
1546
+ }
1547
+ function capLiveText(raw, options) {
1548
+ if (!options.isRealTty) return raw;
1549
+ if (options.maxRows <= 0) return "";
1550
+ if (wrappedRows(raw, options.width) <= options.maxRows) return raw;
1551
+ const lines = raw.split("\n");
1552
+ const room = Math.max(1, options.maxRows - 1);
1553
+ let kept = 0;
1554
+ while (kept < lines.length && wrappedRows(lines.slice(lines.length - kept - 1).join("\n"), options.width) <= room) kept++;
1555
+ const hidden = lines.length - kept;
1556
+ return `\u2026 (${hidden} lines above) \u2026
1557
+ ${lines.slice(hidden).join("\n")}`;
1558
+ }
1559
+ function capLiveMarkdown(raw, options) {
1560
+ if (!options.isRealTty) return raw;
1561
+ if (options.maxRows <= 0) return "";
1562
+ if (markdownRowCount(raw, options.width) <= options.maxRows) return raw;
1563
+ const lines = raw.split("\n");
1564
+ const room = Math.max(1, options.maxRows - 1);
1565
+ let lo = 0;
1566
+ let hi = lines.length;
1567
+ while (lo < hi) {
1568
+ const mid = Math.floor((lo + hi) / 2);
1569
+ if (markdownRowCount(lines.slice(mid).join("\n"), options.width) <= room) hi = mid;
1570
+ else lo = mid + 1;
1571
+ }
1572
+ if (lo >= lines.length) return `\u2026 (${lines.length} lines above) \u2026`;
1573
+ const dropped = lines.slice(0, lo);
1574
+ const fences = dropped.filter((line) => line.trimStart().startsWith("```"));
1575
+ const opener = fences.length % 2 === 1 ? fences.at(-1) : void 0;
1576
+ return `${opener ? `${opener}
1577
+ ` : ""}\u2026 (${lo} lines above) \u2026
1578
+ ${lines.slice(lo).join("\n")}`;
1579
+ }
1580
+
1581
+ // src/ui/McpMenu.tsx
1582
+ import { Box as Box9, Text as Text12, useInput as useInput7 } from "ink";
1583
+ import { useState as useState8 } from "react";
1584
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1585
+ var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
1586
+ function McpMenu({ items, onPick }) {
1587
+ const [index, setIndex] = useState8(0);
1588
+ useInput7((_input, key) => {
1589
+ if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
1590
+ else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
1591
+ else if (key.pageUp) setIndex((i) => Math.max(0, i - 10));
1592
+ else if (key.pageDown) setIndex((i) => Math.min(items.length - 1, i + 10));
1593
+ else if (key.return) onPick(items[index]?.name);
1594
+ else if (key.escape) onPick(void 0);
1595
+ });
1596
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1597
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
1598
+ items.map((item, i) => /* @__PURE__ */ jsxs9(Text12, { color: i === index ? color("green") : void 0, children: [
1599
+ `${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
1600
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
1601
+ ] }, item.name))
1602
+ ] });
1603
+ }
1604
+
1605
+ // src/ui/PlanApproval.tsx
1606
+ import { Box as Box10, Text as Text13, useInput as useInput8 } from "ink";
1607
+ import { useState as useState9 } from "react";
1608
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1609
+ var MAX_PLAN_LINES = 16;
1610
+ function PlanApproval({ plan, onAnswer }) {
1611
+ const planLines = plan.split("\n");
1612
+ const maxOffset = Math.max(0, planLines.length - MAX_PLAN_LINES);
1613
+ const [offset, setOffset] = useState9(0);
1614
+ useInput8((input, key) => {
1615
+ if (key.upArrow) {
1616
+ setOffset((o) => Math.max(0, o - 1));
1617
+ return;
1618
+ }
1619
+ if (key.downArrow) {
1620
+ setOffset((o) => Math.min(maxOffset, o + 1));
1621
+ return;
1622
+ }
1623
+ if (key.pageUp) {
1624
+ setOffset((o) => Math.max(0, o - MAX_PLAN_LINES));
1625
+ return;
1626
+ }
1627
+ if (key.pageDown) {
1628
+ setOffset((o) => Math.min(maxOffset, o + MAX_PLAN_LINES));
1629
+ return;
1630
+ }
1631
+ const k2 = input.toLowerCase();
1632
+ if (k2 === "a") onAnswer("acceptEdits");
1633
+ else if (k2 === "y") onAnswer("default");
1634
+ else if (k2 === "n" || key.escape) onAnswer("no");
1635
+ });
1636
+ const visiblePlan = planLines.length > MAX_PLAN_LINES ? planLines.slice(offset, offset + MAX_PLAN_LINES).join("\n") : plan;
1637
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1638
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: t("Plan from the agent") }),
1639
+ /* @__PURE__ */ jsx13(Markdown, { text: visiblePlan, indent: 5 }),
1640
+ planLines.length > MAX_PLAN_LINES ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `\u2191\u2193 scroll (${offset + 1}\u2013${Math.min(planLines.length, offset + MAX_PLAN_LINES)} / ${planLines.length})` }) : null,
1641
+ /* @__PURE__ */ jsxs10(Text13, { children: [
1642
+ /* @__PURE__ */ jsx13(Text13, { color: color("green"), children: t("[a] yes, auto-edit") }),
1643
+ " ",
1644
+ /* @__PURE__ */ jsx13(Text13, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
1645
+ " ",
1646
+ /* @__PURE__ */ jsx13(Text13, { color: color("red"), children: t("[n] not yet, revise first") })
1647
+ ] })
1648
+ ] });
1649
+ }
1650
+
1651
+ // src/ui/TodoList.tsx
1652
+ import { Box as Box11, Text as Text14 } from "ink";
1653
+ import { jsx as jsx14 } from "react/jsx-runtime";
1654
+ var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
1655
+ function TodoList({ items }) {
1656
+ if (!items.length || items.every((t2) => t2.status === "completed")) return null;
1657
+ 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)) });
1658
+ }
1659
+
1660
+ // src/ui/ToolBlock.tsx
1661
+ import { Box as Box12, Text as Text15 } from "ink";
1662
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1663
+ var PREVIEW_LINES = 5;
1664
+ var DIFF_PREVIEW_LINES = 20;
1665
+ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub, moreBind = "ctrl+o" }) {
1666
+ const isTask = tool === "task";
1667
+ const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
1668
+ const lines = (output ?? "").split("\n");
1669
+ const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
1670
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 2, children: [
1671
+ /* @__PURE__ */ jsxs11(Text15, { children: [
1672
+ /* @__PURE__ */ jsx15(Text15, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
1673
+ /* @__PURE__ */ jsx15(Text15, { bold: true, children: toolLabel(tool) }),
1674
+ /* @__PURE__ */ jsx15(Text15, { children: ` ${target}` }),
1675
+ display && !isDiff && !isTask ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` (${display})` }) : null
1676
+ ] }),
1677
+ sub?.length ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1678
+ 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)),
1679
+ sub.length > 6 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
1680
+ ] }) : null,
1681
+ 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,
1682
+ isDiff ? /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", marginLeft: 3, children: (() => {
1683
+ const diffLines = display.split("\n");
1684
+ const shownDiff = expanded ? diffLines : diffLines.slice(0, DIFF_PREVIEW_LINES);
1685
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
1686
+ /* @__PURE__ */ jsx15(DiffView, { diff: shownDiff.join("\n") }),
1687
+ diffLines.length > shownDiff.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: diffLines.length - shownDiff.length, bind: moreBind }) }) : expanded && diffLines.length > DIFF_PREVIEW_LINES ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 ({bind} to collapse)", { bind: moreBind }) }) : null
1688
+ ] });
1689
+ })() }) : output && (isError || tool === "bash" || expanded) && !(isTask && !isError) ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1690
+ shown.map((line, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
1691
+ lines.length > shown.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: lines.length - shown.length, bind: moreBind }) }) : expanded && lines.length > PREVIEW_LINES ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 ({bind} to collapse)", { bind: moreBind }) }) : null
1692
+ ] }) : null
1693
+ ] });
1694
+ }
1695
+
1696
+ // src/ui/transcript.ts
1697
+ var nextId = 0;
1698
+ var entry = (e) => ({ ...e, id: nextId++ });
1699
+ var NOTICES = {
1700
+ compacted: ["Conversation summarized to fit the model context.", "info"],
1701
+ stepLimit: ['Step limit of {n} reached. Type "continue" to keep going.', "warn"],
1702
+ aborted: ["Cancelled.", "warn"]
1703
+ };
1704
+ var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
1705
+ function endTurn(t2) {
1706
+ return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
1707
+ }
1708
+ var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
1709
+ function finishedCut(text) {
1710
+ let inFence = false;
1711
+ let pos = 0;
1712
+ let cut = -1;
1713
+ const lines = text.split("\n");
1714
+ for (const line of lines.slice(0, -1)) {
1715
+ if (TOOL_MARKUP.test(line)) break;
1716
+ if (line.trimStart().startsWith("```")) inFence = !inFence;
1717
+ const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
1718
+ pos += line.length + 1;
1719
+ if (!inFence && !tableRow) cut = pos - 1;
1720
+ }
1721
+ return cut;
1722
+ }
1723
+ function flushLines(t2) {
1724
+ const last = t2.live.at(-1);
1725
+ if (last?.kind !== "assistant") return t2;
1726
+ const cut = finishedCut(last.text);
1727
+ if (cut < 0) return t2;
1728
+ const chunk = last.text.slice(0, cut);
1729
+ if (!last.cont && !chunk.trim()) return t2;
1730
+ const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
1731
+ return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
1732
+ }
1733
+ function applyEvent(t2, event) {
1734
+ switch (event.type) {
1735
+ case "text": {
1736
+ const last = t2.live.at(-1);
1737
+ if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
1738
+ return flushLines({
1739
+ done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))],
1740
+ live: [entry({ kind: "assistant", text: event.delta })]
1741
+ });
1742
+ }
1743
+ case "thinking": {
1744
+ const last = t2.live.at(-1);
1745
+ if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
1746
+ return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
1747
+ }
1748
+ case "textReplace": {
1749
+ const last = t2.live.at(-1);
1750
+ if (last?.kind !== "assistant") return t2;
1751
+ return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
1752
+ }
1753
+ case "toolStart": {
1754
+ const running = t2.live.some((e) => e.kind === "tool" && !e.done);
1755
+ const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
1756
+ return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
1757
+ }
1758
+ case "toolEnd": {
1759
+ const live = t2.live.map(
1760
+ (e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
1761
+ );
1762
+ const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
1763
+ const cut = firstRunning === -1 ? live.length : firstRunning;
1764
+ return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
1765
+ }
1766
+ case "subagent": {
1767
+ const inner = event.event;
1768
+ if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
1769
+ const update = (e) => {
1770
+ if (e.kind !== "tool" || e.callId !== event.parentId) return e;
1771
+ const sub = e.sub ?? [];
1772
+ if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
1773
+ return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
1774
+ };
1775
+ return { ...t2, live: t2.live.map(update) };
1776
+ }
1777
+ case "error":
1778
+ return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
1779
+ case "stepLimit": {
1780
+ const limit = event.maxSteps;
1781
+ const notice = NOTICES.stepLimit;
1782
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0], { n: limit }), tone: notice[1] })] } : t2;
1783
+ }
1784
+ default: {
1785
+ const notice = NOTICES[event.type];
1786
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
1787
+ }
1788
+ }
1789
+ }
1790
+
1791
+ // src/ui/replay.ts
1792
+ function replayEntries(history) {
1793
+ 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());
1794
+ return [
1795
+ 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" }),
1796
+ ...chat.map((m) => entry(m.role === "user" ? { kind: "user", text: m.text } : { kind: "assistant", text: m.text }))
1797
+ ];
1798
+ }
1799
+
1800
+ // src/clipboardImage.ts
1801
+ import { execFile as execFile2 } from "child_process";
1802
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
1803
+ import { join as join2 } from "path";
1804
+ function clipboardTarget(tmpdir2, now) {
1805
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
1806
+ return join2(tmpdir2, `bccli-clipboard-${stamp}.png`);
1807
+ }
1808
+ function clipboardCommand(platform, target) {
1809
+ if (platform === "win32") {
1810
+ const escaped = target.replace(/'/g, "''");
1811
+ return {
1812
+ cmd: "powershell.exe",
1813
+ args: [
1814
+ "-NoProfile",
1815
+ "-NonInteractive",
1816
+ "-Command",
1817
+ `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' }`
1818
+ ]
1819
+ };
1820
+ }
1821
+ if (platform === "darwin") {
1822
+ return {
1823
+ cmd: "osascript",
1824
+ args: [
1825
+ "-e",
1826
+ "try",
1827
+ "-e",
1828
+ "set d to the clipboard as \xABclass PNGf\xBB",
1829
+ "-e",
1830
+ `set f to open for access POSIX file "${target}" with write permission`,
1831
+ "-e",
1832
+ "set eof f to 0",
1833
+ "-e",
1834
+ "write d to f",
1835
+ "-e",
1836
+ "close access f",
1837
+ "-e",
1838
+ '"OK"',
1839
+ "-e",
1840
+ "on error",
1841
+ "-e",
1842
+ '"EMPTY"',
1843
+ "-e",
1844
+ "end try"
1845
+ ]
1846
+ };
1847
+ }
1848
+ if (platform === "linux") {
1849
+ return { cmd: "sh", args: ["-c", `xclip -selection clipboard -t image/png -o > '${target}' 2>/dev/null && echo OK || echo EMPTY`] };
1850
+ }
1851
+ return void 0;
1852
+ }
1853
+ var defaultRun = (cmd, args) => new Promise((resolve3) => {
1854
+ execFile2(cmd, args, { timeout: 15e3, windowsHide: true }, (error, stdout) => {
1855
+ const code = error ? error.code ?? 1 : 0;
1856
+ resolve3({ code: typeof code === "number" ? code : 1, output: String(stdout ?? "") });
1857
+ });
1858
+ });
1859
+ async function grabClipboardImage(deps) {
1860
+ const target = clipboardTarget(deps.tmpdir, deps.now);
1861
+ const command = clipboardCommand(deps.platform, target);
1862
+ if (!command) return void 0;
1863
+ const run = deps.run ?? defaultRun;
1864
+ const r = await run(command.cmd, command.args);
1865
+ if (r.code === 0 && r.output.includes("OK") && existsSync2(target) && statSync2(target).size > 0) return target;
1866
+ return void 0;
1867
+ }
1868
+
1869
+ // src/ui/App.tsx
1870
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1871
+ function helpText(runtime) {
1872
+ const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
1873
+ const kb = runtime.config.keybinds;
1874
+ return [
1875
+ ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
1876
+ ...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
1877
+ ...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
1878
+ "",
1879
+ t("shift+tab switch permission mode esc cancel the turn"),
1880
+ t("{bind} full output of the last tool \\ + enter new line", { bind: kb.toolOutput.spec.padEnd(10) }),
1881
+ t("{bind} show/hide the model\u2019s thinking", { bind: kb.thinking.spec.padEnd(10) }),
1882
+ t("@file + tab complete file names \u2191\u2193 input history")
1883
+ ].join("\n");
1884
+ }
1885
+ 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.";
1886
+ var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
1887
+ function App({ runtime, initialPrompt, version, updateMessage }) {
1888
+ const { exit } = useApp();
1889
+ const [transcript, setTranscript] = useState10(() => ({
1890
+ done: [entry({ kind: "header" }), ...updateMessage ? [entry({ kind: "notice", text: updateMessage, tone: "warn" })] : [], ...runtime.agent.messages.length ? replayEntries(runtime.session.loadHistory()) : []],
1891
+ live: []
1892
+ }));
1893
+ const [busy, setBusy] = useState10(false);
1894
+ const [cancelling, setCancelling] = useState10(false);
1895
+ const [startedAt, setStartedAt] = useState10(0);
1896
+ const [mode, setMode] = useState10(runtime.agent.permissions.mode);
1897
+ const [reasoning, setReasoning] = useState10(runtime.reasoning);
1898
+ const [tokens, setTokens] = useState10(runtime.agent.lastInputTokens);
1899
+ const [cachedTokens, setCachedTokens] = useState10(runtime.agent.totalUsage.cachedInputTokens ?? 0);
1900
+ const [tokensEstimated, setTokensEstimated] = useState10(runtime.agent.lastInputEstimated);
1901
+ const [asks, setAsks] = useState10([]);
1902
+ const pending = asks[0] ?? null;
1903
+ const askId = useRef2(0);
1904
+ const [picker, setPicker] = useState10(null);
1905
+ const [providerMenu, setProviderMenu] = useState10(null);
1906
+ const [prompt, setPrompt] = useState10(null);
1907
+ const ask = useCallback((label, mask = false) => new Promise((resolve3) => setPrompt({ label, mask, resolve: resolve3 })), []);
1908
+ const [modelLabel, setModelLabel] = useState10(() => labelFor(runtime, runtime.modelRef));
1909
+ const [history, setHistory] = useState10([]);
1910
+ const controller = useRef2(null);
1911
+ const [todos, setTodos] = useState10(runtime.todos.items);
1912
+ const [mcpMenu, setMcpMenu] = useState10(null);
1913
+ const [listPicker, setListPicker] = useState10(null);
1914
+ const [planAsk, setPlanAsk] = useState10(null);
1915
+ const [queued, setQueued] = useState10([]);
1916
+ const [showThinking, setShowThinking] = useState10(runtime.config.thinkingVisible === true);
1917
+ const showThinkingRef = useRef2(showThinking);
1918
+ const [showToolOutput, setShowToolOutput] = useState10(false);
1919
+ const [staticKey, setStaticKey] = useState10(0);
1920
+ const [, setConfigRev] = useState10(0);
1921
+ const lastUserIdx = transcript.done.findLastIndex((e) => e.kind === "user");
1922
+ const { stdin } = useStdin();
1923
+ const { stdout } = useStdout2();
1924
+ const realStdout = isRealTty(stdout);
1925
+ const liveWidth = Math.max(20, streamColumns(stdout) - 3);
1926
+ const liveRowBudget = Math.max(8, streamRows(stdout) - 10);
1927
+ const maxSplitIdxRef = useRef2(0);
1928
+ const refreshStatic = useCallback(() => {
1929
+ if (realStdout) {
1930
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
1931
+ }
1932
+ maxSplitIdxRef.current = 0;
1933
+ setStaticKey((k2) => k2 + 1);
1934
+ }, [stdout, realStdout]);
1935
+ let splitIdx = Math.max(maxSplitIdxRef.current, lastUserIdx > 0 ? lastUserIdx : 0);
1936
+ const entryRows = (e) => {
1937
+ if (e.kind === "thinking") {
1938
+ return showThinking ? Math.min(liveRowBudget, wrappedRows(e.text, liveWidth) + 3) : 2;
1939
+ }
1940
+ if (e.kind === "tool") {
1941
+ return showToolOutput ? Math.min(liveRowBudget, wrappedRows(e.output ?? e.display ?? "", liveWidth) + 2) : 5;
1942
+ }
1943
+ if (e.kind === "assistant") return markdownRowCount(e.text, liveWidth) + 1;
1944
+ if (e.kind === "notice" || e.kind === "user") return wrappedRows(e.text, Math.max(20, liveWidth - 2)) + 1;
1945
+ return 2;
1946
+ };
1947
+ if (realStdout) {
1948
+ const liveLines = transcript.live.reduce((acc, e) => acc + entryRows(e), 0);
1949
+ let doneLines = transcript.done.slice(splitIdx).reduce((acc, e) => acc + entryRows(e), 0);
1950
+ while (splitIdx < transcript.done.length && doneLines + liveLines > liveRowBudget) {
1951
+ doneLines -= entryRows(transcript.done[splitIdx]);
1952
+ splitIdx++;
1953
+ }
1954
+ maxSplitIdxRef.current = splitIdx;
1955
+ }
1956
+ const staticDone = transcript.done.slice(0, splitIdx);
1957
+ const activeTurnDone = transcript.done.slice(splitIdx);
1958
+ const doneRows = realStdout ? activeTurnDone.reduce((acc, e) => acc + entryRows(e), 0) : 0;
1959
+ const liveCaps = transcript.live.map(() => Number.POSITIVE_INFINITY);
1960
+ if (realStdout) {
1961
+ for (const [i, cap] of liveRowCaps(transcript.live.map(entryRows), liveRowBudget - doneRows).entries()) liveCaps[i] = cap;
1962
+ }
1963
+ const activeTurnEntries = [...activeTurnDone, ...transcript.live];
1964
+ const lastThinkingEntryId = activeTurnEntries.findLast((e) => e.kind === "thinking" && !!e.text.trim())?.id;
1965
+ const hasThinking = lastThinkingEntryId !== void 0 || transcript.done.some((e) => e.kind === "thinking" && !!e.text.trim());
1966
+ const liveRootRef = useRef2(null);
1967
+ const thinkingTargetRef = useRef2(null);
1968
+ const spinnerTargetRef = useRef2(null);
1969
+ const thinkingEntryRefs = useRef2(/* @__PURE__ */ new Map());
1970
+ const [injected, setInjected] = useState10();
1971
+ useEffect3(() => runtime.todos.subscribe(setTodos), [runtime]);
1972
+ const extraCommands = [
1973
+ ...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") })),
1974
+ ...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) }) }))
1975
+ ];
1976
+ const lastTool = useRef2(null);
1977
+ const toolTargets = useRef2(/* @__PURE__ */ new Map());
1978
+ const editedInTurn = useRef2(false);
1979
+ const notice = useCallback((text, tone = "info") => {
1980
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
1981
+ }, []);
1982
+ const onEvent = useCallback((event) => {
1983
+ if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
1984
+ if (event.type === "toolEnd") {
1985
+ lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
1986
+ if ((event.tool === "edit" || event.tool === "write") && !event.isError) editedInTurn.current = true;
1987
+ }
1988
+ if (event.type === "usage") {
1989
+ if (event.lastInputTokens !== void 0) {
1990
+ setTokens(event.lastInputTokens);
1991
+ setCachedTokens(event.lastCachedInputTokens ?? 0);
1992
+ setTokensEstimated(event.estimated ?? false);
1993
+ }
1994
+ }
1995
+ if (event.type === "budgetExceeded") {
1996
+ notice(
1997
+ t("Budget exceeded ({kind}): {used} of {limit}. Further model calls are blocked; raise usageCap in ~/.bccli/config.json.", {
1998
+ kind: event.kind,
1999
+ used: Math.round(event.used * 100) / 100,
2000
+ limit: event.limit
2001
+ }),
2002
+ "warn"
2003
+ );
2004
+ }
2005
+ setTranscript((t2) => applyEvent(t2, event));
2006
+ }, [notice]);
2007
+ useEffect3(() => {
2008
+ runtime.agent.onEvent = onEvent;
2009
+ runtime.agent.askPermission = (request) => new Promise((resolve3) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve3 }]));
2010
+ runtime.interaction.approvePlan = (plan) => new Promise((resolve3) => setPlanAsk({ plan, resolve: resolve3 }));
2011
+ runHooks(runtime.config.hooks, "SessionStart", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
2012
+ for (const w of outcome.warnings) notice(w, "warn");
2013
+ }).catch(() => {
2014
+ });
2015
+ runtime.emitPlugins("SessionStart").catch(() => {
2016
+ });
2017
+ }, [runtime, onEvent, notice]);
2018
+ const runTurn = useCallback(
2019
+ async (text) => {
2020
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
2021
+ setBusy(true);
2022
+ setShowToolOutput(false);
2023
+ setStartedAt(Date.now());
2024
+ editedInTurn.current = false;
2025
+ controller.current = new AbortController();
2026
+ const signal = controller.current.signal;
2027
+ const expandedText = expandAtFileMentions(text, runtime.cwd, runtime.agent.readFiles);
2028
+ await runtime.agent.run(expandedText, signal);
2029
+ const commands = runtime.config.verifyCommands;
2030
+ if (editedInTurn.current && commands.length && !signal.aborted) {
2031
+ for (let round = 0; round < MAX_VERIFY_ROUNDS; round++) {
2032
+ notice(t("Verifying edits: {cmds}", { cmds: commands.join(", ") }));
2033
+ const failures = await runVerify(commands, { cwd: runtime.cwd, env: runtime.env, signal });
2034
+ if (!failures.length) {
2035
+ notice(t("Verification passed."));
2036
+ break;
2037
+ }
2038
+ if (round === MAX_VERIFY_ROUNDS - 1 || signal.aborted) {
2039
+ notice(t("Verification still failing after {n} fix round(s). Run the commands manually to see why.", { n: MAX_VERIFY_ROUNDS }), "warn");
2040
+ break;
2041
+ }
2042
+ await runtime.agent.run(verifyFollowup(failures), signal);
2043
+ }
2044
+ }
2045
+ controller.current = null;
2046
+ runHooks(runtime.config.hooks, "Stop", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
2047
+ for (const w of outcome.warnings) notice(w, "warn");
2048
+ }).catch(() => {
2049
+ });
2050
+ runtime.emitPlugins("Stop").catch(() => {
2051
+ });
2052
+ setTranscript(endTurn);
2053
+ setBusy(false);
2054
+ setCancelling(false);
2055
+ },
2056
+ [runtime, notice]
2057
+ );
2058
+ const toggleThinking = useCallback(() => {
2059
+ const next = !showThinkingRef.current;
2060
+ showThinkingRef.current = next;
2061
+ setShowThinking(next);
2062
+ if (maxSplitIdxRef.current > 0) refreshStatic();
2063
+ try {
2064
+ writeGlobalConfig({ thinkingVisible: next }, runtime.env);
2065
+ } catch (error) {
2066
+ notice(t("Could not save thinking visibility: {error}", { error: error.message }), "warn");
2067
+ }
2068
+ }, [runtime, notice, refreshStatic]);
2069
+ const runSlashCommand = useCallback(
2070
+ async (name, args) => {
2071
+ switch (name) {
2072
+ case "help":
2073
+ notice(helpText(runtime));
2074
+ return;
2075
+ case "thinking":
2076
+ case "t": {
2077
+ const v = args.trim().toLowerCase();
2078
+ if (v === "on" || v === "true") {
2079
+ if (!showThinkingRef.current) toggleThinking();
2080
+ } else if (v === "off" || v === "false") {
2081
+ if (showThinkingRef.current) toggleThinking();
2082
+ } else {
2083
+ toggleThinking();
2084
+ }
2085
+ return;
2086
+ }
2087
+ case "output":
2088
+ case "o":
2089
+ setShowToolOutput((prev) => !prev);
2090
+ if (maxSplitIdxRef.current > 0) refreshStatic();
2091
+ return;
2092
+ case "exit":
2093
+ case "quit":
2094
+ exit();
2095
+ return;
2096
+ case "clear":
2097
+ controller.current?.abort();
2098
+ controller.current = null;
2099
+ setQueued([]);
2100
+ setBusy(false);
2101
+ setCancelling(false);
2102
+ runtime.agent.clear();
2103
+ setTokens(0);
2104
+ setCachedTokens(0);
2105
+ setTokensEstimated(false);
2106
+ setShowToolOutput(false);
2107
+ setTranscript({ done: [entry({ kind: "header" })], live: [] });
2108
+ refreshStatic();
2109
+ notice(t("Conversation cleared."));
2110
+ return;
2111
+ case "cost": {
2112
+ const u = runtime.agent.totalUsage;
2113
+ const cache = u.cachedInputTokens ?? 0;
2114
+ notice(
2115
+ t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }) + (cache > 0 ? ` \xB7 ${t("{n} cached", { n: cache.toLocaleString(locale()) })}` : "") + ` \xB7 ${tokensEstimated ? "~" : ""}${t("{n} tokens in context", { n: runtime.agent.lastInputTokens.toLocaleString(locale()) })}`
2116
+ );
2117
+ return;
2118
+ }
2119
+ case "tasks":
2120
+ notice(renderBackgroundTasks());
2121
+ return;
2122
+ case "compact": {
2123
+ setBusy(true);
2124
+ setStartedAt(Date.now());
2125
+ controller.current = new AbortController();
2126
+ try {
2127
+ await runtime.agent.compact(controller.current.signal);
2128
+ } catch (error) {
2129
+ notice(t("Could not summarize: {error}", { error: error.message }), "error");
2130
+ }
2131
+ controller.current = null;
2132
+ setBusy(false);
2133
+ setCancelling(false);
2134
+ return;
2135
+ }
2136
+ case "reasoning": {
2137
+ const apply = (value) => {
2138
+ try {
2139
+ const level = parseReasoningLevel(value, "/reasoning");
2140
+ const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
2141
+ assertReasoningSupported(providerId2, level);
2142
+ runtime.setReasoning(level);
2143
+ writeGlobalConfig({ reasoning: level }, runtime.env);
2144
+ setReasoning(level);
2145
+ notice(t("Reasoning: {level} (saved as default)", { level }));
2146
+ } catch (error) {
2147
+ notice(error.message, "error");
2148
+ }
2149
+ };
2150
+ if (args) {
2151
+ apply(args);
2152
+ return;
2153
+ }
2154
+ const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
2155
+ const supported = supportedReasoningLevels(providerId);
2156
+ setListPicker({
2157
+ title: "Reasoning",
2158
+ items: REASONING_LEVELS.map((level) => ({
2159
+ id: level,
2160
+ label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
2161
+ disabled: !supported.includes(level),
2162
+ hint: supported.includes(level) ? void 0 : t("not supported by this provider")
2163
+ })),
2164
+ onPick: (id) => {
2165
+ setListPicker(null);
2166
+ if (id) apply(id);
2167
+ }
2168
+ });
2169
+ return;
2170
+ }
2171
+ case "model": {
2172
+ if (args) {
2173
+ try {
2174
+ runtime.setModel(args);
2175
+ writeGlobalConfig({ model: args }, runtime.env);
2176
+ setModelLabel(labelFor(runtime, args));
2177
+ notice(t("Model: {model} (saved as default)", { model: args }));
2178
+ } catch (error) {
2179
+ notice(error.message, "error");
2180
+ }
2181
+ return;
2182
+ }
2183
+ try {
2184
+ setPicker(await runtime.listModels());
2185
+ } catch (error) {
2186
+ notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
2187
+ }
2188
+ return;
2189
+ }
2190
+ case "mcp": {
2191
+ const installed = readMcpFile(globalMcpPath(runtime.home));
2192
+ const states = runtime.mcp.states();
2193
+ const items = [
2194
+ ...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
2195
+ ...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
2196
+ ].map((i) => {
2197
+ const s = states.find((st) => st.name === i.name);
2198
+ return { ...i, status: s?.status ?? "off", error: s?.error };
2199
+ });
2200
+ setMcpMenu(items);
2201
+ return;
2202
+ }
2203
+ case "new":
2204
+ controller.current?.abort();
2205
+ controller.current = null;
2206
+ setQueued([]);
2207
+ setBusy(false);
2208
+ setCancelling(false);
2209
+ runtime.newSession();
2210
+ setTokens(0);
2211
+ setCachedTokens(0);
2212
+ setTokensEstimated(false);
2213
+ setShowToolOutput(false);
2214
+ setTranscript({ done: [entry({ kind: "header" })], live: [] });
2215
+ refreshStatic();
2216
+ notice(t("New session started. The previous session stays saved (/resume)."));
2217
+ return;
2218
+ case "session":
2219
+ notice(
2220
+ sessionText({
2221
+ file: runtime.session.file,
2222
+ started: runtime.startedAt,
2223
+ messages: runtime.agent.messages.length,
2224
+ usage: runtime.agent.totalUsage,
2225
+ modelRef: runtime.modelRef
2226
+ })
2227
+ );
2228
+ return;
2229
+ case "resume": {
2230
+ const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
2231
+ if (!others.length) {
2232
+ notice(t("No other sessions in this folder yet."));
2233
+ return;
2234
+ }
2235
+ setListPicker({
2236
+ title: t("Continue a session"),
2237
+ items: others.map((s) => ({
2238
+ id: s.session.file,
2239
+ label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
2240
+ hint: t("{n} messages", { n: s.session.load().length })
2241
+ })),
2242
+ onPick: (id) => {
2243
+ setListPicker(null);
2244
+ const chosen = others.find((s) => s.session.file === id);
2245
+ if (!chosen) return;
2246
+ runtime.resume(chosen.session);
2247
+ setModelLabel(labelFor(runtime, runtime.modelRef));
2248
+ setTokens(runtime.agent.lastInputTokens);
2249
+ setCachedTokens(0);
2250
+ setTokensEstimated(runtime.agent.lastInputEstimated);
2251
+ setShowToolOutput(false);
2252
+ setTranscript({ done: [entry({ kind: "header" }), ...replayEntries(chosen.session.loadHistory())], live: [] });
2253
+ refreshStatic();
2254
+ }
2255
+ });
2256
+ return;
2257
+ }
2258
+ case "status":
2259
+ notice(
2260
+ statusText({
2261
+ version,
2262
+ modelRef: runtime.modelRef,
2263
+ providerLabel: runtime.providerLabel(),
2264
+ mode: runtime.agent.permissions.mode,
2265
+ cwd: runtime.cwd,
2266
+ mcp: runtime.mcp.states(),
2267
+ usage: runtime.agent.totalUsage,
2268
+ lastInputTokens: runtime.agent.lastInputTokens,
2269
+ contextWindow: runtime.agent.getContextWindow()
2270
+ })
2271
+ );
2272
+ return;
2273
+ case "agents":
2274
+ notice(agentsText([...BUILTIN_AGENTS, ...runtime.agentDefs]));
2275
+ return;
2276
+ case "skills":
2277
+ notice(skillsText(runtime.skills, runtime.commands));
2278
+ return;
2279
+ case "doctor": {
2280
+ notice(t("Checking\u2026"));
2281
+ const c = runtime.config;
2282
+ notice(
2283
+ await doctorText(
2284
+ {
2285
+ providers: Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env) })),
2286
+ mcp: runtime.mcp.states(),
2287
+ activeProvider: runtime.providerLabel()
2288
+ },
2289
+ {
2290
+ which: (cmd) => new Promise((done) => execFile3(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
2291
+ nodeVersion: process.version,
2292
+ listModels: () => runtime.agent.provider.listModels()
2293
+ }
2294
+ )
2295
+ );
2296
+ return;
2297
+ }
2298
+ case "permissions": {
2299
+ const rules = runtime.agent.permissions.list();
2300
+ if (!rules.length) {
2301
+ notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
2302
+ return;
2303
+ }
2304
+ setListPicker({
2305
+ title: t("Active permissions (enter: revoke a session permission)"),
2306
+ items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
2307
+ onPick: (id) => {
2308
+ setListPicker(null);
2309
+ const rule = rules.find((r) => r.rule === id);
2310
+ if (!rule) return;
2311
+ 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");
2312
+ else {
2313
+ runtime.agent.permissions.revoke(rule.rule);
2314
+ notice(t("Permission {rule} revoked.", { rule: rule.rule }));
2315
+ }
2316
+ }
2317
+ });
2318
+ return;
2319
+ }
2320
+ case "memory": {
2321
+ if (!args) {
2322
+ const files = instructionFiles(runtime.cwd, runtime.home);
2323
+ 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>."));
2324
+ return;
2325
+ }
2326
+ const { global, text } = parseMemoryArgs(args);
2327
+ if (!text) {
2328
+ notice(t("Write the text: /memory global <text>."), "warn");
2329
+ return;
2330
+ }
2331
+ const file = global ? join3(runtime.home, "BCCLI.md") : join3(runtime.cwd, "AGENTS.md");
2332
+ try {
2333
+ mkdirSync(dirname(file), { recursive: true });
2334
+ appendMemory(file, text);
2335
+ } catch (error) {
2336
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
2337
+ return;
2338
+ }
2339
+ runtime.rebuildSystemPrompt();
2340
+ notice(t("Added to {file}.", { file }));
2341
+ return;
2342
+ }
2343
+ case "init":
2344
+ void runTurn(INIT_PROMPT);
2345
+ return;
2346
+ case "language": {
2347
+ if (!args) {
2348
+ notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
2349
+ return;
2350
+ }
2351
+ const wanted = parseLang(args);
2352
+ if (!wanted) {
2353
+ notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
2354
+ return;
2355
+ }
2356
+ setLanguage(wanted);
2357
+ try {
2358
+ writeGlobalConfig({ language: wanted }, runtime.env);
2359
+ } catch (error) {
2360
+ notice(t("Could not save the language: {error}", { error: error.message }), "error");
2361
+ }
2362
+ notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
2363
+ return;
2364
+ }
2365
+ case "login": {
2366
+ const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
2367
+ const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
2368
+ if (!key) return;
2369
+ const result = await runtime.addProviderKey(id, key);
2370
+ if (!result.ok) {
2371
+ notice(result.error, "error");
2372
+ return;
2373
+ }
2374
+ runtime.setModel(runtime.modelRef);
2375
+ notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
2376
+ return;
2377
+ }
2378
+ case "logout": {
2379
+ const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
2380
+ const removed = removeCredential(id, runtime.env);
2381
+ const envName = runtime.config.providers[id]?.apiKeyEnv;
2382
+ if (envName && runtime.env[envName]) {
2383
+ notice(
2384
+ 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 }),
2385
+ "warn"
2386
+ );
2387
+ return;
2388
+ }
2389
+ if (!removed) {
2390
+ notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
2391
+ return;
2392
+ }
2393
+ try {
2394
+ runtime.setModel(runtime.modelRef);
2395
+ } catch (error) {
2396
+ const fail = async () => {
2397
+ throw error;
2398
+ };
2399
+ runtime.agent.provider = { chat: fail, listModels: fail };
2400
+ }
2401
+ notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
2402
+ return;
2403
+ }
2404
+ case "diff":
2405
+ notice(await gitDiff(runtime.cwd));
2406
+ return;
2407
+ case "worktree":
2408
+ notice(await worktreeListText(runtime.cwd));
2409
+ return;
2410
+ case "pr":
2411
+ void runTurn(prReviewPrompt(args));
2412
+ return;
2413
+ case "undo": {
2414
+ const result = await runtime.checkpoints.undo();
2415
+ if (!result) {
2416
+ notice(t("No file edits to undo."));
2417
+ return;
2418
+ }
2419
+ const rel = (p) => relative2(runtime.cwd, p) || p;
2420
+ notice(
2421
+ [
2422
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
2423
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
2424
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
2425
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
2426
+ t("Note: changes made through bash commands cannot be undone.")
2427
+ ].filter(Boolean).join("\n")
2428
+ );
2429
+ return;
2430
+ }
2431
+ case "redo": {
2432
+ const result = await runtime.checkpoints.redo();
2433
+ if (!result) {
2434
+ notice(t("No file edits to redo."));
2435
+ return;
2436
+ }
2437
+ const rel = (p) => relative2(runtime.cwd, p) || p;
2438
+ notice(
2439
+ [
2440
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
2441
+ result.restored.length ? t("Re-applied: {files}", { files: result.restored.map(rel).join(", ") }) : "",
2442
+ result.deleted.length ? t("Deleted again: {files}", { files: result.deleted.map(rel).join(", ") }) : "",
2443
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : ""
2444
+ ].filter(Boolean).join("\n")
2445
+ );
2446
+ return;
2447
+ }
2448
+ case "rewind": {
2449
+ const parsed = Number.parseInt(args.trim(), 10);
2450
+ const result = await runtime.rewindTurns(Number.isFinite(parsed) && parsed > 0 ? parsed : 1);
2451
+ if (!result) {
2452
+ notice(t("Nothing to rewind."));
2453
+ return;
2454
+ }
2455
+ setTranscript((tr) => {
2456
+ const userIndices = [];
2457
+ tr.done.forEach((e, idx) => {
2458
+ if (e.kind === "user") userIndices.push(idx);
2459
+ });
2460
+ const cutIdx = userIndices[Math.max(0, userIndices.length - result.turns)];
2461
+ return cutIdx !== void 0 ? { done: tr.done.slice(0, cutIdx), live: [] } : tr;
2462
+ });
2463
+ refreshStatic();
2464
+ const rel = (p) => relative2(runtime.cwd, p) || p;
2465
+ notice(
2466
+ [
2467
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
2468
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
2469
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
2470
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
2471
+ t("Rewound {n} turn(s): the conversation continues from before them.", { n: result.turns })
2472
+ ].filter(Boolean).join("\n")
2473
+ );
2474
+ return;
2475
+ }
2476
+ case "copy": {
2477
+ const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
2478
+ if (!last || typeof last.content !== "string") {
2479
+ notice(t("No answer to copy yet."));
2480
+ return;
2481
+ }
2482
+ process.stdout.write(osc52(last.content, !!process.env.TMUX));
2483
+ const file = join3(runtime.home, "last-answer.md");
2484
+ mkdirSync(runtime.home, { recursive: true });
2485
+ writeFileSync(file, last.content);
2486
+ notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
2487
+ return;
2488
+ }
2489
+ case "export": {
2490
+ const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
2491
+ const file = resolve2(runtime.cwd, name2);
2492
+ if (existsSync3(file)) {
2493
+ const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
2494
+ if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
2495
+ notice(t("Export cancelled."));
2496
+ return;
2497
+ }
2498
+ }
2499
+ try {
2500
+ writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
2501
+ } catch (error) {
2502
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
2503
+ return;
2504
+ }
2505
+ notice(t("Conversation saved to {file}.", { file }));
2506
+ return;
2507
+ }
2508
+ case "provider": {
2509
+ const c = runtime.config;
2510
+ setProviderMenu(
2511
+ Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env), baseURL: c.providers[id].baseURL }))
2512
+ );
2513
+ return;
2514
+ }
2515
+ case "config": {
2516
+ const c = runtime.config;
2517
+ const applySetting = (key, val) => {
2518
+ if (key === "theme") writeGlobalConfig({ theme: val }, runtime.env);
2519
+ else if (key === "editor" && (val === "emacs" || val === "vim")) writeGlobalConfig({ editor: val }, runtime.env);
2520
+ else if (key === "repoMap") writeGlobalConfig({ repoMap: val === "true" || val === "on" }, runtime.env);
2521
+ else if (key === "thinkingVisible") {
2522
+ const next = val === "true" || val === "on";
2523
+ writeGlobalConfig({ thinkingVisible: next }, runtime.env);
2524
+ showThinkingRef.current = next;
2525
+ setShowThinking(next);
2526
+ refreshStatic();
2527
+ } else if (key === "thinkingClickCollapse") writeGlobalConfig({ thinkingClickCollapse: val === "true" || val === "on" }, runtime.env);
2528
+ else if (key === "sandbox" && (val === "on" || val === "off")) writeGlobalConfig({ sandbox: val }, runtime.env);
2529
+ else if (key === "networkPolicy" && (val === "allow" || val === "offline")) writeGlobalConfig({ networkPolicy: val }, runtime.env);
2530
+ else return false;
2531
+ runtime.reloadConfig();
2532
+ setConfigRev((n) => n + 1);
2533
+ notice(`${key} = ${val}`);
2534
+ return true;
2535
+ };
2536
+ if (args.trim()) {
2537
+ const [k2, v] = args.trim().split(/\s+/);
2538
+ if (k2 && v && applySetting(k2, v)) return;
2539
+ }
2540
+ setListPicker({
2541
+ title: "Config",
2542
+ items: [
2543
+ { id: `editor:${c.editor === "vim" ? "emacs" : "vim"}`, label: `editor: ${c.editor}`, hint: "toggle emacs/vim" },
2544
+ { id: `repoMap:${!c.repoMap}`, label: `repoMap: ${c.repoMap}`, hint: "toggle true/false" },
2545
+ { id: `thinkingVisible:${!c.thinkingVisible}`, label: `thinkingVisible: ${!!c.thinkingVisible}`, hint: "toggle true/false" },
2546
+ { id: `thinkingClickCollapse:${!c.thinkingClickCollapse}`, label: `thinkingClickCollapse: ${!!c.thinkingClickCollapse}`, hint: "toggle true/false" },
2547
+ { id: `sandbox:${c.sandbox === "on" ? "off" : "on"}`, label: `sandbox: ${c.sandbox}`, hint: "toggle on/off" },
2548
+ { id: `networkPolicy:${c.networkPolicy === "offline" ? "allow" : "offline"}`, label: `networkPolicy: ${c.networkPolicy ?? "allow"}`, hint: "toggle allow/offline" }
2549
+ ],
2550
+ onPick: (picked) => {
2551
+ setListPicker(null);
2552
+ if (!picked) return;
2553
+ const [k2, v] = picked.split(":");
2554
+ if (k2 && v) applySetting(k2, v);
2555
+ }
2556
+ });
2557
+ return;
2558
+ }
2559
+ default:
2560
+ notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
2561
+ }
2562
+ },
2563
+ [runtime, notice, exit, version, runTurn, ask, refreshStatic, toggleThinking, tokensEstimated]
2564
+ );
2565
+ const runSlash = useCallback(
2566
+ (name, args) => runSlashCommand(name, args).catch((error) => notice(`/${name}: ${error.message}`, "error")),
2567
+ [runSlashCommand, notice]
2568
+ );
2569
+ const submit = useCallback(
2570
+ (text) => {
2571
+ setHistory((h) => [...h, text]);
2572
+ if (text.startsWith("#") && text.slice(1).trim()) {
2573
+ void runSlash("memory", text.slice(1).trim());
2574
+ return;
2575
+ }
2576
+ if (text.startsWith("!") && text.slice(1).trim()) {
2577
+ const cmd = text.slice(1).trim();
2578
+ setTranscript((tr) => ({ ...tr, done: [...tr.done, entry({ kind: "user", text })] }));
2579
+ setBusy(true);
2580
+ setStartedAt(Date.now());
2581
+ const ctrl = new AbortController();
2582
+ controller.current = ctrl;
2583
+ const shell = resolveShell({ platform: process.platform, env: runtime.env, exists: existsSync3 });
2584
+ void runCommand(cmd, { cwd: runtime.cwd, timeoutMs: 6e4, signal: ctrl.signal, shell }).then((r) => {
2585
+ const out = r.output.trim() || "(no output)";
2586
+ notice(`$ ${cmd}
2587
+ ${out}`, r.exitCode ? "error" : "info");
2588
+ const ctxMsg = { role: "user", content: `Local shell command executed:
2589
+ $ ${cmd}
2590
+ ${out}` };
2591
+ runtime.agent.messages.push(ctxMsg);
2592
+ runtime.session.append(ctxMsg);
2593
+ }).finally(() => {
2594
+ controller.current = null;
2595
+ setBusy(false);
2596
+ setCancelling(false);
2597
+ });
2598
+ return;
2599
+ }
2600
+ const slash = parseSlash(text);
2601
+ if (!slash) {
2602
+ void runTurn(text);
2603
+ return;
2604
+ }
2605
+ const command = runtime.commands.find((c) => c.name.toLowerCase() === slash.name);
2606
+ const skill = runtime.skills.find((s) => s.name.toLowerCase() === slash.name);
2607
+ if (SLASH_COMMANDS.some((b) => b.name === slash.name) || slash.name === "t" || slash.name === "output" || slash.name === "o" || slash.name === "quit" || !command && !skill) void runSlash(slash.name, slash.args);
2608
+ else if (command) void runTurn(expandCommand(command, slash.args));
2609
+ else if (skill) {
2610
+ let body;
2611
+ try {
2612
+ body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
2613
+ } catch (error) {
2614
+ notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
2615
+ return;
2616
+ }
2617
+ void runTurn(`${body}
2618
+
2619
+ ARGUMENTS: ${slash.args}`);
2620
+ }
2621
+ },
2622
+ [runSlash, runTurn, runtime, notice]
2623
+ );
2624
+ const onPrompt = useCallback(
2625
+ (text) => {
2626
+ if (!busy) {
2627
+ submit(text);
2628
+ return;
2629
+ }
2630
+ if (text.startsWith("#") && text.slice(1).trim()) {
2631
+ submit(text);
2632
+ return;
2633
+ }
2634
+ const slash = parseSlash(text);
2635
+ if (slash) {
2636
+ const turnMutating = /* @__PURE__ */ new Set(["init", "pr", "compact", "rewind", "undo", "redo", "resume"]);
2637
+ const isCustomTurn = !SLASH_COMMANDS.some((b) => b.name === slash.name) && slash.name !== "t" && slash.name !== "output" && slash.name !== "o" && slash.name !== "quit" && (runtime.commands.some((c) => c.name.toLowerCase() === slash.name) || runtime.skills.some((s) => s.name.toLowerCase() === slash.name));
2638
+ if (!turnMutating.has(slash.name) && !isCustomTurn) {
2639
+ submit(text);
2640
+ return;
2641
+ }
2642
+ }
2643
+ setQueued((q) => [...q, text]);
2644
+ },
2645
+ [busy, submit, runtime]
2646
+ );
2647
+ useEffect3(() => {
2648
+ if (busy || pending || planAsk || !queued.length) return;
2649
+ const [next, ...rest] = queued;
2650
+ setQueued(rest);
2651
+ submit(next);
2652
+ }, [busy, pending, planAsk, queued, submit]);
2653
+ const initialSent = useRef2(false);
2654
+ useEffect3(() => {
2655
+ if (initialPrompt && !initialSent.current) {
2656
+ initialSent.current = true;
2657
+ submit(initialPrompt);
2658
+ }
2659
+ }, [initialPrompt, submit]);
2660
+ useEffect3(() => {
2661
+ if (!isRealTty(stdout)) return;
2662
+ stdout.write("\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004h");
2663
+ return () => {
2664
+ stdout.write("\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004l");
2665
+ };
2666
+ }, [stdout]);
2667
+ useEffect3(() => {
2668
+ const modalOpen = !!(pending || planAsk || picker || listPicker || mcpMenu || providerMenu || prompt);
2669
+ if (!runtime.config.thinkingClickCollapse || !busy && !hasThinking || modalOpen || !isTtyStream(stdin) || !isTtyStream(stdout)) return;
2670
+ let click;
2671
+ let mouseBuffer = "";
2672
+ let wheelTimer;
2673
+ const onData = (data) => {
2674
+ const chunk = String(data);
2675
+ if (hasMouseWheelSequence(chunk)) {
2676
+ stdout.write("\x1B[?1000l\x1B[?1006l");
2677
+ if (wheelTimer) clearTimeout(wheelTimer);
2678
+ wheelTimer = setTimeout(() => {
2679
+ stdout.write("\x1B[?1000h\x1B[?1006h");
2680
+ }, 1200);
2681
+ }
2682
+ mouseBuffer += chunk;
2683
+ const reports = parseThinkingReports(mouseBuffer);
2684
+ mouseBuffer = reports.rest.slice(-80);
2685
+ for (const report of reports.events) {
2686
+ if (report.type === "mouse") {
2687
+ click = report.press;
2688
+ stdout.write("\x1B[6n");
2689
+ } else if (click && liveRootRef.current) {
2690
+ const current = click;
2691
+ click = void 0;
2692
+ const root = measureElement(liveRootRef.current);
2693
+ const candidates = [];
2694
+ if (thinkingTargetRef.current) candidates.push(thinkingTargetRef.current);
2695
+ if (spinnerTargetRef.current) candidates.push(spinnerTargetRef.current);
2696
+ for (const el of thinkingEntryRefs.current.values()) {
2697
+ if (!candidates.includes(el)) candidates.push(el);
2698
+ }
2699
+ if (candidates.some((el) => clickedThinking(current, report.cursor.row, root.height, measureElement(el)))) {
2700
+ toggleThinking();
2701
+ }
2702
+ }
2703
+ }
2704
+ };
2705
+ stdin.on("data", onData);
2706
+ stdout.write("\x1B[?1000h\x1B[?1006h");
2707
+ return () => {
2708
+ if (wheelTimer) clearTimeout(wheelTimer);
2709
+ stdin.off("data", onData);
2710
+ stdout.write("\x1B[?1000l\x1B[?1006l");
2711
+ };
2712
+ }, [busy, hasThinking, pending, planAsk, picker, listPicker, mcpMenu, providerMenu, prompt, stdin, stdout, toggleThinking, runtime.config.thinkingClickCollapse]);
2713
+ useInput9((input, key) => {
2714
+ const modalOpen = !!(pending || planAsk || picker || listPicker || mcpMenu || providerMenu || prompt);
2715
+ if (key.tab && key.shift) {
2716
+ const next = nextMode(runtime.agent.permissions.mode);
2717
+ runtime.agent.permissions.mode = next;
2718
+ setMode(next);
2719
+ } else if (key.escape && busy && !modalOpen) {
2720
+ controller.current?.abort();
2721
+ setCancelling(true);
2722
+ if (queued.length) {
2723
+ setQueued([]);
2724
+ notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
2725
+ }
2726
+ } else if (matchKeybind(runtime.config.keybinds.pasteImage, input, key)) {
2727
+ void (async () => {
2728
+ const path = await grabClipboardImage({ platform: process.platform, tmpdir: tmpdir(), now: /* @__PURE__ */ new Date() });
2729
+ if (path) {
2730
+ setInjected((prev) => ({ text: `${path} `, n: (prev?.n ?? 0) + 1 }));
2731
+ notice(t("Clipboard image saved to {path} \u2014 the path is inserted in the input; the model sees it when you send.", { path }));
2732
+ } else {
2733
+ notice(t("No image on the clipboard."), "warn");
2734
+ }
2735
+ })();
2736
+ } else if (matchKeybind(runtime.config.keybinds.thinking, input, key) || key.meta && !key.ctrl && input.toLowerCase() === "t") {
2737
+ toggleThinking();
2738
+ } else if (matchKeybind(runtime.config.keybinds.toolOutput, input, key) || key.meta && !key.ctrl && input.toLowerCase() === "o") {
2739
+ setShowToolOutput((prev) => !prev);
2740
+ if (maxSplitIdxRef.current > 0) refreshStatic();
2741
+ } else if (key.ctrl && input.toLowerCase() === "l") {
2742
+ refreshStatic();
2743
+ }
2744
+ });
2745
+ const pickMcp = async (name) => {
2746
+ const item = mcpMenu?.find((i) => i.name === name);
2747
+ setMcpMenu(null);
2748
+ if (!item) return;
2749
+ if (item.installed) {
2750
+ removeGlobalServer(runtime.home, item.name);
2751
+ await runtime.mcp.remove(item.name);
2752
+ notice(t("MCP {name} removed.", { name: item.name }));
2753
+ return;
2754
+ }
2755
+ const entry2 = CATALOG.find((c) => c.name === item.name);
2756
+ if (!entry2) return;
2757
+ const values = {};
2758
+ for (const input of entry2.inputs ?? []) {
2759
+ const v = await ask(t(input.label), !!input.secret);
2760
+ if (!v) return;
2761
+ values[input.key] = v;
2762
+ }
2763
+ const config = fillTemplate(entry2.config, values);
2764
+ addGlobalServer(runtime.home, entry2.name, config);
2765
+ notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
2766
+ await runtime.mcp.add({ name: entry2.name, config, source: "global" });
2767
+ const state = runtime.mcp.states().find((s) => s.name === entry2.name);
2768
+ if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
2769
+ else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
2770
+ };
2771
+ const pickProvider = async (id) => {
2772
+ setProviderMenu(null);
2773
+ if (!id) return;
2774
+ let target = id;
2775
+ if (id === "custom") {
2776
+ const name = await ask("Id provider (huruf kecil, mis. corp)");
2777
+ const url = name ? await ask("Base URL OpenAI-compatible") : void 0;
2778
+ if (!name || !url) return;
2779
+ writeGlobalConfig({ providers: { [name]: { baseURL: url } } }, runtime.env);
2780
+ runtime.reloadConfig();
2781
+ target = name;
2782
+ }
2783
+ const c = runtime.config;
2784
+ const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
2785
+ if (!hasKey(c, target, runtime.env) || id === "custom") {
2786
+ const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
2787
+ if (key === void 0) return;
2788
+ const result = await runtime.addProviderKey(target, key);
2789
+ if (!result.ok) {
2790
+ notice(result.error, "error");
2791
+ return;
2792
+ }
2793
+ notice(`${providerName(c, target)} siap \xB7 ${result.models} model.`);
2794
+ }
2795
+ setPicker(await runtime.listModels(target));
2796
+ };
2797
+ const answer = (a) => {
2798
+ if (a === "all") {
2799
+ for (const item of asks) item.resolve("all");
2800
+ setAsks([]);
2801
+ setMode("allowAll");
2802
+ return;
2803
+ }
2804
+ pending?.resolve(a);
2805
+ setAsks((q) => q.slice(1));
2806
+ };
2807
+ const capLive = (raw, rows) => capLiveMarkdown(raw, { isRealTty: realStdout, maxRows: rows, width: liveWidth });
2808
+ const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
2809
+ const renderEntry = (e, liveRows) => {
2810
+ const isLive = transcript.live.some((le) => le.id === e.id);
2811
+ if (isLive && liveRows === 0) return null;
2812
+ switch (e.kind) {
2813
+ case "header":
2814
+ return /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text16, { children: [
2815
+ /* @__PURE__ */ jsx16(Text16, { color: color(ACCENT), bold: true, children: " \u273B " }),
2816
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: `BCCLI ${version}` }),
2817
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
2818
+ ] }) }, e.id);
2819
+ case "user":
2820
+ return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `> ${e.text}` }) }, e.id);
2821
+ case "assistant": {
2822
+ const displayText = isLive ? capLive(e.text, liveRows ?? liveRowBudget) : e.text;
2823
+ if (e.cont) {
2824
+ return displayText ? /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Markdown, { text: displayText }) }, e.id) : null;
2825
+ }
2826
+ if (!displayText.trim()) return null;
2827
+ return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, children: [
2828
+ /* @__PURE__ */ jsx16(Text16, { color: color(ACCENT), children: "\u25CF " }),
2829
+ /* @__PURE__ */ jsx16(Markdown, { text: displayText.replace(/^\s+/, "") })
2830
+ ] }, e.id);
2831
+ }
2832
+ case "thinking": {
2833
+ const text = e.text.trim();
2834
+ if (!text) return null;
2835
+ const allLines = text.split("\n");
2836
+ const lines = allLines.length;
2837
+ const shownText = isLive && showThinking ? capLiveText(text, { isRealTty: realStdout, maxRows: (liveRows ?? liveRowBudget) - 3, width: liveWidth }) : text;
2838
+ const isTarget = e.id === lastThinkingEntryId;
2839
+ return /* @__PURE__ */ jsxs12(
2840
+ Box13,
2841
+ {
2842
+ ref: (el) => {
2843
+ if (isTarget) thinkingTargetRef.current = el;
2844
+ if (el) thinkingEntryRefs.current.set(e.id, el);
2845
+ else thinkingEntryRefs.current.delete(e.id);
2846
+ },
2847
+ marginTop: 1,
2848
+ flexDirection: "column",
2849
+ children: [
2850
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", children: [
2851
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: showThinking ? t("\u273B Thinking") : t("\u273B Thinking \xB7 {n} lines", { n: lines }) }),
2852
+ /* @__PURE__ */ jsxs12(Text16, { dimColor: true, children: [
2853
+ " \xB7 ",
2854
+ runtime.config.keybinds.thinking.spec,
2855
+ " / tab",
2856
+ runtime.config.thinkingClickCollapse ? ` \xB7 ${t("click to toggle")}` : ""
2857
+ ] })
2858
+ ] }),
2859
+ showThinking ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2860
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("Model: {model} \xB7 reasoning: {level}", { model: runtime.modelRef, level: reasoning }) }),
2861
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, italic: true, children: shownText })
2862
+ ] }) : null
2863
+ ]
2864
+ },
2865
+ e.id
2866
+ );
2867
+ }
2868
+ case "tool":
2869
+ return /* @__PURE__ */ jsx16(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, expanded: showToolOutput, sub: e.sub, moreBind: runtime.config.keybinds.toolOutput.spec }, e.id);
2870
+ case "notice":
2871
+ 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);
2872
+ }
2873
+ };
2874
+ const activeToolEntry = transcript.live.findLast((e) => e.kind === "tool" && !e.done);
2875
+ const activeSub = activeToolEntry?.sub?.findLast((s) => !s.done);
2876
+ const activeTarget = (activeSub ? `${toolLabel(activeSub.tool)} ${activeSub.target}` : activeToolEntry?.target ?? "").replace(/\s+/g, " ").trim();
2877
+ const activeSpinnerSummary = activeToolEntry ? `${toolLabel(activeToolEntry.tool)}${activeTarget ? ` ${activeTarget.length > 20 ? `${activeTarget.slice(0, 20)}\u2026` : activeTarget}` : ""}` : "";
2878
+ const activeToolSummary = activeToolEntry ? `${toolLabel(activeToolEntry.tool)}${activeTarget ? ` ${activeTarget.length > 68 ? `${activeTarget.slice(0, 68)}\u2026` : activeTarget}` : ""}` : "";
2879
+ return /* @__PURE__ */ jsxs12(Box13, { ref: liveRootRef, flexDirection: "column", children: [
2880
+ /* @__PURE__ */ jsx16(Static, { items: staticDone, children: (e) => renderEntry(e) }, `static-${staticKey}`),
2881
+ activeTurnDone.map((e) => renderEntry(e)),
2882
+ transcript.live.map((e, i) => renderEntry(e, liveCaps[i])),
2883
+ busy && !pending && !planAsk ? /* @__PURE__ */ jsxs12(
2884
+ Box13,
2885
+ {
2886
+ ref: (el) => {
2887
+ spinnerTargetRef.current = el;
2888
+ if (lastThinkingEntryId === void 0) thinkingTargetRef.current = el;
2889
+ },
2890
+ marginTop: 1,
2891
+ flexDirection: "column",
2892
+ children: [
2893
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", children: [
2894
+ /* @__PURE__ */ jsx16(Spinner, { label: cancelling ? t("Cancelling") : activeSpinnerSummary ? `${t("Thinking")} \xB7 ${activeSpinnerSummary}` : t("Thinking"), startedAt }),
2895
+ /* @__PURE__ */ jsxs12(Text16, { dimColor: true, children: [
2896
+ " \xB7 ",
2897
+ runtime.config.keybinds.thinking.spec,
2898
+ " / tab",
2899
+ runtime.config.thinkingClickCollapse ? ` \xB7 ${t("click to toggle")}` : ""
2900
+ ] })
2901
+ ] }),
2902
+ showThinking && lastThinkingEntryId === void 0 ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2903
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("Model: {model} \xB7 reasoning: {level}", { model: runtime.modelRef, level: reasoning }) }),
2904
+ activeToolSummary ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `\u25B6 ${activeToolSummary}` }) : null,
2905
+ !activeToolSummary ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("No reasoning summary received yet; tool activity remains visible above.") }) : null
2906
+ ] }) : null
2907
+ ]
2908
+ }
2909
+ ) : null,
2910
+ pending ? /* @__PURE__ */ jsx16(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
2911
+ picker ? /* @__PURE__ */ jsx16(
2912
+ ModelPicker,
2913
+ {
2914
+ groups: picker,
2915
+ current: runtime.modelRef,
2916
+ onPick: (m) => {
2917
+ setPicker(null);
2918
+ if (m) void runSlash("model", m);
2919
+ }
2920
+ }
2921
+ ) : null,
2922
+ listPicker ? /* @__PURE__ */ jsx16(ListPicker, { title: listPicker.title, items: listPicker.items, onPick: listPicker.onPick }) : null,
2923
+ mcpMenu ? /* @__PURE__ */ jsx16(McpMenu, { items: mcpMenu, onPick: (n) => void pickMcp(n) }) : null,
2924
+ providerMenu ? /* @__PURE__ */ jsx16(ProviderMenu, { entries: providerMenu, onPick: (id) => void pickProvider(id) }) : null,
2925
+ prompt ? /* @__PURE__ */ jsx16(
2926
+ LinePrompt,
2927
+ {
2928
+ label: prompt.label,
2929
+ mask: prompt.mask,
2930
+ onSubmit: (v) => {
2931
+ setPrompt(null);
2932
+ prompt.resolve(v);
2933
+ },
2934
+ onCancel: () => {
2935
+ setPrompt(null);
2936
+ prompt.resolve(void 0);
2937
+ }
2938
+ }
2939
+ ) : null,
2940
+ planAsk ? /* @__PURE__ */ jsx16(
2941
+ PlanApproval,
2942
+ {
2943
+ plan: planAsk.plan,
2944
+ onAnswer: (d) => {
2945
+ planAsk.resolve(d);
2946
+ setPlanAsk(null);
2947
+ if (d !== "no") setMode(d);
2948
+ }
2949
+ }
2950
+ ) : null,
2951
+ /* @__PURE__ */ jsx16(TodoList, { items: todos }),
2952
+ queued.map((q, i) => /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
2953
+ /* @__PURE__ */ jsx16(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, onEmptyTab: toggleThinking, extraCommands, injected, vim: runtime.config.editor === "vim" }),
2954
+ /* @__PURE__ */ jsx16(StatusBar, { mode, tokens, cached: cachedTokens, estimated: tokensEstimated, busy, model: modelLabel, reasoning })
2955
+ ] });
2956
+ }
2957
+
2958
+ // src/ui/stdinFilter.ts
2959
+ var X10_START = "\x1B[M";
2960
+ var X10_PAYLOAD = 3;
2961
+ function stripLegacyMouseReports(chunk, carry = "") {
2962
+ const input = carry + chunk;
2963
+ let text = "";
2964
+ let index = 0;
2965
+ while (index < input.length) {
2966
+ if (input[index] === "\x1B") {
2967
+ const remaining = input.length - index;
2968
+ if (remaining < X10_START.length) {
2969
+ const tail = input.slice(index);
2970
+ if (X10_START.startsWith(tail)) return { text, carry: tail };
2971
+ } else if (input.startsWith(X10_START, index)) {
2972
+ if (remaining < X10_START.length + X10_PAYLOAD) return { text, carry: input.slice(index) };
2973
+ index += X10_START.length + X10_PAYLOAD;
2974
+ continue;
2975
+ }
2976
+ }
2977
+ text += input[index];
2978
+ index++;
2979
+ }
2980
+ return { text, carry: "" };
2981
+ }
2982
+ function installLegacyMouseFilter(source) {
2983
+ const mutable = source;
2984
+ const original = mutable.read.bind(source);
2985
+ let carry = "";
2986
+ mutable.read = (size) => {
2987
+ const chunk = original(size);
2988
+ if (chunk === null) return null;
2989
+ const stripped = stripLegacyMouseReports(String(chunk), carry);
2990
+ carry = stripped.carry;
2991
+ return stripped.text;
2992
+ };
2993
+ return () => {
2994
+ mutable.read = original;
2995
+ };
2996
+ }
2997
+
2998
+ // src/ui/index.tsx
2999
+ import { jsx as jsx17 } from "react/jsx-runtime";
3000
+ async function startInteractive(rt, opts) {
3001
+ if (opts.resume) {
3002
+ const { Session: Session2 } = await import("./session-YM36TGU7.js");
3003
+ const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
3004
+ if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
3005
+ else {
3006
+ list.forEach((s, i) => {
3007
+ console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
3008
+ });
3009
+ const { createInterface } = await import("readline/promises");
3010
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
3011
+ const answer = Number(await rl.question(t("Session number (enter = new): ")));
3012
+ rl.close();
3013
+ process.stdin.resume();
3014
+ const chosen = list[answer - 1];
3015
+ if (chosen) rt.resume(chosen.session);
3016
+ }
3017
+ }
3018
+ const { serversToStart, setProjectTrust } = await import("./config-OVWCUVY7.js");
3019
+ const plan = serversToStart(rt.home, rt.cwd);
3020
+ if (plan.needTrust.length) {
3021
+ const { createInterface } = await import("readline/promises");
3022
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
3023
+ for (const s of plan.needTrust) {
3024
+ const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
3025
+ 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();
3026
+ setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
3027
+ if (answer === "y") plan.start.push(s);
3028
+ }
3029
+ rl.close();
3030
+ process.stdin.resume();
3031
+ }
3032
+ void rt.startMcp(plan.start).catch((error) => {
3033
+ console.error(t("Could not start MCP servers: {error}", { error: error instanceof Error ? error.message : String(error) }));
3034
+ });
3035
+ const disposeMouseFilter = process.stdin.isTTY ? installLegacyMouseFilter(process.stdin) : void 0;
3036
+ const instance = render(/* @__PURE__ */ jsx17(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version, updateMessage: opts.updateMessage }), { exitOnCtrlC: true });
3037
+ await instance.waitUntilExit();
3038
+ disposeMouseFilter?.();
3039
+ await rt.mcp.stop();
3040
+ return 0;
3041
+ }
3042
+ export {
3043
+ startInteractive
3044
+ };