archymedes-cli 2.2.0 → 2.3.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.
@@ -1,18 +1,19 @@
1
1
  import {
2
- CYAN,
3
- DIM,
4
- GREEN,
5
2
  GUTTER,
6
- MAGENTA,
7
- RED,
8
3
  clip,
9
- paint
10
- } from "./archymedes-cdgejs3p.mjs";
4
+ panel
5
+ } from "./archymedes-tsne2m7p.mjs";
11
6
  import {
12
- UNICODE_GLYPHS
13
- } from "./archymedes-5zn4zp84.mjs";
7
+ BOLD,
8
+ DIM,
9
+ UNICODE_GLYPHS,
10
+ paint,
11
+ paintAll,
12
+ roleCode,
13
+ visibleWidth
14
+ } from "./archymedes-a10k8yrk.mjs";
14
15
 
15
- // packages/archymedes-cli/src/code-view.ts
16
+ // src/render/code-view.ts
16
17
  var MAX_DIFF_CELLS = 250000;
17
18
  function diffLines(before, after) {
18
19
  const left = before === "" ? [] : before.split(`
@@ -57,6 +58,12 @@ function diffLines(before, after) {
57
58
  }
58
59
  return result;
59
60
  }
61
+ function diffStat(diff) {
62
+ return {
63
+ added: diff.filter((line) => line.kind === "add").length,
64
+ removed: diff.filter((line) => line.kind === "remove").length
65
+ };
66
+ }
60
67
  function condenseDiff(diff, context = 2) {
61
68
  const keep = new Array(diff.length).fill(false);
62
69
  diff.forEach((line, index) => {
@@ -186,7 +193,7 @@ var KEYWORDS = new Set([
186
193
  "yield"
187
194
  ]);
188
195
  var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
189
- function highlightCode(line, depth) {
196
+ function highlightCode(line, depth, palette) {
190
197
  if (depth === "none")
191
198
  return line;
192
199
  const commentAt = findCommentStart(line);
@@ -194,13 +201,13 @@ function highlightCode(line, depth) {
194
201
  const comment = commentAt === -1 ? "" : paint(line.slice(commentAt), DIM, depth);
195
202
  const painted = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_$][\w$]*)\b/g, (whole, text, numeric, word) => {
196
203
  if (text !== undefined)
197
- return paint(text, GREEN, depth);
204
+ return paint(text, roleCode("success", palette, depth), depth);
198
205
  if (numeric !== undefined)
199
- return paint(numeric, MAGENTA, depth);
206
+ return paint(numeric, roleCode("accent", palette, depth), depth);
200
207
  if (word !== undefined && KEYWORDS.has(word))
201
- return paint(word, CYAN, depth);
208
+ return paint(word, roleCode("primary", palette, depth), depth);
202
209
  if (word !== undefined && LITERALS.has(word))
203
- return paint(word, MAGENTA, depth);
210
+ return paint(word, roleCode("accent", palette, depth), depth);
204
211
  return whole;
205
212
  });
206
213
  return `${painted}${comment}`;
@@ -241,7 +248,7 @@ function renderCode(content, style, options = {}) {
241
248
  const bodyWidth = Math.max(8, style.width - GUTTER.length - numberWidth - 4);
242
249
  const render = (from, to) => lines.slice(from, to).map((line, index) => {
243
250
  const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
244
- const painted = options.highlight === false ? line : highlightCode(line, style.depth);
251
+ const painted = options.highlight === false ? line : highlightCode(line, style.depth, style.palette);
245
252
  return `${number} ${clip(painted, bodyWidth, glyphs)}`;
246
253
  });
247
254
  const hidden = Math.max(0, lines.length - maxLines);
@@ -263,8 +270,8 @@ function renderDiff(diff, style, options = {}) {
263
270
  if (line.kind === "gap")
264
271
  return paint(`${glyphs.middot.repeat(3)} ${line.text}`, DIM, style.depth);
265
272
  const mark = line.kind === "add" ? glyphs.plus : line.kind === "remove" ? glyphs.minus : " ";
266
- const code = line.kind === "add" ? GREEN : line.kind === "remove" ? RED : DIM;
267
- const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth);
273
+ const code = line.kind === "add" ? roleCode("success", style.palette, style.depth) : line.kind === "remove" ? roleCode("error", style.palette, style.depth) : DIM;
274
+ const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth, style.palette);
268
275
  return `${paint(mark, code, style.depth)} ${clip(body, bodyWidth, glyphs)}`;
269
276
  };
270
277
  const rows = condensed.map(row);
@@ -277,8 +284,32 @@ function renderDiff(diff, style, options = {}) {
277
284
  `)
278
285
  };
279
286
  }
280
- function describeChange(stat, depth) {
281
- return `${paint(`+${stat.added}`, GREEN, depth)} ${paint(`-${stat.removed}`, RED, depth)}`;
287
+ function renderFileChange(change, style, options = {}) {
288
+ const language = languageOf(change.path);
289
+ const block = change.kind === "edit" ? renderDiff(diffLines(change.before ?? "", change.after ?? ""), style, { maxLines: options.maxLines }) : renderCode(change.content ?? "", style, { maxLines: options.maxLines, language });
290
+ const badge = change.kind === "edit" ? (() => {
291
+ const stat = diffStat(diffLines(change.before ?? "", change.after ?? ""));
292
+ return `+${stat.added} -${stat.removed}`;
293
+ })() : `${(change.content ?? "").split(`
294
+ `).length} lines · ${language}`;
295
+ const body = block.text.split(`
296
+ `);
297
+ const foot = block.hidden > 0 && options.expandHint ? [paint(`${(style.glyphs ?? UNICODE_GLYPHS).collapsed} ${block.hidden} more line${block.hidden === 1 ? "" : "s"} — ${options.expandHint}`, DIM, style.depth)] : [];
298
+ return {
299
+ text: panel([...body, ...foot], style, { title: `${change.kind === "edit" ? "diff" : "new file"} · ${change.path}`, badge, tone: change.kind === "edit" ? "accent" : "good" }),
300
+ hidden: block.hidden,
301
+ full: panel(block.full.split(`
302
+ `), style, { title: `${change.kind === "edit" ? "diff" : "new file"} · ${change.path}`, badge, tone: change.kind === "edit" ? "accent" : "good" })
303
+ };
304
+ }
305
+ function fenceHeader(language, style) {
306
+ const glyphs = style.glyphs ?? UNICODE_GLYPHS;
307
+ const label = language ? ` ${language} ` : glyphs.boxHorizontal.repeat(4);
308
+ const width = Math.max(0, style.width - GUTTER.length - visibleWidth(label) - 3);
309
+ return `${GUTTER}${paint(glyphs.boxTopLeft + glyphs.boxHorizontal, DIM, style.depth)}${paintAll(label, [roleCode("primary", style.palette, style.depth), BOLD], style.depth)}${paint(glyphs.boxHorizontal.repeat(Math.max(0, width)), DIM, style.depth)}`;
310
+ }
311
+ function describeChange(stat, depth, palette) {
312
+ return `${paint(`+${stat.added}`, roleCode("success", palette, depth), depth)} ${paint(`-${stat.removed}`, roleCode("error", palette, depth), depth)}`;
282
313
  }
283
314
 
284
- export { diffLines, languageOf, highlightCode, renderCode, renderDiff, describeChange };
315
+ export { diffLines, diffStat, languageOf, highlightCode, renderCode, renderDiff, renderFileChange, fenceHeader, describeChange };
@@ -1,15 +1,20 @@
1
1
  import {
2
+ ANSI_PALETTE,
2
3
  ASCII_GLYPHS,
4
+ BOLD,
5
+ DIM,
6
+ ITALIC,
7
+ STRIKE,
3
8
  UNICODE_GLYPHS,
4
9
  borderGlyphsFor,
5
- newMarkdownState,
10
+ paint,
6
11
  rainbowText,
7
- renderMarkdownLine,
8
12
  rgbTo256,
13
+ roleCode,
9
14
  visibleWidth
10
- } from "./archymedes-5zn4zp84.mjs";
15
+ } from "./archymedes-a10k8yrk.mjs";
11
16
 
12
- // packages/archymedes-cli/src/output.ts
17
+ // src/terminal/output.ts
13
18
  var terminalStream = {
14
19
  write(text) {
15
20
  process.stdout.write(text);
@@ -21,17 +26,246 @@ var terminalStream = {
21
26
  return process.stdout.rows;
22
27
  }
23
28
  };
29
+ class OutputRouter {
30
+ target;
31
+ constructor(target = terminalStream) {
32
+ this.target = target;
33
+ }
34
+ get current() {
35
+ return this.target;
36
+ }
37
+ route(target) {
38
+ this.target = target;
39
+ }
40
+ write(text) {
41
+ this.target.write(text);
42
+ }
43
+ get columns() {
44
+ return this.target.columns;
45
+ }
46
+ get rows() {
47
+ return this.target.rows;
48
+ }
49
+ }
50
+ function replayLines(log, count) {
51
+ const lines = log.tail(count);
52
+ return { lines, dropped: log.dropped, omitted: Math.max(0, log.size - lines.length) };
53
+ }
24
54
 
25
- // packages/archymedes-cli/src/tui.ts
26
- var RESET = "\x1B[0m";
27
- var DIM = "\x1B[2m";
28
- var CYAN = "\x1B[36m";
29
- var GREEN = "\x1B[32m";
30
- var YELLOW = "\x1B[33m";
31
- var RED = "\x1B[31m";
32
- function paint(text, code, depth) {
33
- return depth === "none" ? text : `${code}${text}${RESET}`;
55
+ // src/render/markdown.ts
56
+ var colour = (role, palette) => palette?.[role] || ANSI_PALETTE[role];
57
+ var INLINE_PATTERN = /(?<!\\)(`[^`\n]+`)|(?<!\\)(\*\*[^*\n]+\*\*)|(?<!\\)(__[^_\n]+__)|(?<!\\)(~~[^~\n]+~~)|(?<!\\)(\*[^*\n]+\*)|(?<!\\)(_[^_\n]+_)|(!?\[([^\]\n]+)\]\((https?:\/\/[^\s)\n]+)\))|(<(https?:\/\/[^>\s]+)>)/g;
58
+ var unescapeMarkdown = (text) => text.replace(/\\([\\`*_[\]{}()#+.!~>-])/g, "$1");
59
+ function parseInline(line, palette) {
60
+ const tokens = [];
61
+ let lastIndex = 0;
62
+ INLINE_PATTERN.lastIndex = 0;
63
+ for (let match = INLINE_PATTERN.exec(line);match !== null; match = INLINE_PATTERN.exec(line)) {
64
+ if (match.index > lastIndex)
65
+ tokens.push({ text: unescapeMarkdown(line.slice(lastIndex, match.index)), code: "" });
66
+ const [whole, code, boldStars, boldUnderscores, strike, italicStars, italicUnderscores, link, linkLabel, linkUrl, autolink, autolinkUrl] = match;
67
+ if (code !== undefined)
68
+ tokens.push({ text: code.slice(1, -1), code: colour("primary", palette) });
69
+ else if (boldStars !== undefined)
70
+ tokens.push({ text: boldStars.slice(2, -2), code: BOLD });
71
+ else if (boldUnderscores !== undefined)
72
+ tokens.push({ text: boldUnderscores.slice(2, -2), code: BOLD });
73
+ else if (strike !== undefined)
74
+ tokens.push({ text: strike.slice(2, -2), code: STRIKE });
75
+ else if (italicStars !== undefined)
76
+ tokens.push({ text: italicStars.slice(1, -1), code: ITALIC });
77
+ else if (italicUnderscores !== undefined)
78
+ tokens.push({ text: italicUnderscores.slice(1, -1), code: ITALIC });
79
+ else if (link !== undefined)
80
+ tokens.push({ text: `${link.startsWith("!") ? `image: ${linkLabel}` : linkLabel} (${linkUrl})`, code: colour("primary", palette) });
81
+ else if (autolink !== undefined)
82
+ tokens.push({ text: autolinkUrl, code: colour("primary", palette) });
83
+ lastIndex = match.index + whole.length;
84
+ }
85
+ if (lastIndex < line.length)
86
+ tokens.push({ text: unescapeMarkdown(line.slice(lastIndex)), code: "" });
87
+ return tokens.filter((token) => token.text.length > 0);
88
+ }
89
+ function wrapTokens(tokens, width, options) {
90
+ const firstPrefix = options.firstPrefix ?? "";
91
+ const continuationPrefix = options.continuationPrefix ?? "";
92
+ const lines = [];
93
+ let current = "";
94
+ let currentWidth = 0;
95
+ let prefix = firstPrefix;
96
+ let prefixWidth = visibleWidth(firstPrefix);
97
+ const limit = Math.max(1, width);
98
+ const flush = () => {
99
+ lines.push(`${prefix}${current}`.replace(/[ \t]+$/, ""));
100
+ current = "";
101
+ currentWidth = 0;
102
+ prefix = continuationPrefix;
103
+ prefixWidth = visibleWidth(continuationPrefix);
104
+ };
105
+ for (const token of tokens) {
106
+ for (const word of token.text.split(/(\s+)/)) {
107
+ if (word === "")
108
+ continue;
109
+ const wordWidth = visibleWidth(word);
110
+ if (/^\s+$/.test(word)) {
111
+ if (currentWidth > 0 && prefixWidth + currentWidth + wordWidth <= limit) {
112
+ current += word;
113
+ currentWidth += wordWidth;
114
+ }
115
+ continue;
116
+ }
117
+ if (currentWidth > 0 && prefixWidth + currentWidth + wordWidth > limit)
118
+ flush();
119
+ if (prefixWidth + wordWidth > limit) {
120
+ let remaining = word;
121
+ while (remaining.length > 0) {
122
+ const available = Math.max(1, limit - prefixWidth - currentWidth);
123
+ if (visibleWidth(remaining) <= available)
124
+ break;
125
+ const head = [...remaining].slice(0, available).join("");
126
+ current += paint(head, token.code, options.depth);
127
+ remaining = [...remaining].slice(available).join("");
128
+ flush();
129
+ }
130
+ if (remaining.length > 0) {
131
+ current += paint(remaining, token.code, options.depth);
132
+ currentWidth += visibleWidth(remaining);
133
+ }
134
+ continue;
135
+ }
136
+ current += paint(word, token.code, options.depth);
137
+ currentWidth += wordWidth;
138
+ }
139
+ }
140
+ if (current !== "" || lines.length === 0)
141
+ lines.push(`${prefix}${current}`.replace(/[ \t]+$/, ""));
142
+ return lines;
143
+ }
144
+ function newMarkdownState() {
145
+ return { inFence: false, fenceLanguage: "" };
34
146
  }
147
+ var HEADING = /^(#{1,6})\s+(.*)$/;
148
+ var BULLET = /^(\s*)([-*+])\s+(.*)$/;
149
+ var TASK = /^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/;
150
+ var NUMBERED = /^(\s*)(\d+[.)])\s+(.*)$/;
151
+ var BLOCKQUOTE = /^>\s?(.*)$/;
152
+ var RULE = /^\s*([-*_])\s*(?:\1\s*){2,}$/;
153
+ var FENCE = /^\s*```(.*)$/;
154
+ var TABLE_SEPARATOR_CELL = /^:?-{3,}:?$/;
155
+ function tableCells(line) {
156
+ if (!line.includes("|"))
157
+ return null;
158
+ const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
159
+ const cells = trimmed.split("|").map((cell) => cell.trim());
160
+ return cells.length >= 2 ? cells : null;
161
+ }
162
+ function fitCell(text, width, glyphs = UNICODE_GLYPHS) {
163
+ let result = "";
164
+ for (const character of text) {
165
+ if (visibleWidth(result + character) > width) {
166
+ while (visibleWidth(result) > Math.max(0, width - visibleWidth(glyphs.ellipsis)))
167
+ result = [...result].slice(0, -1).join("");
168
+ result += glyphs.ellipsis;
169
+ return result + " ".repeat(Math.max(0, width - visibleWidth(result)));
170
+ }
171
+ result += character;
172
+ }
173
+ return result + " ".repeat(Math.max(0, width - visibleWidth(result)));
174
+ }
175
+ function renderTableLine(cells, separator, width, depth, glyphs) {
176
+ const cellWidth = Math.max(3, Math.floor((Math.max(width, 12) - cells.length - 1) / cells.length));
177
+ if (separator)
178
+ return paint(`${glyphs.boxTeeLeft}${cells.map(() => glyphs.boxHorizontal.repeat(cellWidth)).join(glyphs.boxCross)}${glyphs.boxTeeRight}`, DIM, depth);
179
+ const contentWidth = Math.max(1, cellWidth - 2);
180
+ const rendered = cells.map((cell) => fitCell(parseInline(cell).map((token) => token.text).join(""), contentWidth, glyphs));
181
+ return `${glyphs.boxVertical}${rendered.map((cell) => ` ${cell} `).join(glyphs.boxVertical)}${glyphs.boxVertical}`;
182
+ }
183
+ function renderMarkdownLine(line, state, options) {
184
+ const { width, depth, palette } = options;
185
+ const glyphs = options.glyphs ?? UNICODE_GLYPHS;
186
+ const fence = FENCE.exec(line);
187
+ if (fence) {
188
+ const ruleWidth = Math.max(4, Math.min(width - 4, 40));
189
+ if (state.inFence) {
190
+ state.inFence = false;
191
+ state.fenceLanguage = "";
192
+ return [paint(` ${glyphs.boxBottomLeft}${glyphs.boxHorizontal.repeat(ruleWidth)}`, DIM, depth)];
193
+ }
194
+ state.inFence = true;
195
+ state.fenceLanguage = fence[1].trim();
196
+ const label = state.fenceLanguage ? ` ${state.fenceLanguage} ` : "";
197
+ const drawn = 1 + visibleWidth(label);
198
+ return [paint(` ${glyphs.boxTopLeft}${glyphs.boxHorizontal}${label}${glyphs.boxHorizontal.repeat(Math.max(0, ruleWidth - drawn))}`, DIM, depth)];
199
+ }
200
+ if (state.inFence) {
201
+ return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${paint(line, colour("success", palette), depth)}`];
202
+ }
203
+ const table = tableCells(line);
204
+ if (table) {
205
+ const separator = table.every((cell) => TABLE_SEPARATOR_CELL.test(cell));
206
+ return [renderTableLine(table, separator, width, depth, glyphs)];
207
+ }
208
+ if (RULE.test(line) && line.trim().length > 0) {
209
+ return [paint(" " + glyphs.boxHorizontal.repeat(Math.max(4, Math.min(width - 4, 40))), DIM, depth)];
210
+ }
211
+ const heading = HEADING.exec(line);
212
+ if (heading) {
213
+ const level = heading[1].length;
214
+ const code = level <= 2 ? `${BOLD}${colour("primary", palette)}` : BOLD;
215
+ const tokens = parseInline(heading[2]).map((token) => ({ text: token.text, code }));
216
+ return wrapTokens(tokens, width, { depth });
217
+ }
218
+ const task = TASK.exec(line);
219
+ if (task) {
220
+ return wrapTokens(parseInline(task[3], palette), width, {
221
+ firstPrefix: `${task[1]}${paint(task[2].trim() ? glyphs.checkboxDone : glyphs.checkbox, task[2].trim() ? colour("success", palette) : colour("secondary", palette), depth)} `,
222
+ continuationPrefix: `${task[1]} `,
223
+ depth
224
+ });
225
+ }
226
+ const bullet = BULLET.exec(line);
227
+ if (bullet) {
228
+ const indent = bullet[1];
229
+ return wrapTokens(parseInline(bullet[3], palette), width, {
230
+ firstPrefix: `${indent}${paint(glyphs.bullet, colour("secondary", palette), depth)} `,
231
+ continuationPrefix: `${indent} `,
232
+ depth
233
+ });
234
+ }
235
+ const numbered = NUMBERED.exec(line);
236
+ if (numbered) {
237
+ const indent = numbered[1];
238
+ const marker = numbered[2];
239
+ return wrapTokens(parseInline(numbered[3], palette), width, {
240
+ firstPrefix: `${indent}${paint(marker, colour("secondary", palette), depth)} `,
241
+ continuationPrefix: `${indent}${" ".repeat(marker.length + 1)}`,
242
+ depth
243
+ });
244
+ }
245
+ const quote = BLOCKQUOTE.exec(line);
246
+ if (quote) {
247
+ return wrapTokens([{ text: quote[1], code: DIM }], width, {
248
+ firstPrefix: paint(`${glyphs.boxVertical} `, colour("accent", palette), depth),
249
+ continuationPrefix: paint(`${glyphs.boxVertical} `, colour("accent", palette), depth),
250
+ depth
251
+ });
252
+ }
253
+ if (line.trim() === "")
254
+ return [""];
255
+ return wrapTokens(parseInline(line, palette), width, { depth });
256
+ }
257
+ function renderMarkdown(text, options) {
258
+ const state = newMarkdownState();
259
+ const glyphs = options.glyphs ?? UNICODE_GLYPHS;
260
+ const rendered = text.split(`
261
+ `).flatMap((line) => renderMarkdownLine(line, state, options));
262
+ if (state.inFence)
263
+ rendered.push(paint(` ${glyphs.boxBottomLeft}${glyphs.boxHorizontal.repeat(4)}`, DIM, options.depth));
264
+ return rendered.join(`
265
+ `);
266
+ }
267
+
268
+ // src/render/tui.ts
35
269
  function archymedesSpinnerFrame(index, glyphs = UNICODE_GLYPHS) {
36
270
  const frames = glyphs.spinnerFrames;
37
271
  const normalized = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
@@ -279,7 +513,7 @@ function stepProgress(done, total, options) {
279
513
  });
280
514
  return `${label}${counted} ${bar}`;
281
515
  }
282
- function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent = CYAN) {
516
+ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent = ANSI_PALETTE.primary) {
283
517
  const label = activityLabel(fields.phase ?? "thinking", fields.operation, fields.elapsedMs);
284
518
  const ellipsis = glyphs.ellipsis;
285
519
  const separator = ` ${glyphs.middot} `;
@@ -313,27 +547,115 @@ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent
313
547
  kept = kept.slice(0, -1);
314
548
  }
315
549
  }
550
+
551
+ class StatusBar {
552
+ stream;
553
+ linesDrawn = 0;
554
+ constructor(stream = terminalStream) {
555
+ this.stream = stream;
556
+ }
557
+ render(fields, depth, glyphs = UNICODE_GLYPHS, accent) {
558
+ this.clear();
559
+ const width = this.stream.columns ?? 80;
560
+ const line = formatStatusLine(fields, width, depth, glyphs, accent);
561
+ this.stream.write(`${line}
562
+ `);
563
+ this.linesDrawn = 1;
564
+ }
565
+ renderLine(text) {
566
+ this.clear();
567
+ if (text === "")
568
+ return;
569
+ this.stream.write(`${text}
570
+ `);
571
+ this.linesDrawn = 1;
572
+ }
573
+ clear() {
574
+ if (this.linesDrawn === 0)
575
+ return;
576
+ for (let index = 0;index < this.linesDrawn; index += 1) {
577
+ this.stream.write("\x1B[1A\x1B[2K");
578
+ }
579
+ this.linesDrawn = 0;
580
+ }
581
+ }
316
582
  function rowsOccupied(text, columns) {
317
583
  const width = visibleWidth(text);
318
584
  if (width === 0)
319
585
  return 1;
320
586
  return Math.ceil(width / Math.max(1, columns));
321
587
  }
588
+
589
+ class ReplaceableBlock {
590
+ stream;
591
+ columns;
592
+ entries = [];
593
+ rows = 0;
594
+ constructor(stream = terminalStream, columns = () => process.stdout.columns ?? 80) {
595
+ this.stream = stream;
596
+ this.columns = columns;
597
+ }
598
+ append(text) {
599
+ this.entries.push(text);
600
+ this.stream.write(`${text}
601
+ `);
602
+ this.rows += rowsOccupied(text, this.columns());
603
+ return this.entries.length - 1;
604
+ }
605
+ update(index, text) {
606
+ if (index < 0 || index >= this.entries.length)
607
+ return false;
608
+ this.entries[index] = text;
609
+ for (let row = 0;row < this.rows; row += 1)
610
+ this.stream.write("\x1B[1A\x1B[2K");
611
+ this.stream.write("\r");
612
+ this.rows = 0;
613
+ for (const entry of this.entries) {
614
+ this.stream.write(`${entry}
615
+ `);
616
+ this.rows += rowsOccupied(entry, this.columns());
617
+ }
618
+ return true;
619
+ }
620
+ updateAll(texts) {
621
+ for (let row = 0;row < this.rows; row += 1)
622
+ this.stream.write("\x1B[1A\x1B[2K");
623
+ this.stream.write("\r");
624
+ for (let index = 0;index < texts.length && index < this.entries.length; index += 1)
625
+ this.entries[index] = texts[index];
626
+ this.rows = 0;
627
+ for (const entry of this.entries) {
628
+ this.stream.write(`${entry}
629
+ `);
630
+ this.rows += rowsOccupied(entry, this.columns());
631
+ }
632
+ }
633
+ forget() {
634
+ this.entries = [];
635
+ this.rows = 0;
636
+ }
637
+ get active() {
638
+ return this.entries.length > 0;
639
+ }
640
+ }
641
+
322
642
  class MarkdownStream {
323
643
  stream;
324
644
  depth;
325
645
  columns;
326
646
  live;
327
647
  glyphs;
648
+ palette;
328
649
  pending = "";
329
650
  atLineStart = true;
330
651
  state = newMarkdownState();
331
- constructor(stream = terminalStream, depth = "none", columns = () => process.stdout.columns ?? 80, live = true, glyphs = UNICODE_GLYPHS) {
652
+ constructor(stream = terminalStream, depth = "none", columns = () => process.stdout.columns ?? 80, live = true, glyphs = UNICODE_GLYPHS, palette) {
332
653
  this.stream = stream;
333
654
  this.depth = depth;
334
655
  this.columns = columns;
335
656
  this.live = live;
336
657
  this.glyphs = glyphs;
658
+ this.palette = palette;
337
659
  }
338
660
  get active() {
339
661
  return this.live ? this.pending !== "" : !this.atLineStart;
@@ -370,7 +692,7 @@ class MarkdownStream {
370
692
  if (this.pending !== "")
371
693
  this.finalizeLine();
372
694
  if (this.state.inFence) {
373
- const rendered = renderMarkdownLine("```", this.state, { width: this.columns(), depth: this.depth, glyphs: this.glyphs });
695
+ const rendered = renderMarkdownLine("```", this.state, { width: this.columns(), depth: this.depth, glyphs: this.glyphs, palette: this.palette });
374
696
  this.stream.write(`${rendered.join(`
375
697
  `)}
376
698
  `);
@@ -383,7 +705,7 @@ class MarkdownStream {
383
705
  }
384
706
  finalizeLine() {
385
707
  this.erasePending();
386
- const rendered = renderMarkdownLine(this.pending, this.state, { width: this.columns(), depth: this.depth, glyphs: this.glyphs });
708
+ const rendered = renderMarkdownLine(this.pending, this.state, { width: this.columns(), depth: this.depth, glyphs: this.glyphs, palette: this.palette });
387
709
  this.stream.write(`${rendered.join(`
388
710
  `)}
389
711
  `);
@@ -401,14 +723,14 @@ class MarkdownStream {
401
723
  function box(lines, options) {
402
724
  const terminalWidth = options.width ?? process.stdout.columns ?? 80;
403
725
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
404
- const border = borderGlyphsFor(options.borderStyle ?? "round", glyphs);
726
+ const border = borderGlyphsFor(options.borderStyle ?? options.palette?.borderStyle ?? "round", glyphs);
405
727
  const titleWidth = options.title ? visibleWidth(options.title) : 0;
406
- const titlePaint = TONE_CODES[options.titleColor ?? "cyan"];
728
+ const titlePaint = roleCode(TONE_ROLE[options.titleColor ?? "cyan"], options.palette, options.depth);
407
729
  const rainbowBorder = options.borderColor === "rainbow";
408
730
  const edge = (text, phase = 0) => {
409
731
  if (options.borderColor === undefined)
410
732
  return text;
411
- return rainbowBorder ? rainbowText(text, options.depth, phase) : paint(text, TONE_CODES[options.borderColor], options.depth);
733
+ return rainbowBorder ? rainbowText(text, options.depth, phase) : paint(text, roleCode(TONE_ROLE[options.borderColor], options.palette, options.depth), options.depth);
412
734
  };
413
735
  const contentWidth = Math.min(Math.max(titleWidth, ...lines.map((line) => visibleWidth(line)), 1), Math.max(1, terminalWidth - 4));
414
736
  const horizontal = border.horizontal.repeat(contentWidth + 2);
@@ -454,16 +776,16 @@ function joinHorizontal(left, right, options) {
454
776
  const separator = options.separator ?? " ";
455
777
  return `${padToWidth(left, options.leftWidth, options.leftAlign)}${separator}${padToWidth(right, options.rightWidth, options.rightAlign)}`;
456
778
  }
457
- var TONE_CODES = { cyan: CYAN, green: GREEN, yellow: YELLOW, red: RED };
458
- var MODE_COLORS = { plan: YELLOW, auto: GREEN, build: CYAN, defender: RED };
779
+ var TONE_ROLE = { cyan: "primary", green: "success", yellow: "warning", red: "error" };
780
+ var MODE_ROLE = { plan: "warning", auto: "success", build: "primary", defender: "error" };
459
781
  var PROMPT_CHROME_COLUMNS = 6;
460
782
  function renderPromptBox(options) {
461
783
  const { mode, workspace, depth } = options;
462
784
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
463
785
  const border = borderGlyphsFor(options.palette?.borderStyle ?? options.borderStyle ?? "round", glyphs);
464
786
  const width = Math.max(12, options.width);
465
- const primary = options.palette?.primary ?? CYAN;
466
- const modeColor = options.palette ? { plan: options.palette.warning, auto: options.palette.success, build: primary, defender: options.palette.error }[mode] ?? primary : MODE_COLORS[mode] ?? CYAN;
787
+ const primary = roleCode("primary", options.palette, depth);
788
+ const modeColor = roleCode(MODE_ROLE[mode] ?? "primary", options.palette, depth);
467
789
  const horizontal = (count) => paint(border.horizontal.repeat(Math.max(0, count)), primary, depth);
468
790
  const CHROME = PROMPT_CHROME_COLUMNS;
469
791
  const status = options.status ?? "";
@@ -491,5 +813,34 @@ function renderPromptBox(options) {
491
813
  const bottom = `${paint(border.bottomLeft, primary, depth)}${horizontal(width - 2)}${paint(border.bottomRight, primary, depth)}`;
492
814
  return { top, prefix, bottom };
493
815
  }
816
+ function wrapPlain(text, width) {
817
+ const lines = [];
818
+ let current = "";
819
+ for (const word of text.split(/\s+/)) {
820
+ if (word === "")
821
+ continue;
822
+ const candidate = current === "" ? word : `${current} ${word}`;
823
+ if (visibleWidth(candidate) <= width) {
824
+ current = candidate;
825
+ continue;
826
+ }
827
+ if (current !== "")
828
+ lines.push(current);
829
+ if (visibleWidth(word) > width) {
830
+ let rest = word;
831
+ while (visibleWidth(rest) > width) {
832
+ const chunk = sliceToWidth(rest, width);
833
+ lines.push(chunk);
834
+ rest = rest.slice(chunk.length);
835
+ }
836
+ current = rest;
837
+ } else {
838
+ current = word;
839
+ }
840
+ }
841
+ if (current !== "")
842
+ lines.push(current);
843
+ return lines.length === 0 ? [""] : lines;
844
+ }
494
845
 
495
- export { terminalStream, archymedesSpinnerFrame, SpringAnimator, sparkline, progressBar, table, scrollPercent, scrollIndicator, paginator, stepProgress, formatStatusLine, rowsOccupied, box, sliceToWidth, padToWidth, joinHorizontal, renderPromptBox };
846
+ export { terminalStream, OutputRouter, replayLines, newMarkdownState, renderMarkdownLine, renderMarkdown, archymedesSpinnerFrame, SpringAnimator, sparkline, progressBar, table, scrollPercent, scrollIndicator, paginator, stepProgress, formatStatusLine, StatusBar, rowsOccupied, ReplaceableBlock, MarkdownStream, box, sliceToWidth, padToWidth, joinHorizontal, renderPromptBox, wrapPlain };
@@ -1,4 +1,4 @@
1
- // packages/core/src/sandbox-policy.ts
1
+ // ../core/src/sandbox-policy.ts
2
2
  var ALLOWED_SANDBOX_PROGRAMS = [
3
3
  "bun",
4
4
  "npm",