@botconnector/bccli 0.4.0-beta.37 → 0.4.0-beta.39

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 (39) hide show
  1. package/README.md +8 -0
  2. package/dist/acp-4XE2KOIJ.js +114 -0
  3. package/dist/{args-MNVI2OJI.js → args-BD2PHEKG.js} +4 -4
  4. package/dist/{chunk-TASYQW7B.js → chunk-AVB6M4JE.js} +2 -2
  5. package/dist/{chunk-FCQCZCKO.js → chunk-EONFDWBE.js} +2 -2
  6. package/dist/{chunk-7TABTC32.js → chunk-GGNXHJL6.js} +4 -3
  7. package/dist/chunk-HE33XAPZ.js +341 -0
  8. package/dist/{chunk-HPZSXX4W.js → chunk-ISCT3PN3.js} +2 -2
  9. package/dist/{chunk-BB4CVWHS.js → chunk-JROUQ5YS.js} +6 -45
  10. package/dist/chunk-LN32JIZR.js +56 -0
  11. package/dist/{chunk-4RMP4CNT.js → chunk-M23QBX3V.js} +52 -12
  12. package/dist/chunk-M7LLJQ7M.js +310 -0
  13. package/dist/{chunk-YURAIW7A.js → chunk-NI5JZUZ3.js} +1 -1
  14. package/dist/chunk-PDZPPSLU.js +8 -0
  15. package/dist/chunk-PSO7R5AD.js +10 -0
  16. package/dist/chunk-QETGQXPL.js +2125 -0
  17. package/dist/chunk-RV74TCEP.js +231 -0
  18. package/dist/{chunk-U66JJ6RN.js → chunk-SESBH44L.js} +1 -1
  19. package/dist/{chunk-3JRBXJVB.js → chunk-VM7HYAJS.js} +1 -1
  20. package/dist/{chunk-ASTNW3X2.js → chunk-ZG7HCXYA.js} +89 -9
  21. package/dist/{chunk-HXSREA7S.js → chunk-ZMU7MXCH.js} +184 -295
  22. package/dist/cli.js +65 -2097
  23. package/dist/{config-JRO5EOKZ.js → config-C4KLLDC6.js} +3 -3
  24. package/dist/{integrationCli-ZTZTQD2I.js → integrationCli-HR7CDUSI.js} +2 -2
  25. package/dist/{login-MHVCON5R.js → login-IWBRUNT4.js} +1 -1
  26. package/dist/{mcpCli-5ZSAMBJS.js → mcpCli-5QTXH7XJ.js} +9 -5
  27. package/dist/modelsCli-DKTT3UZD.js +36 -0
  28. package/dist/providerCli-DDH4ZZUY.js +15 -0
  29. package/dist/sdk.js +5114 -0
  30. package/dist/serve-7BP5BVX3.js +514 -0
  31. package/dist/serve-I2VQUTHR.js +178 -0
  32. package/dist/session-H2YL5RSB.js +12 -0
  33. package/dist/setup-FGCFKC7Z.js +23 -0
  34. package/dist/{ui-YLIKFLGN.js → ui-MLOXPAHT.js} +292 -54
  35. package/dist/update-73WI27VM.js +109 -0
  36. package/package.json +5 -2
  37. package/dist/chunk-H5N4ST6B.js +0 -149
  38. package/dist/providerCli-Z3TZ5XSZ.js +0 -14
  39. package/dist/session-IYVTCUBC.js +0 -12
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createRuntime,
4
+ lunaPtcAutoEnabled
5
+ } from "./chunk-QETGQXPL.js";
6
+ import "./chunk-3HCOAJEQ.js";
7
+ import "./chunk-ZMU7MXCH.js";
8
+ import "./chunk-R3ND76LW.js";
9
+ import "./chunk-JROUQ5YS.js";
10
+ import "./chunk-LN32JIZR.js";
11
+ import "./chunk-SESBH44L.js";
12
+ import "./chunk-VM7HYAJS.js";
13
+ import "./chunk-GGNXHJL6.js";
14
+ import "./chunk-NI5JZUZ3.js";
15
+ import "./chunk-AVB6M4JE.js";
16
+ import "./chunk-HE33XAPZ.js";
17
+ import "./chunk-PSO7R5AD.js";
18
+ import "./chunk-M7LLJQ7M.js";
19
+ import "./chunk-M23QBX3V.js";
20
+ export {
21
+ createRuntime,
22
+ lunaPtcAutoEnabled
23
+ };
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ CATALOG,
4
+ fillTemplate
5
+ } from "./chunk-ISCT3PN3.js";
6
+ import {
7
+ MAX_VERIFY_ROUNDS,
8
+ runVerify,
9
+ toolLabel,
10
+ verifyFollowup,
11
+ worktreeListText
12
+ } from "./chunk-RV74TCEP.js";
2
13
  import {
3
14
  BUILTIN_AGENTS,
4
15
  appendMemory,
@@ -9,10 +20,8 @@ import {
9
20
  parseMemoryArgs,
10
21
  renderBackgroundTasks,
11
22
  runHooks,
12
- toolLabel,
13
- validateGlobPattern,
14
- worktreeListText
15
- } from "./chunk-HXSREA7S.js";
23
+ validateGlobPattern
24
+ } from "./chunk-ZMU7MXCH.js";
16
25
  import {
17
26
  nextMode
18
27
  } from "./chunk-R3ND76LW.js";
@@ -20,51 +29,51 @@ import {
20
29
  hasKey,
21
30
  providerName,
22
31
  writeGlobalConfig
23
- } from "./chunk-U66JJ6RN.js";
32
+ } from "./chunk-SESBH44L.js";
24
33
  import {
25
34
  Session
26
- } from "./chunk-3JRBXJVB.js";
35
+ } from "./chunk-VM7HYAJS.js";
27
36
  import {
28
37
  contentText
29
- } from "./chunk-7TABTC32.js";
38
+ } from "./chunk-GGNXHJL6.js";
30
39
  import {
31
40
  REASONING_LEVELS,
32
41
  assertReasoningSupported,
33
42
  parseReasoningLevel,
34
43
  supportedReasoningLevels
35
- } from "./chunk-YURAIW7A.js";
36
- import {
37
- CATALOG,
38
- fillTemplate
39
- } from "./chunk-HPZSXX4W.js";
44
+ } from "./chunk-NI5JZUZ3.js";
40
45
  import {
41
46
  addGlobalServer,
42
47
  globalMcpPath,
43
48
  readMcpFile,
44
49
  removeGlobalServer
45
- } from "./chunk-TASYQW7B.js";
50
+ } from "./chunk-AVB6M4JE.js";
51
+ import "./chunk-PSO7R5AD.js";
46
52
  import {
53
+ ACCENT,
47
54
  PRESETS,
55
+ color,
56
+ matchKeybind,
48
57
  removeCredential
49
- } from "./chunk-H5N4ST6B.js";
58
+ } from "./chunk-M7LLJQ7M.js";
50
59
  import {
51
60
  getLanguage,
52
61
  locale,
53
62
  parseLang,
54
63
  setLanguage,
55
64
  t
56
- } from "./chunk-4RMP4CNT.js";
65
+ } from "./chunk-M23QBX3V.js";
57
66
 
58
67
  // src/ui/index.tsx
59
68
  import { render } from "ink";
60
69
 
61
70
  // src/ui/App.tsx
62
- import { execFile as execFile2 } from "child_process";
63
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
64
- import { homedir } from "os";
65
- import { dirname, join as join2, relative, resolve } from "path";
71
+ import { execFile as execFile3 } from "child_process";
72
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
73
+ import { homedir, tmpdir } from "os";
74
+ import { dirname, join as join3, relative, resolve } from "path";
66
75
  import { Box as Box13, Static, Text as Text16, useApp, useInput as useInput9 } from "ink";
67
- import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } from "react";
76
+ import { useCallback, useEffect as useEffect3, useRef, useState as useState8 } from "react";
68
77
 
69
78
  // src/commands.ts
70
79
  var SLASH_COMMANDS = [
@@ -79,6 +88,7 @@ var SLASH_COMMANDS = [
79
88
  { name: "status", description: "version, model, permission mode, MCP, context" },
80
89
  { name: "permissions", description: "view/revoke permissions" },
81
90
  { name: "undo", description: "undo the file edits of the last turn" },
91
+ { name: "redo", description: "re-apply the file edits that /undo just reverted" },
82
92
  { name: "rewind", description: "step back turns: conversation and file edits (/rewind 2)" },
83
93
  { name: "diff", description: "git diff of the project" },
84
94
  { name: "worktree", description: "list git worktrees; create one with bccli -w <name>" },
@@ -116,6 +126,7 @@ function prReviewPrompt(selector) {
116
126
  return `Review this pull request like a senior reviewer.
117
127
  ${refs}
118
128
  Read the changed files in the repository when the diff alone is not enough to judge the change.
129
+ Never change the repository state while reviewing: do not check out, switch, create, delete, merge or rebase branches, do not reset, stash, commit, pull, or fetch into the working tree, and do not write, edit or delete any file. The review is strictly read-only. To read a file as it exists in another revision, use read-only commands such as \`git show <ref>:<path>\` or \`gh pr diff\`.
119
130
  Write the review as: a 2-3 sentence summary; concrete problems (bugs, security, race conditions, error handling), each with file:line and why it matters; missing or weak tests; smaller style/nit issues; then a final verdict \u2014 approve, request changes, or comment \u2014 with the single most important reason.`;
120
131
  }
121
132
 
@@ -528,12 +539,6 @@ function layoutTable(table, width) {
528
539
  return out;
529
540
  }
530
541
 
531
- // src/ui/theme.ts
532
- function color(name) {
533
- return process.env.NO_COLOR ? void 0 : name;
534
- }
535
- var ACCENT = "green";
536
-
537
542
  // src/ui/Markdown.tsx
538
543
  import { jsx, jsxs } from "react/jsx-runtime";
539
544
  function segment(seg, key) {
@@ -944,7 +949,7 @@ function PermissionPrompt({
944
949
 
945
950
  // src/ui/PromptInput.tsx
946
951
  import { Box as Box8, Text as Text9, useInput as useInput6 } from "ink";
947
- import { useState as useState5 } from "react";
952
+ import { useEffect, useState as useState5 } from "react";
948
953
 
949
954
  // src/ui/complete.ts
950
955
  import { globSync } from "fs";
@@ -970,13 +975,101 @@ function completeFile(value, cwd) {
970
975
  return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
971
976
  }
972
977
 
978
+ // src/ui/vim.ts
979
+ var clamp = (s) => ({ value: s.value, cursor: Math.min(s.cursor, Math.max(0, s.value.length - 1)) });
980
+ var wordStart = (text, pos) => {
981
+ let i = wordRight(text, pos);
982
+ while (i < text.length && /\s/.test(text[i])) i++;
983
+ return i;
984
+ };
985
+ var deleteLine = (s) => {
986
+ const from = lineStart(s.value, s.cursor);
987
+ const nl = s.value.indexOf("\n", s.cursor);
988
+ if (nl === -1) {
989
+ const start = from > 0 ? from - 1 : 0;
990
+ return { value: s.value.slice(0, start), cursor: start };
991
+ }
992
+ return { value: s.value.slice(0, from) + s.value.slice(nl + 1), cursor: from };
993
+ };
994
+ function applyVim(s, mode, input, key, pending) {
995
+ if (key.escape) return { state: clamp(s), mode: "normal", handled: true };
996
+ if (mode === "insert") return { state: s, mode, handled: false };
997
+ if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.ctrl || key.meta || key.tab || key.return) {
998
+ return { state: s, mode, pending, handled: false };
999
+ }
1000
+ if (pending === "d") {
1001
+ if (input === "d") return { state: deleteLine(s), mode, handled: true };
1002
+ if (input === "w") {
1003
+ const to = wordStart(s.value, s.cursor);
1004
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(to), cursor: s.cursor }, mode, handled: true };
1005
+ }
1006
+ return { state: s, mode, handled: true };
1007
+ }
1008
+ switch (input) {
1009
+ case "h":
1010
+ return { state: { ...s, cursor: Math.max(lineStart(s.value, s.cursor), s.cursor - 1) }, mode, handled: true };
1011
+ case "l":
1012
+ return { state: { ...s, cursor: Math.min(lineEnd(s.value, s.cursor), s.cursor + 1) }, mode, handled: true };
1013
+ case "j": {
1014
+ const moved = moveLine(s.value, s.cursor, 1);
1015
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1016
+ }
1017
+ case "k": {
1018
+ const moved = moveLine(s.value, s.cursor, -1);
1019
+ return { state: moved === void 0 ? s : { ...s, cursor: moved }, mode, handled: true };
1020
+ }
1021
+ case "w":
1022
+ return { state: { ...s, cursor: wordStart(s.value, s.cursor) }, mode, handled: true };
1023
+ case "b":
1024
+ return { state: { ...s, cursor: wordLeft(s.value, s.cursor) }, mode, handled: true };
1025
+ case "0":
1026
+ return { state: { ...s, cursor: lineStart(s.value, s.cursor) }, mode, handled: true };
1027
+ case "$":
1028
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode, handled: true };
1029
+ case "x": {
1030
+ if (s.cursor >= s.value.length || s.value[s.cursor] === "\n") return { state: s, mode, handled: true };
1031
+ return { state: { value: s.value.slice(0, s.cursor) + s.value.slice(s.cursor + 1), cursor: s.cursor }, mode, handled: true };
1032
+ }
1033
+ case "i":
1034
+ return { state: s, mode: "insert", handled: true };
1035
+ case "a":
1036
+ return { state: { ...s, cursor: Math.min(s.cursor + 1, s.value.length) }, mode: "insert", handled: true };
1037
+ case "A":
1038
+ return { state: { ...s, cursor: lineEnd(s.value, s.cursor) }, mode: "insert", handled: true };
1039
+ case "o": {
1040
+ const end = lineEnd(s.value, s.cursor);
1041
+ return { state: { value: `${s.value.slice(0, end)}
1042
+ ${s.value.slice(end)}`, cursor: end + 1 }, mode: "insert", handled: true };
1043
+ }
1044
+ case "O": {
1045
+ const start = lineStart(s.value, s.cursor);
1046
+ return { state: { value: `${s.value.slice(0, start)}
1047
+ ${s.value.slice(start)}`, cursor: start }, mode: "insert", handled: true };
1048
+ }
1049
+ case "d":
1050
+ return { state: s, mode, pending: "d", handled: true };
1051
+ default:
1052
+ return { state: s, mode, handled: true };
1053
+ }
1054
+ }
1055
+
973
1056
  // src/ui/PromptInput.tsx
974
1057
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
975
- function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
1058
+ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected, vim }) {
976
1059
  const [line, setLine] = useState5({ value: "", cursor: 0 });
977
1060
  const { value } = line;
978
1061
  const [historyIndex, setHistoryIndex] = useState5(null);
979
1062
  const [selected, setSelected] = useState5(0);
1063
+ const [vimMode, setVimMode] = useState5("insert");
1064
+ const [vimPending, setVimPending] = useState5(void 0);
1065
+ const injectedSeen = useState5(0);
1066
+ useEffect(() => {
1067
+ if (injected && injected.n !== injectedSeen[0]) {
1068
+ injectedSeen[1](injected.n);
1069
+ setLine((current) => insert(current, injected.text));
1070
+ setSelected(0);
1071
+ }
1072
+ }, [injected, injectedSeen]);
980
1073
  const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
981
1074
  const suggestions = value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
982
1075
  const highlighted = Math.min(selected, Math.max(0, suggestions.length - 1));
@@ -1007,6 +1100,16 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
1007
1100
  return;
1008
1101
  }
1009
1102
  }
1103
+ if (vim) {
1104
+ const r = applyVim(line, vimMode, input, key, vimPending);
1105
+ if (r.handled) {
1106
+ setLine(r.state);
1107
+ setVimMode(r.mode);
1108
+ setVimPending(r.pending);
1109
+ setSelected(0);
1110
+ return;
1111
+ }
1112
+ }
1010
1113
  if (key.return) {
1011
1114
  const before = value.slice(0, line.cursor);
1012
1115
  if (before.endsWith("\\")) {
@@ -1018,6 +1121,8 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1018
1121
  if (!text2) return;
1019
1122
  edit(typed(""));
1020
1123
  setHistoryIndex(null);
1124
+ setVimMode("insert");
1125
+ setVimPending(void 0);
1021
1126
  onSubmit(text2);
1022
1127
  return;
1023
1128
  }
@@ -1062,7 +1167,11 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1062
1167
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1063
1168
  /* @__PURE__ */ jsx9(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
1064
1169
  /* @__PURE__ */ jsx9(Text9, { color: color("green"), children: "> " }),
1065
- /* @__PURE__ */ jsx9(EditableText, { state: line, showCursor: !disabled })
1170
+ /* @__PURE__ */ jsx9(EditableText, { state: line, showCursor: !disabled }),
1171
+ vim && vimMode === "normal" && /* @__PURE__ */ jsxs8(Text9, { color: color("green"), children: [
1172
+ " ",
1173
+ t("NORMAL")
1174
+ ] })
1066
1175
  ] }) }),
1067
1176
  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))
1068
1177
  ] });
@@ -1070,12 +1179,12 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1070
1179
 
1071
1180
  // src/ui/Spinner.tsx
1072
1181
  import { Text as Text10 } from "ink";
1073
- import { useEffect, useState as useState6 } from "react";
1182
+ import { useEffect as useEffect2, useState as useState6 } from "react";
1074
1183
  import { jsx as jsx10 } from "react/jsx-runtime";
1075
1184
  var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
1076
1185
  function Spinner({ label, startedAt }) {
1077
1186
  const [tick, setTick] = useState6(0);
1078
- useEffect(() => {
1187
+ useEffect2(() => {
1079
1188
  const id = setInterval(() => setTick((t2) => t2 + 1), 120);
1080
1189
  return () => clearInterval(id);
1081
1190
  }, []);
@@ -1158,7 +1267,7 @@ function TodoList({ items }) {
1158
1267
  import { Box as Box12, Text as Text15 } from "ink";
1159
1268
  import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1160
1269
  var PREVIEW_LINES = 5;
1161
- function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
1270
+ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub, moreBind = "ctrl+o" }) {
1162
1271
  const isTask = tool === "task";
1163
1272
  const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
1164
1273
  const lines = (output ?? "").split("\n");
@@ -1177,7 +1286,7 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
1177
1286
  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,
1178
1287
  isDiff ? /* @__PURE__ */ jsx15(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx15(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1179
1288
  shown.map((line, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
1180
- lines.length > shown.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines (ctrl+o)", { n: lines.length - shown.length }) }) : null
1289
+ lines.length > shown.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: lines.length - shown.length, bind: moreBind }) }) : null
1181
1290
  ] }) : null
1182
1291
  ] });
1183
1292
  }
@@ -1274,18 +1383,88 @@ function applyEvent(t2, event) {
1274
1383
  }
1275
1384
  }
1276
1385
 
1386
+ // src/clipboardImage.ts
1387
+ import { execFile as execFile2 } from "child_process";
1388
+ import { existsSync, statSync as statSync2 } from "fs";
1389
+ import { join as join2 } from "path";
1390
+ function clipboardTarget(tmpdir2, now) {
1391
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15);
1392
+ return join2(tmpdir2, `bccli-clipboard-${stamp}.png`);
1393
+ }
1394
+ function clipboardCommand(platform, target) {
1395
+ if (platform === "win32") {
1396
+ const escaped = target.replace(/'/g, "''");
1397
+ return {
1398
+ cmd: "powershell.exe",
1399
+ args: [
1400
+ "-NoProfile",
1401
+ "-NonInteractive",
1402
+ "-Command",
1403
+ `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' }`
1404
+ ]
1405
+ };
1406
+ }
1407
+ if (platform === "darwin") {
1408
+ return {
1409
+ cmd: "osascript",
1410
+ args: [
1411
+ "-e",
1412
+ "try",
1413
+ "-e",
1414
+ "set d to the clipboard as \xABclass PNGf\xBB",
1415
+ "-e",
1416
+ `set f to open for access POSIX file "${target}" with write permission`,
1417
+ "-e",
1418
+ "set eof f to 0",
1419
+ "-e",
1420
+ "write d to f",
1421
+ "-e",
1422
+ "close access f",
1423
+ "-e",
1424
+ '"OK"',
1425
+ "-e",
1426
+ "on error",
1427
+ "-e",
1428
+ '"EMPTY"',
1429
+ "-e",
1430
+ "end try"
1431
+ ]
1432
+ };
1433
+ }
1434
+ if (platform === "linux") {
1435
+ return { cmd: "sh", args: ["-c", `xclip -selection clipboard -t image/png -o > '${target}' 2>/dev/null && echo OK || echo EMPTY`] };
1436
+ }
1437
+ return void 0;
1438
+ }
1439
+ var defaultRun = (cmd, args) => new Promise((resolve2) => {
1440
+ execFile2(cmd, args, { timeout: 15e3, windowsHide: true }, (error, stdout) => {
1441
+ const code = error ? error.code ?? 1 : 0;
1442
+ resolve2({ code: typeof code === "number" ? code : 1, output: String(stdout ?? "") });
1443
+ });
1444
+ });
1445
+ async function grabClipboardImage(deps) {
1446
+ const target = clipboardTarget(deps.tmpdir, deps.now);
1447
+ const command = clipboardCommand(deps.platform, target);
1448
+ if (!command) return void 0;
1449
+ const run = deps.run ?? defaultRun;
1450
+ const r = await run(command.cmd, command.args);
1451
+ if (r.code === 0 && r.output.includes("OK") && existsSync(target) && statSync2(target).size > 0) return target;
1452
+ return void 0;
1453
+ }
1454
+
1277
1455
  // src/ui/App.tsx
1278
1456
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1279
1457
  function helpText(runtime) {
1280
1458
  const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
1459
+ const kb = runtime.config.keybinds;
1281
1460
  return [
1282
1461
  ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
1283
1462
  ...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
1284
1463
  ...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
1285
1464
  "",
1286
1465
  t("shift+tab switch permission mode esc cancel the turn"),
1287
- t("ctrl+o full output of the last tool \\ + enter new line"),
1288
- t("ctrl+t show/hide the model\u2019s thinking"),
1466
+ t("{bind} full output of the last tool \\ + enter new line", { bind: kb.toolOutput.spec.padEnd(10) }),
1467
+ t("{bind} show/hide the model\u2019s thinking", { bind: kb.thinking.spec.padEnd(10) }),
1289
1468
  t("@file + tab complete file names \u2191\u2193 input history")
1290
1469
  ].join("\n");
1291
1470
  }
@@ -1316,13 +1495,15 @@ function App({ runtime, initialPrompt, version }) {
1316
1495
  const [planAsk, setPlanAsk] = useState8(null);
1317
1496
  const [queued, setQueued] = useState8([]);
1318
1497
  const [showThinking, setShowThinking] = useState8(false);
1319
- useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
1498
+ const [injected, setInjected] = useState8();
1499
+ useEffect3(() => runtime.todos.subscribe(setTodos), [runtime]);
1320
1500
  const extraCommands = [
1321
1501
  ...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") })),
1322
1502
  ...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) }) }))
1323
1503
  ];
1324
1504
  const lastTool = useRef(null);
1325
1505
  const toolTargets = useRef(/* @__PURE__ */ new Map());
1506
+ const editedInTurn = useRef(false);
1326
1507
  const notice = useCallback((text, tone = "info") => {
1327
1508
  setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
1328
1509
  }, []);
@@ -1330,11 +1511,22 @@ function App({ runtime, initialPrompt, version }) {
1330
1511
  if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
1331
1512
  if (event.type === "toolEnd") {
1332
1513
  lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
1514
+ if ((event.tool === "edit" || event.tool === "write") && !event.isError) editedInTurn.current = true;
1333
1515
  }
1334
1516
  if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
1517
+ if (event.type === "budgetExceeded") {
1518
+ notice(
1519
+ t("Budget exceeded ({kind}): {used} of {limit}. Further model calls are blocked; raise usageCap in ~/.bccli/config.json.", {
1520
+ kind: event.kind,
1521
+ used: Math.round(event.used * 100) / 100,
1522
+ limit: event.limit
1523
+ }),
1524
+ "warn"
1525
+ );
1526
+ }
1335
1527
  setTranscript((t2) => applyEvent(t2, event));
1336
- }, []);
1337
- useEffect2(() => {
1528
+ }, [notice]);
1529
+ useEffect3(() => {
1338
1530
  runtime.agent.onEvent = onEvent;
1339
1531
  runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
1340
1532
  runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
@@ -1350,8 +1542,26 @@ function App({ runtime, initialPrompt, version }) {
1350
1542
  setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
1351
1543
  setBusy(true);
1352
1544
  setStartedAt(Date.now());
1545
+ editedInTurn.current = false;
1353
1546
  controller.current = new AbortController();
1354
- await runtime.agent.run(text, controller.current.signal);
1547
+ const signal = controller.current.signal;
1548
+ await runtime.agent.run(text, signal);
1549
+ const commands = runtime.config.verifyCommands;
1550
+ if (editedInTurn.current && commands.length && !signal.aborted) {
1551
+ for (let round = 0; round < MAX_VERIFY_ROUNDS; round++) {
1552
+ notice(t("Verifying edits: {cmds}", { cmds: commands.join(", ") }));
1553
+ const failures = await runVerify(commands, { cwd: runtime.cwd, env: runtime.env, signal });
1554
+ if (!failures.length) {
1555
+ notice(t("Verification passed."));
1556
+ break;
1557
+ }
1558
+ if (round === MAX_VERIFY_ROUNDS - 1 || signal.aborted) {
1559
+ notice(t("Verification still failing after {n} fix round(s). Run the commands manually to see why.", { n: MAX_VERIFY_ROUNDS }), "warn");
1560
+ break;
1561
+ }
1562
+ await runtime.agent.run(verifyFollowup(failures), signal);
1563
+ }
1564
+ }
1355
1565
  controller.current = null;
1356
1566
  runHooks(runtime.config.hooks, "Stop", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
1357
1567
  for (const w of outcome.warnings) notice(w, "warn");
@@ -1555,7 +1765,7 @@ function App({ runtime, initialPrompt, version }) {
1555
1765
  activeProvider: runtime.providerLabel()
1556
1766
  },
1557
1767
  {
1558
- which: (cmd) => new Promise((done) => execFile2(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
1768
+ which: (cmd) => new Promise((done) => execFile3(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
1559
1769
  nodeVersion: process.version,
1560
1770
  listModels: () => runtime.agent.provider.listModels()
1561
1771
  }
@@ -1596,7 +1806,7 @@ function App({ runtime, initialPrompt, version }) {
1596
1806
  notice(t("Write the text: /memory global <text>."), "warn");
1597
1807
  return;
1598
1808
  }
1599
- const file = global ? join2(runtime.home, "BCCLI.md") : join2(runtime.cwd, "AGENTS.md");
1809
+ const file = global ? join3(runtime.home, "BCCLI.md") : join3(runtime.cwd, "AGENTS.md");
1600
1810
  try {
1601
1811
  mkdirSync(dirname(file), { recursive: true });
1602
1812
  appendMemory(file, text);
@@ -1701,6 +1911,23 @@ function App({ runtime, initialPrompt, version }) {
1701
1911
  );
1702
1912
  return;
1703
1913
  }
1914
+ case "redo": {
1915
+ const result = await runtime.checkpoints.redo();
1916
+ if (!result) {
1917
+ notice(t("No file edits to redo."));
1918
+ return;
1919
+ }
1920
+ const rel = (p) => relative(runtime.cwd, p) || p;
1921
+ notice(
1922
+ [
1923
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1924
+ result.restored.length ? t("Re-applied: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1925
+ result.deleted.length ? t("Deleted again: {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1926
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : ""
1927
+ ].filter(Boolean).join("\n")
1928
+ );
1929
+ return;
1930
+ }
1704
1931
  case "rewind": {
1705
1932
  const parsed = Number.parseInt(args.trim(), 10);
1706
1933
  const result = await runtime.rewindTurns(Number.isFinite(parsed) && parsed > 0 ? parsed : 1);
@@ -1727,7 +1954,7 @@ function App({ runtime, initialPrompt, version }) {
1727
1954
  return;
1728
1955
  }
1729
1956
  process.stdout.write(osc52(last.content, !!process.env.TMUX));
1730
- const file = join2(runtime.home, "last-answer.md");
1957
+ const file = join3(runtime.home, "last-answer.md");
1731
1958
  mkdirSync(runtime.home, { recursive: true });
1732
1959
  writeFileSync(file, last.content);
1733
1960
  notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
@@ -1736,7 +1963,7 @@ function App({ runtime, initialPrompt, version }) {
1736
1963
  case "export": {
1737
1964
  const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
1738
1965
  const file = resolve(runtime.cwd, name2);
1739
- if (existsSync(file)) {
1966
+ if (existsSync2(file)) {
1740
1967
  const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
1741
1968
  if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
1742
1969
  notice(t("Export cancelled."));
@@ -1803,14 +2030,14 @@ ARGUMENTS: ${slash.args}`);
1803
2030
  },
1804
2031
  [busy, submit]
1805
2032
  );
1806
- useEffect2(() => {
2033
+ useEffect3(() => {
1807
2034
  if (busy || pending || planAsk || !queued.length) return;
1808
2035
  const [next, ...rest] = queued;
1809
2036
  setQueued(rest);
1810
2037
  submit(next);
1811
2038
  }, [busy, pending, planAsk, queued, submit]);
1812
2039
  const initialSent = useRef(false);
1813
- useEffect2(() => {
2040
+ useEffect3(() => {
1814
2041
  if (initialPrompt && !initialSent.current) {
1815
2042
  initialSent.current = true;
1816
2043
  submit(initialPrompt);
@@ -1828,9 +2055,20 @@ ARGUMENTS: ${slash.args}`);
1828
2055
  setQueued([]);
1829
2056
  notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
1830
2057
  }
1831
- } else if (key.ctrl && input === "t") {
2058
+ } else if (matchKeybind(runtime.config.keybinds.pasteImage, input, key)) {
2059
+ void (async () => {
2060
+ const path = await grabClipboardImage({ platform: process.platform, tmpdir: tmpdir(), now: /* @__PURE__ */ new Date() });
2061
+ if (path) {
2062
+ setInjected((prev) => ({ text: `${path} `, n: (prev?.n ?? 0) + 1 }));
2063
+ notice(t("Clipboard image saved to {path} \u2014 the path is inserted in the input; the model sees it when you send.", { path }));
2064
+ } else {
2065
+ notice(t("No image on the clipboard."), "warn");
2066
+ }
2067
+ })();
2068
+ } else if (matchKeybind(runtime.config.keybinds.thinking, input, key)) {
2069
+ const bind = runtime.config.keybinds.thinking.spec;
1832
2070
  const next = !showThinking;
1833
- const toggled = next ? t("Thinking shown (ctrl+t to hide).") : t("Thinking hidden (ctrl+t to show).");
2071
+ const toggled = next ? t("Thinking shown ({bind} to hide).", { bind }) : t("Thinking hidden ({bind} to show).", { bind });
1834
2072
  setShowThinking(next);
1835
2073
  setTranscript((t2) => {
1836
2074
  const last = [...t2.done, ...t2.live].findLast((e) => e.kind === "thinking");
@@ -1844,7 +2082,7 @@ ARGUMENTS: ${slash.args}`);
1844
2082
  ]
1845
2083
  };
1846
2084
  });
1847
- } else if (key.ctrl && input === "o" && lastTool.current) {
2085
+ } else if (matchKeybind(runtime.config.keybinds.toolOutput, input, key) && lastTool.current) {
1848
2086
  const t2 = lastTool.current;
1849
2087
  notice(`\u23BF ${t2.tool} ${t2.target}
1850
2088
  ${t2.output}`);
@@ -1932,15 +2170,15 @@ ${t2.output}`);
1932
2170
  if (!text) return null;
1933
2171
  if (!(e.open ?? showThinking)) {
1934
2172
  const lines = text.split("\n").length;
1935
- return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("\u273B Thinking \xB7 {n} lines \xB7 ctrl+t to show", { n: lines }) }) }, e.id);
2173
+ return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("\u273B Thinking \xB7 {n} lines \xB7 {bind} to show", { n: lines, bind: runtime.config.keybinds.thinking.spec }) }) }, e.id);
1936
2174
  }
1937
2175
  return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1938
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("\u273B Thinking (ctrl+t to hide)") }),
2176
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("\u273B Thinking ({bind} to hide)", { bind: runtime.config.keybinds.thinking.spec }) }),
1939
2177
  /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, italic: true, children: text }) })
1940
2178
  ] }, e.id);
1941
2179
  }
1942
2180
  case "tool":
1943
- return /* @__PURE__ */ jsx16(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
2181
+ return /* @__PURE__ */ jsx16(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub, moreBind: runtime.config.keybinds.toolOutput.spec }, e.id);
1944
2182
  case "notice":
1945
2183
  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);
1946
2184
  }
@@ -1992,7 +2230,7 @@ ${t2.output}`);
1992
2230
  ) : null,
1993
2231
  /* @__PURE__ */ jsx16(TodoList, { items: todos }),
1994
2232
  queued.map((q, i) => /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
1995
- /* @__PURE__ */ jsx16(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
2233
+ /* @__PURE__ */ jsx16(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands, injected, vim: runtime.config.editor === "vim" }),
1996
2234
  /* @__PURE__ */ jsx16(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
1997
2235
  ] });
1998
2236
  }
@@ -2001,7 +2239,7 @@ ${t2.output}`);
2001
2239
  import { jsx as jsx17 } from "react/jsx-runtime";
2002
2240
  async function startInteractive(rt, opts) {
2003
2241
  if (opts.resume) {
2004
- const { Session: Session2 } = await import("./session-IYVTCUBC.js");
2242
+ const { Session: Session2 } = await import("./session-H2YL5RSB.js");
2005
2243
  const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
2006
2244
  if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
2007
2245
  else {
@@ -2016,7 +2254,7 @@ async function startInteractive(rt, opts) {
2016
2254
  if (chosen) rt.resume(chosen.session);
2017
2255
  }
2018
2256
  }
2019
- const { serversToStart, setProjectTrust } = await import("./config-JRO5EOKZ.js");
2257
+ const { serversToStart, setProjectTrust } = await import("./config-C4KLLDC6.js");
2020
2258
  const plan = serversToStart(rt.home, rt.cwd);
2021
2259
  if (plan.needTrust.length) {
2022
2260
  const { createInterface } = await import("readline/promises");