@botconnector/bccli 0.4.14 → 0.4.15

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var VERSION = true ? "0.4.14" : "0.0.0-dev";
4
+ var VERSION = true ? "0.4.15" : "0.0.0-dev";
5
5
 
6
6
  export {
7
7
  VERSION
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import "./chunk-7GM6NN5E.js";
13
13
  import "./chunk-PSO7R5AD.js";
14
14
  import {
15
15
  VERSION
16
- } from "./chunk-RRE6WRU5.js";
16
+ } from "./chunk-2PAVSETH.js";
17
17
  import {
18
18
  helpText,
19
19
  parseCliArgs
@@ -109,7 +109,7 @@ async function main() {
109
109
  return runModelsCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), fetch: globalThis.fetch });
110
110
  }
111
111
  if (args.command === "update") {
112
- const { runUpdateCommand } = await import("./update-CRFQXBNM.js");
112
+ const { runUpdateCommand } = await import("./update-U4FXFWNV.js");
113
113
  return runUpdateCommand({ env: process.env });
114
114
  }
115
115
  if (args.command === "acp") {
@@ -118,7 +118,7 @@ async function main() {
118
118
  return 0;
119
119
  }
120
120
  if (args.command === "serve") {
121
- const { serveBanner, startServe } = await import("./serve-KBXP23UW.js");
121
+ const { serveBanner, startServe } = await import("./serve-RKV2JCU7.js");
122
122
  const handle = await startServe({ port: args.port, host: args.host, token: args.token, cwd, env: process.env });
123
123
  process.stderr.write(serveBanner(handle, { token: args.token }));
124
124
  await new Promise((resolve) => {
@@ -215,13 +215,13 @@ async function main() {
215
215
  let updateMessage;
216
216
  if (rt.config.updateCheck !== "off" && !process.env.BCCLI_NO_UPDATE_CHECK) {
217
217
  try {
218
- const { checkForUpdate } = await import("./update-CRFQXBNM.js");
218
+ const { checkForUpdate } = await import("./update-U4FXFWNV.js");
219
219
  const update = await checkForUpdate({ home: rt.home, fetch: globalThis.fetch, now: /* @__PURE__ */ new Date() });
220
220
  updateMessage = update;
221
221
  } catch {
222
222
  }
223
223
  }
224
- const { startInteractive } = await import("./ui-K5RLE2R6.js");
224
+ const { startInteractive } = await import("./ui-ICUWKBUG.js");
225
225
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION, updateMessage });
226
226
  }
227
227
  main().then(
@@ -8,7 +8,7 @@ import "./chunk-7GM6NN5E.js";
8
8
  import "./chunk-PSO7R5AD.js";
9
9
  import {
10
10
  VERSION
11
- } from "./chunk-RRE6WRU5.js";
11
+ } from "./chunk-2PAVSETH.js";
12
12
  import {
13
13
  LOOPBACK_BIND,
14
14
  MAX_FRAME_BYTES,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Live assistant text is drawn inside the Ink frame, so on a real terminal it is capped to the rows left after the
3
+ * prompt and status bar; the hidden part is announced instead of silently dropped. Piped stdout has no rows to
4
+ * respect (`bccli | tee log`, CI harnesses), so the text is kept whole there.
5
+ */
6
+ export declare function capLiveMarkdown(raw: string, options: {
7
+ isRealTty: boolean;
8
+ maxLines: number;
9
+ }): string;
@@ -15,8 +15,13 @@ export declare function clickedThinking(click: MousePress, cursorRow: number, ro
15
15
  width: number;
16
16
  height: number;
17
17
  }): boolean;
18
- /** Detect raw SGR/Urxvt wheel packets in a raw stdin data chunk (before ESC is stripped). */
18
+ /** Detect raw SGR wheel packets in a raw stdin data chunk (before ESC is stripped), used to suspend mouse capture. */
19
19
  export declare function hasMouseWheelSequence(chunk: string): boolean;
20
+ /**
21
+ * Sequence fragments Ink 8 does not recognise (mouse, cursor reports) are dropped when complete, but an incomplete
22
+ * escape is flushed as literal input after ~20 ms. That fragment must never be typed into a prompt or filter.
23
+ */
24
+ export declare function isControlReportFragment(input: string): boolean;
20
25
  /** Tokenize split/coalesced Windows ConPTY SGR presses and cursor reports. */
21
26
  export declare function parseThinkingReports(value: string): {
22
27
  events: Array<{
package/dist/ui/tty.d.ts CHANGED
@@ -3,7 +3,9 @@ export declare function isTtyStream(stream: unknown): boolean;
3
3
  /** True only for a real terminal: a TTY that also exposes a file descriptor. */
4
4
  export declare function isRealTty(stream: unknown): boolean;
5
5
  /**
6
- * Terminal width at render time. Reading it instead of listening to `resize` keeps one listener per app
7
- * (App's `useWindowSize`) rather than one per rendered message; App re-renders on resize and passes the new width down.
6
+ * Terminal width at render time. Reading it instead of listening to `resize` keeps the app to the single resize
7
+ * listener Ink already owns (it re-renders the tree on resize), instead of one listener per rendered message.
8
8
  */
9
9
  export declare function streamColumns(stream: unknown): number;
10
+ /** Terminal height at render time; same reasoning as `streamColumns`. */
11
+ export declare function streamRows(stream: unknown): number;
@@ -76,7 +76,7 @@ import { execFile as execFile3 } from "child_process";
76
76
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
77
77
  import { homedir, tmpdir } from "os";
78
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, useWindowSize } from "ink";
79
+ import { Box as Box13, Static, Text as Text16, measureElement, useApp, useInput as useInput9, useStdin, useStdout as useStdout2 } from "ink";
80
80
  import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState10 } from "react";
81
81
 
82
82
  // src/commands.ts
@@ -558,6 +558,10 @@ function streamColumns(stream) {
558
558
  const columns = typeof stream === "object" && stream !== null ? stream.columns : void 0;
559
559
  return typeof columns === "number" && columns > 0 ? columns : 80;
560
560
  }
561
+ function streamRows(stream) {
562
+ const rows = typeof stream === "object" && stream !== null ? stream.rows : void 0;
563
+ return typeof rows === "number" && rows > 0 ? rows : 24;
564
+ }
561
565
 
562
566
  // src/ui/Markdown.tsx
563
567
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -823,11 +827,69 @@ function EditableText({ state, mask = false, showCursor = true }) {
823
827
  ] });
824
828
  }
825
829
 
830
+ // src/ui/thinkingMouse.ts
831
+ function parseMousePress(value) {
832
+ const start = value.indexOf("\x1B[<");
833
+ if (start < 0) return void 0;
834
+ const match = /^<(\d+);(\d+);(\d+)M/.exec(value.slice(start + 2));
835
+ if (!match) return void 0;
836
+ const button = Number(match[1]);
837
+ if (button !== 0) return void 0;
838
+ const x = Number(match[2]);
839
+ const y = Number(match[3]);
840
+ if (x < 1 || y < 1) return void 0;
841
+ return { x, y };
842
+ }
843
+ function parseCursorPosition(value) {
844
+ const start = value.indexOf("\x1B[");
845
+ if (start < 0) return void 0;
846
+ const match = /^(\d+);(\d+)R/.exec(value.slice(start + 2));
847
+ if (!match) return void 0;
848
+ const row = Number(match[1]);
849
+ const col = Number(match[2]);
850
+ if (row < 1 || col < 1) return void 0;
851
+ return { row, col };
852
+ }
853
+ function clickedThinking(click, cursorRow, rootHeight, target) {
854
+ if (!rootHeight || !target.width || !target.height) return false;
855
+ const origin = cursorRow - rootHeight + 1;
856
+ const row = origin + target.y;
857
+ const col = target.x + 1;
858
+ return (click.y === row || click.y === row - 1) && click.x >= col && click.x < col + Math.min(target.width, 72);
859
+ }
860
+ function hasMouseWheelSequence(chunk) {
861
+ return chunk.includes("\x1B[") && /\[<?6[4567];\d+;\d+[mM]/.test(chunk);
862
+ }
863
+ function isControlReportFragment(input) {
864
+ return /^\[<[\d;]*[mM]?$/.test(input) || /^\[(?:\d+;){2}\d+[mM]$/.test(input) || /^\[(?:\d+;)+\d*R?$/.test(input) || /^\[M[\s\S]{0,3}$/.test(input);
865
+ }
866
+ function parseThinkingReports(value) {
867
+ const events = [];
868
+ let offset = 0;
869
+ while (true) {
870
+ const start = value.indexOf("\x1B[", offset);
871
+ if (start < 0) break;
872
+ const finish = /[a-zA-Z~]/.exec(value.slice(start + 2));
873
+ if (!finish) return { events, rest: value.slice(start) };
874
+ const end = start + 2 + finish.index + 1;
875
+ const raw = value.slice(start, end);
876
+ const press = parseMousePress(raw);
877
+ const cursor = parseCursorPosition(raw);
878
+ if (press) events.push({ type: "mouse", press });
879
+ else if (cursor) events.push({ type: "cursor", cursor });
880
+ offset = end;
881
+ }
882
+ const remaining = value.lastIndexOf("\x1B[");
883
+ if (remaining >= offset) return { events, rest: value.slice(remaining) };
884
+ return { events, rest: value.endsWith("\x1B") ? "\x1B" : "" };
885
+ }
886
+
826
887
  // src/ui/LinePrompt.tsx
827
888
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
828
889
  function LinePrompt({ label, mask, onSubmit, onCancel }) {
829
890
  const [line, setLine] = useState({ value: "", cursor: 0 });
830
891
  useInput((input, key) => {
892
+ if (isControlReportFragment(input)) return;
831
893
  if (key.return) onSubmit(line.value.trim());
832
894
  else if (key.escape) onCancel();
833
895
  else if (applyEditKey(line, input, key)) setLine((current) => applyEditKey(current, input, key) ?? current);
@@ -854,6 +916,7 @@ function ListPicker({ title, items, onPick }) {
854
916
  const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
855
917
  const cursor = Math.min(index, Math.max(0, shown.length - 1));
856
918
  useInput2((input, key) => {
919
+ if (isControlReportFragment(input)) return;
857
920
  if (key.upArrow) setIndex(Math.max(0, cursor - 1));
858
921
  else if (key.downArrow) setIndex(Math.min(Math.max(0, shown.length - 1), cursor + 1));
859
922
  else if (key.pageUp) setIndex(Math.max(0, cursor - VISIBLE));
@@ -900,6 +963,7 @@ function ModelPicker({ groups, current, onPick }) {
900
963
  const clampedIndex = Math.min(index, Math.max(0, selectable.length - 1));
901
964
  const cursor = selectable[clampedIndex] ?? -1;
902
965
  useInput3((input, key) => {
966
+ if (isControlReportFragment(input)) return;
903
967
  if (key.upArrow) setIndex(Math.max(0, clampedIndex - 1));
904
968
  else if (key.downArrow) setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + 1));
905
969
  else if (key.pageUp) setIndex(Math.max(0, clampedIndex - VISIBLE2));
@@ -1232,6 +1296,7 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected
1232
1296
  };
1233
1297
  useInput6(
1234
1298
  (input, key) => {
1299
+ if (isControlReportFragment(input)) return;
1235
1300
  if (suggestions.length) {
1236
1301
  if (key.escape) {
1237
1302
  setDismissed(true);
@@ -1379,60 +1444,6 @@ function Spinner({ label, startedAt }) {
1379
1444
  return /* @__PURE__ */ jsx10(Text10, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
1380
1445
  }
1381
1446
 
1382
- // src/ui/thinkingMouse.ts
1383
- function parseMousePress(value) {
1384
- const start = value.indexOf("\x1B[<");
1385
- if (start < 0) return void 0;
1386
- const match = /^<(\d+);(\d+);(\d+)M/.exec(value.slice(start + 2));
1387
- if (!match) return void 0;
1388
- const button = Number(match[1]);
1389
- if (button !== 0) return void 0;
1390
- const x = Number(match[2]);
1391
- const y = Number(match[3]);
1392
- if (x < 1 || y < 1) return void 0;
1393
- return { x, y };
1394
- }
1395
- function parseCursorPosition(value) {
1396
- const start = value.indexOf("\x1B[");
1397
- if (start < 0) return void 0;
1398
- const match = /^(\d+);(\d+)R/.exec(value.slice(start + 2));
1399
- if (!match) return void 0;
1400
- const row = Number(match[1]);
1401
- const col = Number(match[2]);
1402
- if (row < 1 || col < 1) return void 0;
1403
- return { row, col };
1404
- }
1405
- function clickedThinking(click, cursorRow, rootHeight, target) {
1406
- if (!rootHeight || !target.width || !target.height) return false;
1407
- const origin = cursorRow - rootHeight + 1;
1408
- const row = origin + target.y;
1409
- const col = target.x + 1;
1410
- return (click.y === row || click.y === row - 1) && click.x >= col && click.x < col + Math.min(target.width, 72);
1411
- }
1412
- function hasMouseWheelSequence(chunk) {
1413
- return chunk.includes("\x1B[") && /\[<?6[4567];\d+;\d+[mM]/.test(chunk);
1414
- }
1415
- function parseThinkingReports(value) {
1416
- const events = [];
1417
- let offset = 0;
1418
- while (true) {
1419
- const start = value.indexOf("\x1B[", offset);
1420
- if (start < 0) break;
1421
- const finish = /[a-zA-Z~]/.exec(value.slice(start + 2));
1422
- if (!finish) return { events, rest: value.slice(start) };
1423
- const end = start + 2 + finish.index + 1;
1424
- const raw = value.slice(start, end);
1425
- const press = parseMousePress(raw);
1426
- const cursor = parseCursorPosition(raw);
1427
- if (press) events.push({ type: "mouse", press });
1428
- else if (cursor) events.push({ type: "cursor", cursor });
1429
- offset = end;
1430
- }
1431
- const remaining = value.lastIndexOf("\x1B[");
1432
- if (remaining >= offset) return { events, rest: value.slice(remaining) };
1433
- return { events, rest: value.endsWith("\x1B") ? "\x1B" : "" };
1434
- }
1435
-
1436
1447
  // src/ui/StatusBar.tsx
1437
1448
  import { Text as Text11 } from "ink";
1438
1449
  import { jsx as jsx11 } from "react/jsx-runtime";
@@ -1450,6 +1461,20 @@ function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
1450
1461
  return /* @__PURE__ */ jsx11(Text11, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 ${t("shift+tab switch mode")} \xB7 ${t("{n} tokens", { n: formatTokens(tokens) })} \xB7 ${busy ? t("esc to cancel") : t("/ for commands")}` });
1451
1462
  }
1452
1463
 
1464
+ // src/ui/liveCap.ts
1465
+ function capLiveMarkdown(raw, options) {
1466
+ if (!options.isRealTty) return raw;
1467
+ const lines = raw.split("\n");
1468
+ if (lines.length <= options.maxLines) return raw;
1469
+ const hidden = lines.length - options.maxLines;
1470
+ const headFence = lines.slice(0, hidden).find((line) => line.trimStart().startsWith("```"));
1471
+ const prefix = headFence ? `${headFence}
1472
+ \u2026 (${hidden} lines above) \u2026
1473
+ ` : `\u2026 (${hidden} lines above) \u2026
1474
+ `;
1475
+ return `${prefix}${lines.slice(-options.maxLines).join("\n")}`;
1476
+ }
1477
+
1453
1478
  // src/ui/McpMenu.tsx
1454
1479
  import { Box as Box9, Text as Text12, useInput as useInput7 } from "ink";
1455
1480
  import { useState as useState8 } from "react";
@@ -1792,8 +1817,7 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
1792
1817
  const { stdin } = useStdin();
1793
1818
  const { stdout } = useStdout2();
1794
1819
  const realStdout = isRealTty(stdout);
1795
- const { rows: windowRows } = useWindowSize();
1796
- const maxLiveLines = Math.max(8, windowRows - 10);
1820
+ const maxLiveLines = Math.max(8, streamRows(stdout) - 10);
1797
1821
  const maxSplitIdxRef = useRef2(0);
1798
1822
  const refreshStatic = useCallback(() => {
1799
1823
  if (realStdout) {
@@ -2649,18 +2673,7 @@ ARGUMENTS: ${slash.args}`);
2649
2673
  pending?.resolve(a);
2650
2674
  setAsks((q) => q.slice(1));
2651
2675
  };
2652
- const capLiveMarkdown = (raw) => {
2653
- if (!isRealTty) return raw;
2654
- const lines = raw.split("\n");
2655
- if (lines.length <= maxLiveLines) return raw;
2656
- const hidden = lines.length - maxLiveLines;
2657
- const headFence = lines.slice(0, hidden).find((l) => l.trimStart().startsWith("```"));
2658
- const prefix = headFence ? `${headFence}
2659
- \u2026 (${hidden} lines above) \u2026
2660
- ` : `\u2026 (${hidden} lines above) \u2026
2661
- `;
2662
- return `${prefix}${lines.slice(-maxLiveLines).join("\n")}`;
2663
- };
2676
+ const capLive = (raw) => capLiveMarkdown(raw, { isRealTty: realStdout, maxLines: maxLiveLines });
2664
2677
  const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
2665
2678
  const renderEntry = (e) => {
2666
2679
  const isLive = transcript.live.some((le) => le.id === e.id);
@@ -2674,7 +2687,7 @@ ARGUMENTS: ${slash.args}`);
2674
2687
  case "user":
2675
2688
  return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `> ${e.text}` }) }, e.id);
2676
2689
  case "assistant": {
2677
- const displayText = isLive ? capLiveMarkdown(e.text) : e.text;
2690
+ const displayText = isLive ? capLive(e.text) : e.text;
2678
2691
  if (e.cont) {
2679
2692
  return displayText ? /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Markdown, { text: displayText }) }, e.id) : null;
2680
2693
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  VERSION
4
- } from "./chunk-RRE6WRU5.js";
4
+ } from "./chunk-2PAVSETH.js";
5
5
  import {
6
6
  t
7
7
  } from "./chunk-R754XIBJ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {