beecork 2.4.1 → 2.5.0

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 (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +711 -280
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -114,9 +114,10 @@ var config = {
114
114
  // Sub-agent (explore tool)
115
115
  subAgentMaxSteps: num("SUBAGENT_MAX_STEPS", 15),
116
116
  // child explorer's step budget (bounds cost/latency)
117
- // Live status line (bottom row: model · effort · git branch · ~tokens · bg tasks)
118
- statuslineEnabled: !["0", "false", "off", "no"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
119
- // default on; STATUSLINE=0 disables
117
+ // Live status line (bottom row: model · effort · git branch · ~tokens · bg tasks). DEFAULT OFF —
118
+ // the interim scroll-region version clashes with the inline input (banner loss / flicker); it's
119
+ // being replaced by a proper pinned-input UI. Opt in with STATUSLINE=1.
120
+ statuslineEnabled: ["1", "true", "on", "yes"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
120
121
  statuslineRefreshMs: num("STATUSLINE_REFRESH_MS", 2e3),
121
122
  // bar refresh interval
122
123
  // Integrations / modes
@@ -195,7 +196,7 @@ function selfUpdate(timeoutMs = 12e4) {
195
196
  return new Promise((resolve2) => {
196
197
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
197
198
  const child = spawn(npm, ["install", "-g", "beecork@latest"], { stdio: ["ignore", "pipe", "pipe"] });
198
- let out2 = "", done = false;
199
+ let out3 = "", done = false;
199
200
  const finish = (r) => {
200
201
  if (done) return;
201
202
  done = true;
@@ -207,13 +208,13 @@ function selfUpdate(timeoutMs = 12e4) {
207
208
  child.kill("SIGKILL");
208
209
  } catch {
209
210
  }
210
- finish({ ok: false, output: `${out2.trim()}
211
+ finish({ ok: false, output: `${out3.trim()}
211
212
  (update timed out after ${timeoutMs}ms \u2014 run manually: npm install -g beecork@latest)`.trim() });
212
213
  }, timeoutMs);
213
- child.stdout?.on("data", (d) => out2 += d);
214
- child.stderr?.on("data", (d) => out2 += d);
214
+ child.stdout?.on("data", (d) => out3 += d);
215
+ child.stderr?.on("data", (d) => out3 += d);
215
216
  child.on("error", (e) => finish({ ok: false, output: e.message }));
216
- child.on("close", (code) => finish({ ok: code === 0, output: out2.trim() }));
217
+ child.on("close", (code) => finish({ ok: code === 0, output: out3.trim() }));
217
218
  });
218
219
  }
219
220
 
@@ -255,27 +256,65 @@ function lineDiff(oldText, newText) {
255
256
  lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
256
257
  }
257
258
  }
258
- const out2 = [];
259
+ const out3 = [];
259
260
  let i = 0;
260
261
  let j = 0;
261
262
  while (i < m && j < n) {
262
263
  if (a[i] === b[j]) {
263
- out2.push(" " + a[i]);
264
+ out3.push(" " + a[i]);
264
265
  i++;
265
266
  j++;
266
267
  } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
267
- out2.push("- " + a[i]);
268
+ out3.push("- " + a[i]);
268
269
  i++;
269
270
  } else {
270
- out2.push("+ " + b[j]);
271
+ out3.push("+ " + b[j]);
271
272
  j++;
272
273
  }
273
274
  }
274
- while (i < m) out2.push("- " + a[i++]);
275
- while (j < n) out2.push("+ " + b[j++]);
276
- return out2.join("\n");
275
+ while (i < m) out3.push("- " + a[i++]);
276
+ while (j < n) out3.push("+ " + b[j++]);
277
+ return out3.join("\n");
277
278
  }
278
279
 
280
+ // src/ansi.ts
281
+ var ESC = "\x1B";
282
+ var CSI = "\x1B[";
283
+ var ansi = {
284
+ // --- cursor visibility ---
285
+ hideCursor: CSI + "?25l",
286
+ showCursor: CSI + "?25h",
287
+ // --- save / restore cursor (position + attributes) ---
288
+ saveCursor: ESC + "7",
289
+ restoreCursor: ESC + "8",
290
+ // --- clearing ---
291
+ clearLine: CSI + "2K",
292
+ // entire current line (cursor row unchanged)
293
+ clearToEnd: CSI + "J",
294
+ // cursor → end of screen
295
+ clearScreen: CSI + "2J",
296
+ // whole viewport
297
+ clearScrollback: CSI + "3J",
298
+ // scrollback buffer (xterm extension)
299
+ cr: "\r",
300
+ // carriage return (column 1, same row)
301
+ home: CSI + "H",
302
+ // row 1, col 1
303
+ // --- motion (n omitted → terminal default of 1) ---
304
+ up: (n = 1) => CSI + n + "A",
305
+ forward: (n = 1) => CSI + n + "C",
306
+ moveTo: (row, col = 1) => CSI + row + ";" + col + "H",
307
+ // --- scroll region (DECSTBM); 1-based, inclusive. reset → full screen ---
308
+ setRegion: (top, bottom) => CSI + top + ";" + bottom + "r",
309
+ resetRegion: CSI + "r",
310
+ // --- bracketed paste mode ---
311
+ bracketedPasteOn: CSI + "?2004h",
312
+ bracketedPasteOff: CSI + "?2004l",
313
+ // --- reverse video (used to draw the pinned input's block cursor) ---
314
+ reverse: CSI + "7m",
315
+ reverseOff: CSI + "27m"
316
+ };
317
+
279
318
  // src/ui.ts
280
319
  var useColor = process.env.NO_COLOR ? false : process.env.FORCE_COLOR ? true : Boolean(process.stdout.isTTY);
281
320
  var paint = (code) => (s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
@@ -293,12 +332,12 @@ var color = {
293
332
  };
294
333
  var isPrintableCodePoint = (c) => c >= 32 && c !== 127 && !(c >= 128 && c <= 159);
295
334
  function stripControl(s) {
296
- let out2 = "";
335
+ let out3 = "";
297
336
  for (const ch of s) {
298
337
  const c = ch.codePointAt(0);
299
- if (c === 9 || c === 10 || isPrintableCodePoint(c)) out2 += ch;
338
+ if (c === 9 || c === 10 || isPrintableCodePoint(c)) out3 += ch;
300
339
  }
301
- return out2;
340
+ return out3;
302
341
  }
303
342
  var stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
304
343
  function charWidth(cp) {
@@ -417,19 +456,19 @@ function startSpinner(label) {
417
456
  if (!process.stdout.isTTY || !useColor) return () => {
418
457
  };
419
458
  let i = 0;
420
- const draw2 = () => {
459
+ const draw = () => {
421
460
  if (steeringOnScreen) return;
422
- process.stdout.write("\r " + color.brand(SPINNER[i = (i + 1) % SPINNER.length]) + " " + color.dim(label));
461
+ process.stdout.write(ansi.cr + " " + color.brand(SPINNER[i = (i + 1) % SPINNER.length]) + " " + color.dim(label));
423
462
  };
424
- process.stdout.write("\x1B[?25l");
425
- draw2();
426
- const timer = setInterval(draw2, 80);
463
+ process.stdout.write(ansi.hideCursor);
464
+ draw();
465
+ const timer = setInterval(draw, 80);
427
466
  let stopped = false;
428
467
  return () => {
429
468
  if (stopped) return;
430
469
  stopped = true;
431
470
  clearInterval(timer);
432
- process.stdout.write("\r\x1B[2K");
471
+ process.stdout.write(ansi.cr + ansi.clearLine);
433
472
  };
434
473
  }
435
474
  function markLines(width) {
@@ -464,21 +503,21 @@ function printBanner(model, sources) {
464
503
  " | |_) | __/ __/ (_| (_) | | | < ",
465
504
  " |_.__/ \\___|\\___|\\___\\___/|_| |_|\\_\\"
466
505
  ];
467
- const mark = markLines(24);
468
- const markW = Math.max(0, ...mark.map((l) => l.length));
506
+ const mark2 = markLines(24);
507
+ const markW = Math.max(0, ...mark2.map((l) => l.length));
469
508
  const wordW = Math.max(...word.map((l) => l.length));
470
- const cols = process.stdout.columns || 80;
509
+ const cols2 = process.stdout.columns || 80;
471
510
  console.log();
472
- if (cols >= markW + 3 + wordW + 2) {
473
- const h = Math.max(mark.length, word.length);
474
- const mPad = Math.floor((h - mark.length) / 2);
511
+ if (cols2 >= markW + 3 + wordW + 2) {
512
+ const h = Math.max(mark2.length, word.length);
513
+ const mPad = Math.floor((h - mark2.length) / 2);
475
514
  const wPad = Math.floor((h - word.length) / 2);
476
515
  for (let i = 0; i < h; i++) {
477
- const m = (mark[i - mPad] ?? "").padEnd(markW);
516
+ const m = (mark2[i - mPad] ?? "").padEnd(markW);
478
517
  const w = word[i - wPad] ?? "";
479
518
  console.log(" " + color.brand(`${m} ${w}`));
480
519
  }
481
- } else if (cols >= wordW + 2) {
520
+ } else if (cols2 >= wordW + 2) {
482
521
  for (const w of word) console.log(" " + color.brand(w));
483
522
  } else {
484
523
  console.log(" " + color.brand("\u{1F41D} beecork"));
@@ -499,7 +538,7 @@ function printBanner(model, sources) {
499
538
  const plain = (l, v) => l ? l.padEnd(lw) + " " + v : v;
500
539
  const bw = Math.max(...rows2.map(([l, v]) => plain(l, v).length));
501
540
  const row = ([l, v]) => l ? color.bold(l.padEnd(lw)) + color.dim(" " + v) : color.dim(v);
502
- if (cols < bw + 6) {
541
+ if (cols2 < bw + 6) {
503
542
  for (const r of rows2) console.log(" " + row(r));
504
543
  } else {
505
544
  console.log(" " + color.dim("\u256D\u2500" + "\u2500".repeat(bw) + "\u2500\u256E"));
@@ -517,6 +556,39 @@ import { readFile as readFile4 } from "node:fs/promises";
517
556
 
518
557
  // src/input.ts
519
558
  import { emitKeypressEvents } from "node:readline";
559
+
560
+ // src/layout.ts
561
+ function rowColOf(s) {
562
+ return { row: (s.match(/\n/g) || []).length, col: s.length - (s.lastIndexOf("\n") + 1) };
563
+ }
564
+ function inputLayout(text, before, promptW, cols2) {
565
+ const c = Math.max(1, cols2);
566
+ const physRows = text.split("\n").map((l) => Math.max(1, Math.ceil((promptW + displayWidth(l)) / c)));
567
+ const totalPhys = physRows.reduce((a, b) => a + b, 0);
568
+ const curLogicalRow = (before.match(/\n/g) || []).length;
569
+ const curCol = promptW + displayWidth(before.slice(before.lastIndexOf("\n") + 1));
570
+ const physBefore = physRows.slice(0, curLogicalRow).reduce((a, b) => a + b, 0);
571
+ const curPhysRow = physBefore + Math.floor(curCol / c);
572
+ const curPhysCol = curCol % c;
573
+ return { totalPhys, curPhysRow, curPhysCol };
574
+ }
575
+ function moveVertIndex(buf2, cur2, dir) {
576
+ const lines = buf2.split("\n");
577
+ const { row, col } = rowColOf(buf2.slice(0, cur2));
578
+ const target = row + dir;
579
+ if (target < 0 || target >= lines.length) return null;
580
+ let idx = 0;
581
+ for (let i = 0; i < target; i++) idx += lines[i].length + 1;
582
+ return idx + Math.min(col, lines[target].length);
583
+ }
584
+ function windowStart(text, cur2, avail) {
585
+ let start = 0;
586
+ if (displayWidth(text.slice(0, cur2)) >= avail)
587
+ while (start < cur2 && displayWidth(text.slice(start, cur2)) >= avail) start++;
588
+ return start;
589
+ }
590
+
591
+ // src/input.ts
520
592
  var out = (s) => process.stdout.write(s);
521
593
  var active = null;
522
594
  var started = false;
@@ -524,13 +596,13 @@ function initInput() {
524
596
  if (started || !process.stdin.isTTY) return;
525
597
  started = true;
526
598
  process.stdin.setRawMode(true);
527
- out("\x1B[?2004l");
599
+ out(ansi.bracketedPasteOff);
528
600
  emitKeypressEvents(process.stdin);
529
601
  process.stdin.on("keypress", (str, key) => active?.(str, key));
530
602
  }
531
603
  function teardownInput() {
532
604
  if (started && process.stdin.isTTY) {
533
- out("\x1B[?2004h\x1B[?25h");
605
+ out(ansi.bracketedPasteOn + ansi.showCursor);
534
606
  process.stdin.setRawMode(false);
535
607
  process.stdin.pause();
536
608
  }
@@ -552,27 +624,27 @@ function readPrompt(opts) {
552
624
  ...(opts.skills ?? []).map((n) => ({ name: "/" + n, desc: "skill" }))
553
625
  ];
554
626
  return new Promise((resolve2) => {
555
- let buf = "";
556
- let cur = 0;
627
+ let buf2 = "";
628
+ let cur2 = 0;
557
629
  let hist = history.length;
558
- let sel = 0;
559
- let menuHidden = false;
560
- const BURST_IDLE_MS = 8;
561
- let burstLen = 0;
562
- let burstTimer = null;
563
- let pendingRender = false;
630
+ let sel2 = 0;
631
+ let menuHidden2 = false;
632
+ const BURST_IDLE_MS2 = 8;
633
+ let burstLen2 = 0;
634
+ let burstTimer2 = null;
635
+ let pendingRender2 = false;
564
636
  const matches = () => {
565
637
  if (opts.mask) return [];
566
- const m = buf.match(/^\/(\S*)$/);
638
+ const m = buf2.match(/^\/(\S*)$/);
567
639
  if (!m) return [];
568
640
  const pre = "/" + m[1];
569
641
  return all.filter((c) => c.name.startsWith(pre)).slice(0, MENU_MAX);
570
642
  };
571
- const menu = () => menuHidden ? [] : matches();
643
+ const menu = () => menuHidden2 ? [] : matches();
572
644
  const highlight = () => {
573
- if (opts.mask) return "*".repeat(buf.length);
574
- const m = buf.match(/^(\/\S*)([\s\S]*)$/);
575
- if (!m) return buf;
645
+ if (opts.mask) return "*".repeat(buf2.length);
646
+ const m = buf2.match(/^(\/\S*)([\s\S]*)$/);
647
+ if (!m) return buf2;
576
648
  const [, token, rest] = m;
577
649
  const known = all.some((c) => c.name === token);
578
650
  const partial = all.some((c) => c.name.startsWith(token));
@@ -580,7 +652,6 @@ function readPrompt(opts) {
580
652
  return styled + rest;
581
653
  };
582
654
  let lastCurRow = 0;
583
- const rowColOf = (s) => ({ row: (s.match(/\n/g) || []).length, col: s.length - (s.lastIndexOf("\n") + 1) });
584
655
  function drawBlock() {
585
656
  const prompt = opts.promptString();
586
657
  const promptW = stripAnsi(prompt).length;
@@ -590,199 +661,189 @@ function readPrompt(opts) {
590
661
  for (let i = 1; i < lines.length; i++) out("\n" + indent + lines[i]);
591
662
  return { promptW };
592
663
  }
593
- function render() {
664
+ function render2() {
594
665
  const mm = menu();
595
- if (sel >= mm.length) sel = Math.max(0, mm.length - 1);
596
- out("\x1B[?25l");
597
- if (lastCurRow > 0) out(`\x1B[${lastCurRow}A`);
598
- out("\r\x1B[J");
666
+ if (sel2 >= mm.length) sel2 = Math.max(0, mm.length - 1);
667
+ out(ansi.hideCursor);
668
+ if (lastCurRow > 0) out(ansi.up(lastCurRow));
669
+ out(ansi.cr + ansi.clearToEnd);
599
670
  const { promptW } = drawBlock();
600
- const cols = Math.max(1, process.stdout.columns || 80);
671
+ const cols2 = Math.max(1, process.stdout.columns || 80);
601
672
  for (let i = 0; i < mm.length; i++) {
602
673
  const m = mm[i];
603
674
  const name = m.name.padEnd(10);
604
- const maxDesc = Math.max(0, cols - name.length - 4);
675
+ const maxDesc = Math.max(0, cols2 - name.length - 4);
605
676
  const desc = m.desc.length > maxDesc ? m.desc.slice(0, Math.max(0, maxDesc - 1)) + "\u2026" : m.desc;
606
- out("\n" + (i === sel ? color.green("\u203A " + name) + " " + color.dim(desc) : color.dim(" " + name + " " + desc)));
607
- }
608
- const text = opts.mask ? "*".repeat(buf.length) : buf;
609
- const physRows = text.split("\n").map((l) => Math.max(1, Math.ceil((promptW + displayWidth(l)) / cols)));
610
- const totalInputPhys = physRows.reduce((a, b) => a + b, 0);
611
- const before = opts.mask ? text : buf.slice(0, cur);
612
- const curLogicalRow = (before.match(/\n/g) || []).length;
613
- const curCol = promptW + displayWidth(before.slice(before.lastIndexOf("\n") + 1));
614
- const physBefore = physRows.slice(0, curLogicalRow).reduce((a, b) => a + b, 0);
615
- const curPhysRow = physBefore + Math.floor(curCol / cols);
616
- const curPhysCol = curCol % cols;
617
- const lastDrawnRow = totalInputPhys - 1 + mm.length;
618
- if (lastDrawnRow > curPhysRow) out(`\x1B[${lastDrawnRow - curPhysRow}A`);
619
- out("\r" + (curPhysCol > 0 ? `\x1B[${curPhysCol}C` : "") + "\x1B[?25h");
677
+ out("\n" + (i === sel2 ? color.green("\u203A " + name) + " " + color.dim(desc) : color.dim(" " + name + " " + desc)));
678
+ }
679
+ const text = opts.mask ? "*".repeat(buf2.length) : buf2;
680
+ const before = opts.mask ? text : buf2.slice(0, cur2);
681
+ const { totalPhys, curPhysRow, curPhysCol } = inputLayout(text, before, promptW, cols2);
682
+ const lastDrawnRow = totalPhys - 1 + mm.length;
683
+ if (lastDrawnRow > curPhysRow) out(ansi.up(lastDrawnRow - curPhysRow));
684
+ out(ansi.cr + (curPhysCol > 0 ? ansi.forward(curPhysCol) : "") + ansi.showCursor);
620
685
  lastCurRow = curPhysRow;
621
686
  }
622
687
  function finish(result) {
623
688
  restore();
624
- if (burstTimer) {
625
- clearTimeout(burstTimer);
626
- burstTimer = null;
627
- }
628
- pendingRender = false;
629
- out("\x1B[?25l");
630
- if (lastCurRow > 0) out(`\x1B[${lastCurRow}A`);
631
- out("\r\x1B[J");
689
+ if (burstTimer2) {
690
+ clearTimeout(burstTimer2);
691
+ burstTimer2 = null;
692
+ }
693
+ pendingRender2 = false;
694
+ out(ansi.hideCursor);
695
+ if (lastCurRow > 0) out(ansi.up(lastCurRow));
696
+ out(ansi.cr + ansi.clearToEnd);
632
697
  drawBlock();
633
698
  out("\n");
634
699
  if (result.type === "line" && result.value.trim() && !opts.mask) history.push(result.value);
635
700
  resolve2(result);
636
701
  }
637
- function insert(s) {
638
- buf = buf.slice(0, cur) + s + buf.slice(cur);
639
- cur += s.length;
640
- menuHidden = false;
641
- sel = 0;
642
- pendingRender = true;
702
+ function insert2(s) {
703
+ buf2 = buf2.slice(0, cur2) + s + buf2.slice(cur2);
704
+ cur2 += s.length;
705
+ menuHidden2 = false;
706
+ sel2 = 0;
707
+ pendingRender2 = true;
643
708
  }
644
- function onKey(str, key) {
709
+ function onKey2(str, key) {
645
710
  const mm = menu();
646
- burstLen++;
647
- if (burstTimer) clearTimeout(burstTimer);
648
- burstTimer = setTimeout(() => {
649
- burstTimer = null;
650
- burstLen = 0;
651
- if (pendingRender) {
652
- pendingRender = false;
653
- render();
711
+ burstLen2++;
712
+ if (burstTimer2) clearTimeout(burstTimer2);
713
+ burstTimer2 = setTimeout(() => {
714
+ burstTimer2 = null;
715
+ burstLen2 = 0;
716
+ if (pendingRender2) {
717
+ pendingRender2 = false;
718
+ render2();
654
719
  }
655
- }, BURST_IDLE_MS);
720
+ }, BURST_IDLE_MS2);
656
721
  if (isEnter(key)) {
657
- if (key?.shift || key?.meta || burstLen > 1) {
658
- insert("\n");
722
+ if (key?.shift || key?.meta || burstLen2 > 1) {
723
+ insert2("\n");
659
724
  return;
660
725
  }
661
- return finish({ type: "line", value: buf });
726
+ return finish({ type: "line", value: buf2 });
662
727
  }
663
728
  if (key?.ctrl && key.name === "c") {
664
- if (buf) {
665
- buf = "";
666
- cur = 0;
667
- render();
729
+ if (buf2) {
730
+ buf2 = "";
731
+ cur2 = 0;
732
+ render2();
668
733
  } else finish({ type: "quit" });
669
734
  return;
670
735
  }
671
736
  if (key?.ctrl && key.name === "d") {
672
- if (!buf) finish({ type: "eof" });
737
+ if (!buf2) finish({ type: "eof" });
673
738
  return;
674
739
  }
675
740
  if (key?.name === "tab" && key.shift) {
676
741
  opts.onShiftTab?.();
677
- render();
742
+ render2();
678
743
  return;
679
744
  }
680
745
  if (key?.name === "tab") {
681
746
  if (mm.length) {
682
- buf = mm[sel].name + " ";
683
- cur = buf.length;
684
- menuHidden = false;
685
- render();
747
+ buf2 = mm[sel2].name + " ";
748
+ cur2 = buf2.length;
749
+ menuHidden2 = false;
750
+ render2();
686
751
  }
687
752
  return;
688
753
  }
689
754
  if (key?.name === "up") {
690
755
  if (mm.length) {
691
- sel = (sel - 1 + mm.length) % mm.length;
692
- render();
693
- } else if (buf.includes("\n")) moveVert(-1);
756
+ sel2 = (sel2 - 1 + mm.length) % mm.length;
757
+ render2();
758
+ } else if (buf2.includes("\n")) moveVert(-1);
694
759
  else histPrev();
695
760
  return;
696
761
  }
697
762
  if (key?.name === "down") {
698
763
  if (mm.length) {
699
- sel = (sel + 1) % mm.length;
700
- render();
701
- } else if (buf.includes("\n")) moveVert(1);
764
+ sel2 = (sel2 + 1) % mm.length;
765
+ render2();
766
+ } else if (buf2.includes("\n")) moveVert(1);
702
767
  else histNext();
703
768
  return;
704
769
  }
705
770
  if (key?.name === "left") {
706
- if (cur > 0) cur--;
707
- render();
771
+ if (cur2 > 0) cur2--;
772
+ render2();
708
773
  return;
709
774
  }
710
775
  if (key?.name === "right") {
711
- if (cur < buf.length) cur++;
712
- render();
776
+ if (cur2 < buf2.length) cur2++;
777
+ render2();
713
778
  return;
714
779
  }
715
780
  if (key?.name === "home" || key?.ctrl && key.name === "a") {
716
- cur = 0;
717
- render();
781
+ cur2 = 0;
782
+ render2();
718
783
  return;
719
784
  }
720
785
  if (key?.name === "end" || key?.ctrl && key.name === "e") {
721
- cur = buf.length;
722
- render();
786
+ cur2 = buf2.length;
787
+ render2();
723
788
  return;
724
789
  }
725
790
  if (key?.ctrl && key.name === "u") {
726
- buf = buf.slice(cur);
727
- cur = 0;
728
- menuHidden = false;
729
- render();
791
+ buf2 = buf2.slice(cur2);
792
+ cur2 = 0;
793
+ menuHidden2 = false;
794
+ render2();
730
795
  return;
731
796
  }
732
797
  if (key?.name === "backspace") {
733
- if (cur > 0) {
734
- buf = buf.slice(0, cur - 1) + buf.slice(cur);
735
- cur--;
736
- menuHidden = false;
737
- render();
798
+ if (cur2 > 0) {
799
+ buf2 = buf2.slice(0, cur2 - 1) + buf2.slice(cur2);
800
+ cur2--;
801
+ menuHidden2 = false;
802
+ render2();
738
803
  }
739
804
  return;
740
805
  }
741
806
  if (key?.name === "delete") {
742
- if (cur < buf.length) {
743
- buf = buf.slice(0, cur) + buf.slice(cur + 1);
744
- menuHidden = false;
745
- render();
807
+ if (cur2 < buf2.length) {
808
+ buf2 = buf2.slice(0, cur2) + buf2.slice(cur2 + 1);
809
+ menuHidden2 = false;
810
+ render2();
746
811
  }
747
812
  return;
748
813
  }
749
814
  if (key?.name === "escape") {
750
815
  if (mm.length) {
751
- menuHidden = true;
752
- render();
816
+ menuHidden2 = true;
817
+ render2();
753
818
  }
754
819
  return;
755
820
  }
756
821
  if (str && !key?.ctrl && !key?.meta && [...str].length === 1) {
757
- if (isPrintableCodePoint(str.codePointAt(0))) insert(str);
822
+ if (isPrintableCodePoint(str.codePointAt(0))) insert2(str);
758
823
  }
759
824
  }
760
825
  function moveVert(dir) {
761
- const lines = buf.split("\n");
762
- const { row, col } = rowColOf(buf.slice(0, cur));
763
- const target = row + dir;
764
- if (target < 0 || target >= lines.length) return;
765
- let idx = 0;
766
- for (let i = 0; i < target; i++) idx += lines[i].length + 1;
767
- cur = idx + Math.min(col, lines[target].length);
768
- render();
826
+ const idx = moveVertIndex(buf2, cur2, dir);
827
+ if (idx === null) return;
828
+ cur2 = idx;
829
+ render2();
769
830
  }
770
831
  function histPrev() {
771
832
  if (history.length === 0) return;
772
833
  hist = Math.max(0, hist - 1);
773
- buf = history[hist] ?? "";
774
- cur = buf.length;
775
- render();
834
+ buf2 = history[hist] ?? "";
835
+ cur2 = buf2.length;
836
+ render2();
776
837
  }
777
838
  function histNext() {
778
839
  if (hist >= history.length) return;
779
840
  hist += 1;
780
- buf = hist === history.length ? "" : history[hist];
781
- cur = buf.length;
782
- render();
841
+ buf2 = hist === history.length ? "" : history[hist];
842
+ cur2 = buf2.length;
843
+ render2();
783
844
  }
784
- const restore = pushKeyHandler(onKey);
785
- render();
845
+ const restore = pushKeyHandler(onKey2);
846
+ render2();
786
847
  });
787
848
  }
788
849
  function readChoice(prompt) {
@@ -803,36 +864,36 @@ function readChoice(prompt) {
803
864
  function selectMenu(opts) {
804
865
  if (!process.stdin.isTTY || opts.items.length === 0) return Promise.resolve(null);
805
866
  return new Promise((resolve2) => {
806
- let sel = Math.max(0, Math.min(opts.initial ?? 0, opts.items.length - 1));
867
+ let sel2 = Math.max(0, Math.min(opts.initial ?? 0, opts.items.length - 1));
807
868
  let drawn = 0;
808
- function render() {
809
- out("\x1B[?25l");
810
- if (drawn > 0) out(`\x1B[${drawn}A`);
811
- out("\r\x1B[J");
869
+ function render2() {
870
+ out(ansi.hideCursor);
871
+ if (drawn > 0) out(ansi.up(drawn));
872
+ out(ansi.cr + ansi.clearToEnd);
812
873
  out(color.dim(opts.title) + "\n");
813
874
  opts.items.forEach((it, i) => {
814
- const row = i === sel ? color.green("\u203A " + it.label) : " " + it.label;
875
+ const row = i === sel2 ? color.green("\u203A " + it.label) : " " + it.label;
815
876
  out(row + (it.hint ? color.dim(" " + it.hint) : "") + "\n");
816
877
  });
817
878
  drawn = opts.items.length + 1;
818
879
  }
819
880
  function finish(v) {
820
881
  restore();
821
- if (drawn > 0) out(`\x1B[${drawn}A\r\x1B[J`);
822
- out("\x1B[?25h");
882
+ if (drawn > 0) out(ansi.up(drawn) + ansi.cr + ansi.clearToEnd);
883
+ out(ansi.showCursor);
823
884
  resolve2(v);
824
885
  }
825
886
  const restore = pushKeyHandler((_str, key) => {
826
887
  if (key?.name === "up") {
827
- sel = (sel - 1 + opts.items.length) % opts.items.length;
828
- render();
888
+ sel2 = (sel2 - 1 + opts.items.length) % opts.items.length;
889
+ render2();
829
890
  } else if (key?.name === "down") {
830
- sel = (sel + 1) % opts.items.length;
831
- render();
832
- } else if (isEnter(key)) finish(opts.items[sel].value);
891
+ sel2 = (sel2 + 1) % opts.items.length;
892
+ render2();
893
+ } else if (isEnter(key)) finish(opts.items[sel2].value);
833
894
  else if (key?.name === "escape" || key?.name === "q" || key?.ctrl && key.name === "c") finish(null);
834
895
  });
835
- render();
896
+ render2();
836
897
  });
837
898
  }
838
899
 
@@ -842,46 +903,46 @@ function renderFileBox(path, startLine, lines, hasMore) {
842
903
  const p = stripControl(path);
843
904
  const numW = String(startLine + Math.max(0, lines.length - 1)).length;
844
905
  const header = startLine === 1 && !hasMore ? `${lines.length} line${lines.length === 1 ? "" : "s"}` : `lines ${startLine}\u2013${startLine + lines.length - 1}${hasMore ? " (+ more)" : ""}`;
845
- const out2 = [color.dim("\u256D\u2500 ") + color.bold(p) + color.dim(` ${header}`)];
846
- lines.forEach((l, i) => out2.push(color.dim("\u2502 ") + color.dim(String(startLine + i).padStart(numW)) + " " + stripControl(l)));
847
- if (hasMore) out2.push(color.dim("\u2502 ") + color.dim(`\u2026 more (show ${p} offset ${startLine + lines.length})`));
848
- out2.push(color.dim("\u2570\u2500"));
849
- return out2.join("\n") + "\n";
906
+ const out3 = [color.dim("\u256D\u2500 ") + color.bold(p) + color.dim(` ${header}`)];
907
+ lines.forEach((l, i) => out3.push(color.dim("\u2502 ") + color.dim(String(startLine + i).padStart(numW)) + " " + stripControl(l)));
908
+ if (hasMore) out3.push(color.dim("\u2502 ") + color.dim(`\u2026 more (show ${p} offset ${startLine + lines.length})`));
909
+ out3.push(color.dim("\u2570\u2500"));
910
+ return out3.join("\n") + "\n";
850
911
  }
851
912
  function renderListing(path, names) {
852
913
  const safe = names.map(stripControl);
853
- const out2 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${safe.length} entr${safe.length === 1 ? "y" : "ies"}`)];
914
+ const out3 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${safe.length} entr${safe.length === 1 ? "y" : "ies"}`)];
854
915
  if (safe.length === 0) {
855
- out2.push(color.dim("\u2502 ") + color.dim("(empty)"));
916
+ out3.push(color.dim("\u2502 ") + color.dim("(empty)"));
856
917
  } else {
857
918
  const avail = BOX_W() - 2;
858
919
  const colW = Math.min(Math.max(...safe.map((n) => n.length)) + 2, 40);
859
- const cols = Math.max(1, Math.floor(avail / colW));
860
- const rows2 = Math.ceil(safe.length / cols);
920
+ const cols2 = Math.max(1, Math.floor(avail / colW));
921
+ const rows2 = Math.ceil(safe.length / cols2);
861
922
  for (let r = 0; r < rows2; r++) {
862
923
  let line = "";
863
- for (let c = 0; c < cols; c++) {
924
+ for (let c = 0; c < cols2; c++) {
864
925
  const idx = c * rows2 + r;
865
926
  if (idx >= safe.length) continue;
866
927
  const n = safe[idx];
867
928
  line += (n.endsWith("/") ? color.cyan(n) : n) + " ".repeat(Math.max(1, colW - n.length));
868
929
  }
869
- out2.push(color.dim("\u2502 ") + line.replace(/\s+$/, ""));
930
+ out3.push(color.dim("\u2502 ") + line.replace(/\s+$/, ""));
870
931
  }
871
932
  }
872
- out2.push(color.dim("\u2570\u2500"));
873
- return out2.join("\n") + "\n";
933
+ out3.push(color.dim("\u2570\u2500"));
934
+ return out3.join("\n") + "\n";
874
935
  }
875
936
  function renderTree(path, items, truncated) {
876
937
  const dirs = items.filter((it) => it.isDir).length;
877
- const out2 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${items.length} entries \xB7 ${dirs} folders`)];
938
+ const out3 = [color.dim("\u256D\u2500 ") + color.bold(stripControl(path)) + color.dim(` ${items.length} entries \xB7 ${dirs} folders`)];
878
939
  for (const it of items) {
879
940
  const nm = stripControl(it.name);
880
- out2.push(color.dim("\u2502 ") + color.dim(it.prefix) + (it.isDir ? color.cyan(nm) : nm));
941
+ out3.push(color.dim("\u2502 ") + color.dim(it.prefix) + (it.isDir ? color.cyan(nm) : nm));
881
942
  }
882
- if (truncated) out2.push(color.dim("\u2502 ") + color.dim("\u2026 (truncated)"));
883
- out2.push(color.dim("\u2570\u2500"));
884
- return out2.join("\n") + "\n";
943
+ if (truncated) out3.push(color.dim("\u2502 ") + color.dim("\u2026 (truncated)"));
944
+ out3.push(color.dim("\u2570\u2500"));
945
+ return out3.join("\n") + "\n";
885
946
  }
886
947
  var SHOW_MARK = "";
887
948
  var SHOW_KINDS = ["file", "dir", "tree"];
@@ -990,14 +1051,14 @@ function renderTable(rows2) {
990
1051
  })
991
1052
  );
992
1053
  const width = Array.from({ length: ncol }, (_, c) => Math.max(...styled.map((r) => visLen(r[c]))));
993
- const out2 = styled.map((r) => {
1054
+ const out3 = styled.map((r) => {
994
1055
  const cells = r.map((cell, c) => cell + " ".repeat(Math.max(0, width[c] - visLen(cell))));
995
1056
  return " " + cells.join(color.dim(" \u2502 "));
996
1057
  });
997
- return out2.join("\n") + "\n";
1058
+ return out3.join("\n") + "\n";
998
1059
  }
999
1060
  function createMarkdownStream(write) {
1000
- let buf = "";
1061
+ let buf2 = "";
1001
1062
  let scanFrom = 0;
1002
1063
  let inCode = false;
1003
1064
  let first = true;
@@ -1036,19 +1097,19 @@ function createMarkdownStream(write) {
1036
1097
  }
1037
1098
  return {
1038
1099
  push(text) {
1039
- buf += text;
1100
+ buf2 += text;
1040
1101
  let i;
1041
- while ((i = buf.indexOf("\n", scanFrom)) >= 0) {
1042
- emit(buf.slice(0, i));
1043
- buf = buf.slice(i + 1);
1102
+ while ((i = buf2.indexOf("\n", scanFrom)) >= 0) {
1103
+ emit(buf2.slice(0, i));
1104
+ buf2 = buf2.slice(i + 1);
1044
1105
  scanFrom = 0;
1045
1106
  }
1046
- scanFrom = buf.length;
1107
+ scanFrom = buf2.length;
1047
1108
  },
1048
1109
  end() {
1049
- if (buf) {
1050
- emit(buf);
1051
- buf = "";
1110
+ if (buf2) {
1111
+ emit(buf2);
1112
+ buf2 = "";
1052
1113
  }
1053
1114
  flushTable();
1054
1115
  if (inCode) {
@@ -1249,7 +1310,7 @@ import { homedir as homedir3 } from "node:os";
1249
1310
  import { basename } from "node:path";
1250
1311
  function pathGuard(args) {
1251
1312
  const { abs, inRoot } = resolveInRoot(String(args.path ?? "."));
1252
- return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}` };
1313
+ return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}`, cacheKey: `path:${abs}` };
1253
1314
  }
1254
1315
  var SECRET_FILE = /(^|\/)(\.env(\.[\w.-]+)?|[\w.-]*\.(env|pem|key|secret|pfx|p12|jks|keystore)|id_(rsa|ed25519|ecdsa|dsa)|credentials|\.git-credentials|\.pgpass|\.npmrc|\.netrc)$/i;
1255
1316
  function isSecretPath(userPath) {
@@ -1470,13 +1531,13 @@ async function atomicWrite(abs, content) {
1470
1531
  }
1471
1532
  var READ_PREFIX = /^ *\d+ {2}/;
1472
1533
  function allIndexOf(hay, needle) {
1473
- const out2 = [];
1534
+ const out3 = [];
1474
1535
  let i = hay.indexOf(needle);
1475
1536
  while (i !== -1) {
1476
- out2.push(i);
1537
+ out3.push(i);
1477
1538
  i = hay.indexOf(needle, i + 1);
1478
1539
  }
1479
- return out2;
1540
+ return out3;
1480
1541
  }
1481
1542
  function stripReadPrefix(text) {
1482
1543
  const lines = text.split("\n");
@@ -1971,11 +2032,11 @@ ${res.closest}` : `Re-read the file and copy the exact text (including whitespac
1971
2032
  ${stderr}` : "") || "(no output)";
1972
2033
  } catch (err) {
1973
2034
  const e = err;
1974
- const out2 = `${e.stdout ?? ""}${e.stderr ? `
2035
+ const out3 = `${e.stdout ?? ""}${e.stderr ? `
1975
2036
  [stderr]
1976
2037
  ${e.stderr}` : ""}`.trim();
1977
- return `Error: command failed${out2 ? `:
1978
- ${out2}` : `: ${String(e.message ?? err)}`}`;
2038
+ return `Error: command failed${out3 ? `:
2039
+ ${out3}` : `: ${String(e.message ?? err)}`}`;
1979
2040
  }
1980
2041
  }
1981
2042
  },
@@ -2220,14 +2281,14 @@ function validateArgs(tool, args) {
2220
2281
  async function runVerify(signal) {
2221
2282
  try {
2222
2283
  const { stdout, stderr } = await runShell(config.verifyCommand, { timeout: config.verifyTimeoutMs, maxBuffer: config.maxToolBuffer, signal });
2223
- const out2 = `${stdout}${stderr}`.trim();
2224
- return `passed \u2713${out2 ? `
2225
- ${out2.slice(-800)}` : ""}`;
2284
+ const out3 = `${stdout}${stderr}`.trim();
2285
+ return `passed \u2713${out3 ? `
2286
+ ${out3.slice(-800)}` : ""}`;
2226
2287
  } catch (err) {
2227
2288
  const e = err;
2228
- const out2 = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || String(e.message ?? err);
2289
+ const out3 = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || String(e.message ?? err);
2229
2290
  return `FAILED \u2717
2230
- ${out2.slice(-1500)}`;
2291
+ ${out3.slice(-1500)}`;
2231
2292
  }
2232
2293
  }
2233
2294
 
@@ -2260,12 +2321,12 @@ function parseSSELine(line) {
2260
2321
  }
2261
2322
  const delta = parsed.choices?.[0]?.delta;
2262
2323
  if (!delta) return null;
2263
- const out2 = {};
2264
- if (delta.content) out2.content = delta.content;
2265
- if (delta.tool_calls) out2.toolCalls = delta.tool_calls;
2266
- if (typeof delta.reasoning === "string" && delta.reasoning) out2.reasoning = delta.reasoning;
2267
- if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) out2.reasoningDetails = delta.reasoning_details;
2268
- return out2;
2324
+ const out3 = {};
2325
+ if (delta.content) out3.content = delta.content;
2326
+ if (delta.tool_calls) out3.toolCalls = delta.tool_calls;
2327
+ if (typeof delta.reasoning === "string" && delta.reasoning) out3.reasoning = delta.reasoning;
2328
+ if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) out3.reasoningDetails = delta.reasoning_details;
2329
+ return out3;
2269
2330
  }
2270
2331
  function buildRequestBody(opts) {
2271
2332
  const { model, messages, includeTools, effort, reasoningSupported, extra, tools } = opts;
@@ -2388,7 +2449,7 @@ async function callModel(messages, includeTools = true, signal, opts) {
2388
2449
  stopSpinner();
2389
2450
  if (!quiet) {
2390
2451
  if (!printedText) {
2391
- process.stdout.write("\n" + color.cyan("bee: "));
2452
+ process.stdout.write((printedReasoning ? "\n\n" : "\n") + color.cyan("bee: "));
2392
2453
  printedText = true;
2393
2454
  }
2394
2455
  const safe = stripControl(ev.content);
@@ -2671,7 +2732,7 @@ async function saveSession(messages) {
2671
2732
  }
2672
2733
  function sanitizeSession(raw) {
2673
2734
  if (!Array.isArray(raw)) return null;
2674
- const out2 = [];
2735
+ const out3 = [];
2675
2736
  for (const m of raw) {
2676
2737
  if (!m || typeof m !== "object") return null;
2677
2738
  const role = m.role;
@@ -2684,9 +2745,9 @@ function sanitizeSession(raw) {
2684
2745
  if (Array.isArray(tc)) msg.tool_calls = tc;
2685
2746
  const tcid = m.tool_call_id;
2686
2747
  if (typeof tcid === "string") msg.tool_call_id = tcid;
2687
- out2.push(msg);
2748
+ out3.push(msg);
2688
2749
  }
2689
- return dropIncompleteToolTail(out2);
2750
+ return dropIncompleteToolTail(out3);
2690
2751
  }
2691
2752
  function dropIncompleteToolTail(messages) {
2692
2753
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -2729,15 +2790,15 @@ async function loadLatestSession() {
2729
2790
  async function listSessions() {
2730
2791
  try {
2731
2792
  const files = (await readdir2(sessionsDir())).filter((f) => f.endsWith(".json"));
2732
- const out2 = [];
2793
+ const out3 = [];
2733
2794
  for (const f of files) {
2734
2795
  const msgs = await readSession(f);
2735
2796
  if (!msgs || !msgs.length) continue;
2736
2797
  const firstUser = msgs.find((m) => m.role === "user");
2737
2798
  const preview = (firstUser?.content ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
2738
- out2.push({ file: f, when: Number(f.replace(".json", "")) || 0, count: msgs.length, preview });
2799
+ out3.push({ file: f, when: Number(f.replace(".json", "")) || 0, count: msgs.length, preview });
2739
2800
  }
2740
- return out2.sort((a, b) => b.when - a.when);
2801
+ return out3.sort((a, b) => b.when - a.when);
2741
2802
  } catch {
2742
2803
  return [];
2743
2804
  }
@@ -2796,7 +2857,7 @@ var SYSTEM_PROMPT = `You are beecork, a coding assistant working in a terminal o
2796
2857
  # Safety
2797
2858
  - Be careful with anything that deletes or overwrites. Don't do destructive things unless the user clearly asked.
2798
2859
  - Don't commit or push to git, publish, deploy, or take other outward or hard-to-undo actions unless the user explicitly asked.`;
2799
- async function askApproval(ask, call, reason) {
2860
+ async function askApproval(ask, call, reason, offerAlways = true) {
2800
2861
  let args = {};
2801
2862
  try {
2802
2863
  args = JSON.parse(call.function.arguments);
@@ -2818,8 +2879,9 @@ async function askApproval(ask, call, reason) {
2818
2879
  } else {
2819
2880
  console.log(color.yellow(` ${stripControl(call.function.arguments)}`));
2820
2881
  }
2821
- const answer = (await ask(color.yellow(" allow? [y]es / [n]o / [a]lways: "))).trim().toLowerCase();
2822
- if (answer === "a" || answer === "always") return "always";
2882
+ const prompt = offerAlways ? " allow? [y]es / [n]o / [a]lways: " : " allow? [y]es / [n]o: ";
2883
+ const answer = (await ask(color.yellow(prompt))).trim().toLowerCase();
2884
+ if (offerAlways && (answer === "a" || answer === "always")) return "always";
2823
2885
  if (answer === "y" || answer === "yes") return "once";
2824
2886
  return "deny";
2825
2887
  }
@@ -2830,8 +2892,9 @@ function decideApproval(tool, args, ctx) {
2830
2892
  if (ctx.dangerouslySkip) return { action: "run" };
2831
2893
  const guard = tool?.guard?.(args);
2832
2894
  if (guard?.needsApproval) {
2895
+ if (guard.cacheKey && ctx.approvedGuardKeys?.has(guard.cacheKey)) return { action: "run" };
2833
2896
  if (ctx.autoApprove) return { action: "deny", kind: "headless", reason: guard.reason ?? "blocked" };
2834
- return { action: "ask", cacheable: false, reason: guard.reason };
2897
+ return guard.cacheKey ? { action: "ask", cacheable: true, reason: guard.reason, guardKey: guard.cacheKey } : { action: "ask", cacheable: false, reason: guard.reason };
2835
2898
  }
2836
2899
  if (tool?.needsApproval && !ctx.autoApprove && ctx.mode !== "auto") {
2837
2900
  if (tool.alwaysAsk) return { action: "ask", cacheable: false };
@@ -2867,7 +2930,7 @@ async function handleAskUser(args) {
2867
2930
  return askUserMessage(question, choice, true);
2868
2931
  }
2869
2932
  async function handleToolCall(call, messages, step, deps) {
2870
- const { approvedTools, callCounts, ask, signal } = deps;
2933
+ const { approvedTools, approvedGuardKeys, callCounts, ask, signal } = deps;
2871
2934
  const pushTool = (content) => messages.push({ role: "tool", tool_call_id: call.id, content });
2872
2935
  const sig = `${call.function.name}:${call.function.arguments}`;
2873
2936
  const seen = (callCounts.get(sig) ?? 0) + 1;
@@ -2892,6 +2955,7 @@ async function handleToolCall(call, messages, step, deps) {
2892
2955
  mode: state.mode,
2893
2956
  autoApprove: config.autoApprove,
2894
2957
  approvedTools,
2958
+ approvedGuardKeys,
2895
2959
  toolName: call.function.name,
2896
2960
  dangerouslySkip: config.dangerouslySkipPermissions
2897
2961
  });
@@ -2906,15 +2970,19 @@ async function handleToolCall(call, messages, step, deps) {
2906
2970
  return;
2907
2971
  }
2908
2972
  if (decision.action === "ask") {
2909
- const answer = await askApproval(ask, call, decision.cacheable ? void 0 : decision.reason);
2973
+ const answer = await askApproval(ask, call, decision.reason, decision.cacheable);
2910
2974
  if (answer === "deny") {
2911
2975
  console.log(color.red(" \u21B3 denied") + "\n");
2912
- pushTool(decision.cacheable ? "The user DENIED permission to run this tool. Do not retry it." : `The user DENIED this (${decision.reason}). Do not retry, and do not route around it with run_bash/cat or another tool.`);
2976
+ pushTool(decision.reason ? `The user DENIED this (${decision.reason}). Do not retry, and do not route around it with run_bash/cat or another tool.` : "The user DENIED permission to run this tool. Do not retry it.");
2913
2977
  return;
2914
2978
  }
2915
- if (decision.cacheable && answer === "always") {
2916
- approvedTools.add(call.function.name);
2917
- void addProjectApproval(call.function.name);
2979
+ if (answer === "always" && decision.cacheable) {
2980
+ if (decision.guardKey) {
2981
+ approvedGuardKeys.add(decision.guardKey);
2982
+ } else {
2983
+ approvedTools.add(call.function.name);
2984
+ void addProjectApproval(call.function.name);
2985
+ }
2918
2986
  }
2919
2987
  }
2920
2988
  if (config.traceFile) trace.push({ tool: call.function.name, args: call.function.arguments, step });
@@ -2970,13 +3038,13 @@ ${content}` }];
2970
3038
  }
2971
3039
  return [...messages, { role: "user", content }];
2972
3040
  }
2973
- async function runTurn(messages, userInput, ask, approvedTools, signal, steering) {
3041
+ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal, steering2) {
2974
3042
  messages.push({ role: "user", content: userInput });
2975
3043
  const snapshot = messages.slice();
2976
3044
  try {
2977
3045
  let answered = false;
2978
3046
  const callCounts = /* @__PURE__ */ new Map();
2979
- const deps = { approvedTools, callCounts, ask, signal };
3047
+ const deps = { approvedTools, approvedGuardKeys, callCounts, ask, signal };
2980
3048
  for (let step = 0; step < config.maxSteps && !answered && !signal?.aborted; step++) {
2981
3049
  try {
2982
3050
  messages = await compactIfNeeded(messages, signal);
@@ -2984,7 +3052,7 @@ async function runTurn(messages, userInput, ask, approvedTools, signal, steering
2984
3052
  console.error(color.red(`
2985
3053
  [compaction failed: ${err.message} \u2014 continuing]`) + "\n");
2986
3054
  }
2987
- if (steering?.length) messages = applySteering(messages, steering.splice(0));
3055
+ if (steering2?.length) messages = applySteering(messages, steering2.splice(0));
2988
3056
  const message = await callModel(messages, true, signal);
2989
3057
  messages.push(message);
2990
3058
  if (!hasContent2(message)) {
@@ -2998,7 +3066,7 @@ async function runTurn(messages, userInput, ask, approvedTools, signal, steering
2998
3066
  await handleToolCall(call, messages, step, deps);
2999
3067
  }
3000
3068
  } else {
3001
- answered = !steering?.length;
3069
+ answered = !steering2?.length;
3002
3070
  }
3003
3071
  }
3004
3072
  if (signal?.aborted) {
@@ -3070,62 +3138,395 @@ async function runtimeContext() {
3070
3138
  });
3071
3139
  }
3072
3140
 
3073
- // src/statusline.ts
3141
+ // src/chrome.ts
3074
3142
  import { execFile as execFile2 } from "node:child_process";
3143
+ var out2 = (s) => process.stdout.write(s);
3144
+ var rows = () => process.stdout.rows ?? 24;
3145
+ var cols = () => process.stdout.columns ?? 80;
3075
3146
  var active2 = false;
3076
- var drawTimer = null;
3077
- var gitTimer = null;
3147
+ var turnActive = false;
3148
+ var buf = "";
3149
+ var cur = 0;
3150
+ var allItems = [];
3151
+ var sel = 0;
3152
+ var menuHidden = false;
3153
+ var menuH = 0;
3154
+ var prevMenuH = 0;
3155
+ var lastRegionB = -1;
3156
+ var pickItems = null;
3157
+ var pickSel = 0;
3158
+ var pickTitle = "";
3159
+ var pickResolve = null;
3078
3160
  var branch = "";
3079
- var tokensOf = null;
3080
- var rows = () => process.stdout.rows ?? 24;
3161
+ var tokensOf = () => 0;
3162
+ var onResult = null;
3163
+ var onInterrupt = null;
3164
+ var steering = [];
3165
+ var restoreKeys = null;
3166
+ var gitTimer = null;
3167
+ var statusTimer = null;
3168
+ var lastRows = 0;
3169
+ var BURST_IDLE_MS = 8;
3170
+ var burstLen = 0;
3171
+ var burstTimer = null;
3172
+ var pendingRender = false;
3173
+ var statusRow = () => Math.max(1, rows());
3174
+ var borderBottomRow = () => Math.max(1, rows() - 1);
3175
+ var inputRow = () => Math.max(1, rows() - 2);
3176
+ var borderTopRow = () => Math.max(1, rows() - 3);
3177
+ var mark = () => color.green("\u203A ");
3178
+ var PW = 2;
3179
+ function modeSegment() {
3180
+ return state.mode === "auto" ? color.yellow("auto-approve") : state.mode === "readonly" ? color.cyan("read-only") : color.green("normal");
3181
+ }
3182
+ function statusText() {
3183
+ const model = state.model.split("/").pop() ?? state.model;
3184
+ const tok = tokensOf();
3185
+ const ctxK = Math.round(config.maxContextTokens / 1e3);
3186
+ const parts = [
3187
+ modeSegment(),
3188
+ color.cyan(model),
3189
+ color.dim(state.reasoningEffort),
3190
+ ...branch ? [color.green(branch)] : [],
3191
+ color.dim(`~${Math.round(tok / 1e3)}k/${ctxK}k`)
3192
+ ];
3193
+ const bg = runningTaskCount();
3194
+ if (bg > 0) parts.push(color.yellow(`${bg} task${bg === 1 ? "" : "s"}`));
3195
+ return parts.join(color.dim(" \xB7 "));
3196
+ }
3081
3197
  function pollGit() {
3082
- execFile2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1500, windowsHide: true }, (err, out2) => {
3198
+ execFile2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1500, windowsHide: true }, (err, o) => {
3083
3199
  if (err) {
3084
3200
  branch = "";
3085
3201
  return;
3086
3202
  }
3087
- const b = String(out2).trim();
3088
- execFile2("git", ["status", "--porcelain"], { timeout: 1500, windowsHide: true }, (e2, out22) => {
3089
- branch = b + (!e2 && String(out22).trim() ? "*" : "");
3203
+ const b = String(o).trim();
3204
+ execFile2("git", ["status", "--porcelain"], { timeout: 1500, windowsHide: true }, (e2, o2) => {
3205
+ branch = b + (!e2 && String(o2).trim() ? "*" : "");
3090
3206
  });
3091
3207
  });
3092
3208
  }
3093
- function segments() {
3094
- const parts = [state.model.split("/").pop() ?? state.model, state.reasoningEffort];
3095
- if (branch) parts.push(branch);
3096
- if (tokensOf) parts.push(`~${Math.round(tokensOf() / 1e3)}k`);
3097
- const bg = runningTaskCount();
3098
- if (bg > 0) parts.push(`${bg} task${bg === 1 ? "" : "s"}`);
3099
- return parts.join(" \xB7 ");
3209
+ function drawStatus() {
3210
+ const s = statusText();
3211
+ const vis = stripAnsi(s);
3212
+ out2(ansi.moveTo(statusRow()) + ansi.clearLine + (vis.length <= cols() ? s : stripAnsi(s).slice(0, cols() - 1)));
3100
3213
  }
3101
- function draw() {
3214
+ function drawBorder(row) {
3215
+ out2(ansi.moveTo(row) + ansi.clearLine + color.dim("\u2500".repeat(Math.max(1, cols()))));
3216
+ }
3217
+ var closePick = () => {
3218
+ pickItems = null;
3219
+ pickSel = 0;
3220
+ pickTitle = "";
3221
+ };
3222
+ function chromePick(items, initial = 0, title = "") {
3223
+ return new Promise((resolve2) => {
3224
+ if (!active2 || !items.length) {
3225
+ resolve2(null);
3226
+ return;
3227
+ }
3228
+ pickItems = items;
3229
+ pickSel = Math.min(Math.max(0, initial), items.length - 1);
3230
+ pickTitle = title;
3231
+ pickResolve = resolve2;
3232
+ render();
3233
+ });
3234
+ }
3235
+ var flat = (s) => s.replace(/\n/g, "\u23CE");
3236
+ function drawInput() {
3237
+ const disp = flat(buf);
3238
+ const start = windowStart(disp, cur, Math.max(1, cols() - PW));
3239
+ const shown = disp.slice(start);
3240
+ const ci = cur - start;
3241
+ let body;
3242
+ if (!buf) {
3243
+ body = ansi.reverse + " " + ansi.reverseOff + color.dim("type a message \xB7 / for commands \xB7 Shift+Tab mode");
3244
+ } else {
3245
+ const at = ci < shown.length ? shown[ci] : " ";
3246
+ body = shown.slice(0, ci) + ansi.reverse + at + ansi.reverseOff + (ci < shown.length ? shown.slice(ci + 1) : "");
3247
+ }
3248
+ out2(ansi.moveTo(inputRow()) + ansi.clearLine + mark() + body);
3249
+ }
3250
+ function currentMenu() {
3251
+ if (menuHidden || turnActive) return [];
3252
+ const m = buf.match(/^\/(\S*)$/);
3253
+ if (!m) return [];
3254
+ const pre = "/" + m[1];
3255
+ return allItems.filter((c) => c.name.startsWith(pre)).slice(0, 8);
3256
+ }
3257
+ function render() {
3102
3258
  if (!active2) return;
3103
- process.stdout.write(`\x1B7\x1B[${rows()};1H\x1B[2K${color.dim(segments())}\x1B8`);
3259
+ const picker = pickItems;
3260
+ const list = picker ? picker.slice(0, 12).map((it, i) => ({ text: it.label + (it.hint ? " " + color.dim(it.hint) : ""), on: i === pickSel })) : currentMenu().map((it, i) => ({ text: it.name.padEnd(12) + " " + it.desc, on: i === sel }));
3261
+ if (!picker && sel >= list.length) sel = Math.max(0, list.length - 1);
3262
+ menuH = list.length;
3263
+ out2(ansi.hideCursor + ansi.saveCursor);
3264
+ const regionB = Math.max(1, rows() - 4 - menuH);
3265
+ if (regionB !== lastRegionB) {
3266
+ out2(ansi.setRegion(1, regionB));
3267
+ lastRegionB = regionB;
3268
+ }
3269
+ for (let r = borderTopRow() - prevMenuH; r < borderTopRow() - menuH; r++) out2(ansi.moveTo(r) + ansi.clearLine);
3270
+ prevMenuH = menuH;
3271
+ const base = borderTopRow() - menuH;
3272
+ for (let i = 0; i < list.length; i++) {
3273
+ out2(ansi.moveTo(base + i) + ansi.clearLine + (list[i].on ? color.green("\u203A ") + list[i].text : color.dim(" ") + list[i].text));
3274
+ }
3275
+ drawBorder(borderTopRow());
3276
+ if (picker) out2(ansi.moveTo(inputRow()) + ansi.clearLine + mark() + color.dim(pickTitle || "\u2191/\u2193 to choose \xB7 Enter to select \xB7 Esc to cancel"));
3277
+ else drawInput();
3278
+ drawBorder(borderBottomRow());
3279
+ drawStatus();
3280
+ out2(ansi.restoreCursor);
3281
+ lastRows = rows();
3282
+ pendingRender = false;
3104
3283
  }
3105
3284
  function onResize() {
3106
3285
  if (!active2) return;
3107
- process.stdout.write(`\x1B[1;${rows() - 1}r`);
3108
- draw();
3286
+ const newRows = rows();
3287
+ if (lastRows > 0 && newRows > lastRows) {
3288
+ out2(ansi.saveCursor);
3289
+ const oldTop = Math.max(1, lastRows - 3 - prevMenuH);
3290
+ for (let r = oldTop; r <= lastRows; r++) out2(ansi.moveTo(r) + ansi.clearLine);
3291
+ out2(ansi.restoreCursor);
3292
+ }
3293
+ lastRegionB = -1;
3294
+ render();
3295
+ }
3296
+ function refreshChrome() {
3297
+ if (!active2) return;
3298
+ out2(ansi.hideCursor + ansi.saveCursor);
3299
+ drawStatus();
3300
+ out2(ansi.restoreCursor);
3301
+ }
3302
+ var edited = () => {
3303
+ menuHidden = false;
3304
+ sel = 0;
3305
+ };
3306
+ function insert(s) {
3307
+ buf = buf.slice(0, cur) + s + buf.slice(cur);
3308
+ cur += s.length;
3309
+ edited();
3310
+ pendingRender = true;
3311
+ }
3312
+ function resetBurst() {
3313
+ if (burstTimer) {
3314
+ clearTimeout(burstTimer);
3315
+ burstTimer = null;
3316
+ }
3317
+ burstLen = 0;
3318
+ pendingRender = false;
3319
+ }
3320
+ function onKey(str, key) {
3321
+ if (pickItems) {
3322
+ const n = pickItems.length;
3323
+ if (key?.name === "up") {
3324
+ pickSel = (pickSel - 1 + n) % n;
3325
+ render();
3326
+ return;
3327
+ }
3328
+ if (key?.name === "down") {
3329
+ pickSel = (pickSel + 1) % n;
3330
+ render();
3331
+ return;
3332
+ }
3333
+ if (key?.name === "return" || key?.name === "enter") {
3334
+ const v = pickItems[pickSel].value;
3335
+ closePick();
3336
+ render();
3337
+ pickResolve?.(v);
3338
+ pickResolve = null;
3339
+ return;
3340
+ }
3341
+ if (key?.name === "escape" || key?.ctrl && key.name === "c") {
3342
+ closePick();
3343
+ render();
3344
+ pickResolve?.(null);
3345
+ pickResolve = null;
3346
+ return;
3347
+ }
3348
+ return;
3349
+ }
3350
+ burstLen++;
3351
+ if (burstTimer) clearTimeout(burstTimer);
3352
+ burstTimer = setTimeout(() => {
3353
+ burstTimer = null;
3354
+ burstLen = 0;
3355
+ if (pendingRender) render();
3356
+ }, BURST_IDLE_MS);
3357
+ const mm = currentMenu();
3358
+ if (key?.ctrl && key.name === "c") {
3359
+ if (buf) {
3360
+ buf = "";
3361
+ cur = 0;
3362
+ edited();
3363
+ resetBurst();
3364
+ render();
3365
+ return;
3366
+ }
3367
+ if (turnActive) onInterrupt?.();
3368
+ else {
3369
+ const r = onResult;
3370
+ onResult = null;
3371
+ r?.({ type: "quit" });
3372
+ }
3373
+ return;
3374
+ }
3375
+ if (key?.name === "escape") {
3376
+ if (mm.length) {
3377
+ menuHidden = true;
3378
+ render();
3379
+ } else if (buf) {
3380
+ buf = "";
3381
+ cur = 0;
3382
+ render();
3383
+ }
3384
+ return;
3385
+ }
3386
+ if (key?.name === "tab" && key.shift) {
3387
+ state.mode = nextMode(state.mode);
3388
+ render();
3389
+ return;
3390
+ }
3391
+ if (key?.name === "tab") {
3392
+ if (mm.length) {
3393
+ buf = mm[sel].name + " ";
3394
+ cur = buf.length;
3395
+ edited();
3396
+ render();
3397
+ }
3398
+ return;
3399
+ }
3400
+ if (key?.name === "up") {
3401
+ if (mm.length) {
3402
+ sel = (sel - 1 + mm.length) % mm.length;
3403
+ render();
3404
+ }
3405
+ return;
3406
+ }
3407
+ if (key?.name === "down") {
3408
+ if (mm.length) {
3409
+ sel = (sel + 1) % mm.length;
3410
+ render();
3411
+ }
3412
+ return;
3413
+ }
3414
+ if (key?.name === "return" || key?.name === "enter") {
3415
+ if (key?.shift || key?.meta || burstLen > 1) {
3416
+ insert("\n");
3417
+ return;
3418
+ }
3419
+ resetBurst();
3420
+ const line = buf;
3421
+ buf = "";
3422
+ cur = 0;
3423
+ edited();
3424
+ render();
3425
+ if (turnActive) {
3426
+ if (line.trim()) steering.push(line.trim());
3427
+ return;
3428
+ }
3429
+ if (line.trim()) out2("\n" + mark() + line + "\n");
3430
+ const r = onResult;
3431
+ onResult = null;
3432
+ r?.({ type: "line", value: line });
3433
+ return;
3434
+ }
3435
+ if (key?.name === "backspace") {
3436
+ if (cur > 0) {
3437
+ buf = buf.slice(0, cur - 1) + buf.slice(cur);
3438
+ cur--;
3439
+ edited();
3440
+ render();
3441
+ }
3442
+ return;
3443
+ }
3444
+ if (key?.name === "delete") {
3445
+ if (cur < buf.length) {
3446
+ buf = buf.slice(0, cur) + buf.slice(cur + 1);
3447
+ edited();
3448
+ render();
3449
+ }
3450
+ return;
3451
+ }
3452
+ if (key?.name === "left") {
3453
+ if (cur > 0) {
3454
+ cur--;
3455
+ render();
3456
+ }
3457
+ return;
3458
+ }
3459
+ if (key?.name === "right") {
3460
+ if (cur < buf.length) {
3461
+ cur++;
3462
+ render();
3463
+ }
3464
+ return;
3465
+ }
3466
+ if (key && (key.name === "home" || key.ctrl && key.name === "a")) {
3467
+ cur = 0;
3468
+ render();
3469
+ return;
3470
+ }
3471
+ if (key && (key.name === "end" || key.ctrl && key.name === "e")) {
3472
+ cur = buf.length;
3473
+ render();
3474
+ return;
3475
+ }
3476
+ if (key?.ctrl && key.name === "u") {
3477
+ buf = buf.slice(cur);
3478
+ cur = 0;
3479
+ edited();
3480
+ render();
3481
+ return;
3482
+ }
3483
+ if (str && !key?.ctrl && !key?.meta && [...str].length === 1 && isPrintableCodePoint(str.codePointAt(0))) {
3484
+ insert(str);
3485
+ }
3109
3486
  }
3110
- function startStatusline(tokens) {
3111
- if (active2 || !process.stdout.isTTY || !config.statuslineEnabled) return;
3487
+ function startChrome(opts) {
3488
+ if (active2 || !process.stdout.isTTY) return;
3112
3489
  active2 = true;
3113
- tokensOf = tokens;
3114
- process.stdout.write(`\x1B[1;${rows() - 1}r`);
3490
+ tokensOf = opts.tokens;
3491
+ allItems = opts.items;
3492
+ onInterrupt = opts.onInterrupt;
3493
+ out2(ansi.bracketedPasteOff);
3115
3494
  pollGit();
3116
- draw();
3117
- drawTimer = setInterval(draw, config.statuslineRefreshMs);
3495
+ restoreKeys = pushKeyHandler(onKey);
3496
+ render();
3497
+ statusTimer = setInterval(refreshChrome, config.statuslineRefreshMs);
3118
3498
  gitTimer = setInterval(pollGit, 5e3);
3119
3499
  process.stdout.on("resize", onResize);
3120
3500
  }
3121
- function stopStatusline() {
3501
+ function nextLine() {
3502
+ return new Promise((resolve2) => {
3503
+ turnActive = false;
3504
+ render();
3505
+ onResult = resolve2;
3506
+ });
3507
+ }
3508
+ function beginTurn() {
3509
+ steering.length = 0;
3510
+ turnActive = true;
3511
+ return steering;
3512
+ }
3513
+ function endTurn() {
3514
+ turnActive = false;
3515
+ render();
3516
+ }
3517
+ function stopChrome() {
3122
3518
  if (!active2) return;
3123
3519
  active2 = false;
3124
- if (drawTimer) clearInterval(drawTimer);
3125
- if (gitTimer) clearInterval(gitTimer);
3520
+ restoreKeys?.();
3521
+ restoreKeys = null;
3126
3522
  process.stdout.removeListener("resize", onResize);
3127
- process.stdout.write(`\x1B[r\x1B[${rows()};1H\x1B[2K`);
3523
+ if (statusTimer) clearInterval(statusTimer);
3524
+ if (gitTimer) clearInterval(gitTimer);
3525
+ resetBurst();
3526
+ out2(ansi.resetRegion);
3527
+ for (const r of [statusRow(), borderBottomRow(), inputRow(), borderTopRow()]) out2(ansi.moveTo(r) + ansi.clearLine);
3128
3528
  }
3529
+ var chromeEnabled = () => config.statuslineEnabled && !!process.stdout.isTTY;
3129
3530
 
3130
3531
  // src/commands.ts
3131
3532
  import { writeFile as writeFile4, mkdir as mkdir4, chmod as chmod3 } from "node:fs/promises";
@@ -3179,6 +3580,13 @@ ${extra}` : "");
3179
3580
  }
3180
3581
 
3181
3582
  // src/commands.ts
3583
+ async function pick(opts) {
3584
+ if (chromeEnabled()) {
3585
+ const v = await chromePick(opts.items.map((it) => ({ label: it.label, hint: it.hint, value: it.value })), opts.initial, opts.title);
3586
+ return v == null ? null : v;
3587
+ }
3588
+ return selectMenu(opts);
3589
+ }
3182
3590
  var SLASH_COMMANDS = [
3183
3591
  { name: "/model", desc: "switch model (menu; /model <term> searches)" },
3184
3592
  { name: "/effort", desc: "set reasoning effort (off/low/medium/high/max)" },
@@ -3263,13 +3671,13 @@ async function handleCommand(input, messages) {
3263
3671
  }
3264
3672
  } else if (cmd === "/clear") {
3265
3673
  messages.splice(1);
3266
- if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3674
+ if (process.stdout.isTTY) process.stdout.write(ansi.clearScreen + ansi.clearScrollback + ansi.home);
3267
3675
  console.log(color.dim("conversation cleared (kept the system prompt, your model + settings).") + "\n");
3268
3676
  } else if (cmd === "/resume") {
3269
3677
  const sessions = process.stdin.isTTY ? await listSessions() : [];
3270
3678
  let restored = [];
3271
3679
  if (sessions.length > 1) {
3272
- const choice = await selectMenu({
3680
+ const choice = await pick({
3273
3681
  title: "resume which session? \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3274
3682
  items: sessions.map((s) => ({
3275
3683
  label: s.preview || "(no first message)",
@@ -3322,7 +3730,7 @@ async function handleCommand(input, messages) {
3322
3730
  }
3323
3731
  async function pickModel() {
3324
3732
  if (!process.stdin.isTTY) return showRecommended();
3325
- const choice = await selectMenu({
3733
+ const choice = await pick({
3326
3734
  title: "switch model \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3327
3735
  initial: Math.max(0, RECOMMENDED_MODELS.findIndex((m) => m.slug === state.model)),
3328
3736
  items: RECOMMENDED_MODELS.map((m) => ({
@@ -3345,7 +3753,7 @@ async function pickEffort() {
3345
3753
  console.log(color.cyan(`reasoning effort: ${state.reasoningEffort}`) + color.dim(` (levels: ${EFFORTS.join(" / ")})`) + "\n");
3346
3754
  return;
3347
3755
  }
3348
- const choice = await selectMenu({
3756
+ const choice = await pick({
3349
3757
  title: "reasoning effort \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3350
3758
  initial: Math.max(0, EFFORTS.indexOf(state.reasoningEffort)),
3351
3759
  items: EFFORTS.map((e) => ({
@@ -3380,7 +3788,7 @@ async function searchModels(term) {
3380
3788
  }
3381
3789
  const priceOf = (m) => m.pricing?.prompt != null ? `$${(parseFloat(m.pricing.prompt) * 1e6).toFixed(2)}/1M` : "?";
3382
3790
  if (process.stdin.isTTY) {
3383
- const choice = await selectMenu({
3791
+ const choice = await pick({
3384
3792
  title: `models matching "${term}" \u2014 \u2191/\u2193 then Enter (Esc to cancel)`,
3385
3793
  items: matches.map((m) => ({
3386
3794
  label: m.id,
@@ -3469,6 +3877,7 @@ ${instr.trusted}`;
3469
3877
  }
3470
3878
  let messages = [{ role: "system", content: systemContent }];
3471
3879
  const approvedTools = /* @__PURE__ */ new Set();
3880
+ const approvedGuardKeys = /* @__PURE__ */ new Set();
3472
3881
  for (const t of settings.alwaysAllow) approvedTools.add(t);
3473
3882
  for (const t of projectApprovals) approvedTools.add(t);
3474
3883
  let saved = false;
@@ -3488,7 +3897,7 @@ ${instr.trusted}`;
3488
3897
  };
3489
3898
  const tty = !!process.stdin.isTTY;
3490
3899
  process.on("exit", killAllTasks);
3491
- process.on("exit", stopStatusline);
3900
+ process.on("exit", stopChrome);
3492
3901
  if (tty) {
3493
3902
  initInput();
3494
3903
  process.on("exit", teardownInput);
@@ -3507,6 +3916,7 @@ ${instr.trusted}`;
3507
3916
  });
3508
3917
  }
3509
3918
  const rl = tty ? null : createInterface({ input: process.stdin, output: process.stdout, completer });
3919
+ if (chromeEnabled()) process.stdout.write(ansi.clearScreen + ansi.clearScrollback + ansi.home);
3510
3920
  printBanner(state.model, instr.sources.map(tildify));
3511
3921
  if (config.dangerouslySkipPermissions) {
3512
3922
  console.log(color.red("\u26A0 --dangerously-skip-permissions is ON: the approval gate is OFF. Out-of-root paths and risky") + "\n" + color.red(" shell commands will RUN unprompted. Use only in a disposable sandbox. (read-only mode + catastrophic-") + "\n" + color.red(" command refusal still apply.)") + "\n");
@@ -3556,10 +3966,19 @@ ${instr.trusted}`;
3556
3966
  }
3557
3967
  });
3558
3968
  }
3559
- startStatusline(() => estimateTokens(messages));
3969
+ if (chromeEnabled())
3970
+ startChrome({
3971
+ tokens: () => estimateTokens(messages),
3972
+ items: [...SLASH_COMMANDS, ...skills.map((s) => ({ name: "/" + s.name, desc: "skill" }))],
3973
+ onInterrupt: () => activeTurn?.abort()
3974
+ });
3560
3975
  while (true) {
3561
3976
  let userInput;
3562
- if (tty) {
3977
+ if (chromeEnabled()) {
3978
+ const r = await nextLine();
3979
+ if (r.type === "quit") break;
3980
+ userInput = r.value;
3981
+ } else if (tty) {
3563
3982
  const r = await readPrompt({
3564
3983
  promptString,
3565
3984
  commands: SLASH_COMMANDS,
@@ -3597,16 +4016,27 @@ ${instr.trusted}`;
3597
4016
  continue;
3598
4017
  }
3599
4018
  }
4019
+ if (chromeEnabled()) {
4020
+ activeTurn = new AbortController();
4021
+ const steering3 = beginTurn();
4022
+ try {
4023
+ messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering3);
4024
+ } finally {
4025
+ activeTurn = null;
4026
+ endTurn();
4027
+ }
4028
+ continue;
4029
+ }
3600
4030
  activeTurn = new AbortController();
3601
4031
  let modeChangedMidTurn = false;
3602
- const steering = [];
4032
+ const steering2 = [];
3603
4033
  let typed = "";
3604
- const redrawSteer = () => process.stdout.write("\r\x1B[2K" + color.cyan("\xBB ") + color.dim(typed));
4034
+ const redrawSteer = () => process.stdout.write(ansi.cr + ansi.clearLine + color.cyan("\xBB ") + color.dim(typed));
3605
4035
  const clearSteer = () => {
3606
- process.stdout.write("\r\x1B[2K");
4036
+ process.stdout.write(ansi.cr + ansi.clearLine);
3607
4037
  setSteeringActive(false);
3608
4038
  };
3609
- const restoreKeys = tty ? pushKeyHandler((str, key) => {
4039
+ const restoreKeys2 = tty ? pushKeyHandler((str, key) => {
3610
4040
  if (key && (key.name === "escape" || key.ctrl && key.name === "c")) {
3611
4041
  activeTurn?.abort();
3612
4042
  return;
@@ -3621,7 +4051,7 @@ ${instr.trusted}`;
3621
4051
  typed = "";
3622
4052
  clearSteer();
3623
4053
  if (note) {
3624
- steering.push(note);
4054
+ steering2.push(note);
3625
4055
  console.log(color.dim(`\xBB queued for next step: ${note}`));
3626
4056
  }
3627
4057
  return;
@@ -3641,9 +4071,9 @@ ${instr.trusted}`;
3641
4071
  }) : () => {
3642
4072
  };
3643
4073
  try {
3644
- messages = await runTurn(messages, userInput, ask, approvedTools, activeTurn.signal, steering);
4074
+ messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering2);
3645
4075
  } finally {
3646
- restoreKeys();
4076
+ restoreKeys2();
3647
4077
  setSteeringActive(false);
3648
4078
  activeTurn = null;
3649
4079
  if (modeChangedMidTurn) console.log(color.yellow(`\u25B8 mode: ${modeLabel(state.mode)}`));
@@ -3651,6 +4081,7 @@ ${instr.trusted}`;
3651
4081
  if (bg > 0) console.log(color.dim(`\u25B8 ${bg} background task${bg === 1 ? "" : "s"} running \u2014 check_task / stop_task`));
3652
4082
  }
3653
4083
  }
4084
+ stopChrome();
3654
4085
  teardownInput();
3655
4086
  rl?.close();
3656
4087
  await persist();