@standardagents/code 0.6.7 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2228,6 +2228,92 @@ async function readClipboardImage() {
2228
2228
  function imagePlaceholder(seq) {
2229
2229
  return `[#Image ${seq}]`;
2230
2230
  }
2231
+ var INPUT_BOX_MARGIN = 1;
2232
+ function inputBoxBorderColor() {
2233
+ return "\x1B[38;5;240m";
2234
+ }
2235
+ function borderLevelDots(level) {
2236
+ const n = Math.max(1, Math.min(5, level));
2237
+ return "\u25CF".repeat(n) + "\u25CB".repeat(5 - n);
2238
+ }
2239
+ function borderLevelDotsStyled(level, levelColor, emptyColor = "\x1B[38;5;240m", reset = "\x1B[0m") {
2240
+ const n = Math.max(1, Math.min(5, level));
2241
+ let out = "";
2242
+ for (let i = 1; i <= 5; i++) {
2243
+ out += i <= n ? `${levelColor}\u25CF${reset}` : `${emptyColor}\u25CB${reset}`;
2244
+ }
2245
+ return out;
2246
+ }
2247
+ function inputBoxGeometry(cols2) {
2248
+ const margin = INPUT_BOX_MARGIN;
2249
+ const boxW = Math.max(12, cols2 - 1 - margin);
2250
+ const contentW = Math.max(4, boxW - 4);
2251
+ const leftPad = margin + 2;
2252
+ return { margin, boxW, contentW, leftPad };
2253
+ }
2254
+ function boxVisibleWidth(s) {
2255
+ let w = 0;
2256
+ for (const ch of s.replace(/\x1b\[[0-9;]*m/g, "")) {
2257
+ const cp = ch.codePointAt(0);
2258
+ w += isWideCodePoint(cp) ? 2 : 1;
2259
+ }
2260
+ return w;
2261
+ }
2262
+ function buildBoxTop(boxW, levelText, borderColor, levelColor, reset = "\x1B[0m", levelStyled) {
2263
+ const labelPlain = ` ${levelText} `;
2264
+ const rightFill = 1;
2265
+ const budget = Math.max(1, boxW - 2 - rightFill);
2266
+ const plain = labelPlain.length > budget ? labelPlain.slice(0, Math.max(1, budget)) : labelPlain;
2267
+ const leftFill = Math.max(1, boxW - 2 - plain.length - rightFill);
2268
+ const mid = levelStyled !== void 0 ? ` ${levelStyled} ` : `${levelColor}${plain}${reset}`;
2269
+ return `${borderColor}\u256D${"\u2500".repeat(leftFill)}${reset}` + mid + `${borderColor}${"\u2500".repeat(rightFill)}\u256E${reset}`;
2270
+ }
2271
+ function buildBoxBottom(boxW, borderColor, reset = "\x1B[0m") {
2272
+ return `${borderColor}\u2570${"\u2500".repeat(Math.max(1, boxW - 2))}\u256F${reset}`;
2273
+ }
2274
+ function buildBoxTopPlain(boxW, borderColor, reset = "\x1B[0m") {
2275
+ return `${borderColor}\u256D${"\u2500".repeat(Math.max(1, boxW - 2))}\u256E${reset}`;
2276
+ }
2277
+ function buildBoxBody(content, contentW, borderColor, reset = "\x1B[0m") {
2278
+ const vw = boxVisibleWidth(content);
2279
+ const pad = Math.max(0, contentW - vw);
2280
+ return `${borderColor}\u2502${reset} ${content}${" ".repeat(pad)} ${borderColor}\u2502${reset}`;
2281
+ }
2282
+ function wrapInputBodyLines(inputBuffer, prefix, prefixWidth, contentW) {
2283
+ const indent = " ".repeat(Math.max(0, prefixWidth));
2284
+ const lines = inputBuffer.split("\n");
2285
+ const out = [];
2286
+ let isFirstPhysical = true;
2287
+ for (let i = 0; i < lines.length; i++) {
2288
+ const text = lines[i];
2289
+ let remaining = text;
2290
+ do {
2291
+ const lead = isFirstPhysical ? prefix : indent;
2292
+ const avail = Math.max(0, contentW - prefixWidth);
2293
+ let take = Math.min(remaining.length, avail);
2294
+ if (take === 0 && avail === 0 && remaining.length > 0) take = 1;
2295
+ const chunk = remaining.slice(0, take);
2296
+ remaining = remaining.slice(take);
2297
+ out.push(lead + chunk);
2298
+ isFirstPhysical = false;
2299
+ if (remaining.length === 0) break;
2300
+ } while (remaining.length > 0);
2301
+ }
2302
+ if (out.length === 0) out.push(prefix);
2303
+ return out;
2304
+ }
2305
+ function buildInputBoxRows(opts) {
2306
+ const { cols: cols2, inputBuffer, prefix, prefixWidth, level, levelColor } = opts;
2307
+ const borderColor = opts.borderColor ?? inputBoxBorderColor();
2308
+ const geo = inputBoxGeometry(cols2);
2309
+ const body = wrapInputBodyLines(inputBuffer, prefix, prefixWidth, geo.contentW);
2310
+ const dots = borderLevelDots(level);
2311
+ const dotsStyled = borderLevelDotsStyled(level, levelColor);
2312
+ const top = buildBoxTop(geo.boxW, dots, borderColor, levelColor, "\x1B[0m", dotsStyled);
2313
+ const bottom = buildBoxBottom(geo.boxW, borderColor);
2314
+ const pad = " ".repeat(geo.margin);
2315
+ return [pad + top, ...body.map((b) => pad + buildBoxBody(b, geo.contentW, borderColor)), pad + bottom];
2316
+ }
2231
2317
  var C = {
2232
2318
  reset: "\x1B[0m",
2233
2319
  dim: "\x1B[2m",
@@ -2415,10 +2501,21 @@ var Tui = class _Tui {
2415
2501
  }
2416
2502
  /** Tear down the bottom region and restore the terminal (called on quit). */
2417
2503
  end() {
2504
+ this.started = false;
2505
+ this.working = false;
2506
+ this.subagents = [];
2418
2507
  if (this.quitTimer) clearTimeout(this.quitTimer);
2419
2508
  this.quitTimer = null;
2420
2509
  if (this.streamIdleTimer) clearTimeout(this.streamIdleTimer);
2421
2510
  this.streamIdleTimer = null;
2511
+ if (this.streamRedrawTimer) {
2512
+ clearTimeout(this.streamRedrawTimer);
2513
+ this.streamRedrawTimer = null;
2514
+ }
2515
+ if (this.spinnerTimer) {
2516
+ clearInterval(this.spinnerTimer);
2517
+ this.spinnerTimer = null;
2518
+ }
2422
2519
  this.clearBottom();
2423
2520
  process.stdout.write("\x1B[?2004l\x1B[?25h");
2424
2521
  }
@@ -2651,45 +2748,58 @@ var Tui = class _Tui {
2651
2748
  }
2652
2749
  // ─── input layout + vertical caret movement ────────────────────────────────
2653
2750
  /**
2654
- * The input's physical rows (same wrapping math as renderBottom: logical
2655
- * lines split on "\n", line 0 led by the prompt prefix, each wrapping at the
2656
- * terminal width) plus where the caret sits among them. Each row records the
2657
- * buffer index of its first character, its character count, and the visual
2658
- * column its first character renders at (only row 0 is offset, by the
2659
- * prompt). Drives ↑/↓: row 0 is "the top line" (history recall territory),
2660
- * anything below moves the caret instead.
2751
+ * The input's physical rows (same wrapping math as the boxed input body:
2752
+ * logical lines split on "\n", line 0 led by the prompt prefix, each wrapping
2753
+ * at the box content width) plus where the caret sits among them. Each row
2754
+ * records the buffer index of its first character, its character count, and
2755
+ * the visual column its first character renders at (only the first physical
2756
+ * row of line 0 is offset, by the prompt). Drives ↑/↓: row 0 is "the top
2757
+ * line" (history recall territory), anything below moves the caret instead.
2661
2758
  */
2662
2759
  inputLayout() {
2663
2760
  const cols2 = process.stdout.columns || 80;
2761
+ const { contentW } = inputBoxGeometry(cols2);
2664
2762
  const pw = this.visibleWidth(this.promptPrefix());
2665
2763
  const lines = this.inputBuffer.split("\n");
2666
2764
  const rows = [];
2667
2765
  let offset = 0;
2668
2766
  for (let i = 0; i < lines.length; i++) {
2669
- const lead = i === 0 ? pw : 0;
2670
2767
  const len = lines[i].length;
2671
- const nRows = Math.max(1, Math.ceil((lead + len) / cols2));
2672
- for (let j = 0; j < nRows; j++) {
2673
- const charStart = Math.max(0, j * cols2 - lead);
2674
- const charEnd = Math.min(len, (j + 1) * cols2 - lead);
2675
- rows.push({ start: offset + charStart, len: Math.max(0, charEnd - charStart), colOffset: j === 0 ? lead : 0 });
2676
- }
2768
+ let remaining = len;
2769
+ let charStart = 0;
2770
+ do {
2771
+ const avail = Math.max(0, contentW - pw);
2772
+ let take = Math.min(remaining, avail);
2773
+ if (take === 0 && avail === 0 && remaining > 0) take = 1;
2774
+ rows.push({ start: offset + charStart, len: take, colOffset: pw });
2775
+ charStart += take;
2776
+ remaining -= take;
2777
+ } while (remaining > 0);
2677
2778
  offset += len + 1;
2678
2779
  }
2679
- let pos = this.cursorPos;
2680
- let caretLine = 0;
2681
- while (caretLine < lines.length - 1 && pos > lines[caretLine].length) {
2682
- pos -= lines[caretLine].length + 1;
2683
- caretLine++;
2780
+ let caretRow = 0;
2781
+ let caretCol = 0;
2782
+ let found = false;
2783
+ for (let r = 0; r < rows.length; r++) {
2784
+ const row = rows[r];
2785
+ const rowEnd = row.start + row.len;
2786
+ const isLast = r === rows.length - 1;
2787
+ const nextStart = isLast ? rowEnd : rows[r + 1].start;
2788
+ if (this.cursorPos < rowEnd || this.cursorPos === rowEnd && (isLast || nextStart > rowEnd)) {
2789
+ caretRow = r;
2790
+ caretCol = row.colOffset + (this.cursorPos - row.start);
2791
+ found = true;
2792
+ break;
2793
+ }
2794
+ }
2795
+ if (!found && rows.length) {
2796
+ caretRow = rows.length - 1;
2797
+ const row = rows[caretRow];
2798
+ caretCol = row.colOffset + row.len;
2684
2799
  }
2685
- const caretCell = (caretLine === 0 ? pw : 0) + pos;
2686
- let caretRow = Math.floor(caretCell / cols2);
2687
- for (let i = 0; i < caretLine; i++) {
2688
- const lead = i === 0 ? pw : 0;
2689
- caretRow += Math.max(1, Math.ceil((lead + lines[i].length) / cols2));
2800
+ while (caretRow >= rows.length) {
2801
+ rows.push({ start: this.inputBuffer.length, len: 0, colOffset: 0 });
2690
2802
  }
2691
- const caretCol = caretCell % cols2;
2692
- while (caretRow >= rows.length) rows.push({ start: this.inputBuffer.length, len: 0, colOffset: 0 });
2693
2803
  return { rows, caretRow, caretCol, rowCount: rows.length };
2694
2804
  }
2695
2805
  /** Move the caret one visual row up/down, keeping the column when possible. */
@@ -2814,19 +2924,19 @@ var Tui = class _Tui {
2814
2924
  return parts.length ? `${C.gray}${parts.join(" ")}${C.reset}` : "";
2815
2925
  }
2816
2926
  /**
2817
- * Understated context gauge, right-aligned on the summary line: just "N%",
2818
- * dim, and INVISIBLE until the conversation is more than 85% of the way to
2819
- * the compaction point (the percentage is scaled so 100% = where background
2820
- * compaction triggers, not the raw model window). Most sessions never see it.
2927
+ * Context-to-compaction gauge, right-aligned on the status line: just "N%"
2928
+ * in dull grey. Shown whenever we know a value (any fill 5%, 10%, 20%…).
2929
+ * The percentage is scaled so 100% = where background compaction triggers,
2930
+ * not the raw model window.
2821
2931
  */
2822
2932
  contextGaugeText() {
2823
- if (this.contextPct == null || this.contextPct <= 85) return "";
2824
- return `${C.dim}${C.gray}${this.contextPct}%${C.reset}`;
2933
+ if (this.contextPct == null) return "";
2934
+ return `\x1B[38;5;240m${this.contextPct}%${C.reset}`;
2825
2935
  }
2826
2936
  /**
2827
2937
  * Set the context-window fill percentage (0–100), or null to hide it.
2828
- * Driven by the runtime's `context_usage` KV (latest request input tokens ÷
2829
- * model context window).
2938
+ * Driven by the runtime's `context_usage` KV, scaled to the compaction
2939
+ * trigger (see index.ts poll). Always painted on the status line far right.
2830
2940
  */
2831
2941
  setContextPct(pct) {
2832
2942
  const next = pct == null ? null : Math.max(0, Math.min(100, Math.round(pct)));
@@ -3024,10 +3134,14 @@ var Tui = class _Tui {
3024
3134
  if (up > 0) process.stdout.write(`\x1B[${up}A`);
3025
3135
  }
3026
3136
  /**
3027
- * Render the bottom region: an optional step line, then the prompt + input
3028
- * (wrapping across as many rows as needed), with the caret placed at cursorPos.
3029
- * Uses only relative cursor moves so it survives terminal scrolling when the
3030
- * region grows near the bottom of the screen.
3137
+ * Render the bottom region: status + subagents above a side-margined expanding
3138
+ * input box (level in the top border far right), with the caret on the input
3139
+ * body. Uses only relative cursor moves so it survives terminal scrolling when
3140
+ * the region grows near the bottom of the screen.
3141
+ *
3142
+ * Layout (top → bottom):
3143
+ * preview · ruler · notice · quit · status · subagents · goal ·
3144
+ * box top (level) · box body · box bottom · palette
3031
3145
  */
3032
3146
  renderBottom() {
3033
3147
  if (!this.started || this.takeoverHandler) return;
@@ -3043,69 +3157,66 @@ var Tui = class _Tui {
3043
3157
  };
3044
3158
  const previewLines = this.streamPreviewLines(cols2);
3045
3159
  for (const line of previewLines) writeHudRow(line);
3046
- const rulerRows = 1;
3047
3160
  writeHudRow(`${C.dim}${"\u2500".repeat(cols2)}${C.reset}`);
3161
+ const geo = inputBoxGeometry(cols2);
3162
+ const workPad = " ".repeat(geo.margin);
3163
+ const workCols = Math.max(8, cols2 - geo.margin);
3048
3164
  const noticeLine = this.connected ? null : `${C.yellow}\u26A0 lost connection to the workspace \u2014 reconnecting\u2026${C.reset}`;
3049
- const noticeRows = noticeLine ? 1 : 0;
3050
- if (noticeLine) writeHudRow(noticeLine);
3165
+ if (noticeLine) writeHudRow(workPad + noticeLine);
3051
3166
  const quitLine = this.quitArmed ? `${C.dim}Press Control-C again to exit${C.reset}` : null;
3052
- const quitRows = quitLine ? 1 : 0;
3053
- if (quitLine) writeHudRow(quitLine);
3167
+ if (quitLine) writeHudRow(workPad + quitLine);
3168
+ const statusLine = this.statusLineText(workCols);
3169
+ if (statusLine) writeHudRow(workPad + statusLine);
3054
3170
  const frame = FRAMES[Math.floor(Date.now() / SPINNER_MS) % FRAMES.length];
3055
3171
  for (const sub of this.subagents) {
3056
3172
  const color = sub.agentName === COMPACTION_AGENT ? COMPACTION_COLOR : SUBAGENT_COLORS[this.subagentColorByID.get(sub.id) ?? 0];
3057
- const budget = cols2 - 10;
3173
+ const budget = workCols - 10;
3058
3174
  let label = sub.label;
3059
3175
  if (budget < 1) label = "";
3060
3176
  else if (label.length > budget) label = label.slice(0, Math.max(0, budget - 1)) + "\u2026";
3061
3177
  const line = `${color}${frame}${C.reset} ${color}${label}${C.reset} ${C.dim}working${C.reset}`;
3062
- writeHudRow(line);
3063
- }
3064
- const statusLine = this.statusLineText(cols2);
3065
- const statusRows = statusLine ? 1 : 0;
3066
- if (statusLine) writeHudRow(statusLine);
3067
- const goalLines = this.goalLines(cols2);
3068
- for (const line of goalLines) writeHudRow(line);
3069
- const aboveRows = previewLines.length + rulerRows + noticeRows + quitRows + this.subagents.length + statusRows + goalLines.length;
3178
+ writeHudRow(workPad + line);
3179
+ }
3180
+ const goalLines = this.goalLines(workCols);
3181
+ for (const line of goalLines) writeHudRow(workPad + line);
3070
3182
  const prefix = this.promptPrefix();
3071
3183
  const pw = this.visibleWidth(prefix);
3072
- const lines = this.inputBuffer.split("\n");
3073
- const tail = [prefix + lines[0]];
3074
- for (let i = 1; i < lines.length; i++) tail.push("\r\n" + lines[i]);
3075
- const rowsOf = (len, lead) => Math.max(1, Math.ceil((lead + len) / cols2));
3076
- const lineRows = lines.map((l, i) => rowsOf(l.length, i === 0 ? pw : 0));
3077
- const inputRows = lineRows.reduce((a, b) => a + b, 0);
3078
- let pos = this.cursorPos;
3079
- let caretLine = 0;
3080
- while (caretLine < lines.length - 1 && pos > lines[caretLine].length) {
3081
- pos -= lines[caretLine].length + 1;
3082
- caretLine++;
3083
- }
3084
- const caretCell = (caretLine === 0 ? pw : 0) + pos;
3085
- let caretRow = Math.floor(caretCell / cols2);
3086
- for (let i = 0; i < caretLine; i++) caretRow += lineRows[i];
3087
- const caretCol = caretCell % cols2;
3184
+ const boxRows = buildInputBoxRows({
3185
+ cols: cols2,
3186
+ inputBuffer: this.inputBuffer,
3187
+ prefix,
3188
+ prefixWidth: pw,
3189
+ level: this.level,
3190
+ levelColor: this.levelColor(),
3191
+ borderColor: inputBoxBorderColor()
3192
+ });
3193
+ const boxTop = boxRows[0];
3194
+ const boxBottom = boxRows[boxRows.length - 1];
3195
+ const boxBody = boxRows.slice(1, -1);
3196
+ writeHudRow(boxTop);
3197
+ const aboveBodyWidths = hudWidths.slice();
3198
+ const aboveBodyRows = aboveBodyWidths.length;
3199
+ for (const row of boxBody) writeHudRow(row);
3200
+ writeHudRow(boxBottom);
3088
3201
  const paletteBlock = this.paletteBlockLines(cols2);
3089
- for (const line of paletteBlock) tail.push("\r\n" + line);
3090
- if (paletteBlock.length > 0) {
3091
- tail.push("\r");
3092
- const up = inputRows - 1 + paletteBlock.length - caretRow;
3093
- if (up > 0) tail.push(`\x1B[${up}A`);
3094
- if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
3095
- this.lastCursorRow = aboveRows + caretRow;
3096
- } else if (this.cursorPos < this.inputBuffer.length) {
3097
- tail.push("\r");
3098
- const up = inputRows - 1 - caretRow;
3099
- if (up > 0) tail.push(`\x1B[${up}A`);
3100
- if (caretCol > 0) tail.push(`\x1B[${caretCol}C`);
3101
- this.lastCursorRow = aboveRows + caretRow;
3102
- } else {
3103
- this.lastCursorRow = aboveRows + (inputRows - 1);
3104
- }
3105
- this.drawnHudWidths = hudWidths;
3202
+ for (const line of paletteBlock) writeHudRow(line);
3203
+ const layout = this.inputLayout();
3204
+ const bodyRowCount = Math.max(1, boxBody.length);
3205
+ const caretBodyRow = Math.max(0, Math.min(layout.caretRow, bodyRowCount - 1));
3206
+ const caretRegionRow = aboveBodyRows + caretBodyRow;
3207
+ const caretScreenCol = Math.min(rowCap - 1, geo.leftPad + layout.caretCol);
3208
+ this.lastCursorRow = caretRegionRow;
3209
+ this.drawnHudWidths = aboveBodyWidths;
3106
3210
  this.lastDrawnCols = cols2;
3107
3211
  this.bottomDrawn = true;
3108
- const out = "\x1B[J" + hudRows.join("\r\n") + "\r\n" + tail.join("");
3212
+ const totalRows = hudRows.length;
3213
+ const up = Math.max(0, totalRows - 1 - caretRegionRow);
3214
+ let out = "\x1B[J" + hudRows.join("\r\n");
3215
+ if (totalRows > 0) {
3216
+ out += "\r";
3217
+ if (up > 0) out += `\x1B[${up}A`;
3218
+ if (caretScreenCol > 0) out += `\x1B[${caretScreenCol}C`;
3219
+ }
3109
3220
  process.stdout.write(out);
3110
3221
  }
3111
3222
  clearBottom() {
@@ -3520,44 +3631,62 @@ var Tui = class _Tui {
3520
3631
  };
3521
3632
  });
3522
3633
  }
3523
- /** Arrow-key selection menu (slash menu, process menu, resume). Pauses input. */
3634
+ /**
3635
+ * Arrow-key selection menu (slash menu, process menu, resume). Pauses input.
3636
+ * Boxed chrome matches the bottom input HUD: side margin + grey rounded
3637
+ * border, selected row with a brand-tinted pointer.
3638
+ */
3524
3639
  select(title, items) {
3525
3640
  return new Promise((resolve) => {
3526
3641
  let idx = 0;
3527
3642
  this.beginTakeover();
3643
+ const cols2 = process.stdout.columns || 80;
3644
+ const geo = inputBoxGeometry(cols2);
3645
+ const border = inputBoxBorderColor();
3646
+ const pad = " ".repeat(geo.margin);
3647
+ const rowCap = Math.max(1, cols2 - 1);
3528
3648
  if (title) process.stdout.write(`
3529
- ${title}
3530
-
3649
+ ${pad}${title}
3531
3650
  `);
3532
- const renderLine = (i) => {
3533
- const w = process.stdout.columns || 80;
3651
+ else process.stdout.write("\n");
3652
+ const renderItemContent = (i) => {
3534
3653
  const it = items[i];
3535
3654
  const sel = i === idx;
3536
3655
  const hint = it.hint ?? "";
3537
3656
  const hintW = hint.length;
3538
3657
  const pointerW = 2;
3539
- const labelMax = Math.max(6, w - 2 - pointerW - (hintW ? hintW + 2 : 0));
3540
- let label = it.label;
3541
- if (label.length > labelMax) label = label.slice(0, labelMax - 1) + "\u2026";
3658
+ const labelMax = Math.max(4, geo.contentW - pointerW - (hintW ? hintW + 2 : 0));
3659
+ let label = it.label.replace(/\s+/g, " ").trim();
3660
+ if (label.length > labelMax) label = label.slice(0, Math.max(0, labelMax - 1)) + "\u2026";
3542
3661
  const pointer = sel ? `${C.magenta}\u276F${C.reset} ` : " ";
3543
- const styledLabel = sel ? `${C.bold}${C.cyan}${label}${C.reset}` : label;
3544
- let line = `${pointer}${styledLabel}`;
3662
+ const styledLabel = sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
3663
+ let content = `${pointer}${styledLabel}`;
3545
3664
  if (hintW) {
3546
- const gap = Math.max(2, w - 2 - pointerW - label.length - hintW);
3547
- line += `${" ".repeat(gap)}${C.gray}${hint}${C.reset}`;
3665
+ const used = pointerW + label.length;
3666
+ const gap = Math.max(1, geo.contentW - used - hintW);
3667
+ content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
3548
3668
  }
3549
- return line;
3669
+ return content;
3550
3670
  };
3551
- const draw = (moveUp) => {
3552
- if (moveUp) process.stdout.write(`\x1B[${items.length}A`);
3553
- for (let i = 0; i < items.length; i++) process.stdout.write(`\r\x1B[K${renderLine(i)}
3671
+ const writeRow = (line) => {
3672
+ const row = this.clampVisible(sanitizeHudRow(line), rowCap);
3673
+ process.stdout.write(`\r\x1B[K${row}
3554
3674
  `);
3555
3675
  };
3676
+ const draw = (moveUp) => {
3677
+ if (moveUp) process.stdout.write(`\x1B[${items.length + 1}A`);
3678
+ else writeRow(pad + buildBoxTopPlain(geo.boxW, border));
3679
+ for (let i = 0; i < items.length; i++) {
3680
+ writeRow(pad + buildBoxBody(renderItemContent(i), geo.contentW, border));
3681
+ }
3682
+ writeRow(pad + buildBoxBottom(geo.boxW, border));
3683
+ };
3556
3684
  draw(false);
3557
- const titleRows = title ? 3 : 0;
3685
+ const titleRows = title ? 2 : 1;
3686
+ const boxRows = 1 + items.length + 1;
3558
3687
  const erase = () => {
3559
3688
  process.stdout.write("\r");
3560
- const up = titleRows + items.length;
3689
+ const up = titleRows + boxRows;
3561
3690
  if (up > 0) process.stdout.write(`\x1B[${up}A`);
3562
3691
  process.stdout.write("\x1B[J");
3563
3692
  };
@@ -4533,11 +4662,12 @@ function printAssistant(tui, text) {
4533
4662
  }
4534
4663
  function startLoader(label) {
4535
4664
  const frames = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
4665
+ const pad = " ".repeat(INPUT_BOX_MARGIN);
4536
4666
  stdout.write("\x1B[?25l");
4537
4667
  const draw = () => {
4538
4668
  const now = Date.now();
4539
4669
  const f = frames[Math.floor(now / 70) % frames.length];
4540
- stdout.write(`\r\x1B[K${brandCycleColor(now)}${f}${c.reset} ${c.dim}${label}\u2026${c.reset}`);
4670
+ stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c.reset} ${c.dim}${label}\u2026${c.reset}`);
4541
4671
  };
4542
4672
  draw();
4543
4673
  const timer = setInterval(draw, 70);
@@ -4860,7 +4990,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
4860
4990
  const tilde = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
4861
4991
  const shortDir = tilde.length > 38 ? "\u2026" + tilde.slice(-37) : tilde;
4862
4992
  const picked = await tui.select(
4863
- `${c.bold}${c.magenta}Resume a session${c.reset} ${c.gray}${shortDir}${c.reset} ${c.dim}\u2191\u2193 \xB7 enter \xB7 esc to cancel${c.reset}`,
4993
+ `${c.bold}${gradientText("Resume a session")}${c.reset} ${c.gray}${shortDir}${c.reset} ${c.dim}\u2191\u2193 \xB7 enter \xB7 esc${c.reset}`,
4864
4994
  items
4865
4995
  );
4866
4996
  if (typeof picked === "string") {