archymedes-cli 2.1.0 → 2.2.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.
@@ -6,7 +6,7 @@ import {
6
6
  runChooser,
7
7
  terminalColumns,
8
8
  windowStart
9
- } from "./archymedes-brkqgrxv.mjs";
9
+ } from "./archymedes-wjaqydyg.mjs";
10
10
  import {
11
11
  DEFAULT_OUTPUT_CEILING,
12
12
  OpenAIAgentTurnProvider,
@@ -35,6 +35,13 @@ import {
35
35
  wanderGradeCounts,
36
36
  wanderPlannerInstructions
37
37
  } from "./archymedes-76z6n643.mjs";
38
+ import {
39
+ describeChange,
40
+ diffLines,
41
+ highlightCode,
42
+ renderCode,
43
+ renderDiff
44
+ } from "./archymedes-sc3mwttb.mjs";
38
45
  import {
39
46
  BrailleCanvas,
40
47
  StreamSeries,
@@ -46,7 +53,7 @@ import {
46
53
  scatterChart,
47
54
  seriesBounds,
48
55
  waveLine
49
- } from "./archymedes-2tp22t9a.mjs";
56
+ } from "./archymedes-pg3pvh9r.mjs";
50
57
  import {
51
58
  COMMANDS,
52
59
  CONTROL_LANGUAGES,
@@ -59,7 +66,25 @@ import {
59
66
  scrollFraction,
60
67
  t,
61
68
  visibleLines
62
- } from "./archymedes-k1bx08z9.mjs";
69
+ } from "./archymedes-npcrm9a0.mjs";
70
+ import {
71
+ SpringAnimator,
72
+ archymedesSpinnerFrame,
73
+ box,
74
+ formatStatusLine,
75
+ padToWidth,
76
+ paginator,
77
+ progressBar,
78
+ renderPromptBox,
79
+ rowsOccupied,
80
+ scrollIndicator,
81
+ scrollPercent,
82
+ sliceToWidth,
83
+ sparkline,
84
+ stepProgress,
85
+ table,
86
+ terminalStream
87
+ } from "./archymedes-r54qthg5.mjs";
63
88
  import {
64
89
  BOLD,
65
90
  CYAN,
@@ -79,37 +104,20 @@ import {
79
104
  paintAll,
80
105
  panel,
81
106
  rule
82
- } from "./archymedes-44c1mqas.mjs";
107
+ } from "./archymedes-cdgejs3p.mjs";
83
108
  import {
84
109
  ASCII_GLYPHS,
85
- SpringAnimator,
110
+ BUILTIN_THEME_SOURCES,
86
111
  UNICODE_GLYPHS,
87
- archymedesSpinnerFrame,
88
112
  borderGlyphsFor,
89
- box,
90
- formatStatusLine,
91
113
  newMarkdownState,
92
- padToWidth,
93
- paginator,
94
- progressBar,
114
+ parseThemeSource,
115
+ rainbowText,
95
116
  renderMarkdown,
96
117
  renderMarkdownLine,
97
- renderPromptBox,
98
- rowsOccupied,
99
- scrollIndicator,
100
- scrollPercent,
101
- sliceToWidth,
102
- sparkline,
103
- stepProgress,
104
- table,
105
- terminalStream,
118
+ rgbTo256,
106
119
  visibleWidth
107
- } from "./archymedes-dwvn4c51.mjs";
108
- import {
109
- BUILTIN_THEME_SOURCES,
110
- parseThemeSource,
111
- rgbTo256
112
- } from "./archymedes-jecyxv5c.mjs";
120
+ } from "./archymedes-5zn4zp84.mjs";
113
121
  import {
114
122
  __require
115
123
  } from "./archymedes-x67v3n58.mjs";
@@ -10012,227 +10020,6 @@ async function runDefenderTriage(keys, findings, paint3, options) {
10012
10020
  return { toFix, findings: state.findings };
10013
10021
  }
10014
10022
 
10015
- // packages/archymedes-cli/src/code-view.ts
10016
- var MAX_DIFF_CELLS = 250000;
10017
- function diffLines(before, after) {
10018
- const left = before === "" ? [] : before.split(`
10019
- `);
10020
- const right = after === "" ? [] : after.split(`
10021
- `);
10022
- if (left.length * right.length > MAX_DIFF_CELLS) {
10023
- return [
10024
- ...left.map((text) => ({ kind: "remove", text })),
10025
- ...right.map((text) => ({ kind: "add", text }))
10026
- ];
10027
- }
10028
- const lengths = Array.from({ length: left.length + 1 }, () => new Array(right.length + 1).fill(0));
10029
- for (let i2 = left.length - 1;i2 >= 0; i2 -= 1) {
10030
- for (let j2 = right.length - 1;j2 >= 0; j2 -= 1) {
10031
- lengths[i2][j2] = left[i2] === right[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(lengths[i2 + 1][j2], lengths[i2][j2 + 1]);
10032
- }
10033
- }
10034
- const result = [];
10035
- let i = 0;
10036
- let j = 0;
10037
- while (i < left.length && j < right.length) {
10038
- if (left[i] === right[j]) {
10039
- result.push({ kind: "context", text: left[i] });
10040
- i += 1;
10041
- j += 1;
10042
- } else if (lengths[i + 1][j] >= lengths[i][j + 1]) {
10043
- result.push({ kind: "remove", text: left[i] });
10044
- i += 1;
10045
- } else {
10046
- result.push({ kind: "add", text: right[j] });
10047
- j += 1;
10048
- }
10049
- }
10050
- while (i < left.length) {
10051
- result.push({ kind: "remove", text: left[i] });
10052
- i += 1;
10053
- }
10054
- while (j < right.length) {
10055
- result.push({ kind: "add", text: right[j] });
10056
- j += 1;
10057
- }
10058
- return result;
10059
- }
10060
- function condenseDiff(diff, context = 2) {
10061
- const keep = new Array(diff.length).fill(false);
10062
- diff.forEach((line, index) => {
10063
- if (line.kind === "context")
10064
- return;
10065
- for (let offset = -context;offset <= context; offset += 1) {
10066
- const target = index + offset;
10067
- if (target >= 0 && target < diff.length)
10068
- keep[target] = true;
10069
- }
10070
- });
10071
- const result = [];
10072
- let skipped = 0;
10073
- diff.forEach((line, index) => {
10074
- if (keep[index]) {
10075
- if (skipped > 0) {
10076
- result.push({ kind: "gap", text: `${skipped} unchanged line${skipped === 1 ? "" : "s"}` });
10077
- skipped = 0;
10078
- }
10079
- result.push(line);
10080
- return;
10081
- }
10082
- skipped += 1;
10083
- });
10084
- if (skipped > 0)
10085
- result.push({ kind: "gap", text: `${skipped} unchanged line${skipped === 1 ? "" : "s"}` });
10086
- return result;
10087
- }
10088
- var KEYWORDS = new Set([
10089
- "async",
10090
- "await",
10091
- "break",
10092
- "case",
10093
- "catch",
10094
- "class",
10095
- "const",
10096
- "continue",
10097
- "def",
10098
- "default",
10099
- "elif",
10100
- "else",
10101
- "end",
10102
- "enum",
10103
- "export",
10104
- "extends",
10105
- "finally",
10106
- "fn",
10107
- "for",
10108
- "from",
10109
- "func",
10110
- "function",
10111
- "if",
10112
- "impl",
10113
- "import",
10114
- "in",
10115
- "interface",
10116
- "let",
10117
- "match",
10118
- "mod",
10119
- "new",
10120
- "package",
10121
- "private",
10122
- "public",
10123
- "pub",
10124
- "raise",
10125
- "return",
10126
- "static",
10127
- "struct",
10128
- "switch",
10129
- "throw",
10130
- "trait",
10131
- "try",
10132
- "type",
10133
- "use",
10134
- "var",
10135
- "void",
10136
- "while",
10137
- "with",
10138
- "yield"
10139
- ]);
10140
- var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
10141
- function highlightCode(line, depth) {
10142
- if (depth === "none")
10143
- return line;
10144
- const commentAt = findCommentStart(line);
10145
- const code = commentAt === -1 ? line : line.slice(0, commentAt);
10146
- const comment = commentAt === -1 ? "" : paint(line.slice(commentAt), DIM, depth);
10147
- const painted = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_$][\w$]*)\b/g, (whole, text, numeric, word) => {
10148
- if (text !== undefined)
10149
- return paint(text, GREEN, depth);
10150
- if (numeric !== undefined)
10151
- return paint(numeric, MAGENTA, depth);
10152
- if (word !== undefined && KEYWORDS.has(word))
10153
- return paint(word, CYAN, depth);
10154
- if (word !== undefined && LITERALS.has(word))
10155
- return paint(word, MAGENTA, depth);
10156
- return whole;
10157
- });
10158
- return `${painted}${comment}`;
10159
- }
10160
- function findCommentStart(line) {
10161
- let quote;
10162
- for (let index = 0;index < line.length; index += 1) {
10163
- const character = line[index];
10164
- if (quote) {
10165
- if (character === "\\") {
10166
- index += 1;
10167
- continue;
10168
- }
10169
- if (character === quote)
10170
- quote = undefined;
10171
- continue;
10172
- }
10173
- if (character === '"' || character === "'" || character === "`") {
10174
- quote = character;
10175
- continue;
10176
- }
10177
- if (character === "#")
10178
- return index;
10179
- if (character === "/" && line[index + 1] === "/")
10180
- return index;
10181
- if (character === "-" && line[index + 1] === "-")
10182
- return index;
10183
- }
10184
- return -1;
10185
- }
10186
- function renderCode(content, style, options = {}) {
10187
- const glyphs = style.glyphs ?? UNICODE_GLYPHS;
10188
- const lines = content.replace(/\n$/, "").split(`
10189
- `);
10190
- const maxLines = options.maxLines ?? 16;
10191
- const start = options.startLine ?? 1;
10192
- const numberWidth = String(start + lines.length - 1).length;
10193
- const bodyWidth = Math.max(8, style.width - GUTTER.length - numberWidth - 4);
10194
- const render = (from, to) => lines.slice(from, to).map((line, index) => {
10195
- const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
10196
- const painted = options.highlight === false ? line : highlightCode(line, style.depth);
10197
- return `${number} ${clip(painted, bodyWidth, glyphs)}`;
10198
- });
10199
- const hidden = Math.max(0, lines.length - maxLines);
10200
- const shown = render(0, hidden > 0 ? maxLines : lines.length);
10201
- return {
10202
- text: shown.join(`
10203
- `),
10204
- hidden,
10205
- full: render(0, lines.length).join(`
10206
- `)
10207
- };
10208
- }
10209
- function renderDiff(diff, style, options = {}) {
10210
- const glyphs = style.glyphs ?? UNICODE_GLYPHS;
10211
- const maxLines = options.maxLines ?? 16;
10212
- const condensed = condenseDiff(diff);
10213
- const bodyWidth = Math.max(8, style.width - GUTTER.length - 4);
10214
- const row = (line) => {
10215
- if (line.kind === "gap")
10216
- return paint(`${glyphs.middot.repeat(3)} ${line.text}`, DIM, style.depth);
10217
- const mark = line.kind === "add" ? glyphs.plus : line.kind === "remove" ? glyphs.minus : " ";
10218
- const code = line.kind === "add" ? GREEN : line.kind === "remove" ? RED : DIM;
10219
- const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth);
10220
- return `${paint(mark, code, style.depth)} ${clip(body, bodyWidth, glyphs)}`;
10221
- };
10222
- const rows = condensed.map(row);
10223
- const hidden = Math.max(0, rows.length - maxLines);
10224
- return {
10225
- text: (hidden > 0 ? rows.slice(0, maxLines) : rows).join(`
10226
- `),
10227
- hidden,
10228
- full: diff.map(row).join(`
10229
- `)
10230
- };
10231
- }
10232
- function describeChange(stat2, depth) {
10233
- return `${paint(`+${stat2.added}`, GREEN, depth)} ${paint(`-${stat2.removed}`, RED, depth)}`;
10234
- }
10235
-
10236
10023
  // packages/archymedes-cli/src/dropup.ts
10237
10024
  var PLAIN = { dim: (text) => text, cyan: (text) => text, green: (text) => text };
10238
10025
  function dropupWindow(count, rows, selected) {
@@ -10812,7 +10599,8 @@ function renderGallery(options) {
10812
10599
  panel(["gutter only, no frame"], style, { title: "output", gutterOnly: true }),
10813
10600
  box(["a box with a title", "and a second row"], { depth, width, title: "you", titleColor: "green", glyphs }),
10814
10601
  box(["a box whose border carries the state"], { depth, width, title: "failed", titleColor: "red", borderColor: "red", glyphs }),
10815
- box(["double border"], { depth, width, title: "double", glyphs, borderStyle: "double" })
10602
+ box(["double border"], { depth, width, title: "double", glyphs, borderStyle: "double" }),
10603
+ box(["the /theme rainbow border"], { depth, width, title: "rainbow", titleColor: "cyan", borderColor: "rainbow", glyphs })
10816
10604
  ]));
10817
10605
  parts.push(section("key/value and tables", style, [
10818
10606
  keyValues([["provider", "anthropic"], ["model", "claude-opus-5"], ["workspace", "/home/you/project"]], style),
@@ -11404,6 +11192,7 @@ var COMMAND_GROUP = {
11404
11192
  "/scan": "work",
11405
11193
  "/wander": "work",
11406
11194
  "/voice": "work",
11195
+ "/cat": "review",
11407
11196
  "/files": "work",
11408
11197
  "/pull": "work",
11409
11198
  "/where": "review",
@@ -11423,6 +11212,7 @@ var COMMAND_GROUP = {
11423
11212
  "/settings": "setup",
11424
11213
  "/providers": "setup",
11425
11214
  "/theme": "setup",
11215
+ "/layout": "setup",
11426
11216
  "/update": "setup"
11427
11217
  };
11428
11218
  function navSignals(context) {
@@ -13661,6 +13451,18 @@ function detectColorDepth(environment, isTTY) {
13661
13451
  }
13662
13452
  var STAR_GLYPHS2 = UNICODE_GLYPHS.starGlyphs;
13663
13453
 
13454
+ // packages/archymedes-cli/src/fixed-screen.ts
13455
+ var ESCAPE = "\x1B";
13456
+ var CSI = `${ESCAPE}[`;
13457
+ var ENTER_ALTERNATE_SCREEN = `${CSI}?1049h`;
13458
+ var LEAVE_ALTERNATE_SCREEN = `${CSI}?1049l`;
13459
+ var HIDE_CURSOR = `${CSI}?25l`;
13460
+ var SHOW_CURSOR = `${CSI}?25h`;
13461
+ var ENABLE_MOUSE = `${CSI}?1000h${CSI}?1006h`;
13462
+ var DISABLE_MOUSE = `${CSI}?1006l${CSI}?1000l`;
13463
+ var BEGIN_SYNC = `${CSI}?2026h`;
13464
+ var END_SYNC = `${CSI}?2026l`;
13465
+
13664
13466
  // packages/archymedes-cli/src/identity.ts
13665
13467
  function renderGeometry(angle = 0, ascii = false) {
13666
13468
  const width = 23;
@@ -13671,8 +13473,11 @@ function renderGeometry(angle = 0, ascii = false) {
13671
13473
  const yaw = angle + 0.25;
13672
13474
  const rx = (x * Math.cos(yaw) + z * Math.sin(yaw)) * scale;
13673
13475
  const rz = (-x * Math.sin(yaw) + z * Math.cos(yaw)) * scale;
13674
- const ry = y * scale;
13675
- return [Math.round(11 + rx * 9), Math.round(5 - (ry * 0.94 - rz * 0.34) * 4)];
13476
+ const tilt = 0.3 + Math.sin(angle) * 0.18;
13477
+ const ry = y * scale * Math.cos(tilt) - rz * Math.sin(tilt);
13478
+ const depth = y * scale * Math.sin(tilt) + rz * Math.cos(tilt);
13479
+ const perspective = 3.8 / (3.8 + depth);
13480
+ return [Math.max(0, Math.min(22, Math.round(11 + rx * perspective * 8))), Math.max(0, Math.min(10, Math.round(5 - ry * perspective * 3.8)))];
13676
13481
  };
13677
13482
  const outer = vertices.map((point) => project(point, 1));
13678
13483
  const inner = vertices.map((point) => project(point, 0.43));
@@ -13704,19 +13509,21 @@ async function writeIdentity(options, output, motion) {
13704
13509
  `);
13705
13510
  if (!motion.enabled || motion.signal?.aborted || options.width < 64 || options.rows < 24)
13706
13511
  return;
13707
- const frames = 12;
13512
+ const frames = 28;
13708
13513
  for (let frame = 1;frame <= frames; frame++) {
13709
- await new Promise((resolve) => setTimeout(resolve, 45));
13514
+ await new Promise((resolve) => setTimeout(resolve, 18));
13710
13515
  if (motion.signal?.aborted)
13711
13516
  return;
13712
13517
  const size = motion.size();
13713
13518
  if (size.width !== options.width || size.rows !== options.rows)
13714
13519
  return;
13715
- const rendered = renderIdentity({ ...options, angle: Math.sin(frame / frames * Math.PI) * 0.65 });
13716
- output.write(`\x1B[11F${rendered.split(`
13520
+ const progress = frame / frames;
13521
+ const eased = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
13522
+ const rendered = renderIdentity({ ...options, angle: frame === frames ? 0 : eased * Math.PI * 2 });
13523
+ output.write(`${BEGIN_SYNC}\x1B[11F${rendered.split(`
13717
13524
  `).map((line) => `\x1B[2K${line}`).join(`
13718
13525
  `)}
13719
- `);
13526
+ ${END_SYNC}`);
13720
13527
  }
13721
13528
  }
13722
13529
  function renderIdentity(options) {
@@ -13745,17 +13552,774 @@ function renderIdentity(options) {
13745
13552
  const artWidth = Math.max(...geometry.map(visibleWidth));
13746
13553
  const gap = 3;
13747
13554
  const margin = 2;
13555
+ const rainbow = palette.theme === "rainbow";
13556
+ const phase = (options.angle ?? 0) / (Math.PI * 2);
13557
+ const paintDrawing = (drawing) => rainbow ? rainbowText(drawing, palette.depth, phase) : paint(drawing, palette.primary, palette.depth);
13748
13558
  return geometry.map((line, index) => {
13749
13559
  const drawing = line + " ".repeat(artWidth - visibleWidth(line));
13750
- return " ".repeat(margin) + paint(drawing, palette.primary, palette.depth) + " ".repeat(gap) + paint(clipTo(clean(text[index] ?? ""), width - margin - artWidth - gap, glyphs), codes[index] ?? "", palette.depth);
13560
+ return " ".repeat(margin) + paintDrawing(drawing) + " ".repeat(gap) + paint(clipTo(clean(text[index] ?? ""), width - margin - artWidth - gap, glyphs), codes[index] ?? "", palette.depth);
13751
13561
  }).join(`
13752
13562
  `);
13753
13563
  }
13754
13564
 
13755
- // packages/archymedes-cli/src/tui.ts
13756
- var RESET3 = "\x1B[0m";
13757
- var DIM2 = "\x1B[2m";
13758
- var CYAN2 = "\x1B[36m";
13565
+ // packages/archymedes-cli/src/layout.ts
13566
+ var GOLDEN_RATIO = 1.618033988749895;
13567
+ var MAX_CONTENT_WIDTH = Math.round(80 * GOLDEN_RATIO);
13568
+ var FOOTER_ROWS = 3;
13569
+ var MIN_TRANSCRIPT_ROWS = 3;
13570
+ function computeLayout(rows, columns) {
13571
+ const safeRows = Math.max(1, Math.floor(rows));
13572
+ const safeColumns = Math.max(1, Math.floor(columns));
13573
+ const footerRows = Math.min(FOOTER_ROWS, Math.max(0, safeRows - MIN_TRANSCRIPT_ROWS));
13574
+ const scrollBottom = safeRows - footerRows;
13575
+ return {
13576
+ rows: safeRows,
13577
+ columns: safeColumns,
13578
+ scrollTop: 1,
13579
+ scrollBottom,
13580
+ footerRows,
13581
+ statusRow: footerRows >= 2 ? scrollBottom + 1 : undefined,
13582
+ inputRow: footerRows >= 2 ? scrollBottom + 2 : footerRows === 1 ? scrollBottom + 1 : undefined,
13583
+ promptBottomRow: footerRows >= 3 ? scrollBottom + 3 : undefined,
13584
+ contentWidth: Math.min(safeColumns, MAX_CONTENT_WIDTH)
13585
+ };
13586
+ }
13587
+
13588
+ // packages/archymedes-cli/src/screen.ts
13589
+ class PinnedScreen {
13590
+ stream;
13591
+ layout;
13592
+ suggestionRows = 0;
13593
+ holdRegion;
13594
+ regionActive = false;
13595
+ constructor(stream, options = {}) {
13596
+ this.stream = stream;
13597
+ this.layout = computeLayout(stream.rows ?? 24, stream.columns ?? 80);
13598
+ this.holdRegion = options.holdRegion ?? true;
13599
+ this.headerRows = options.headerRows ?? 0;
13600
+ this.reserveHeader();
13601
+ }
13602
+ headerRows;
13603
+ reserveHeader() {
13604
+ this.layout.scrollTop = 1 + Math.min(this.headerRows, Math.max(0, this.layout.scrollBottom - 3));
13605
+ }
13606
+ get pinned() {
13607
+ return this.holdRegion;
13608
+ }
13609
+ get current() {
13610
+ return this.layout;
13611
+ }
13612
+ enter() {
13613
+ if (!this.holdRegion)
13614
+ return;
13615
+ this.setRegion();
13616
+ this.parkInTranscript();
13617
+ }
13618
+ resize() {
13619
+ this.suggestionRows = 0;
13620
+ this.layout = computeLayout(this.stream.rows ?? 24, this.stream.columns ?? 80);
13621
+ this.reserveHeader();
13622
+ if (this.holdRegion) {
13623
+ this.setRegion();
13624
+ this.parkInTranscript();
13625
+ } else if (this.regionActive) {
13626
+ this.releaseRegion();
13627
+ }
13628
+ return this.layout;
13629
+ }
13630
+ setRegion() {
13631
+ this.stream.write(this.layout.footerRows === 0 ? "\x1B[r" : `\x1B[${this.layout.scrollTop};${this.layout.scrollBottom}r`);
13632
+ this.regionActive = this.layout.footerRows !== 0;
13633
+ }
13634
+ releaseRegion() {
13635
+ if (!this.regionActive)
13636
+ return;
13637
+ this.stream.write("\x1B[r");
13638
+ this.regionActive = false;
13639
+ }
13640
+ parkInTranscript() {
13641
+ if (!this.holdRegion || this.layout.footerRows === 0)
13642
+ return;
13643
+ this.stream.write(`\x1B[${this.layout.scrollBottom};1H`);
13644
+ }
13645
+ renderStatus(text) {
13646
+ if (!this.holdRegion || !this.layout.statusRow)
13647
+ return;
13648
+ this.stream.write(`\x1B7\x1B[?25l\x1B[${this.layout.statusRow};1H\x1B[2K${text}\x1B8\x1B[?25h`);
13649
+ }
13650
+ renderPromptBottom(text) {
13651
+ if (!this.holdRegion || !this.layout.promptBottomRow)
13652
+ return;
13653
+ this.stream.write(`\x1B7\x1B[?25l\x1B[${this.layout.promptBottomRow};1H\x1B[2K${text}\x1B8\x1B[?25h`);
13654
+ }
13655
+ positionInput() {
13656
+ if (!this.holdRegion || !this.layout.inputRow)
13657
+ return;
13658
+ this.stream.write(`\x1B[${this.layout.inputRow};1H\x1B[2K`);
13659
+ }
13660
+ renderSuggestions(lines) {
13661
+ if (this.layout.footerRows === 0)
13662
+ return;
13663
+ const wanted = Math.min(lines.length, this.maxSuggestionRows());
13664
+ if (wanted === 0) {
13665
+ this.clearSuggestions();
13666
+ return;
13667
+ }
13668
+ this.applyRows(wanted, lines);
13669
+ }
13670
+ applyRows(target, lines) {
13671
+ if (target > this.suggestionRows) {
13672
+ const extra = target - this.suggestionRows;
13673
+ this.regionActive = true;
13674
+ this.stream.write(`\x1B7\x1B[${this.layout.scrollTop};${this.layout.scrollBottom}r`);
13675
+ this.stream.write(`\x1B[${extra}S`);
13676
+ this.suggestionRows = target;
13677
+ this.stream.write(`\x1B[${this.layout.scrollTop};${this.transcriptBottom()}r\x1B8`);
13678
+ } else if (target < this.suggestionRows) {
13679
+ const top2 = this.transcriptBottom() + 1;
13680
+ this.stream.write("\x1B7\x1B[?25l");
13681
+ for (let offset = target;offset < this.suggestionRows; offset += 1)
13682
+ this.stream.write(`\x1B[${top2 + offset};1H\x1B[2K`);
13683
+ this.suggestionRows = target;
13684
+ this.stream.write(`\x1B[${this.layout.scrollTop};${this.transcriptBottom()}r\x1B8\x1B[?25h`);
13685
+ }
13686
+ const top = this.transcriptBottom() + 1;
13687
+ this.stream.write("\x1B7\x1B[?25l");
13688
+ for (let offset = 0;offset < this.suggestionRows; offset += 1) {
13689
+ this.stream.write(`\x1B[${top + offset};1H\x1B[2K${lines[offset] ?? ""}`);
13690
+ }
13691
+ this.stream.write("\x1B8\x1B[?25h");
13692
+ }
13693
+ clearSuggestions() {
13694
+ if (this.suggestionRows === 0)
13695
+ return;
13696
+ const top = this.transcriptBottom() + 1;
13697
+ this.stream.write("\x1B7\x1B[?25l");
13698
+ for (let offset = 0;offset < this.suggestionRows; offset += 1)
13699
+ this.stream.write(`\x1B[${top + offset};1H\x1B[2K`);
13700
+ this.suggestionRows = 0;
13701
+ this.stream.write(this.holdRegion ? `\x1B[${this.layout.scrollTop};${this.layout.scrollBottom}r\x1B8\x1B[?25h` : "\x1B[r\x1B8\x1B[?25h");
13702
+ if (!this.holdRegion)
13703
+ this.regionActive = false;
13704
+ }
13705
+ transcriptBottom() {
13706
+ return this.layout.scrollBottom - this.suggestionRows;
13707
+ }
13708
+ maxSuggestionRows() {
13709
+ return Math.max(0, Math.min(8, this.layout.scrollBottom - this.layout.scrollTop - 2));
13710
+ }
13711
+ exit() {
13712
+ this.stream.write("\x1B[r");
13713
+ this.stream.write(`\x1B[${this.layout.rows};1H
13714
+ `);
13715
+ }
13716
+ }
13717
+
13718
+ // packages/archymedes-cli/src/identity.ts
13719
+ function renderGeometry2(angle = 0, ascii = false) {
13720
+ const width = 23;
13721
+ const height = 11;
13722
+ const cells = Array.from({ length: height }, () => Array(width).fill(" "));
13723
+ const vertices = [[0, 1.15, 0], [-1, -0.7, 0.65], [1, -0.7, 0.65], [0, -0.7, -1]];
13724
+ const project = ([x, y, z], scale) => {
13725
+ const yaw = angle + 0.25;
13726
+ const rx = (x * Math.cos(yaw) + z * Math.sin(yaw)) * scale;
13727
+ const rz = (-x * Math.sin(yaw) + z * Math.cos(yaw)) * scale;
13728
+ const tilt = 0.3 + Math.sin(angle) * 0.18;
13729
+ const ry = y * scale * Math.cos(tilt) - rz * Math.sin(tilt);
13730
+ const depth = y * scale * Math.sin(tilt) + rz * Math.cos(tilt);
13731
+ const perspective = 3.8 / (3.8 + depth);
13732
+ return [Math.max(0, Math.min(22, Math.round(11 + rx * perspective * 8))), Math.max(0, Math.min(10, Math.round(5 - ry * perspective * 3.8)))];
13733
+ };
13734
+ const outer = vertices.map((point) => project(point, 1));
13735
+ const inner = vertices.map((point) => project(point, 0.43));
13736
+ const line = (a, b, faint = false) => {
13737
+ const dx = b[0] - a[0];
13738
+ const dy = b[1] - a[1];
13739
+ const steps = Math.max(Math.abs(dx), Math.abs(dy));
13740
+ const glyph = faint ? "." : Math.abs(dx) > Math.abs(dy) * 2 ? "-" : dx === 0 ? "|" : dx * dy > 0 ? "\\" : "/";
13741
+ for (let step = 1;step < steps; step++) {
13742
+ const x = Math.round(a[0] + dx * step / steps);
13743
+ const y = Math.round(a[1] + dy * step / steps);
13744
+ if (cells[y]?.[x] !== undefined && (!faint || cells[y][x] === " "))
13745
+ cells[y][x] = glyph;
13746
+ }
13747
+ };
13748
+ for (let i = 0;i < 4; i++)
13749
+ line(outer[i], inner[i], true);
13750
+ for (const points of [outer, inner]) {
13751
+ for (let i = 0;i < 4; i++)
13752
+ for (let j = i + 1;j < 4; j++)
13753
+ line(points[i], points[j], j === 3);
13754
+ }
13755
+ for (const [x, y] of [...outer, ...inner])
13756
+ cells[y][x] = ascii ? "+" : "◇";
13757
+ return cells.map((row) => row.join(""));
13758
+ }
13759
+ function renderIdentity2(options) {
13760
+ const { palette } = options;
13761
+ const glyphs = options.glyphs ?? UNICODE_GLYPHS;
13762
+ const width = Math.max(1, Math.floor(options.width));
13763
+ const ascii = glyphs.boxHorizontal === ASCII_GLYPHS.boxHorizontal;
13764
+ const mark = ascii ? "/\\" : "△";
13765
+ const text = [
13766
+ `ARCHYMEDES ${options.version}`,
13767
+ "A place to think. The tools to build.",
13768
+ "",
13769
+ `${options.mode} ${glyphs.middot} ${options.workspace}`,
13770
+ options.model,
13771
+ `/help ${glyphs.middot} /guide ${glyphs.middot} /workspace`,
13772
+ ""
13773
+ ];
13774
+ const codes = [palette.primary + BOLD, palette.text, "", palette.secondary, palette.muted, palette.primary];
13775
+ const clean = (value) => value.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
13776
+ if (width < 64 || options.rows < 20) {
13777
+ const lines = [`${mark} ${text[0]}`, text[3], text[4]];
13778
+ return lines.map((line, index) => paint(clipTo(clean(line), width, glyphs), index === 0 ? codes[0] : palette.muted, palette.depth)).join(`
13779
+ `);
13780
+ }
13781
+ const geometry = renderGeometry2(options.angle, ascii);
13782
+ const artWidth = Math.max(...geometry.map(visibleWidth));
13783
+ const gap = 3;
13784
+ const margin = 2;
13785
+ const rainbow = palette.theme === "rainbow";
13786
+ const phase = (options.angle ?? 0) / (Math.PI * 2);
13787
+ const paintDrawing = (drawing) => rainbow ? rainbowText(drawing, palette.depth, phase) : paint(drawing, palette.primary, palette.depth);
13788
+ return geometry.map((line, index) => {
13789
+ const drawing = line + " ".repeat(artWidth - visibleWidth(line));
13790
+ return " ".repeat(margin) + paintDrawing(drawing) + " ".repeat(gap) + paint(clipTo(clean(text[index] ?? ""), width - margin - artWidth - gap, glyphs), codes[index] ?? "", palette.depth);
13791
+ }).join(`
13792
+ `);
13793
+ }
13794
+
13795
+ // packages/archymedes-cli/src/workspace-frame.ts
13796
+ function frameText(text, width) {
13797
+ let result = "";
13798
+ let columns = 0;
13799
+ const safe = text.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[(?![0-9;]*m)[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[^\[]/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, (c) => c === "\x1B" ? c : c === "\t" ? " " : "");
13800
+ for (const part of safe.split(/(\x1b\[[0-9;]*m)/)) {
13801
+ if (/^\x1b\[[0-9;]*m$/.test(part)) {
13802
+ result += part;
13803
+ continue;
13804
+ }
13805
+ for (const { segment } of new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(part.replace(/\x1b/g, ""))) {
13806
+ if (columns + visibleWidth(segment) > width)
13807
+ return result + (result.includes("\x1B") ? "\x1B[0m" : "");
13808
+ result += segment;
13809
+ columns += visibleWidth(segment);
13810
+ }
13811
+ }
13812
+ return result + (result.includes("\x1B") ? "\x1B[0m" : "");
13813
+ }
13814
+ function workspaceHeader(context, width, phase = 0, position = "LIVE") {
13815
+ const { palette: p, glyphs: g } = context;
13816
+ const color = { plan: p.warning, build: p.primary, auto: p.success, defender: p.error }[context.mode] ?? p.primary;
13817
+ const ink = (s, c = p.muted) => paint(s, c, p.depth);
13818
+ const clean = (s) => s.replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
13819
+ const ascii = g === ASCII_GLYPHS || g.boxHorizontal === "-";
13820
+ const pulse = context.busy || position === "MENU" || position.startsWith("HISTORY");
13821
+ const marks = ascii ? ["/\\", "<>", "\\/"] : ["◇", "◈", "◆", "◈"];
13822
+ const mark = pulse ? marks[phase % marks.length] : ascii ? "/\\" : "◇";
13823
+ const title = ` ${mark} ARCHYMEDES`;
13824
+ const tail = width >= 72 ? `v${context.version} ${position} ` : `${position} `;
13825
+ const room = Math.max(0, width - visibleWidth(title) - tail.length - 2);
13826
+ const workspace = clipTo(clean(context.workspace), room, g);
13827
+ const first = ink(title, p.primary + BOLD) + " " + ink(workspace, p.text) + " ".repeat(Math.max(1, width - visibleWidth(title) - 2 - visibleWidth(workspace) - tail.length)) + ink(tail, context.busy || position !== "LIVE" ? color + BOLD : p.muted);
13828
+ const modes = ["plan", "build", "auto", "defender"].map((mode) => mode === context.mode ? ink(` ${mode.toUpperCase()} `, color + BOLD + REVERSE) : ink(` ${mode} `, p.muted)).join(ink("│", p.muted));
13829
+ const second = width >= 48 ? ` ${modes}` : ` ${ink(`[${context.mode.toUpperCase()}]`, color + BOLD)}`;
13830
+ const hint = width >= 90 ? ` /mode choose Ctrl+G menu` : "";
13831
+ const model = clean(context.model);
13832
+ const navigation = width >= 72 ? "PgUp/PgDn history Esc live " : "";
13833
+ const modelWidth = Math.max(0, width - navigation.length - 3);
13834
+ const shownModel = clipTo(model, modelWidth, g);
13835
+ const third = ` ${ink(shownModel, p.secondary)}${" ".repeat(Math.max(1, width - visibleWidth(shownModel) - navigation.length - 1))}${ink(navigation)}`;
13836
+ const rule2 = ink(` ${g.boxHorizontal.repeat(Math.max(0, width - 2))} `, p.muted);
13837
+ return [frameText(first, width), frameText(second + ink(hint), width), frameText(third, width), frameText(rule2, width)];
13838
+ }
13839
+
13840
+ class WorkspaceFrame extends PinnedScreen {
13841
+ output;
13842
+ context;
13843
+ history;
13844
+ motion;
13845
+ active = false;
13846
+ timer;
13847
+ phase = 0;
13848
+ offset = 0;
13849
+ overlay;
13850
+ cachedLog;
13851
+ cachedSize = -1;
13852
+ cachedWidth = -1;
13853
+ cachedPending = "";
13854
+ lines = [];
13855
+ introLog;
13856
+ introSize = -1;
13857
+ introPending = "";
13858
+ suggestions = [];
13859
+ heldText;
13860
+ cachedHeldText;
13861
+ introMotion = true;
13862
+ constructor(output, context, history, motion = true) {
13863
+ super(output, { holdRegion: true, headerRows: 4 });
13864
+ this.output = output;
13865
+ this.context = context;
13866
+ this.history = history;
13867
+ this.motion = motion;
13868
+ }
13869
+ enter() {
13870
+ if (this.active)
13871
+ return;
13872
+ this.active = true;
13873
+ process.once("exit", this.restoreOnExit);
13874
+ process.once("SIGTERM", this.terminate);
13875
+ if (!this.introLog) {
13876
+ this.introLog = this.history();
13877
+ this.introSize = this.history().size;
13878
+ this.introPending = this.history().pending;
13879
+ }
13880
+ this.output.write(`${ENTER_ALTERNATE_SCREEN}\x1B[2J`);
13881
+ super.enter();
13882
+ this.refresh();
13883
+ if (this.motion) {
13884
+ this.timer = setInterval(() => {
13885
+ this.phase++;
13886
+ if (this.introMotion && this.showIntro() && !this.overlay)
13887
+ this.refresh();
13888
+ else if (this.context().busy || this.overlay || this.offset)
13889
+ this.drawHeader();
13890
+ }, 120);
13891
+ this.timer.unref();
13892
+ }
13893
+ }
13894
+ exit() {
13895
+ if (!this.active)
13896
+ return;
13897
+ clearInterval(this.timer);
13898
+ this.timer = undefined;
13899
+ this.active = false;
13900
+ process.off("exit", this.restoreOnExit);
13901
+ process.off("SIGTERM", this.terminate);
13902
+ this.output.write(`\x1B[r\x1B[?25h${LEAVE_ALTERNATE_SCREEN}`);
13903
+ }
13904
+ restoreOnExit = () => this.exit();
13905
+ terminate = () => {
13906
+ this.exit();
13907
+ process.exit(143);
13908
+ };
13909
+ resize() {
13910
+ this.suggestions = [];
13911
+ const layout = super.resize();
13912
+ if (this.active) {
13913
+ this.output.write("\x1B[2J");
13914
+ this.refresh();
13915
+ }
13916
+ return layout;
13917
+ }
13918
+ write(text) {
13919
+ if (!this.active || !this.offset && !this.overlay)
13920
+ this.output.write(text);
13921
+ else
13922
+ this.drawHeader();
13923
+ }
13924
+ renderStatus(text) {
13925
+ super.renderStatus(text);
13926
+ this.drawHeader();
13927
+ }
13928
+ drawHeader() {
13929
+ if (!this.active)
13930
+ return;
13931
+ const label = this.overlay ? "MENU" : this.offset ? `HISTORY -${this.offset}` : this.context().busy ? "WORKING" : "LIVE";
13932
+ const rows = workspaceHeader(this.context(), this.current.columns, this.phase, label).slice(0, this.current.scrollTop - 1);
13933
+ this.output.write(`${BEGIN_SYNC}\x1B7${rows.map((line, i) => `\x1B[${i + 1};1H\x1B[2K${line}`).join("")}\x1B8${END_SYNC}`);
13934
+ }
13935
+ projected() {
13936
+ const log = this.history();
13937
+ const width = Math.max(1, this.current.columns - 1);
13938
+ if (this.cachedLog === log && this.cachedSize === log.size + log.dropped && this.cachedPending === log.pending && this.cachedWidth === width && this.cachedHeldText === this.heldText)
13939
+ return this.lines;
13940
+ this.cachedLog = log;
13941
+ this.cachedSize = log.size + log.dropped;
13942
+ this.cachedPending = log.pending;
13943
+ this.cachedWidth = width;
13944
+ this.cachedHeldText = this.heldText;
13945
+ const plain = (this.heldText ?? [...log.lines, ...log.pending ? [log.pending] : []].join(`
13946
+ `)).replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]|\x1b./g, "").split(`
13947
+ `).map((line) => line.replace(/^.*\r(?=.)/, "").replace(/\r/g, "")).join(`
13948
+ `);
13949
+ this.lines = [];
13950
+ for (const line of plain.split(`
13951
+ `)) {
13952
+ let row = "";
13953
+ let used = 0;
13954
+ for (const { segment } of new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(line)) {
13955
+ const size = visibleWidth(segment);
13956
+ if (used + size > width && row) {
13957
+ this.lines.push(row);
13958
+ row = "";
13959
+ used = 0;
13960
+ }
13961
+ row += size > width ? "?" : segment;
13962
+ used += Math.min(size, width);
13963
+ }
13964
+ this.lines.push(row);
13965
+ }
13966
+ return this.lines;
13967
+ }
13968
+ showIntro() {
13969
+ return this.history() === this.introLog && this.history().size === this.introSize && this.history().pending === this.introPending && !this.context().busy && !this.offset;
13970
+ }
13971
+ refresh() {
13972
+ if (!this.active)
13973
+ return;
13974
+ const height = this.current.scrollBottom - this.current.scrollTop + 1 - this.suggestions.length;
13975
+ let lines = this.projected();
13976
+ this.offset = Math.min(this.offset, Math.max(0, lines.length - height));
13977
+ if (!this.offset && this.heldText !== undefined) {
13978
+ this.heldText = undefined;
13979
+ lines = this.projected();
13980
+ }
13981
+ const end = Math.max(0, lines.length - this.offset);
13982
+ let body = lines.slice(Math.max(0, end - height), end);
13983
+ if (this.showIntro()) {
13984
+ const context = this.context();
13985
+ const warnings = lines.filter((line) => /No session spend cap|No price configured|No current.*rate/.test(line));
13986
+ body = renderIdentity2({
13987
+ ...context,
13988
+ width: this.current.columns - 1,
13989
+ rows: height >= 15 ? 24 : 12,
13990
+ angle: this.phase * 0.065
13991
+ }).split(`
13992
+ `);
13993
+ body.push("", ...warnings.map((line) => paint(line, context.palette.warning, context.palette.depth)));
13994
+ body.push(paint(" /mode choose your tools /palette find any action", context.palette.muted, context.palette.depth));
13995
+ body = body.slice(0, height);
13996
+ }
13997
+ if (this.overlay !== undefined) {
13998
+ const { palette: p, glyphs: g } = this.context();
13999
+ const cardWidth = Math.max(1, Math.min(88, this.current.columns - 6));
14000
+ const inset = " ".repeat(Math.max(0, Math.floor((this.current.columns - cardWidth) / 2)));
14001
+ const border = (s) => paint(s, p.primary, p.depth);
14002
+ const muted = (s) => paint(s, p.muted, p.depth);
14003
+ const inner = Math.max(0, cardWidth - 4);
14004
+ const edge = g.boxHorizontal.repeat(Math.max(0, cardWidth - 2));
14005
+ const rows = this.overlay.split(`
14006
+ `).slice(0, Math.max(1, height - 4));
14007
+ body = rows.map((line) => {
14008
+ const text = frameText(line, inner);
14009
+ return `${inset}${border(g.boxVertical)} ${text}${" ".repeat(Math.max(0, inner - visibleWidth(text)))} ${border(g.boxVertical)}`;
14010
+ });
14011
+ if (height >= 4 && cardWidth >= 4) {
14012
+ body = [
14013
+ `${inset}${border(g.boxTopLeft)}${border(edge)}${border(g.boxTopRight)}`,
14014
+ ...body,
14015
+ `${inset}${muted(g.boxBottomLeft)}${muted(edge)}${muted(g.boxBottomRight)}`
14016
+ ];
14017
+ }
14018
+ const top = Math.max(0, Math.floor((height - body.length) / 3));
14019
+ body = [...Array(top).fill(""), ...body];
14020
+ } else
14021
+ body = [...Array(Math.max(0, Math.floor((height - body.length) * (this.showIntro() ? 0.45 : 1)))).fill(""), ...body];
14022
+ this.output.write(`${BEGIN_SYNC}\x1B7${Array.from({ length: height }, (_, i) => `\x1B[${this.current.scrollTop + i};1H\x1B[2K${frameText(body[i] ?? "", this.current.columns - 1)}`).join("")}\x1B8${END_SYNC}`);
14023
+ this.drawHeader();
14024
+ }
14025
+ renderSuggestions(lines) {
14026
+ this.suggestions = lines.slice(0, Math.max(0, Math.min(8, this.current.scrollBottom - this.current.scrollTop - 2)));
14027
+ super.renderSuggestions(lines);
14028
+ this.refresh();
14029
+ }
14030
+ clearSuggestions() {
14031
+ this.suggestions = [];
14032
+ super.clearSuggestions();
14033
+ this.refresh();
14034
+ }
14035
+ navigate(direction) {
14036
+ if (this.overlay)
14037
+ return;
14038
+ if (direction === "up" && !this.offset) {
14039
+ const log = this.history();
14040
+ this.heldText = [...log.lines, ...log.pending ? [log.pending] : []].join(`
14041
+ `);
14042
+ }
14043
+ const page = Math.max(1, this.current.scrollBottom - this.current.scrollTop - 1);
14044
+ this.offset = direction === "live" ? 0 : Math.max(0, this.offset + (direction === "up" ? page : -page));
14045
+ if (!this.offset)
14046
+ this.heldText = undefined;
14047
+ this.refresh();
14048
+ }
14049
+ get browsing() {
14050
+ return this.offset > 0;
14051
+ }
14052
+ stopIntroMotion() {
14053
+ this.introMotion = false;
14054
+ }
14055
+ menu = {
14056
+ paint: (frame) => {
14057
+ this.overlay = frame;
14058
+ this.refresh();
14059
+ },
14060
+ erase: () => {
14061
+ this.overlay = undefined;
14062
+ this.refresh();
14063
+ this.parkInTranscript();
14064
+ },
14065
+ height: () => Math.max(1, this.current.scrollBottom - this.current.scrollTop - 1),
14066
+ width: () => Math.max(1, Math.min(88, this.current.columns - 6) - 4)
14067
+ };
14068
+ }
14069
+
14070
+ // packages/archymedes-cli/src/model-picker.ts
14071
+ function initialSelection(rows, current) {
14072
+ const index = rows.findIndex((row) => row.kind === "model" && row.choice.provider === current.provider && row.choice.model === current.model);
14073
+ return index >= 0 ? index : 0;
14074
+ }
14075
+ function renderModelPicker(frame, options) {
14076
+ const { paint: paint3 } = options;
14077
+ const glyphs = options.glyphs ?? UNICODE_GLYPHS;
14078
+ const height = options.height ?? 10;
14079
+ const columns = terminalColumns(options.width);
14080
+ const start = windowStart(frame.selected, frame.rows.length, height);
14081
+ const visible = frame.rows.slice(start, start + height);
14082
+ const naturalWidth = Math.max(0, ...visible.map((row) => visibleWidth(row.kind === "model" ? row.choice.model : row.label)));
14083
+ const labelBudget = Math.max(0, Math.min(naturalWidth, Math.floor(Math.max(0, columns - 11) * 0.65)));
14084
+ const lines = [];
14085
+ for (const [offset, row] of visible.entries()) {
14086
+ if (row.header)
14087
+ lines.push(paint3.cyan(clipTo(` ${row.header}`, columns)));
14088
+ const active = start + offset === frame.selected;
14089
+ const cursor = active ? paint3.green(glyphs.prompt) : " ";
14090
+ const number = offset < 9 ? `${offset + 1}.` : " ";
14091
+ if (columns < 9) {
14092
+ const label = row.kind === "model" ? row.choice.model : row.label;
14093
+ lines.push(active ? paint3.green(clipTo(`${glyphs.prompt}${label}`, columns)) : clipTo(label, columns));
14094
+ continue;
14095
+ }
14096
+ if (row.kind === "settings") {
14097
+ lines.push(` ${cursor} ${paint3.dim(number)} ${paint3.yellow(clipTo(row.label, columns - 7))}`);
14098
+ continue;
14099
+ }
14100
+ const isCurrent = row.choice.provider === options.current.provider && row.choice.model === options.current.model;
14101
+ const tags = [row.choice.isProviderDefault ? "default" : "", isCurrent ? "current" : ""].filter(Boolean).join(", ");
14102
+ const shownModel = clipTo(row.choice.model, labelBudget);
14103
+ const padded = shownModel + " ".repeat(Math.max(0, labelBudget - visibleWidth(shownModel)));
14104
+ const tail = `${options.price(row.choice)}${tags ? ` (${tags})` : ""}`;
14105
+ const room = Math.max(0, columns - visibleWidth(` ${active ? glyphs.prompt : " "} ${number} ${isCurrent ? glyphs.circleFull : " "} ${padded} `));
14106
+ lines.push(` ${cursor} ${paint3.dim(number)} ${isCurrent ? paint3.green(glyphs.circleFull) : " "} ${padded} ${paint3.dim(clipTo(tail, room))}`);
14107
+ }
14108
+ lines.push(paint3.dim(clipTo(` ${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} t table ${glyphs.middot} Esc cancel`, columns)));
14109
+ return lines.join(`
14110
+ `);
14111
+ }
14112
+ function advanceModelPicker(state, rows, input, options = {}) {
14113
+ const name = input.key.name;
14114
+ const last = Math.max(0, rows.length - 1);
14115
+ if (name === "escape" || input.key.ctrl && (name === "c" || name === "g"))
14116
+ return { state, done: {} };
14117
+ if (name === "return" || name === "enter") {
14118
+ const row = rows.length > 0 ? rows[Math.max(0, Math.min(state.selected, rows.length - 1))] : undefined;
14119
+ if (!row)
14120
+ return { state, done: {} };
14121
+ return { state, done: { result: row.kind === "model" ? { kind: "model", choice: row.choice } : { kind: "settings" } } };
14122
+ }
14123
+ if (name === "up" || input.key.ctrl && name === "p")
14124
+ return { state: { selected: Math.max(0, state.selected - 1) } };
14125
+ if (name === "down" || input.key.ctrl && name === "n")
14126
+ return { state: { selected: Math.min(last, state.selected + 1) } };
14127
+ if (name === "home")
14128
+ return { state: { selected: 0 } };
14129
+ if (name === "end")
14130
+ return { state: { selected: last } };
14131
+ if ((input.str === "t" || input.str === "T") && !input.key.ctrl && !input.key.meta) {
14132
+ return { state, done: { result: { kind: "table" } } };
14133
+ }
14134
+ if (input.str && /^[1-9]$/.test(input.str) && !input.key.ctrl && !input.key.meta) {
14135
+ const height = options.height ?? 10;
14136
+ const start = windowStart(state.selected, rows.length, height);
14137
+ const shown = Math.min(height, rows.length - start);
14138
+ const digit = Number(input.str);
14139
+ if (digit > shown)
14140
+ return { state };
14141
+ return { state: { selected: start + digit - 1 } };
14142
+ }
14143
+ return { state };
14144
+ }
14145
+ async function runModelPicker(keys, paint3, options) {
14146
+ const { rows } = options;
14147
+ let state = { selected: initialSelection(rows, options.current) };
14148
+ const liveOptions = () => {
14149
+ const size = options.getSize?.();
14150
+ const height = Math.max(1, Math.min(options.height ?? 10, size?.height ?? Number.POSITIVE_INFINITY));
14151
+ return { ...options, width: size?.width ?? options.width, height };
14152
+ };
14153
+ paint3(renderModelPicker({ rows, selected: state.selected }, liveOptions()));
14154
+ for await (const input of keys) {
14155
+ const current = liveOptions();
14156
+ const step = advanceModelPicker(state, rows, input, { height: current.height });
14157
+ state = step.state;
14158
+ if (step.done)
14159
+ return step.done.result;
14160
+ paint3(renderModelPicker({ rows, selected: state.selected }, liveOptions()));
14161
+ }
14162
+ return;
14163
+ }
14164
+
14165
+ // packages/archymedes-cli/src/shortcuts.ts
14166
+ function replaceLine(readline, text) {
14167
+ readline.write(null, { ctrl: true, name: "e" });
14168
+ readline.write(null, { ctrl: true, name: "u" });
14169
+ readline.write(text);
14170
+ }
14171
+ function submit(readline, command) {
14172
+ replaceLine(readline, command);
14173
+ if (!command.endsWith(" "))
14174
+ readline.write(null, { name: "return" });
14175
+ }
14176
+ function painter(output) {
14177
+ let rows = 0;
14178
+ const erase = () => {
14179
+ for (let row = 0;row < rows; row += 1)
14180
+ output.write("\x1B[1A\x1B[2K");
14181
+ if (rows > 0)
14182
+ output.write("\r");
14183
+ rows = 0;
14184
+ };
14185
+ return {
14186
+ erase,
14187
+ paint: (frame) => {
14188
+ erase();
14189
+ output.write(`${frame}
14190
+ `);
14191
+ const columns = output.columns ?? 80;
14192
+ rows = frame.split(`
14193
+ `).reduce((total, line) => total + rowsOccupied(line, columns), 0);
14194
+ }
14195
+ };
14196
+ }
14197
+ var workspaceMenu;
14198
+ function menuRows(host) {
14199
+ return host.output === process.stdout && workspaceMenu ? workspaceMenu.height() : host.output.rows ?? 24;
14200
+ }
14201
+ function menuColumns(host) {
14202
+ return host.output === process.stdout && workspaceMenu ? workspaceMenu.width() : host.output.columns ?? 80;
14203
+ }
14204
+ function setWorkspaceMenu(surface) {
14205
+ workspaceMenu = surface;
14206
+ }
14207
+ async function withBorrowedKeyboard(host, self, body, surface = (host.output === process.stdout ? workspaceMenu : undefined) ?? painter(host.output)) {
14208
+ const borrowed = host.input.listeners("keypress").filter((listener) => listener !== self);
14209
+ for (const listener of borrowed)
14210
+ host.input.off("keypress", listener);
14211
+ const pending = [];
14212
+ let wake;
14213
+ const collect = (str, key) => {
14214
+ pending.push({ ...str === undefined ? {} : { str }, key: key ?? {} });
14215
+ wake?.();
14216
+ };
14217
+ host.input.on("keypress", collect);
14218
+ const onResize = () => {
14219
+ pending.push({ key: {} });
14220
+ wake?.();
14221
+ };
14222
+ host.output.on("resize", onResize);
14223
+ async function* keys() {
14224
+ for (;; ) {
14225
+ while (pending.length > 0)
14226
+ yield pending.shift();
14227
+ await new Promise((resolve) => {
14228
+ wake = resolve;
14229
+ });
14230
+ wake = undefined;
14231
+ }
14232
+ }
14233
+ try {
14234
+ return await body(keys(), surface.paint);
14235
+ } finally {
14236
+ surface.erase();
14237
+ host.output.off("resize", onResize);
14238
+ host.input.off("keypress", collect);
14239
+ for (const listener of borrowed)
14240
+ host.input.on("keypress", listener);
14241
+ }
14242
+ }
14243
+ async function openPalette(host, self, options = {}) {
14244
+ const chords = Object.fromEntries(host.registry.bindings.map((binding) => [binding.command, formatBinding(binding.chord)]));
14245
+ const sized = {
14246
+ width: host.output.columns ?? 80,
14247
+ ...options,
14248
+ getSize: options.getSize ?? (() => ({ width: menuColumns(host), height: Math.max(1, menuRows(host) - 3) }))
14249
+ };
14250
+ return withBorrowedKeyboard(host, self, (keys, paint3) => runCommandPalette(keys, paletteEntries(chords), paint3, sized));
14251
+ }
14252
+ async function openDefenderTriage(host, findings, options, self) {
14253
+ const sized = {
14254
+ width: options.width ?? host.output.columns ?? 80,
14255
+ rows: options.rows ?? Math.max(12, (host.output.rows ?? 24) - 2),
14256
+ ...options,
14257
+ getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(12, (host.output.rows ?? 24) - 2) }))
14258
+ };
14259
+ return withBorrowedKeyboard(host, self, (keys, paint3) => runDefenderTriage(keys, findings, paint3, sized));
14260
+ }
14261
+ async function openChooser(host, items, options, self) {
14262
+ const sized = {
14263
+ width: host.output.columns ?? 80,
14264
+ ...options,
14265
+ getSize: options.getSize ?? (() => ({ width: menuColumns(host), height: Math.max(1, menuRows(host) - 4) }))
14266
+ };
14267
+ return withBorrowedKeyboard(host, self, (keys, paint3) => runChooser(keys, items, paint3, sized));
14268
+ }
14269
+ async function openTable(host, options, self) {
14270
+ const sized = {
14271
+ width: host.output.columns ?? 80,
14272
+ ...options,
14273
+ getSize: options.getSize ?? (() => ({ width: menuColumns(host), height: Math.max(1, menuRows(host) - 2) }))
14274
+ };
14275
+ return withBorrowedKeyboard(host, self, (keys, paint3) => runTable(keys, paint3, sized));
14276
+ }
14277
+ async function openModelPicker(host, options, self) {
14278
+ const sized = {
14279
+ width: host.output.columns ?? 80,
14280
+ ...options,
14281
+ getSize: options.getSize ?? (() => ({ width: menuColumns(host), height: Math.max(1, menuRows(host) - 4) }))
14282
+ };
14283
+ return withBorrowedKeyboard(host, self, (keys, paint3) => runModelPicker(keys, paint3, sized));
14284
+ }
14285
+ function formatBinding(chord) {
14286
+ const key = /^f\d+$/.test(chord.key) ? chord.key.toUpperCase() : chord.key.toUpperCase();
14287
+ return `${chord.ctrl ? "Ctrl+" : ""}${chord.meta ? "Alt+" : ""}${chord.shift ? "Shift+" : ""}${key}`;
14288
+ }
14289
+ function installShortcuts(host) {
14290
+ let busy = false;
14291
+ const run = (options) => {
14292
+ busy = true;
14293
+ openPalette(host, onKeypress, options).then((chosen) => {
14294
+ if (chosen)
14295
+ submit(host.readline, chosen);
14296
+ }).finally(() => {
14297
+ busy = false;
14298
+ host.readline.prompt(true);
14299
+ });
14300
+ };
14301
+ const onKeypress = (str, key) => {
14302
+ if (busy || !key)
14303
+ return;
14304
+ const command = host.registry.match(key);
14305
+ if (!command)
14306
+ return;
14307
+ if (host.onIntercept?.(command))
14308
+ return;
14309
+ if (command !== "/palette") {
14310
+ submit(host.readline, command);
14311
+ return;
14312
+ }
14313
+ run({ direction: "up" });
14314
+ };
14315
+ host.input.on("keypress", onKeypress);
14316
+ return () => host.input.off("keypress", onKeypress);
14317
+ }
14318
+
14319
+ // packages/archymedes-cli/src/tui.ts
14320
+ var RESET3 = "\x1B[0m";
14321
+ var DIM2 = "\x1B[2m";
14322
+ var CYAN2 = "\x1B[36m";
13759
14323
  var GREEN2 = "\x1B[32m";
13760
14324
  var YELLOW2 = "\x1B[33m";
13761
14325
  var RED2 = "\x1B[31m";
@@ -14299,14 +14863,21 @@ function box2(lines, options) {
14299
14863
  const border = borderGlyphsFor(options.borderStyle ?? "round", glyphs);
14300
14864
  const titleWidth = options.title ? visibleWidth(options.title) : 0;
14301
14865
  const titlePaint = TONE_CODES[options.titleColor ?? "cyan"];
14302
- const edge = (text) => options.borderColor === undefined ? text : paint3(text, TONE_CODES[options.borderColor], options.depth);
14866
+ const rainbowBorder = options.borderColor === "rainbow";
14867
+ const edge = (text, phase = 0) => {
14868
+ if (options.borderColor === undefined)
14869
+ return text;
14870
+ return rainbowBorder ? rainbowText(text, options.depth, phase) : paint3(text, TONE_CODES[options.borderColor], options.depth);
14871
+ };
14303
14872
  const contentWidth = Math.min(Math.max(titleWidth, ...lines.map((line) => visibleWidth(line)), 1), Math.max(1, terminalWidth - 4));
14304
14873
  const horizontal = border.horizontal.repeat(contentWidth + 2);
14305
- const top = options.title ? `${edge(`${border.topLeft}${border.horizontal}`)} ${paint3(options.title, titlePaint, options.depth)} ${edge(`${border.horizontal.repeat(Math.max(0, contentWidth - titleWidth - 1))}${border.topRight}`)}` : edge(`${border.topLeft}${horizontal}${border.topRight}`);
14306
- const bottom = edge(`${border.bottomLeft}${horizontal}${border.bottomRight}`);
14307
- const body = lines.map((line) => {
14874
+ const top = options.title ? `${edge(`${border.topLeft}${border.horizontal}`, 0)} ${paint3(options.title, titlePaint, options.depth)} ${edge(`${border.horizontal.repeat(Math.max(0, contentWidth - titleWidth - 1))}${border.topRight}`, 0.08)}` : edge(`${border.topLeft}${horizontal}${border.topRight}`, 0);
14875
+ const bottomPhase = (lines.length + 1) * 0.1;
14876
+ const bottom = edge(`${border.bottomLeft}${horizontal}${border.bottomRight}`, bottomPhase);
14877
+ const body = lines.map((line, index) => {
14308
14878
  const clipped = visibleWidth(line) > contentWidth ? `${sliceToWidth2(line, contentWidth - visibleWidth(glyphs.ellipsis))}${glyphs.ellipsis}` : line;
14309
- return `${edge(border.vertical)} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${edge(border.vertical)}`;
14879
+ const rowPhase = (index + 1) * 0.1;
14880
+ return `${edge(border.vertical, rowPhase)} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${edge(border.vertical, rowPhase)}`;
14310
14881
  });
14311
14882
  return [top, ...body, bottom].join(`
14312
14883
  `);
@@ -14793,31 +15364,8 @@ function renderMarkdown2(text, options) {
14793
15364
  `);
14794
15365
  }
14795
15366
 
14796
- // packages/archymedes-cli/src/layout.ts
14797
- var GOLDEN_RATIO = 1.618033988749895;
14798
- var MAX_CONTENT_WIDTH = Math.round(80 * GOLDEN_RATIO);
14799
- var FOOTER_ROWS = 3;
14800
- var MIN_TRANSCRIPT_ROWS = 3;
14801
- function computeLayout(rows, columns) {
14802
- const safeRows = Math.max(1, Math.floor(rows));
14803
- const safeColumns = Math.max(1, Math.floor(columns));
14804
- const footerRows = Math.min(FOOTER_ROWS, Math.max(0, safeRows - MIN_TRANSCRIPT_ROWS));
14805
- const scrollBottom = safeRows - footerRows;
14806
- return {
14807
- rows: safeRows,
14808
- columns: safeColumns,
14809
- scrollTop: 1,
14810
- scrollBottom,
14811
- footerRows,
14812
- statusRow: footerRows >= 2 ? scrollBottom + 1 : undefined,
14813
- inputRow: footerRows >= 2 ? scrollBottom + 2 : footerRows === 1 ? scrollBottom + 1 : undefined,
14814
- promptBottomRow: footerRows >= 3 ? scrollBottom + 3 : undefined,
14815
- contentWidth: Math.min(safeColumns, MAX_CONTENT_WIDTH)
14816
- };
14817
- }
14818
-
14819
15367
  // packages/archymedes-cli/src/screen.ts
14820
- class PinnedScreen {
15368
+ class PinnedScreen2 {
14821
15369
  stream;
14822
15370
  layout;
14823
15371
  suggestionRows = 0;
@@ -14827,6 +15375,12 @@ class PinnedScreen {
14827
15375
  this.stream = stream;
14828
15376
  this.layout = computeLayout(stream.rows ?? 24, stream.columns ?? 80);
14829
15377
  this.holdRegion = options.holdRegion ?? true;
15378
+ this.headerRows = options.headerRows ?? 0;
15379
+ this.reserveHeader();
15380
+ }
15381
+ headerRows;
15382
+ reserveHeader() {
15383
+ this.layout.scrollTop = 1 + Math.min(this.headerRows, Math.max(0, this.layout.scrollBottom - 3));
14830
15384
  }
14831
15385
  get pinned() {
14832
15386
  return this.holdRegion;
@@ -14843,6 +15397,7 @@ class PinnedScreen {
14843
15397
  resize() {
14844
15398
  this.suggestionRows = 0;
14845
15399
  this.layout = computeLayout(this.stream.rows ?? 24, this.stream.columns ?? 80);
15400
+ this.reserveHeader();
14846
15401
  if (this.holdRegion) {
14847
15402
  this.setRegion();
14848
15403
  this.parkInTranscript();
@@ -14930,7 +15485,7 @@ class PinnedScreen {
14930
15485
  return this.layout.scrollBottom - this.suggestionRows;
14931
15486
  }
14932
15487
  maxSuggestionRows() {
14933
- return Math.max(0, Math.min(8, this.layout.scrollBottom - 3));
15488
+ return Math.max(0, Math.min(8, this.layout.scrollBottom - this.layout.scrollTop - 2));
14934
15489
  }
14935
15490
  exit() {
14936
15491
  this.stream.write("\x1B[r");
@@ -15171,6 +15726,7 @@ function defineCommands(table3) {
15171
15726
  return Object.entries(table3).map(([name, command]) => ({ name, ...command }));
15172
15727
  }
15173
15728
  var COMMANDS2 = defineCommands({
15729
+ "/layout": { args: "[fixed|scrollback]", description: "Toggle the fixed workspace or terminal scrollback layout" },
15174
15730
  "/mode": { args: "[plan|build|auto|defender]", description: "Show or switch the permission mode" },
15175
15731
  "/plan": { description: "Switch to plan mode — read and reason, no writes" },
15176
15732
  "/build": { description: "Switch to build mode — edits need approval" },
@@ -15194,7 +15750,7 @@ var COMMANDS2 = defineCommands({
15194
15750
  "/slow": { args: "[on|strict|off]", description: "Spend at a slower pace: fewer model rounds, smaller replies, a pause between turns" },
15195
15751
  "/workspace": { description: "Open the control panel: every tab and watched job, live, on one screen" },
15196
15752
  "/watch": { args: "[id|show <id>|stop <id>]", description: "Follow a background job's output without giving up the prompt" },
15197
- "/theme": { args: "[name|list|where]", description: "Change the colours — blueprint, parchment, chalkboard, high-contrast, or a .tss file of your own" },
15753
+ "/theme": { args: "[name|list|where]", description: "Change the colours — blueprint, parchment, chalkboard, high-contrast, rainbow, or a .tss file of your own" },
15198
15754
  "/tab": { args: "[new|next|prev|close|rename|N]", description: "Several pieces of work, one at a time — only the tab in front runs (/detach for parallel)" },
15199
15755
  "/pull": { args: "[dir]", description: "Copy sandbox files here" },
15200
15756
  "/where": { description: "Show the current workspace" },
@@ -15216,6 +15772,7 @@ var COMMANDS2 = defineCommands({
15216
15772
  "/guide": { args: "[topic|search <text>|all]", description: "The user guide, as a screen you can browse — or print one topic with /guide <topic>" },
15217
15773
  "/files": { description: "Browse the project tree — expand folders, preview a file, pick one to @mention" },
15218
15774
  "/edit": { args: "<path>", description: "Open a file in the built-in editor — through the workspace, so it works in a sandbox too" },
15775
+ "/cat": { args: "<path>", description: "Print a file into the transcript — markdown rendered like a document, code numbered and highlighted" },
15219
15776
  "/help": { args: "[all|work|review|steer|parallel|learn|setup]", description: "Commands grouped by purpose" },
15220
15777
  "/exit": { description: "Leave" }
15221
15778
  });
@@ -15506,323 +16063,90 @@ function resolveBindings(overrides = {}, options = {}) {
15506
16063
  const editing = RESERVED_CHORDS[id];
15507
16064
  if (editing && !RELOCATED_EDITING[id]) {
15508
16065
  conflicts.push({ command, chord: formatChord(chord), reason: `reserved for line editing (${editing})` });
15509
- continue;
15510
- }
15511
- const owner = taken.get(id);
15512
- if (owner) {
15513
- conflicts.push({ command, chord: formatChord(chord), reason: `already bound to ${owner}` });
15514
- continue;
15515
- }
15516
- const relocation = RELOCATED_EDITING[id];
15517
- if (relocation)
15518
- displaced.push({ chord: formatChord(chord), command, ...relocation });
15519
- taken.set(id, command);
15520
- bindings.push({ command, chord, description });
15521
- }
15522
- }
15523
- return { bindings, conflicts, displaced };
15524
- }
15525
- function isLikelyDeliverable(chord, environment = {}) {
15526
- if (environment.TERM === "dumb")
15527
- return false;
15528
- if (chord.key === "tab" && (chord.ctrl || chord.shift))
15529
- return false;
15530
- if (FUNCTION_KEY.test(chord.key))
15531
- return !environment.TMUX;
15532
- if (chord.meta)
15533
- return !environment.TMUX;
15534
- return true;
15535
- }
15536
-
15537
- class KeyBindingRegistry {
15538
- environment;
15539
- byChord = new Map;
15540
- conflicts;
15541
- displaced;
15542
- constructor(overrides = {}, environment = {}) {
15543
- this.environment = environment;
15544
- const resolved = resolveBindings(overrides);
15545
- this.conflicts = resolved.conflicts;
15546
- this.displaced = resolved.displaced;
15547
- for (const binding of resolved.bindings)
15548
- this.byChord.set(chordId(binding.chord), binding);
15549
- }
15550
- match(key) {
15551
- const chord = chordFromKeypress(key);
15552
- return chord ? this.byChord.get(chordId(chord))?.command : undefined;
15553
- }
15554
- get bindings() {
15555
- return [...this.byChord.values()];
15556
- }
15557
- groupedByCommand() {
15558
- const byCommand = new Map;
15559
- for (const binding of this.bindings) {
15560
- const entry = byCommand.get(binding.command) ?? { chords: [], description: binding.description };
15561
- entry.chords.push(binding.chord);
15562
- byCommand.set(binding.command, entry);
15563
- }
15564
- return byCommand;
15565
- }
15566
- shortcutLabels() {
15567
- return new Map([...this.groupedByCommand()].map(([command, { chords }]) => [command, chords.map(formatChord).join(", ")]));
15568
- }
15569
- render() {
15570
- const rows = [...this.groupedByCommand()].map(([command, { chords, description }]) => {
15571
- const deliverable = chords.some((chord) => isLikelyDeliverable(chord, this.environment));
15572
- return [
15573
- chords.map(formatChord).join(", "),
15574
- deliverable ? `${description} (${command})` : `${description} — this terminal may not send it; use ${command}`
15575
- ];
15576
- });
15577
- const width = Math.max(16, ...rows.map(([key]) => key.length));
15578
- const lines = ["Feature keys", ...rows.map(([key, label]) => ` ${key.padEnd(width + 2)}${label}`)];
15579
- if (this.displaced.length > 0) {
15580
- lines.push("", "Line editing that moved");
15581
- for (const moved of this.displaced) {
15582
- lines.push(` ${moved.instead.padEnd(width + 2)}${moved.lost} — ${moved.chord} runs ${moved.command} now`);
15583
- }
15584
- }
15585
- if (this.conflicts.length > 0) {
15586
- lines.push("", "Not bound");
15587
- for (const conflict of this.conflicts)
15588
- lines.push(` ${conflict.chord.padEnd(width + 2)}${conflict.command} — ${conflict.reason}`);
15589
- }
15590
- return lines.join(`
15591
- `);
15592
- }
15593
- }
15594
-
15595
- // packages/archymedes-cli/src/model-picker.ts
15596
- function initialSelection(rows, current) {
15597
- const index = rows.findIndex((row) => row.kind === "model" && row.choice.provider === current.provider && row.choice.model === current.model);
15598
- return index >= 0 ? index : 0;
15599
- }
15600
- function renderModelPicker(frame, options) {
15601
- const { paint: paint5 } = options;
15602
- const glyphs = options.glyphs ?? UNICODE_GLYPHS;
15603
- const height = options.height ?? 10;
15604
- const columns = terminalColumns(options.width);
15605
- const start = windowStart(frame.selected, frame.rows.length, height);
15606
- const visible = frame.rows.slice(start, start + height);
15607
- const naturalWidth = Math.max(0, ...visible.map((row) => visibleWidth(row.kind === "model" ? row.choice.model : row.label)));
15608
- const labelBudget = Math.max(0, Math.min(naturalWidth, Math.floor(Math.max(0, columns - 11) * 0.65)));
15609
- const lines = [];
15610
- for (const [offset, row] of visible.entries()) {
15611
- if (row.header)
15612
- lines.push(paint5.cyan(clipTo(` ${row.header}`, columns)));
15613
- const active = start + offset === frame.selected;
15614
- const cursor = active ? paint5.green(glyphs.prompt) : " ";
15615
- const number = offset < 9 ? `${offset + 1}.` : " ";
15616
- if (columns < 9) {
15617
- const label = row.kind === "model" ? row.choice.model : row.label;
15618
- lines.push(active ? paint5.green(clipTo(`${glyphs.prompt}${label}`, columns)) : clipTo(label, columns));
15619
- continue;
15620
- }
15621
- if (row.kind === "settings") {
15622
- lines.push(` ${cursor} ${paint5.dim(number)} ${paint5.yellow(clipTo(row.label, columns - 7))}`);
15623
- continue;
15624
- }
15625
- const isCurrent = row.choice.provider === options.current.provider && row.choice.model === options.current.model;
15626
- const tags = [row.choice.isProviderDefault ? "default" : "", isCurrent ? "current" : ""].filter(Boolean).join(", ");
15627
- const shownModel = clipTo(row.choice.model, labelBudget);
15628
- const padded = shownModel + " ".repeat(Math.max(0, labelBudget - visibleWidth(shownModel)));
15629
- const tail = `${options.price(row.choice)}${tags ? ` (${tags})` : ""}`;
15630
- const room = Math.max(0, columns - visibleWidth(` ${active ? glyphs.prompt : " "} ${number} ${isCurrent ? glyphs.circleFull : " "} ${padded} `));
15631
- lines.push(` ${cursor} ${paint5.dim(number)} ${isCurrent ? paint5.green(glyphs.circleFull) : " "} ${padded} ${paint5.dim(clipTo(tail, room))}`);
15632
- }
15633
- lines.push(paint5.dim(clipTo(` ${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} t table ${glyphs.middot} Esc cancel`, columns)));
15634
- return lines.join(`
15635
- `);
15636
- }
15637
- function advanceModelPicker(state, rows, input, options = {}) {
15638
- const name = input.key.name;
15639
- const last = Math.max(0, rows.length - 1);
15640
- if (name === "escape" || input.key.ctrl && (name === "c" || name === "g"))
15641
- return { state, done: {} };
15642
- if (name === "return" || name === "enter") {
15643
- const row = rows.length > 0 ? rows[Math.max(0, Math.min(state.selected, rows.length - 1))] : undefined;
15644
- if (!row)
15645
- return { state, done: {} };
15646
- return { state, done: { result: row.kind === "model" ? { kind: "model", choice: row.choice } : { kind: "settings" } } };
15647
- }
15648
- if (name === "up" || input.key.ctrl && name === "p")
15649
- return { state: { selected: Math.max(0, state.selected - 1) } };
15650
- if (name === "down" || input.key.ctrl && name === "n")
15651
- return { state: { selected: Math.min(last, state.selected + 1) } };
15652
- if (name === "home")
15653
- return { state: { selected: 0 } };
15654
- if (name === "end")
15655
- return { state: { selected: last } };
15656
- if ((input.str === "t" || input.str === "T") && !input.key.ctrl && !input.key.meta) {
15657
- return { state, done: { result: { kind: "table" } } };
15658
- }
15659
- if (input.str && /^[1-9]$/.test(input.str) && !input.key.ctrl && !input.key.meta) {
15660
- const height = options.height ?? 10;
15661
- const start = windowStart(state.selected, rows.length, height);
15662
- const shown = Math.min(height, rows.length - start);
15663
- const digit = Number(input.str);
15664
- if (digit > shown)
15665
- return { state };
15666
- return { state: { selected: start + digit - 1 } };
15667
- }
15668
- return { state };
15669
- }
15670
- async function runModelPicker(keys, paint5, options) {
15671
- const { rows } = options;
15672
- let state = { selected: initialSelection(rows, options.current) };
15673
- const liveOptions = () => {
15674
- const size = options.getSize?.();
15675
- const height = Math.max(1, Math.min(options.height ?? 10, size?.height ?? Number.POSITIVE_INFINITY));
15676
- return { ...options, width: size?.width ?? options.width, height };
15677
- };
15678
- paint5(renderModelPicker({ rows, selected: state.selected }, liveOptions()));
15679
- for await (const input of keys) {
15680
- const current = liveOptions();
15681
- const step = advanceModelPicker(state, rows, input, { height: current.height });
15682
- state = step.state;
15683
- if (step.done)
15684
- return step.done.result;
15685
- paint5(renderModelPicker({ rows, selected: state.selected }, liveOptions()));
15686
- }
15687
- return;
15688
- }
15689
-
15690
- // packages/archymedes-cli/src/shortcuts.ts
15691
- function replaceLine(readline, text) {
15692
- readline.write(null, { ctrl: true, name: "e" });
15693
- readline.write(null, { ctrl: true, name: "u" });
15694
- readline.write(text);
15695
- }
15696
- function submit(readline, command) {
15697
- replaceLine(readline, command);
15698
- if (!command.endsWith(" "))
15699
- readline.write(null, { name: "return" });
15700
- }
15701
- function painter(output) {
15702
- let rows = 0;
15703
- const erase = () => {
15704
- for (let row = 0;row < rows; row += 1)
15705
- output.write("\x1B[1A\x1B[2K");
15706
- if (rows > 0)
15707
- output.write("\r");
15708
- rows = 0;
15709
- };
15710
- return {
15711
- erase,
15712
- paint: (frame) => {
15713
- erase();
15714
- output.write(`${frame}
15715
- `);
15716
- const columns = output.columns ?? 80;
15717
- rows = frame.split(`
15718
- `).reduce((total, line) => total + rowsOccupied(line, columns), 0);
16066
+ continue;
16067
+ }
16068
+ const owner = taken.get(id);
16069
+ if (owner) {
16070
+ conflicts.push({ command, chord: formatChord(chord), reason: `already bound to ${owner}` });
16071
+ continue;
16072
+ }
16073
+ const relocation = RELOCATED_EDITING[id];
16074
+ if (relocation)
16075
+ displaced.push({ chord: formatChord(chord), command, ...relocation });
16076
+ taken.set(id, command);
16077
+ bindings.push({ command, chord, description });
15719
16078
  }
15720
- };
16079
+ }
16080
+ return { bindings, conflicts, displaced };
15721
16081
  }
15722
- async function withBorrowedKeyboard(host, self, body, surface = painter(host.output)) {
15723
- const borrowed = host.input.listeners("keypress").filter((listener) => listener !== self);
15724
- for (const listener of borrowed)
15725
- host.input.off("keypress", listener);
15726
- const pending = [];
15727
- let wake;
15728
- const collect = (str, key) => {
15729
- pending.push({ ...str === undefined ? {} : { str }, key: key ?? {} });
15730
- wake?.();
15731
- };
15732
- host.input.on("keypress", collect);
15733
- async function* keys() {
15734
- for (;; ) {
15735
- while (pending.length > 0)
15736
- yield pending.shift();
15737
- await new Promise((resolve) => {
15738
- wake = resolve;
15739
- });
15740
- wake = undefined;
16082
+ function isLikelyDeliverable(chord, environment = {}) {
16083
+ if (environment.TERM === "dumb")
16084
+ return false;
16085
+ if (chord.key === "tab" && (chord.ctrl || chord.shift))
16086
+ return false;
16087
+ if (FUNCTION_KEY.test(chord.key))
16088
+ return !environment.TMUX;
16089
+ if (chord.meta)
16090
+ return !environment.TMUX;
16091
+ return true;
16092
+ }
16093
+
16094
+ class KeyBindingRegistry {
16095
+ environment;
16096
+ byChord = new Map;
16097
+ conflicts;
16098
+ displaced;
16099
+ constructor(overrides = {}, environment = {}) {
16100
+ this.environment = environment;
16101
+ const resolved = resolveBindings(overrides);
16102
+ this.conflicts = resolved.conflicts;
16103
+ this.displaced = resolved.displaced;
16104
+ for (const binding of resolved.bindings)
16105
+ this.byChord.set(chordId(binding.chord), binding);
16106
+ }
16107
+ match(key) {
16108
+ const chord = chordFromKeypress(key);
16109
+ return chord ? this.byChord.get(chordId(chord))?.command : undefined;
16110
+ }
16111
+ get bindings() {
16112
+ return [...this.byChord.values()];
16113
+ }
16114
+ groupedByCommand() {
16115
+ const byCommand = new Map;
16116
+ for (const binding of this.bindings) {
16117
+ const entry = byCommand.get(binding.command) ?? { chords: [], description: binding.description };
16118
+ entry.chords.push(binding.chord);
16119
+ byCommand.set(binding.command, entry);
15741
16120
  }
16121
+ return byCommand;
15742
16122
  }
15743
- try {
15744
- return await body(keys(), surface.paint);
15745
- } finally {
15746
- surface.erase();
15747
- host.input.off("keypress", collect);
15748
- for (const listener of borrowed)
15749
- host.input.on("keypress", listener);
16123
+ shortcutLabels() {
16124
+ return new Map([...this.groupedByCommand()].map(([command, { chords }]) => [command, chords.map(formatChord).join(", ")]));
15750
16125
  }
15751
- }
15752
- async function openPalette(host, self, options = {}) {
15753
- const chords = Object.fromEntries(host.registry.bindings.map((binding) => [binding.command, formatBinding(binding.chord)]));
15754
- const sized = {
15755
- width: host.output.columns ?? 80,
15756
- ...options,
15757
- getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(1, (host.output.rows ?? 24) - 3) }))
15758
- };
15759
- return withBorrowedKeyboard(host, self, (keys, paint5) => runCommandPalette(keys, paletteEntries(chords), paint5, sized));
15760
- }
15761
- async function openDefenderTriage(host, findings, options, self) {
15762
- const sized = {
15763
- width: options.width ?? host.output.columns ?? 80,
15764
- rows: options.rows ?? Math.max(12, (host.output.rows ?? 24) - 2),
15765
- ...options,
15766
- getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(12, (host.output.rows ?? 24) - 2) }))
15767
- };
15768
- return withBorrowedKeyboard(host, self, (keys, paint5) => runDefenderTriage(keys, findings, paint5, sized));
15769
- }
15770
- async function openChooser(host, items, options, self) {
15771
- const sized = {
15772
- width: host.output.columns ?? 80,
15773
- ...options,
15774
- getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(1, (host.output.rows ?? 24) - 4) }))
15775
- };
15776
- return withBorrowedKeyboard(host, self, (keys, paint5) => runChooser(keys, items, paint5, sized));
15777
- }
15778
- async function openTable(host, options, self) {
15779
- const sized = {
15780
- width: host.output.columns ?? 80,
15781
- ...options,
15782
- getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(1, (host.output.rows ?? 24) - 2) }))
15783
- };
15784
- return withBorrowedKeyboard(host, self, (keys, paint5) => runTable(keys, paint5, sized));
15785
- }
15786
- async function openModelPicker(host, options, self) {
15787
- const sized = {
15788
- width: host.output.columns ?? 80,
15789
- ...options,
15790
- getSize: options.getSize ?? (() => ({ width: host.output.columns ?? 80, height: Math.max(1, (host.output.rows ?? 24) - 4) }))
15791
- };
15792
- return withBorrowedKeyboard(host, self, (keys, paint5) => runModelPicker(keys, paint5, sized));
15793
- }
15794
- function formatBinding(chord) {
15795
- const key = /^f\d+$/.test(chord.key) ? chord.key.toUpperCase() : chord.key.toUpperCase();
15796
- return `${chord.ctrl ? "Ctrl+" : ""}${chord.meta ? "Alt+" : ""}${chord.shift ? "Shift+" : ""}${key}`;
15797
- }
15798
- function installShortcuts(host) {
15799
- let busy = false;
15800
- const run = (options) => {
15801
- busy = true;
15802
- openPalette(host, onKeypress, options).then((chosen) => {
15803
- if (chosen)
15804
- submit(host.readline, chosen);
15805
- }).finally(() => {
15806
- busy = false;
15807
- host.readline.prompt(true);
16126
+ render() {
16127
+ const rows = [...this.groupedByCommand()].map(([command, { chords, description }]) => {
16128
+ const deliverable = chords.some((chord) => isLikelyDeliverable(chord, this.environment));
16129
+ return [
16130
+ chords.map(formatChord).join(", "),
16131
+ deliverable ? `${description} (${command})` : `${description} — this terminal may not send it; use ${command}`
16132
+ ];
15808
16133
  });
15809
- };
15810
- const onKeypress = (str, key) => {
15811
- if (busy || !key)
15812
- return;
15813
- const command = host.registry.match(key);
15814
- if (!command)
15815
- return;
15816
- if (host.onIntercept?.(command))
15817
- return;
15818
- if (command !== "/palette") {
15819
- submit(host.readline, command);
15820
- return;
16134
+ const width = Math.max(16, ...rows.map(([key]) => key.length));
16135
+ const lines = ["Feature keys", ...rows.map(([key, label]) => ` ${key.padEnd(width + 2)}${label}`)];
16136
+ if (this.displaced.length > 0) {
16137
+ lines.push("", "Line editing that moved");
16138
+ for (const moved of this.displaced) {
16139
+ lines.push(` ${moved.instead.padEnd(width + 2)}${moved.lost} — ${moved.chord} runs ${moved.command} now`);
16140
+ }
15821
16141
  }
15822
- run({ direction: "up" });
15823
- };
15824
- host.input.on("keypress", onKeypress);
15825
- return () => host.input.off("keypress", onKeypress);
16142
+ if (this.conflicts.length > 0) {
16143
+ lines.push("", "Not bound");
16144
+ for (const conflict of this.conflicts)
16145
+ lines.push(` ${conflict.chord.padEnd(width + 2)}${conflict.command} — ${conflict.reason}`);
16146
+ }
16147
+ return lines.join(`
16148
+ `);
16149
+ }
15826
16150
  }
15827
16151
 
15828
16152
  // packages/archymedes-cli/src/chooser.ts
@@ -15948,7 +16272,11 @@ function renderChooser2(state, items, options) {
15948
16272
  const shownLabel = clip2(item.label, labelBudget);
15949
16273
  const padded = shownLabel + " ".repeat(Math.max(0, labelBudget - visibleWidth(shownLabel)));
15950
16274
  const marker = active ? paint5.green(glyphs.prompt) : fadingOut ? paint5.dim(glyphs.prompt) : " ";
15951
- const head = `${marker} ${paint5.dim(number)} ${padded} `;
16275
+ const focus = Math.max(0, Math.min(1, options.focusProgress ?? 1));
16276
+ const graphemes = [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(padded)].map((part) => part.segment);
16277
+ const illuminated = Math.round(graphemes.length * focus);
16278
+ const label = active ? paint5.cyan(graphemes.slice(0, illuminated).join("")) + graphemes.slice(illuminated).join("") : padded;
16279
+ const head = `${marker} ${paint5.dim(number)} ${label} `;
15952
16280
  const tail = `${item.hint ? ` ${item.hint}` : ""}${item.description ? ` ${item.description}` : ""}`;
15953
16281
  lines.push(` ${head}${paint5.dim(clip2(tail, Math.max(0, room - visibleWidth(padded) - 2)))}`);
15954
16282
  }
@@ -15992,39 +16320,45 @@ async function runChooser2(keys, items, paint5, options) {
15992
16320
  glide?.stop();
15993
16321
  glide = undefined;
15994
16322
  };
15995
- for await (const input of keys) {
15996
- const current = liveOptions();
15997
- const previousSelected = state.selected;
15998
- const step = advanceChooser(state, items, input, {
15999
- filter: options.filter ?? false,
16000
- page: options.page ?? current.height ?? 8,
16001
- ...current.height === undefined ? {} : { height: current.height }
16002
- });
16003
- state = step.state;
16004
- if (step.done) {
16323
+ try {
16324
+ for await (const input of keys) {
16325
+ const current = liveOptions();
16326
+ const previousSelected = state.selected;
16327
+ const step = advanceChooser(state, items, input, {
16328
+ filter: options.filter ?? false,
16329
+ page: options.page ?? current.height ?? 8,
16330
+ ...current.height === undefined ? {} : { height: current.height }
16331
+ });
16332
+ state = step.state;
16333
+ if (step.done) {
16334
+ settleGlide();
16335
+ if (step.done.index === undefined)
16336
+ return;
16337
+ return filterItems(items, state.query)[step.done.index]?.value;
16338
+ }
16005
16339
  settleGlide();
16006
- if (step.done.index === undefined)
16007
- return;
16008
- return filterItems(items, state.query)[step.done.index]?.value;
16340
+ const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
16341
+ const motion = options.motion ?? (process.env.ARCHYMEDES_NO_MOTION !== "1" && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb");
16342
+ if (!motion || !isSingleStep || previousSelected === state.selected) {
16343
+ paint5(renderChooser2(state, items, liveOptions()));
16344
+ continue;
16345
+ }
16346
+ paint5(renderChooser2(state, items, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
16347
+ const animator = new SpringAnimator(0, (value) => {
16348
+ if (value < 0.95) {
16349
+ paint5(renderChooser2(state, items, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
16350
+ return;
16351
+ }
16352
+ animator.stop();
16353
+ glide = undefined;
16354
+ paint5(renderChooser2(state, items, liveOptions()));
16355
+ }, { intervalMs: 30 });
16356
+ glide = animator;
16357
+ animator.retarget(1);
16009
16358
  }
16359
+ } finally {
16010
16360
  settleGlide();
16011
- const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
16012
- if (!isSingleStep || previousSelected === state.selected) {
16013
- paint5(renderChooser2(state, items, liveOptions()));
16014
- continue;
16015
- }
16016
- paint5(renderChooser2(state, items, { ...liveOptions(), transitionFrom: previousSelected }));
16017
- const animator = new SpringAnimator(0, (value) => {
16018
- if (value < 0.6)
16019
- return;
16020
- animator.stop();
16021
- glide = undefined;
16022
- paint5(renderChooser2(state, items, liveOptions()));
16023
- }, { intervalMs: 30 });
16024
- glide = animator;
16025
- animator.retarget(1);
16026
- }
16027
- settleGlide();
16361
+ }
16028
16362
  return;
16029
16363
  }
16030
16364
 
@@ -17301,7 +17635,7 @@ import { createInterface as createInterface3 } from "node:readline/promises";
17301
17635
  // packages/archymedes-cli/package.json
17302
17636
  var package_default = {
17303
17637
  name: "archymedes-cli",
17304
- version: "2.1.0",
17638
+ version: "2.2.0",
17305
17639
  description: "Archymedes CLI — a coding agent that shows its work. Runs against your working tree or an isolated remote sandbox, with guarded approvals, git checkpoints, and exact, measured cost accounting.",
17306
17640
  keywords: [
17307
17641
  "cli",
@@ -20127,18 +20461,21 @@ var GUIDE_TOPICS = [
20127
20461
  {
20128
20462
  id: "look",
20129
20463
  title: "How it looks",
20130
- summary: "Themes, symbols, and terminals that need help.",
20464
+ summary: "Themes, symbols, the fixed workspace, and terminals that need help.",
20131
20465
  body: [
20132
20466
  "Archymedes ships a blueprint theme and three others, and reads themes you write yourself as .tss files in .archymedes/themes — the same format TermUI apps use, so a palette written once works in both.",
20133
- "If your terminal draws question marks instead of symbols, --ascii switches to characters every terminal has. If you would rather have a status line pinned to the bottom row, --pin does that, at the cost of your terminal's scrollback."
20467
+ "The fixed workspace keeps a mode rail and composer anchored while the transcript scrolls underneath. Start with --layout fixed or ARCHYMEDES_LAYOUT=fixed, switch mid-session with /layout, and use Page Up / Page Down to read retained history (Escape returns to live output). Menus open inside the transcript area so the composer never jumps.",
20468
+ "If your terminal draws question marks instead of symbols, --ascii switches to characters every terminal has. If you would rather have a status line pinned to the bottom row, --pin does that, at the cost of your terminal's scrollback. ARCHYMEDES_NO_MOTION=1 keeps the opening geometry and menu focus still."
20134
20469
  ],
20135
20470
  examples: [
20136
20471
  { input: "/theme list", effect: "every theme available, including your own" },
20137
20472
  { input: "/theme chalkboard", effect: "change the colours immediately" },
20138
20473
  { input: "/theme where", effect: "where to put a theme file of your own" },
20474
+ { input: "/layout fixed", effect: "mode rail, anchored composer, menus in the transcript" },
20475
+ { input: "/layout scrollback", effect: "return to the ordinary terminal log" },
20139
20476
  { input: "archymedes --ascii", effect: "plain characters for terminals that mangle symbols" }
20140
20477
  ],
20141
- covers: ["/theme"]
20478
+ covers: ["/theme", "/layout"]
20142
20479
  },
20143
20480
  {
20144
20481
  id: "extend",
@@ -20153,10 +20490,11 @@ var GUIDE_TOPICS = [
20153
20490
  examples: [
20154
20491
  { input: "/tools", effect: "every tool, skill, plugin, MCP server and hook" },
20155
20492
  { input: "/voice", effect: "record a prompt, then edit it before sending" },
20493
+ { input: "/cat README.md", effect: "read a file in the transcript, with markdown formatting or numbered code" },
20156
20494
  { input: "/files", effect: "browse the project tree and @mention a file" },
20157
20495
  { input: "/edit src/app.ts", effect: "open a file in the built-in editor" }
20158
20496
  ],
20159
- covers: ["/tools", "/voice", "/files", "/edit"]
20497
+ covers: ["/tools", "/voice", "/files", "/edit", "/cat"]
20160
20498
  }
20161
20499
  ];
20162
20500
  function findTopic(id) {
@@ -20349,12 +20687,31 @@ var ARCHYMEDES = `
20349
20687
  --border-focus: #e7bb78;
20350
20688
  }
20351
20689
  `;
20690
+ var RAINBOW = `
20691
+ /* Every hue at once — the opening art and the thinking spinner sweep the full spectrum. */
20692
+ @theme rainbow {
20693
+ --primary: #ff5f5f;
20694
+ --secondary: #5fd7ff;
20695
+ --accent: #ffd75f;
20696
+ --bg: #0c0c0c;
20697
+ --surface: #161616;
20698
+ --text: #f2f2f2;
20699
+ --text-muted: #9a9a9a;
20700
+ --success: #5fff87;
20701
+ --warning: #ffaf5f;
20702
+ --error: #ff3b3b;
20703
+ --border: double;
20704
+ --border-color: #8a8a8a;
20705
+ --border-focus: #d75fff;
20706
+ }
20707
+ `;
20352
20708
  var BUILTIN_THEME_SOURCES2 = {
20353
20709
  archymedes: ARCHYMEDES,
20354
20710
  blueprint: BLUEPRINT,
20355
20711
  parchment: PARCHMENT,
20356
20712
  chalkboard: CHALKBOARD,
20357
- "high-contrast": HIGH_CONTRAST
20713
+ "high-contrast": HIGH_CONTRAST,
20714
+ rainbow: RAINBOW
20358
20715
  };
20359
20716
  var DEFAULT_THEME_NAME = "archymedes";
20360
20717
  var NAMED_COLORS = {
@@ -20423,6 +20780,15 @@ function colorCode2(value, depth) {
20423
20780
  return parsed < 8 ? `\x1B[${30 + parsed}m` : `\x1B[${90 + parsed - 8}m`;
20424
20781
  return depth === "truecolor" ? `\x1B[38;2;${parsed.r};${parsed.g};${parsed.b}m` : `\x1B[38;5;${rgbTo2562(parsed)}m`;
20425
20782
  }
20783
+ function rainbowHex(t2) {
20784
+ const hue = (t2 % 1 + 1) % 1 * 360;
20785
+ const c = 0.5;
20786
+ const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
20787
+ const m = 0.6 - c / 2;
20788
+ const [r1, g1, b1] = hue < 60 ? [c, x, 0] : hue < 120 ? [x, c, 0] : hue < 180 ? [0, c, x] : hue < 240 ? [0, x, c] : hue < 300 ? [x, 0, c] : [c, 0, x];
20789
+ const channel = (value) => Math.round((value + m) * 255).toString(16).padStart(2, "0");
20790
+ return `#${channel(r1)}${channel(g1)}${channel(b1)}`;
20791
+ }
20426
20792
  var DEFAULT_TOKENS = {
20427
20793
  primary: "cyan",
20428
20794
  secondary: "blue",
@@ -22451,13 +22817,13 @@ function clip2(text, width, glyphs = UNICODE_GLYPHS) {
22451
22817
  }
22452
22818
 
22453
22819
  // packages/archymedes-cli/src/code-view.ts
22454
- var MAX_DIFF_CELLS2 = 250000;
22820
+ var MAX_DIFF_CELLS = 250000;
22455
22821
  function diffLines2(before, after) {
22456
22822
  const left = before === "" ? [] : before.split(`
22457
22823
  `);
22458
22824
  const right = after === "" ? [] : after.split(`
22459
22825
  `);
22460
- if (left.length * right.length > MAX_DIFF_CELLS2) {
22826
+ if (left.length * right.length > MAX_DIFF_CELLS) {
22461
22827
  return [
22462
22828
  ...left.map((text) => ({ kind: "remove", text })),
22463
22829
  ...right.map((text) => ({ kind: "add", text }))
@@ -22501,7 +22867,7 @@ function diffStat(diff) {
22501
22867
  removed: diff.filter((line) => line.kind === "remove").length
22502
22868
  };
22503
22869
  }
22504
- function condenseDiff2(diff, context = 2) {
22870
+ function condenseDiff(diff, context = 2) {
22505
22871
  const keep = new Array(diff.length).fill(false);
22506
22872
  diff.forEach((line, index) => {
22507
22873
  if (line.kind === "context")
@@ -22577,7 +22943,7 @@ function languageOf(filePath) {
22577
22943
  return name.toLowerCase();
22578
22944
  return extension || "text";
22579
22945
  }
22580
- var KEYWORDS2 = new Set([
22946
+ var KEYWORDS = new Set([
22581
22947
  "async",
22582
22948
  "await",
22583
22949
  "break",
@@ -22629,11 +22995,11 @@ var KEYWORDS2 = new Set([
22629
22995
  "with",
22630
22996
  "yield"
22631
22997
  ]);
22632
- var LITERALS2 = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
22998
+ var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
22633
22999
  function highlightCode2(line, depth) {
22634
23000
  if (depth === "none")
22635
23001
  return line;
22636
- const commentAt = findCommentStart2(line);
23002
+ const commentAt = findCommentStart(line);
22637
23003
  const code = commentAt === -1 ? line : line.slice(0, commentAt);
22638
23004
  const comment = commentAt === -1 ? "" : paint(line.slice(commentAt), DIM, depth);
22639
23005
  const painted = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_$][\w$]*)\b/g, (whole, text, numeric, word) => {
@@ -22641,15 +23007,15 @@ function highlightCode2(line, depth) {
22641
23007
  return paint(text, GREEN, depth);
22642
23008
  if (numeric !== undefined)
22643
23009
  return paint(numeric, MAGENTA, depth);
22644
- if (word !== undefined && KEYWORDS2.has(word))
23010
+ if (word !== undefined && KEYWORDS.has(word))
22645
23011
  return paint(word, CYAN, depth);
22646
- if (word !== undefined && LITERALS2.has(word))
23012
+ if (word !== undefined && LITERALS.has(word))
22647
23013
  return paint(word, MAGENTA, depth);
22648
23014
  return whole;
22649
23015
  });
22650
23016
  return `${painted}${comment}`;
22651
23017
  }
22652
- function findCommentStart2(line) {
23018
+ function findCommentStart(line) {
22653
23019
  let quote;
22654
23020
  for (let index = 0;index < line.length; index += 1) {
22655
23021
  const character = line[index];
@@ -22701,7 +23067,7 @@ function renderCode2(content, style, options = {}) {
22701
23067
  function renderDiff2(diff, style, options = {}) {
22702
23068
  const glyphs = style.glyphs ?? UNICODE_GLYPHS;
22703
23069
  const maxLines = options.maxLines ?? 16;
22704
- const condensed = condenseDiff2(diff);
23070
+ const condensed = condenseDiff(diff);
22705
23071
  const bodyWidth = Math.max(8, style.width - GUTTER.length - 4);
22706
23072
  const row = (line) => {
22707
23073
  if (line.kind === "gap")
@@ -22739,6 +23105,12 @@ function renderFileChange(change, style, options = {}) {
22739
23105
  `), style, { title: `${change.kind === "edit" ? "diff" : "new file"} · ${change.path}`, badge, tone: change.kind === "edit" ? "accent" : "good" })
22740
23106
  };
22741
23107
  }
23108
+ function fenceHeader(language, style) {
23109
+ const glyphs = style.glyphs ?? UNICODE_GLYPHS;
23110
+ const label = language ? ` ${language} ` : glyphs.boxHorizontal.repeat(4);
23111
+ const width = Math.max(0, style.width - GUTTER.length - visibleWidth(label) - 3);
23112
+ return `${GUTTER}${paint(glyphs.boxTopLeft + glyphs.boxHorizontal, DIM, style.depth)}${paintAll(label, [CYAN, BOLD], style.depth)}${paint(glyphs.boxHorizontal.repeat(Math.max(0, width)), DIM, style.depth)}`;
23113
+ }
22742
23114
 
22743
23115
  // packages/archymedes-cli/src/test-report.ts
22744
23116
  function toMs(value) {
@@ -23642,7 +24014,12 @@ function parseArgs(argv) {
23642
24014
  parsed.pin = true;
23643
24015
  else if (argument === "--no-pin")
23644
24016
  parsed.pin = false;
23645
- else if (argument === "--theme") {
24017
+ else if (argument === "--layout") {
24018
+ const value = argv[++index];
24019
+ if (value !== "fixed" && value !== "scrollback")
24020
+ throw new Error("--layout expects fixed or scrollback");
24021
+ parsed.layout = value;
24022
+ } else if (argument === "--theme") {
23646
24023
  const next = argv[index + 1];
23647
24024
  if (next && !next.startsWith("-") && /^[A-Za-z0-9_-]+$/.test(next)) {
23648
24025
  parsed.theme = next;
@@ -23811,6 +24188,8 @@ ${style.bold(t2(language, "help.transcript"))}
23811
24188
  /expand [N|all|list] Unfold written code, a test run, or a long result
23812
24189
  archymedes --ascii Draw with plain ASCII when the terminal mangles symbols
23813
24190
  archymedes --theme chalkboard Start in a named theme (/theme list shows them all)
24191
+ archymedes --layout fixed Fixed workspace, mode rail and anchored composer
24192
+ /layout [fixed|scrollback] Switch layouts mid-session; PgUp/PgDn read history
23814
24193
  archymedes --pin Pin the status line to the bottom row. Costs the terminal's
23815
24194
  scrollback: a reserved footer means scrolled-off lines are
23816
24195
  never saved, so this is off unless you ask for it.
@@ -23832,6 +24211,15 @@ var toolLines = new ReplaceableBlock(out);
23832
24211
  var markdown = new MarkdownStream(out, "none");
23833
24212
  var spinner;
23834
24213
  var screen;
24214
+ var sessionStream = {
24215
+ write: (text) => screen instanceof WorkspaceFrame ? screen.write(text) : terminalStream2.write(text),
24216
+ get columns() {
24217
+ return process.stdout.columns;
24218
+ },
24219
+ get rows() {
24220
+ return process.stdout.rows;
24221
+ }
24222
+ };
23835
24223
  var glyphs = UNICODE_GLYPHS2;
23836
24224
  var renderDepth = "none";
23837
24225
  var expandables = new ExpandableStore;
@@ -24685,10 +25073,14 @@ Setup cancelled.
24685
25073
  };
24686
25074
  configureRendering(depth, !args.json && Boolean(process.stdout.isTTY), sessionGlyphs, activeTheme ? buildPalette(activeTheme, depth) : NO_COLOR_PALETTE2);
24687
25075
  let mode = args.mode;
25076
+ let bindFixedNavigation = () => {};
25077
+ let unbindFixedNavigation = () => {};
24688
25078
  let pace = args.pace;
24689
25079
  let memories = await loadMemories(args.root, environment);
24690
25080
  let exitCleanly = () => {
24691
25081
  screen?.exit();
25082
+ setWorkspaceMenu(undefined);
25083
+ unbindFixedNavigation();
24692
25084
  uninstallShortcuts();
24693
25085
  readline.close();
24694
25086
  abandonPrompt();
@@ -24938,7 +25330,9 @@ Setup cancelled.
24938
25330
  resolvedModelId = tab.payload.modelId;
24939
25331
  tab.payload.sink.setLive(true);
24940
25332
  out.route(tab.payload.sink);
24941
- if (options.replay)
25333
+ if (screen instanceof WorkspaceFrame)
25334
+ screen.navigate("live");
25335
+ else if (options.replay)
24942
25336
  replayTab(tab);
24943
25337
  };
24944
25338
  function replayTab(tab) {
@@ -24956,7 +25350,7 @@ Setup cancelled.
24956
25350
  out.write(`${line}
24957
25351
  `);
24958
25352
  }
24959
- const firstSink = new TabSink(terminalStream2, { live: true });
25353
+ const firstSink = new TabSink(sessionStream, { live: true });
24960
25354
  tabs.adopt(path27.basename(args.root) || "archymedes", {
24961
25355
  agent,
24962
25356
  ledger,
@@ -25007,13 +25401,19 @@ Setup cancelled.
25007
25401
  };
25008
25402
  const terminalControls = () => ({
25009
25403
  clearStatus: () => statusBar.clear(),
25010
- releaseScreen: () => screen?.exit(),
25404
+ releaseScreen: () => {
25405
+ screen?.exit();
25406
+ setWorkspaceMenu(undefined);
25407
+ },
25011
25408
  uninstallShortcuts: () => uninstallShortcuts(),
25012
25409
  installShortcuts: () => installShortcutsAgain(),
25013
25410
  pauseInput: () => readline.pause(),
25014
25411
  resumeInput: () => readline.resume(),
25015
25412
  restoreScreen: () => {
25016
25413
  screen?.enter();
25414
+ if (screen instanceof WorkspaceFrame)
25415
+ setWorkspaceMenu(screen.menu);
25416
+ bindFixedNavigation();
25017
25417
  showIdleStatus();
25018
25418
  }
25019
25419
  });
@@ -25022,6 +25422,22 @@ Setup cancelled.
25022
25422
  columns: process.stdout.columns ?? 80,
25023
25423
  rows: process.stdout.rows ?? 24
25024
25424
  });
25425
+ const explainCode = async (content, target) => {
25426
+ const started = Date.now();
25427
+ const turn = await model.complete({
25428
+ messages: [
25429
+ { role: "system", content: "Explain the given source file to a developer reading it for the first time. Cover what it does, its key functions or exports, and any non-obvious design decisions. Plain prose, no code fences, under 200 words." },
25430
+ { role: "user", content: `File: ${target}
25431
+
25432
+ ${content}` }
25433
+ ],
25434
+ tools: [],
25435
+ maxOutputTokens: 500,
25436
+ safetyIdentifier: agent.sessionId
25437
+ });
25438
+ ledger.record({ usage: turn.usage, iterations: 1, toolCalls: 0, elapsedMs: Date.now() - started });
25439
+ return turn.content.trim() || "The model returned no explanation.";
25440
+ };
25025
25441
  const editFile = async (target) => {
25026
25442
  let existing = "";
25027
25443
  try {
@@ -25036,13 +25452,14 @@ Setup cancelled.
25036
25452
  }
25037
25453
  let saved;
25038
25454
  const outcome = await withFullScreen(screenCapabilities(), terminalControls(), async () => {
25039
- const { runEditorScreen } = await import("./editor-screen-51z7z11j.mjs");
25455
+ const { runEditorScreen } = await import("./editor-screen-1b3z96tn.mjs");
25040
25456
  saved = await runEditorScreen({
25041
25457
  columns: process.stdout.columns ?? 80,
25042
25458
  rows: process.stdout.rows ?? 24,
25043
25459
  path: target,
25044
25460
  content: existing,
25045
- palette
25461
+ palette,
25462
+ explain: explainCode
25046
25463
  });
25047
25464
  });
25048
25465
  if (!outcome.ok) {
@@ -25109,7 +25526,7 @@ Setup cancelled.
25109
25526
  `));
25110
25527
  return;
25111
25528
  }
25112
- const sink = new TabSink(terminalStream2);
25529
+ const sink = new TabSink(sessionStream);
25113
25530
  const stream = new JobStream({
25114
25531
  root: args.root,
25115
25532
  id,
@@ -25228,6 +25645,8 @@ Setup cancelled.
25228
25645
  unbindSigint();
25229
25646
  watched.stopAll();
25230
25647
  screen?.exit();
25648
+ setWorkspaceMenu(undefined);
25649
+ unbindFixedNavigation();
25231
25650
  uninstallShortcuts();
25232
25651
  readline.close();
25233
25652
  abandonPrompt();
@@ -25418,7 +25837,8 @@ Setup cancelled.
25418
25837
  steps: activity.steps,
25419
25838
  badge: paceBadge(pace, glyphs)
25420
25839
  });
25421
- spinner = new Spinner(() => screen?.pinned ? showStatus(formatStatusLine2(fields(), statusRoomFor(screen.current.columns), depth, glyphs, palette.primary)) : statusBar.render(fields(), depth, glyphs, palette.primary), 120, glyphs, SPINNER_START_DELAY_MS);
25840
+ const spinnerAccent = () => activeTheme?.name === "rainbow" ? colorCode2(rainbowHex(Date.now() / 1500), depth) : palette.primary;
25841
+ spinner = new Spinner(() => screen?.pinned ? showStatus(formatStatusLine2(fields(), statusRoomFor(screen.current.columns), depth, glyphs, spinnerAccent())) : statusBar.render(fields(), depth, glyphs, spinnerAccent()), 120, glyphs, SPINNER_START_DELAY_MS);
25422
25842
  spinner.start();
25423
25843
  }
25424
25844
  turnActive = true;
@@ -25666,7 +26086,7 @@ ${style.yellow(`Stopped at the ${formatMoney2(fromUnits2(args.budget, display))}
25666
26086
  palette,
25667
26087
  glyphs
25668
26088
  }, out, {
25669
- enabled: ttyMode && !readline.line && environment.TERM !== "dumb" && environment.NO_COLOR === undefined && environment.ARCHYMEDES_NO_MOTION !== "1",
26089
+ enabled: ttyMode && (args.layout ?? environment.ARCHYMEDES_LAYOUT) !== "fixed" && !readline.line && environment.TERM !== "dumb" && environment.NO_COLOR === undefined && environment.ARCHYMEDES_NO_MOTION !== "1",
25670
26090
  signal: identityMotion.signal,
25671
26091
  size: () => ({ width: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24 })
25672
26092
  });
@@ -25712,13 +26132,44 @@ ${starters}
25712
26132
  `);
25713
26133
  }
25714
26134
  const pinFooter = args.pin || (environment.ARCHYMEDES_PIN ?? "") !== "" && environment.ARCHYMEDES_PIN !== "0";
25715
- if (ttyMode) {
25716
- screen = new PinnedScreen(process.stdout, { holdRegion: pinFooter });
26135
+ const setLayout = (fixed) => {
26136
+ screen?.exit();
26137
+ setWorkspaceMenu(undefined);
26138
+ screen = fixed ? new WorkspaceFrame(process.stdout, () => ({ version: ARCHYMEDES_CLI_VERSION, workspace: path27.basename(args.root), model: `${spec.label} / ${resolvedModelId}`, mode, palette, glyphs, busy: turnActive }), () => tabs.active.payload.sink.log, environment.ARCHYMEDES_NO_MOTION !== "1" && environment.NO_COLOR === undefined && environment.TERM !== "dumb") : new PinnedScreen2(process.stdout, { holdRegion: pinFooter });
25717
26139
  screen.enter();
26140
+ if (screen instanceof WorkspaceFrame)
26141
+ setWorkspaceMenu(screen.menu);
25718
26142
  showIdleStatus();
26143
+ };
26144
+ if (ttyMode) {
26145
+ setLayout((args.layout ?? environment.ARCHYMEDES_LAYOUT) === "fixed");
26146
+ const fixedNavigation = (_str, key) => {
26147
+ if (!(screen instanceof WorkspaceFrame))
26148
+ return;
26149
+ screen.stopIntroMotion();
26150
+ if (key?.name === "pageup")
26151
+ screen.navigate("up");
26152
+ else if (key?.name === "pagedown")
26153
+ screen.navigate("down");
26154
+ else if (screen.browsing && key?.name === "escape")
26155
+ screen.navigate("live");
26156
+ };
26157
+ unbindFixedNavigation = () => {
26158
+ process.stdin.off("keypress", fixedNavigation);
26159
+ };
26160
+ bindFixedNavigation = () => {
26161
+ unbindFixedNavigation();
26162
+ process.stdin.on("keypress", fixedNavigation);
26163
+ };
26164
+ bindFixedNavigation();
25719
26165
  process.stdout.on("resize", () => {
25720
26166
  screen?.resize();
25721
26167
  showIdleStatus();
26168
+ if (screen instanceof WorkspaceFrame && !turnActive) {
26169
+ screen.positionInput();
26170
+ readline.prompt(true);
26171
+ showIdleStatus();
26172
+ }
25722
26173
  });
25723
26174
  const browseSuggestions = async (line, suggestions) => {
25724
26175
  if (!screen)
@@ -25971,6 +26422,8 @@ ${starters}
25971
26422
  const label = `${style.cyan(modeLabel)}${style.dim(` ${glyphs.caret} `)}`;
25972
26423
  const promptLabel = screen?.pinned ? promptFrame(idleStatusLine()).prefix : inlineBar() ? promptBox.draw(mode, where, idleStatusLine()) : screen ? label : `
25973
26424
  ${label}`;
26425
+ if (queued === undefined && screen instanceof WorkspaceFrame)
26426
+ queueMicrotask(() => showIdleStatus());
25974
26427
  rawInput = queued ?? await askForInput(promptLabel);
25975
26428
  } catch (error) {
25976
26429
  if (isReadlineExit(error) || exitRequested)
@@ -25979,10 +26432,31 @@ ${label}`;
25979
26432
  }
25980
26433
  promptBox.erase(rawInput);
25981
26434
  screen?.clearSuggestions();
26435
+ if (screen instanceof WorkspaceFrame && screen.browsing)
26436
+ screen.navigate("live");
25982
26437
  screen?.parkInTranscript();
25983
26438
  let input = rawInput.trim();
25984
26439
  if (!input)
25985
26440
  continue;
26441
+ if (input === "/layout" || input.startsWith("/layout ")) {
26442
+ const choice = input.slice(7).trim();
26443
+ if (!ttyMode) {
26444
+ out.write(` Fixed layout requires an interactive terminal.
26445
+ `);
26446
+ continue;
26447
+ }
26448
+ if (choice && choice !== "fixed" && choice !== "scrollback") {
26449
+ out.write(` Use /layout fixed or /layout scrollback.
26450
+ `);
26451
+ continue;
26452
+ }
26453
+ const fixed = choice ? choice === "fixed" : !(screen instanceof WorkspaceFrame);
26454
+ setLayout(fixed);
26455
+ out.write(style.dim(fixed ? ` Fixed workspace \u2014 mode rail and composer stay put. /layout scrollback returns to the log.
26456
+ ` : ` Scrollback layout \u2014 the terminal keeps a normal log. /layout fixed restores the workspace.
26457
+ `));
26458
+ continue;
26459
+ }
25986
26460
  usage.record(input);
25987
26461
  if (input === "/palette") {
25988
26462
  const chosen = interactive ? await openPalette({ readline, input: process.stdin, output: process.stdout, registry: keys }, undefined, {
@@ -26106,6 +26580,14 @@ ${label}`;
26106
26580
  }
26107
26581
  continue;
26108
26582
  }
26583
+ if (input === "/mode" && screen instanceof WorkspaceFrame) {
26584
+ const modes = ["plan", "build", "auto", "defender"];
26585
+ const descriptions = ["Read and reason; no changes", "Edit with your approval", "Apply ordinary changes automatically", "Security review; fixes require approval"];
26586
+ const chosen = await openChooser({ readline, input: process.stdin, output: process.stdout }, modes.map((value, i) => ({ value, label: value, description: descriptions[i], hint: value === mode ? "current" : undefined })), { title: "Choose how Archymedes works", initialIndex: modes.indexOf(mode), paint: surfacePaint, glyphs });
26587
+ if (!chosen)
26588
+ continue;
26589
+ input = `/mode ${chosen}`;
26590
+ }
26109
26591
  const modeCommand = parseModeCommand(input);
26110
26592
  if (modeCommand?.type === "show") {
26111
26593
  const posture2 = mode === "plan" ? "read-only; write and command tools are unavailable" : mode === "build" ? "workspace changes ask for approval" : mode === "defender" ? "security review; every change still asks for approval" : "ordinary workspace changes are pre-approved; sensitive and external actions still ask";
@@ -26444,7 +26926,7 @@ ${note4(`you ${glyphs.middot} ${files.user}`, style_)}
26444
26926
  };
26445
26927
  };
26446
26928
  const outcome = await withFullScreen(screenCapabilities(), terminalControls(), async () => {
26447
- const { runWorkspace } = await import("./workspace-screen-3zztn4by.mjs");
26929
+ const { runWorkspace } = await import("./workspace-screen-w88mcw4w.mjs");
26448
26930
  await runWorkspace({ read: readSnapshot });
26449
26931
  });
26450
26932
  if (!outcome.ok)
@@ -26457,7 +26939,7 @@ ${note4(`you ${glyphs.middot} ${files.user}`, style_)}
26457
26939
  const style_ = sectionStyle();
26458
26940
  const openGuideScreen = async (startAt) => {
26459
26941
  const outcome = await withFullScreen(screenCapabilities(), terminalControls(), async () => {
26460
- const { runGuideScreen } = await import("./guide-screen-mfvwkdhb.mjs");
26942
+ const { runGuideScreen } = await import("./guide-screen-aj9n9v80.mjs");
26461
26943
  await runGuideScreen({
26462
26944
  columns: process.stdout.columns ?? 80,
26463
26945
  rows: process.stdout.rows ?? 24,
@@ -26519,7 +27001,7 @@ ${note4(`you ${glyphs.middot} ${files.user}`, style_)}
26519
27001
  if (input === "/files") {
26520
27002
  let picked;
26521
27003
  const outcome = await withFullScreen(screenCapabilities(), terminalControls(), async () => {
26522
- const { runFileScreen } = await import("./file-screen-eg28a3ky.mjs");
27004
+ const { runFileScreen } = await import("./file-screen-m7q1xcer.mjs");
26523
27005
  picked = await runFileScreen({
26524
27006
  columns: process.stdout.columns ?? 80,
26525
27007
  rows: process.stdout.rows ?? 24,
@@ -26554,6 +27036,36 @@ ${note4(`you ${glyphs.middot} ${files.user}`, style_)}
26554
27036
  await editFile(target);
26555
27037
  continue;
26556
27038
  }
27039
+ if (input === "/cat" || input.startsWith("/cat ")) {
27040
+ const target = input.slice("/cat".length).trim();
27041
+ if (!target) {
27042
+ out.write(style.yellow(` Usage: /cat <path>
27043
+ `));
27044
+ continue;
27045
+ }
27046
+ const style_ = sectionStyle();
27047
+ try {
27048
+ const file = await workspace.readFile(target, {});
27049
+ out.write(`${rule2(style_, { label: target, tone: "accent" })}
27050
+ `);
27051
+ if (/\.(md|markdown)$/i.test(target)) {
27052
+ out.write(`${renderMarkdown2(file.content, { width: contentWidth(), depth: renderDepth, glyphs })}
27053
+ `);
27054
+ } else {
27055
+ const language2 = languageOf(target);
27056
+ out.write(`${fenceHeader(language2, style_)}
27057
+ `);
27058
+ writeFoldable(target, renderCode2(file.content, style_, { maxLines: FOLD_AFTER_LINES, language: language2 }));
27059
+ }
27060
+ if (file.truncated)
27061
+ out.write(`${note4(`\u2026 ${file.totalLines} lines total, longer than a session cares to hold at once`, style_)}
27062
+ `);
27063
+ } catch (error) {
27064
+ out.write(style.yellow(` Could not read "${target}": ${error instanceof Error ? error.message : String(error)}
27065
+ `));
27066
+ }
27067
+ continue;
27068
+ }
26557
27069
  const themeCommand = parseThemeCommand(input);
26558
27070
  if (themeCommand) {
26559
27071
  const style_ = sectionStyle();
@@ -26884,7 +27396,7 @@ ${note4(`you ${glyphs.middot} ${files.user}`, style_)}
26884
27396
  agent: newTabClient,
26885
27397
  ledger: new CostLedger2({ prices: wanted.prices, display, rates, catalog: PRICE_CATALOG2, ...approvedBudget ? { budget: approvedBudget } : {} }),
26886
27398
  mode,
26887
- sink: new TabSink(terminalStream2),
27399
+ sink: new TabSink(sessionStream),
26888
27400
  provider: wanted.provider,
26889
27401
  spec: wanted.spec,
26890
27402
  prices: wanted.prices,