beecork 2.4.2 → 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 (2) hide show
  1. package/dist/index.js +686 -264
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -196,7 +196,7 @@ function selfUpdate(timeoutMs = 12e4) {
196
196
  return new Promise((resolve2) => {
197
197
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
198
198
  const child = spawn(npm, ["install", "-g", "beecork@latest"], { stdio: ["ignore", "pipe", "pipe"] });
199
- let out2 = "", done = false;
199
+ let out3 = "", done = false;
200
200
  const finish = (r) => {
201
201
  if (done) return;
202
202
  done = true;
@@ -208,13 +208,13 @@ function selfUpdate(timeoutMs = 12e4) {
208
208
  child.kill("SIGKILL");
209
209
  } catch {
210
210
  }
211
- finish({ ok: false, output: `${out2.trim()}
211
+ finish({ ok: false, output: `${out3.trim()}
212
212
  (update timed out after ${timeoutMs}ms \u2014 run manually: npm install -g beecork@latest)`.trim() });
213
213
  }, timeoutMs);
214
- child.stdout?.on("data", (d) => out2 += d);
215
- child.stderr?.on("data", (d) => out2 += d);
214
+ child.stdout?.on("data", (d) => out3 += d);
215
+ child.stderr?.on("data", (d) => out3 += d);
216
216
  child.on("error", (e) => finish({ ok: false, output: e.message }));
217
- child.on("close", (code) => finish({ ok: code === 0, output: out2.trim() }));
217
+ child.on("close", (code) => finish({ ok: code === 0, output: out3.trim() }));
218
218
  });
219
219
  }
220
220
 
@@ -256,27 +256,65 @@ function lineDiff(oldText, newText) {
256
256
  lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
257
257
  }
258
258
  }
259
- const out2 = [];
259
+ const out3 = [];
260
260
  let i = 0;
261
261
  let j = 0;
262
262
  while (i < m && j < n) {
263
263
  if (a[i] === b[j]) {
264
- out2.push(" " + a[i]);
264
+ out3.push(" " + a[i]);
265
265
  i++;
266
266
  j++;
267
267
  } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
268
- out2.push("- " + a[i]);
268
+ out3.push("- " + a[i]);
269
269
  i++;
270
270
  } else {
271
- out2.push("+ " + b[j]);
271
+ out3.push("+ " + b[j]);
272
272
  j++;
273
273
  }
274
274
  }
275
- while (i < m) out2.push("- " + a[i++]);
276
- while (j < n) out2.push("+ " + b[j++]);
277
- 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");
278
278
  }
279
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
+
280
318
  // src/ui.ts
281
319
  var useColor = process.env.NO_COLOR ? false : process.env.FORCE_COLOR ? true : Boolean(process.stdout.isTTY);
282
320
  var paint = (code) => (s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
@@ -294,12 +332,12 @@ var color = {
294
332
  };
295
333
  var isPrintableCodePoint = (c) => c >= 32 && c !== 127 && !(c >= 128 && c <= 159);
296
334
  function stripControl(s) {
297
- let out2 = "";
335
+ let out3 = "";
298
336
  for (const ch of s) {
299
337
  const c = ch.codePointAt(0);
300
- if (c === 9 || c === 10 || isPrintableCodePoint(c)) out2 += ch;
338
+ if (c === 9 || c === 10 || isPrintableCodePoint(c)) out3 += ch;
301
339
  }
302
- return out2;
340
+ return out3;
303
341
  }
304
342
  var stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
305
343
  function charWidth(cp) {
@@ -418,19 +456,19 @@ function startSpinner(label) {
418
456
  if (!process.stdout.isTTY || !useColor) return () => {
419
457
  };
420
458
  let i = 0;
421
- const draw2 = () => {
459
+ const draw = () => {
422
460
  if (steeringOnScreen) return;
423
- 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));
424
462
  };
425
- process.stdout.write("\x1B[?25l");
426
- draw2();
427
- const timer = setInterval(draw2, 80);
463
+ process.stdout.write(ansi.hideCursor);
464
+ draw();
465
+ const timer = setInterval(draw, 80);
428
466
  let stopped = false;
429
467
  return () => {
430
468
  if (stopped) return;
431
469
  stopped = true;
432
470
  clearInterval(timer);
433
- process.stdout.write("\r\x1B[2K");
471
+ process.stdout.write(ansi.cr + ansi.clearLine);
434
472
  };
435
473
  }
436
474
  function markLines(width) {
@@ -465,21 +503,21 @@ function printBanner(model, sources) {
465
503
  " | |_) | __/ __/ (_| (_) | | | < ",
466
504
  " |_.__/ \\___|\\___|\\___\\___/|_| |_|\\_\\"
467
505
  ];
468
- const mark = markLines(24);
469
- 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));
470
508
  const wordW = Math.max(...word.map((l) => l.length));
471
- const cols = process.stdout.columns || 80;
509
+ const cols2 = process.stdout.columns || 80;
472
510
  console.log();
473
- if (cols >= markW + 3 + wordW + 2) {
474
- const h = Math.max(mark.length, word.length);
475
- 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);
476
514
  const wPad = Math.floor((h - word.length) / 2);
477
515
  for (let i = 0; i < h; i++) {
478
- const m = (mark[i - mPad] ?? "").padEnd(markW);
516
+ const m = (mark2[i - mPad] ?? "").padEnd(markW);
479
517
  const w = word[i - wPad] ?? "";
480
518
  console.log(" " + color.brand(`${m} ${w}`));
481
519
  }
482
- } else if (cols >= wordW + 2) {
520
+ } else if (cols2 >= wordW + 2) {
483
521
  for (const w of word) console.log(" " + color.brand(w));
484
522
  } else {
485
523
  console.log(" " + color.brand("\u{1F41D} beecork"));
@@ -500,7 +538,7 @@ function printBanner(model, sources) {
500
538
  const plain = (l, v) => l ? l.padEnd(lw) + " " + v : v;
501
539
  const bw = Math.max(...rows2.map(([l, v]) => plain(l, v).length));
502
540
  const row = ([l, v]) => l ? color.bold(l.padEnd(lw)) + color.dim(" " + v) : color.dim(v);
503
- if (cols < bw + 6) {
541
+ if (cols2 < bw + 6) {
504
542
  for (const r of rows2) console.log(" " + row(r));
505
543
  } else {
506
544
  console.log(" " + color.dim("\u256D\u2500" + "\u2500".repeat(bw) + "\u2500\u256E"));
@@ -518,6 +556,39 @@ import { readFile as readFile4 } from "node:fs/promises";
518
556
 
519
557
  // src/input.ts
520
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
521
592
  var out = (s) => process.stdout.write(s);
522
593
  var active = null;
523
594
  var started = false;
@@ -525,13 +596,13 @@ function initInput() {
525
596
  if (started || !process.stdin.isTTY) return;
526
597
  started = true;
527
598
  process.stdin.setRawMode(true);
528
- out("\x1B[?2004l");
599
+ out(ansi.bracketedPasteOff);
529
600
  emitKeypressEvents(process.stdin);
530
601
  process.stdin.on("keypress", (str, key) => active?.(str, key));
531
602
  }
532
603
  function teardownInput() {
533
604
  if (started && process.stdin.isTTY) {
534
- out("\x1B[?2004h\x1B[?25h");
605
+ out(ansi.bracketedPasteOn + ansi.showCursor);
535
606
  process.stdin.setRawMode(false);
536
607
  process.stdin.pause();
537
608
  }
@@ -553,27 +624,27 @@ function readPrompt(opts) {
553
624
  ...(opts.skills ?? []).map((n) => ({ name: "/" + n, desc: "skill" }))
554
625
  ];
555
626
  return new Promise((resolve2) => {
556
- let buf = "";
557
- let cur = 0;
627
+ let buf2 = "";
628
+ let cur2 = 0;
558
629
  let hist = history.length;
559
- let sel = 0;
560
- let menuHidden = false;
561
- const BURST_IDLE_MS = 8;
562
- let burstLen = 0;
563
- let burstTimer = null;
564
- 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;
565
636
  const matches = () => {
566
637
  if (opts.mask) return [];
567
- const m = buf.match(/^\/(\S*)$/);
638
+ const m = buf2.match(/^\/(\S*)$/);
568
639
  if (!m) return [];
569
640
  const pre = "/" + m[1];
570
641
  return all.filter((c) => c.name.startsWith(pre)).slice(0, MENU_MAX);
571
642
  };
572
- const menu = () => menuHidden ? [] : matches();
643
+ const menu = () => menuHidden2 ? [] : matches();
573
644
  const highlight = () => {
574
- if (opts.mask) return "*".repeat(buf.length);
575
- const m = buf.match(/^(\/\S*)([\s\S]*)$/);
576
- 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;
577
648
  const [, token, rest] = m;
578
649
  const known = all.some((c) => c.name === token);
579
650
  const partial = all.some((c) => c.name.startsWith(token));
@@ -581,7 +652,6 @@ function readPrompt(opts) {
581
652
  return styled + rest;
582
653
  };
583
654
  let lastCurRow = 0;
584
- const rowColOf = (s) => ({ row: (s.match(/\n/g) || []).length, col: s.length - (s.lastIndexOf("\n") + 1) });
585
655
  function drawBlock() {
586
656
  const prompt = opts.promptString();
587
657
  const promptW = stripAnsi(prompt).length;
@@ -591,199 +661,189 @@ function readPrompt(opts) {
591
661
  for (let i = 1; i < lines.length; i++) out("\n" + indent + lines[i]);
592
662
  return { promptW };
593
663
  }
594
- function render() {
664
+ function render2() {
595
665
  const mm = menu();
596
- if (sel >= mm.length) sel = Math.max(0, mm.length - 1);
597
- out("\x1B[?25l");
598
- if (lastCurRow > 0) out(`\x1B[${lastCurRow}A`);
599
- 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);
600
670
  const { promptW } = drawBlock();
601
- const cols = Math.max(1, process.stdout.columns || 80);
671
+ const cols2 = Math.max(1, process.stdout.columns || 80);
602
672
  for (let i = 0; i < mm.length; i++) {
603
673
  const m = mm[i];
604
674
  const name = m.name.padEnd(10);
605
- const maxDesc = Math.max(0, cols - name.length - 4);
675
+ const maxDesc = Math.max(0, cols2 - name.length - 4);
606
676
  const desc = m.desc.length > maxDesc ? m.desc.slice(0, Math.max(0, maxDesc - 1)) + "\u2026" : m.desc;
607
- out("\n" + (i === sel ? color.green("\u203A " + name) + " " + color.dim(desc) : color.dim(" " + name + " " + desc)));
608
- }
609
- const text = opts.mask ? "*".repeat(buf.length) : buf;
610
- const physRows = text.split("\n").map((l) => Math.max(1, Math.ceil((promptW + displayWidth(l)) / cols)));
611
- const totalInputPhys = physRows.reduce((a, b) => a + b, 0);
612
- const before = opts.mask ? text : buf.slice(0, cur);
613
- const curLogicalRow = (before.match(/\n/g) || []).length;
614
- const curCol = promptW + displayWidth(before.slice(before.lastIndexOf("\n") + 1));
615
- const physBefore = physRows.slice(0, curLogicalRow).reduce((a, b) => a + b, 0);
616
- const curPhysRow = physBefore + Math.floor(curCol / cols);
617
- const curPhysCol = curCol % cols;
618
- const lastDrawnRow = totalInputPhys - 1 + mm.length;
619
- if (lastDrawnRow > curPhysRow) out(`\x1B[${lastDrawnRow - curPhysRow}A`);
620
- 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);
621
685
  lastCurRow = curPhysRow;
622
686
  }
623
687
  function finish(result) {
624
688
  restore();
625
- if (burstTimer) {
626
- clearTimeout(burstTimer);
627
- burstTimer = null;
628
- }
629
- pendingRender = false;
630
- out("\x1B[?25l");
631
- if (lastCurRow > 0) out(`\x1B[${lastCurRow}A`);
632
- 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);
633
697
  drawBlock();
634
698
  out("\n");
635
699
  if (result.type === "line" && result.value.trim() && !opts.mask) history.push(result.value);
636
700
  resolve2(result);
637
701
  }
638
- function insert(s) {
639
- buf = buf.slice(0, cur) + s + buf.slice(cur);
640
- cur += s.length;
641
- menuHidden = false;
642
- sel = 0;
643
- 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;
644
708
  }
645
- function onKey(str, key) {
709
+ function onKey2(str, key) {
646
710
  const mm = menu();
647
- burstLen++;
648
- if (burstTimer) clearTimeout(burstTimer);
649
- burstTimer = setTimeout(() => {
650
- burstTimer = null;
651
- burstLen = 0;
652
- if (pendingRender) {
653
- pendingRender = false;
654
- 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();
655
719
  }
656
- }, BURST_IDLE_MS);
720
+ }, BURST_IDLE_MS2);
657
721
  if (isEnter(key)) {
658
- if (key?.shift || key?.meta || burstLen > 1) {
659
- insert("\n");
722
+ if (key?.shift || key?.meta || burstLen2 > 1) {
723
+ insert2("\n");
660
724
  return;
661
725
  }
662
- return finish({ type: "line", value: buf });
726
+ return finish({ type: "line", value: buf2 });
663
727
  }
664
728
  if (key?.ctrl && key.name === "c") {
665
- if (buf) {
666
- buf = "";
667
- cur = 0;
668
- render();
729
+ if (buf2) {
730
+ buf2 = "";
731
+ cur2 = 0;
732
+ render2();
669
733
  } else finish({ type: "quit" });
670
734
  return;
671
735
  }
672
736
  if (key?.ctrl && key.name === "d") {
673
- if (!buf) finish({ type: "eof" });
737
+ if (!buf2) finish({ type: "eof" });
674
738
  return;
675
739
  }
676
740
  if (key?.name === "tab" && key.shift) {
677
741
  opts.onShiftTab?.();
678
- render();
742
+ render2();
679
743
  return;
680
744
  }
681
745
  if (key?.name === "tab") {
682
746
  if (mm.length) {
683
- buf = mm[sel].name + " ";
684
- cur = buf.length;
685
- menuHidden = false;
686
- render();
747
+ buf2 = mm[sel2].name + " ";
748
+ cur2 = buf2.length;
749
+ menuHidden2 = false;
750
+ render2();
687
751
  }
688
752
  return;
689
753
  }
690
754
  if (key?.name === "up") {
691
755
  if (mm.length) {
692
- sel = (sel - 1 + mm.length) % mm.length;
693
- render();
694
- } 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);
695
759
  else histPrev();
696
760
  return;
697
761
  }
698
762
  if (key?.name === "down") {
699
763
  if (mm.length) {
700
- sel = (sel + 1) % mm.length;
701
- render();
702
- } else if (buf.includes("\n")) moveVert(1);
764
+ sel2 = (sel2 + 1) % mm.length;
765
+ render2();
766
+ } else if (buf2.includes("\n")) moveVert(1);
703
767
  else histNext();
704
768
  return;
705
769
  }
706
770
  if (key?.name === "left") {
707
- if (cur > 0) cur--;
708
- render();
771
+ if (cur2 > 0) cur2--;
772
+ render2();
709
773
  return;
710
774
  }
711
775
  if (key?.name === "right") {
712
- if (cur < buf.length) cur++;
713
- render();
776
+ if (cur2 < buf2.length) cur2++;
777
+ render2();
714
778
  return;
715
779
  }
716
780
  if (key?.name === "home" || key?.ctrl && key.name === "a") {
717
- cur = 0;
718
- render();
781
+ cur2 = 0;
782
+ render2();
719
783
  return;
720
784
  }
721
785
  if (key?.name === "end" || key?.ctrl && key.name === "e") {
722
- cur = buf.length;
723
- render();
786
+ cur2 = buf2.length;
787
+ render2();
724
788
  return;
725
789
  }
726
790
  if (key?.ctrl && key.name === "u") {
727
- buf = buf.slice(cur);
728
- cur = 0;
729
- menuHidden = false;
730
- render();
791
+ buf2 = buf2.slice(cur2);
792
+ cur2 = 0;
793
+ menuHidden2 = false;
794
+ render2();
731
795
  return;
732
796
  }
733
797
  if (key?.name === "backspace") {
734
- if (cur > 0) {
735
- buf = buf.slice(0, cur - 1) + buf.slice(cur);
736
- cur--;
737
- menuHidden = false;
738
- render();
798
+ if (cur2 > 0) {
799
+ buf2 = buf2.slice(0, cur2 - 1) + buf2.slice(cur2);
800
+ cur2--;
801
+ menuHidden2 = false;
802
+ render2();
739
803
  }
740
804
  return;
741
805
  }
742
806
  if (key?.name === "delete") {
743
- if (cur < buf.length) {
744
- buf = buf.slice(0, cur) + buf.slice(cur + 1);
745
- menuHidden = false;
746
- render();
807
+ if (cur2 < buf2.length) {
808
+ buf2 = buf2.slice(0, cur2) + buf2.slice(cur2 + 1);
809
+ menuHidden2 = false;
810
+ render2();
747
811
  }
748
812
  return;
749
813
  }
750
814
  if (key?.name === "escape") {
751
815
  if (mm.length) {
752
- menuHidden = true;
753
- render();
816
+ menuHidden2 = true;
817
+ render2();
754
818
  }
755
819
  return;
756
820
  }
757
821
  if (str && !key?.ctrl && !key?.meta && [...str].length === 1) {
758
- if (isPrintableCodePoint(str.codePointAt(0))) insert(str);
822
+ if (isPrintableCodePoint(str.codePointAt(0))) insert2(str);
759
823
  }
760
824
  }
761
825
  function moveVert(dir) {
762
- const lines = buf.split("\n");
763
- const { row, col } = rowColOf(buf.slice(0, cur));
764
- const target = row + dir;
765
- if (target < 0 || target >= lines.length) return;
766
- let idx = 0;
767
- for (let i = 0; i < target; i++) idx += lines[i].length + 1;
768
- cur = idx + Math.min(col, lines[target].length);
769
- render();
826
+ const idx = moveVertIndex(buf2, cur2, dir);
827
+ if (idx === null) return;
828
+ cur2 = idx;
829
+ render2();
770
830
  }
771
831
  function histPrev() {
772
832
  if (history.length === 0) return;
773
833
  hist = Math.max(0, hist - 1);
774
- buf = history[hist] ?? "";
775
- cur = buf.length;
776
- render();
834
+ buf2 = history[hist] ?? "";
835
+ cur2 = buf2.length;
836
+ render2();
777
837
  }
778
838
  function histNext() {
779
839
  if (hist >= history.length) return;
780
840
  hist += 1;
781
- buf = hist === history.length ? "" : history[hist];
782
- cur = buf.length;
783
- render();
841
+ buf2 = hist === history.length ? "" : history[hist];
842
+ cur2 = buf2.length;
843
+ render2();
784
844
  }
785
- const restore = pushKeyHandler(onKey);
786
- render();
845
+ const restore = pushKeyHandler(onKey2);
846
+ render2();
787
847
  });
788
848
  }
789
849
  function readChoice(prompt) {
@@ -804,36 +864,36 @@ function readChoice(prompt) {
804
864
  function selectMenu(opts) {
805
865
  if (!process.stdin.isTTY || opts.items.length === 0) return Promise.resolve(null);
806
866
  return new Promise((resolve2) => {
807
- 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));
808
868
  let drawn = 0;
809
- function render() {
810
- out("\x1B[?25l");
811
- if (drawn > 0) out(`\x1B[${drawn}A`);
812
- 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);
813
873
  out(color.dim(opts.title) + "\n");
814
874
  opts.items.forEach((it, i) => {
815
- const row = i === sel ? color.green("\u203A " + it.label) : " " + it.label;
875
+ const row = i === sel2 ? color.green("\u203A " + it.label) : " " + it.label;
816
876
  out(row + (it.hint ? color.dim(" " + it.hint) : "") + "\n");
817
877
  });
818
878
  drawn = opts.items.length + 1;
819
879
  }
820
880
  function finish(v) {
821
881
  restore();
822
- if (drawn > 0) out(`\x1B[${drawn}A\r\x1B[J`);
823
- out("\x1B[?25h");
882
+ if (drawn > 0) out(ansi.up(drawn) + ansi.cr + ansi.clearToEnd);
883
+ out(ansi.showCursor);
824
884
  resolve2(v);
825
885
  }
826
886
  const restore = pushKeyHandler((_str, key) => {
827
887
  if (key?.name === "up") {
828
- sel = (sel - 1 + opts.items.length) % opts.items.length;
829
- render();
888
+ sel2 = (sel2 - 1 + opts.items.length) % opts.items.length;
889
+ render2();
830
890
  } else if (key?.name === "down") {
831
- sel = (sel + 1) % opts.items.length;
832
- render();
833
- } 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);
834
894
  else if (key?.name === "escape" || key?.name === "q" || key?.ctrl && key.name === "c") finish(null);
835
895
  });
836
- render();
896
+ render2();
837
897
  });
838
898
  }
839
899
 
@@ -843,46 +903,46 @@ function renderFileBox(path, startLine, lines, hasMore) {
843
903
  const p = stripControl(path);
844
904
  const numW = String(startLine + Math.max(0, lines.length - 1)).length;
845
905
  const header = startLine === 1 && !hasMore ? `${lines.length} line${lines.length === 1 ? "" : "s"}` : `lines ${startLine}\u2013${startLine + lines.length - 1}${hasMore ? " (+ more)" : ""}`;
846
- const out2 = [color.dim("\u256D\u2500 ") + color.bold(p) + color.dim(` ${header}`)];
847
- lines.forEach((l, i) => out2.push(color.dim("\u2502 ") + color.dim(String(startLine + i).padStart(numW)) + " " + stripControl(l)));
848
- if (hasMore) out2.push(color.dim("\u2502 ") + color.dim(`\u2026 more (show ${p} offset ${startLine + lines.length})`));
849
- out2.push(color.dim("\u2570\u2500"));
850
- 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";
851
911
  }
852
912
  function renderListing(path, names) {
853
913
  const safe = names.map(stripControl);
854
- 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"}`)];
855
915
  if (safe.length === 0) {
856
- out2.push(color.dim("\u2502 ") + color.dim("(empty)"));
916
+ out3.push(color.dim("\u2502 ") + color.dim("(empty)"));
857
917
  } else {
858
918
  const avail = BOX_W() - 2;
859
919
  const colW = Math.min(Math.max(...safe.map((n) => n.length)) + 2, 40);
860
- const cols = Math.max(1, Math.floor(avail / colW));
861
- 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);
862
922
  for (let r = 0; r < rows2; r++) {
863
923
  let line = "";
864
- for (let c = 0; c < cols; c++) {
924
+ for (let c = 0; c < cols2; c++) {
865
925
  const idx = c * rows2 + r;
866
926
  if (idx >= safe.length) continue;
867
927
  const n = safe[idx];
868
928
  line += (n.endsWith("/") ? color.cyan(n) : n) + " ".repeat(Math.max(1, colW - n.length));
869
929
  }
870
- out2.push(color.dim("\u2502 ") + line.replace(/\s+$/, ""));
930
+ out3.push(color.dim("\u2502 ") + line.replace(/\s+$/, ""));
871
931
  }
872
932
  }
873
- out2.push(color.dim("\u2570\u2500"));
874
- return out2.join("\n") + "\n";
933
+ out3.push(color.dim("\u2570\u2500"));
934
+ return out3.join("\n") + "\n";
875
935
  }
876
936
  function renderTree(path, items, truncated) {
877
937
  const dirs = items.filter((it) => it.isDir).length;
878
- 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`)];
879
939
  for (const it of items) {
880
940
  const nm = stripControl(it.name);
881
- 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));
882
942
  }
883
- if (truncated) out2.push(color.dim("\u2502 ") + color.dim("\u2026 (truncated)"));
884
- out2.push(color.dim("\u2570\u2500"));
885
- 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";
886
946
  }
887
947
  var SHOW_MARK = "";
888
948
  var SHOW_KINDS = ["file", "dir", "tree"];
@@ -991,14 +1051,14 @@ function renderTable(rows2) {
991
1051
  })
992
1052
  );
993
1053
  const width = Array.from({ length: ncol }, (_, c) => Math.max(...styled.map((r) => visLen(r[c]))));
994
- const out2 = styled.map((r) => {
1054
+ const out3 = styled.map((r) => {
995
1055
  const cells = r.map((cell, c) => cell + " ".repeat(Math.max(0, width[c] - visLen(cell))));
996
1056
  return " " + cells.join(color.dim(" \u2502 "));
997
1057
  });
998
- return out2.join("\n") + "\n";
1058
+ return out3.join("\n") + "\n";
999
1059
  }
1000
1060
  function createMarkdownStream(write) {
1001
- let buf = "";
1061
+ let buf2 = "";
1002
1062
  let scanFrom = 0;
1003
1063
  let inCode = false;
1004
1064
  let first = true;
@@ -1037,19 +1097,19 @@ function createMarkdownStream(write) {
1037
1097
  }
1038
1098
  return {
1039
1099
  push(text) {
1040
- buf += text;
1100
+ buf2 += text;
1041
1101
  let i;
1042
- while ((i = buf.indexOf("\n", scanFrom)) >= 0) {
1043
- emit(buf.slice(0, i));
1044
- 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);
1045
1105
  scanFrom = 0;
1046
1106
  }
1047
- scanFrom = buf.length;
1107
+ scanFrom = buf2.length;
1048
1108
  },
1049
1109
  end() {
1050
- if (buf) {
1051
- emit(buf);
1052
- buf = "";
1110
+ if (buf2) {
1111
+ emit(buf2);
1112
+ buf2 = "";
1053
1113
  }
1054
1114
  flushTable();
1055
1115
  if (inCode) {
@@ -1471,13 +1531,13 @@ async function atomicWrite(abs, content) {
1471
1531
  }
1472
1532
  var READ_PREFIX = /^ *\d+ {2}/;
1473
1533
  function allIndexOf(hay, needle) {
1474
- const out2 = [];
1534
+ const out3 = [];
1475
1535
  let i = hay.indexOf(needle);
1476
1536
  while (i !== -1) {
1477
- out2.push(i);
1537
+ out3.push(i);
1478
1538
  i = hay.indexOf(needle, i + 1);
1479
1539
  }
1480
- return out2;
1540
+ return out3;
1481
1541
  }
1482
1542
  function stripReadPrefix(text) {
1483
1543
  const lines = text.split("\n");
@@ -1972,11 +2032,11 @@ ${res.closest}` : `Re-read the file and copy the exact text (including whitespac
1972
2032
  ${stderr}` : "") || "(no output)";
1973
2033
  } catch (err) {
1974
2034
  const e = err;
1975
- const out2 = `${e.stdout ?? ""}${e.stderr ? `
2035
+ const out3 = `${e.stdout ?? ""}${e.stderr ? `
1976
2036
  [stderr]
1977
2037
  ${e.stderr}` : ""}`.trim();
1978
- return `Error: command failed${out2 ? `:
1979
- ${out2}` : `: ${String(e.message ?? err)}`}`;
2038
+ return `Error: command failed${out3 ? `:
2039
+ ${out3}` : `: ${String(e.message ?? err)}`}`;
1980
2040
  }
1981
2041
  }
1982
2042
  },
@@ -2221,14 +2281,14 @@ function validateArgs(tool, args) {
2221
2281
  async function runVerify(signal) {
2222
2282
  try {
2223
2283
  const { stdout, stderr } = await runShell(config.verifyCommand, { timeout: config.verifyTimeoutMs, maxBuffer: config.maxToolBuffer, signal });
2224
- const out2 = `${stdout}${stderr}`.trim();
2225
- return `passed \u2713${out2 ? `
2226
- ${out2.slice(-800)}` : ""}`;
2284
+ const out3 = `${stdout}${stderr}`.trim();
2285
+ return `passed \u2713${out3 ? `
2286
+ ${out3.slice(-800)}` : ""}`;
2227
2287
  } catch (err) {
2228
2288
  const e = err;
2229
- const out2 = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || String(e.message ?? err);
2289
+ const out3 = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || String(e.message ?? err);
2230
2290
  return `FAILED \u2717
2231
- ${out2.slice(-1500)}`;
2291
+ ${out3.slice(-1500)}`;
2232
2292
  }
2233
2293
  }
2234
2294
 
@@ -2261,12 +2321,12 @@ function parseSSELine(line) {
2261
2321
  }
2262
2322
  const delta = parsed.choices?.[0]?.delta;
2263
2323
  if (!delta) return null;
2264
- const out2 = {};
2265
- if (delta.content) out2.content = delta.content;
2266
- if (delta.tool_calls) out2.toolCalls = delta.tool_calls;
2267
- if (typeof delta.reasoning === "string" && delta.reasoning) out2.reasoning = delta.reasoning;
2268
- if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length) out2.reasoningDetails = delta.reasoning_details;
2269
- 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;
2270
2330
  }
2271
2331
  function buildRequestBody(opts) {
2272
2332
  const { model, messages, includeTools, effort, reasoningSupported, extra, tools } = opts;
@@ -2672,7 +2732,7 @@ async function saveSession(messages) {
2672
2732
  }
2673
2733
  function sanitizeSession(raw) {
2674
2734
  if (!Array.isArray(raw)) return null;
2675
- const out2 = [];
2735
+ const out3 = [];
2676
2736
  for (const m of raw) {
2677
2737
  if (!m || typeof m !== "object") return null;
2678
2738
  const role = m.role;
@@ -2685,9 +2745,9 @@ function sanitizeSession(raw) {
2685
2745
  if (Array.isArray(tc)) msg.tool_calls = tc;
2686
2746
  const tcid = m.tool_call_id;
2687
2747
  if (typeof tcid === "string") msg.tool_call_id = tcid;
2688
- out2.push(msg);
2748
+ out3.push(msg);
2689
2749
  }
2690
- return dropIncompleteToolTail(out2);
2750
+ return dropIncompleteToolTail(out3);
2691
2751
  }
2692
2752
  function dropIncompleteToolTail(messages) {
2693
2753
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -2730,15 +2790,15 @@ async function loadLatestSession() {
2730
2790
  async function listSessions() {
2731
2791
  try {
2732
2792
  const files = (await readdir2(sessionsDir())).filter((f) => f.endsWith(".json"));
2733
- const out2 = [];
2793
+ const out3 = [];
2734
2794
  for (const f of files) {
2735
2795
  const msgs = await readSession(f);
2736
2796
  if (!msgs || !msgs.length) continue;
2737
2797
  const firstUser = msgs.find((m) => m.role === "user");
2738
2798
  const preview = (firstUser?.content ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
2739
- 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 });
2740
2800
  }
2741
- return out2.sort((a, b) => b.when - a.when);
2801
+ return out3.sort((a, b) => b.when - a.when);
2742
2802
  } catch {
2743
2803
  return [];
2744
2804
  }
@@ -2978,7 +3038,7 @@ ${content}` }];
2978
3038
  }
2979
3039
  return [...messages, { role: "user", content }];
2980
3040
  }
2981
- async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal, steering) {
3041
+ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, signal, steering2) {
2982
3042
  messages.push({ role: "user", content: userInput });
2983
3043
  const snapshot = messages.slice();
2984
3044
  try {
@@ -2992,7 +3052,7 @@ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKey
2992
3052
  console.error(color.red(`
2993
3053
  [compaction failed: ${err.message} \u2014 continuing]`) + "\n");
2994
3054
  }
2995
- if (steering?.length) messages = applySteering(messages, steering.splice(0));
3055
+ if (steering2?.length) messages = applySteering(messages, steering2.splice(0));
2996
3056
  const message = await callModel(messages, true, signal);
2997
3057
  messages.push(message);
2998
3058
  if (!hasContent2(message)) {
@@ -3006,7 +3066,7 @@ async function runTurn(messages, userInput, ask, approvedTools, approvedGuardKey
3006
3066
  await handleToolCall(call, messages, step, deps);
3007
3067
  }
3008
3068
  } else {
3009
- answered = !steering?.length;
3069
+ answered = !steering2?.length;
3010
3070
  }
3011
3071
  }
3012
3072
  if (signal?.aborted) {
@@ -3078,62 +3138,395 @@ async function runtimeContext() {
3078
3138
  });
3079
3139
  }
3080
3140
 
3081
- // src/statusline.ts
3141
+ // src/chrome.ts
3082
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;
3083
3146
  var active2 = false;
3084
- var drawTimer = null;
3085
- 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;
3086
3160
  var branch = "";
3087
- var tokensOf = null;
3088
- 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
+ }
3089
3197
  function pollGit() {
3090
- 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) => {
3091
3199
  if (err) {
3092
3200
  branch = "";
3093
3201
  return;
3094
3202
  }
3095
- const b = String(out2).trim();
3096
- execFile2("git", ["status", "--porcelain"], { timeout: 1500, windowsHide: true }, (e2, out22) => {
3097
- 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() ? "*" : "");
3098
3206
  });
3099
3207
  });
3100
3208
  }
3101
- function segments() {
3102
- const parts = [state.model.split("/").pop() ?? state.model, state.reasoningEffort];
3103
- if (branch) parts.push(branch);
3104
- if (tokensOf) parts.push(`~${Math.round(tokensOf() / 1e3)}k`);
3105
- const bg = runningTaskCount();
3106
- if (bg > 0) parts.push(`${bg} task${bg === 1 ? "" : "s"}`);
3107
- 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)));
3213
+ }
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);
3108
3256
  }
3109
- function draw() {
3257
+ function render() {
3110
3258
  if (!active2) return;
3111
- 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;
3112
3283
  }
3113
3284
  function onResize() {
3114
3285
  if (!active2) return;
3115
- process.stdout.write(`\x1B[1;${rows() - 1}r`);
3116
- 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
+ }
3117
3486
  }
3118
- function startStatusline(tokens) {
3119
- if (active2 || !process.stdout.isTTY || !config.statuslineEnabled) return;
3487
+ function startChrome(opts) {
3488
+ if (active2 || !process.stdout.isTTY) return;
3120
3489
  active2 = true;
3121
- tokensOf = tokens;
3122
- process.stdout.write(`\x1B[1;${rows() - 1}r`);
3490
+ tokensOf = opts.tokens;
3491
+ allItems = opts.items;
3492
+ onInterrupt = opts.onInterrupt;
3493
+ out2(ansi.bracketedPasteOff);
3123
3494
  pollGit();
3124
- draw();
3125
- drawTimer = setInterval(draw, config.statuslineRefreshMs);
3495
+ restoreKeys = pushKeyHandler(onKey);
3496
+ render();
3497
+ statusTimer = setInterval(refreshChrome, config.statuslineRefreshMs);
3126
3498
  gitTimer = setInterval(pollGit, 5e3);
3127
3499
  process.stdout.on("resize", onResize);
3128
3500
  }
3129
- 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() {
3130
3518
  if (!active2) return;
3131
3519
  active2 = false;
3132
- if (drawTimer) clearInterval(drawTimer);
3133
- if (gitTimer) clearInterval(gitTimer);
3520
+ restoreKeys?.();
3521
+ restoreKeys = null;
3134
3522
  process.stdout.removeListener("resize", onResize);
3135
- 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);
3136
3528
  }
3529
+ var chromeEnabled = () => config.statuslineEnabled && !!process.stdout.isTTY;
3137
3530
 
3138
3531
  // src/commands.ts
3139
3532
  import { writeFile as writeFile4, mkdir as mkdir4, chmod as chmod3 } from "node:fs/promises";
@@ -3187,6 +3580,13 @@ ${extra}` : "");
3187
3580
  }
3188
3581
 
3189
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
+ }
3190
3590
  var SLASH_COMMANDS = [
3191
3591
  { name: "/model", desc: "switch model (menu; /model <term> searches)" },
3192
3592
  { name: "/effort", desc: "set reasoning effort (off/low/medium/high/max)" },
@@ -3271,13 +3671,13 @@ async function handleCommand(input, messages) {
3271
3671
  }
3272
3672
  } else if (cmd === "/clear") {
3273
3673
  messages.splice(1);
3274
- 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);
3275
3675
  console.log(color.dim("conversation cleared (kept the system prompt, your model + settings).") + "\n");
3276
3676
  } else if (cmd === "/resume") {
3277
3677
  const sessions = process.stdin.isTTY ? await listSessions() : [];
3278
3678
  let restored = [];
3279
3679
  if (sessions.length > 1) {
3280
- const choice = await selectMenu({
3680
+ const choice = await pick({
3281
3681
  title: "resume which session? \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3282
3682
  items: sessions.map((s) => ({
3283
3683
  label: s.preview || "(no first message)",
@@ -3330,7 +3730,7 @@ async function handleCommand(input, messages) {
3330
3730
  }
3331
3731
  async function pickModel() {
3332
3732
  if (!process.stdin.isTTY) return showRecommended();
3333
- const choice = await selectMenu({
3733
+ const choice = await pick({
3334
3734
  title: "switch model \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3335
3735
  initial: Math.max(0, RECOMMENDED_MODELS.findIndex((m) => m.slug === state.model)),
3336
3736
  items: RECOMMENDED_MODELS.map((m) => ({
@@ -3353,7 +3753,7 @@ async function pickEffort() {
3353
3753
  console.log(color.cyan(`reasoning effort: ${state.reasoningEffort}`) + color.dim(` (levels: ${EFFORTS.join(" / ")})`) + "\n");
3354
3754
  return;
3355
3755
  }
3356
- const choice = await selectMenu({
3756
+ const choice = await pick({
3357
3757
  title: "reasoning effort \u2014 \u2191/\u2193 then Enter (Esc to cancel)",
3358
3758
  initial: Math.max(0, EFFORTS.indexOf(state.reasoningEffort)),
3359
3759
  items: EFFORTS.map((e) => ({
@@ -3388,7 +3788,7 @@ async function searchModels(term) {
3388
3788
  }
3389
3789
  const priceOf = (m) => m.pricing?.prompt != null ? `$${(parseFloat(m.pricing.prompt) * 1e6).toFixed(2)}/1M` : "?";
3390
3790
  if (process.stdin.isTTY) {
3391
- const choice = await selectMenu({
3791
+ const choice = await pick({
3392
3792
  title: `models matching "${term}" \u2014 \u2191/\u2193 then Enter (Esc to cancel)`,
3393
3793
  items: matches.map((m) => ({
3394
3794
  label: m.id,
@@ -3497,7 +3897,7 @@ ${instr.trusted}`;
3497
3897
  };
3498
3898
  const tty = !!process.stdin.isTTY;
3499
3899
  process.on("exit", killAllTasks);
3500
- process.on("exit", stopStatusline);
3900
+ process.on("exit", stopChrome);
3501
3901
  if (tty) {
3502
3902
  initInput();
3503
3903
  process.on("exit", teardownInput);
@@ -3516,6 +3916,7 @@ ${instr.trusted}`;
3516
3916
  });
3517
3917
  }
3518
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);
3519
3920
  printBanner(state.model, instr.sources.map(tildify));
3520
3921
  if (config.dangerouslySkipPermissions) {
3521
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");
@@ -3565,10 +3966,19 @@ ${instr.trusted}`;
3565
3966
  }
3566
3967
  });
3567
3968
  }
3568
- 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
+ });
3569
3975
  while (true) {
3570
3976
  let userInput;
3571
- if (tty) {
3977
+ if (chromeEnabled()) {
3978
+ const r = await nextLine();
3979
+ if (r.type === "quit") break;
3980
+ userInput = r.value;
3981
+ } else if (tty) {
3572
3982
  const r = await readPrompt({
3573
3983
  promptString,
3574
3984
  commands: SLASH_COMMANDS,
@@ -3606,16 +4016,27 @@ ${instr.trusted}`;
3606
4016
  continue;
3607
4017
  }
3608
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
+ }
3609
4030
  activeTurn = new AbortController();
3610
4031
  let modeChangedMidTurn = false;
3611
- const steering = [];
4032
+ const steering2 = [];
3612
4033
  let typed = "";
3613
- 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));
3614
4035
  const clearSteer = () => {
3615
- process.stdout.write("\r\x1B[2K");
4036
+ process.stdout.write(ansi.cr + ansi.clearLine);
3616
4037
  setSteeringActive(false);
3617
4038
  };
3618
- const restoreKeys = tty ? pushKeyHandler((str, key) => {
4039
+ const restoreKeys2 = tty ? pushKeyHandler((str, key) => {
3619
4040
  if (key && (key.name === "escape" || key.ctrl && key.name === "c")) {
3620
4041
  activeTurn?.abort();
3621
4042
  return;
@@ -3630,7 +4051,7 @@ ${instr.trusted}`;
3630
4051
  typed = "";
3631
4052
  clearSteer();
3632
4053
  if (note) {
3633
- steering.push(note);
4054
+ steering2.push(note);
3634
4055
  console.log(color.dim(`\xBB queued for next step: ${note}`));
3635
4056
  }
3636
4057
  return;
@@ -3650,9 +4071,9 @@ ${instr.trusted}`;
3650
4071
  }) : () => {
3651
4072
  };
3652
4073
  try {
3653
- messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering);
4074
+ messages = await runTurn(messages, userInput, ask, approvedTools, approvedGuardKeys, activeTurn.signal, steering2);
3654
4075
  } finally {
3655
- restoreKeys();
4076
+ restoreKeys2();
3656
4077
  setSteeringActive(false);
3657
4078
  activeTurn = null;
3658
4079
  if (modeChangedMidTurn) console.log(color.yellow(`\u25B8 mode: ${modeLabel(state.mode)}`));
@@ -3660,6 +4081,7 @@ ${instr.trusted}`;
3660
4081
  if (bg > 0) console.log(color.dim(`\u25B8 ${bg} background task${bg === 1 ? "" : "s"} running \u2014 check_task / stop_task`));
3661
4082
  }
3662
4083
  }
4084
+ stopChrome();
3663
4085
  teardownInput();
3664
4086
  rl?.close();
3665
4087
  await persist();