archymedes-cli 2.1.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,141 +1,62 @@
1
1
  import {
2
- rgbTo256
3
- } from "./archymedes-jecyxv5c.mjs";
2
+ ANSI_PALETTE,
3
+ ASCII_GLYPHS,
4
+ BOLD,
5
+ DIM,
6
+ ITALIC,
7
+ STRIKE,
8
+ UNICODE_GLYPHS,
9
+ borderGlyphsFor,
10
+ paint,
11
+ rainbowText,
12
+ rgbTo256,
13
+ roleCode,
14
+ visibleWidth
15
+ } from "./archymedes-a10k8yrk.mjs";
4
16
 
5
- // packages/archymedes-cli/src/glyphs.ts
6
- var UNICODE_GLYPHS = {
7
- starGlyphs: [".", "·", "✧", "✦", "✶"],
8
- spinnerFrames: [" ◜ ", " ◠ ", " ◝ ", " ◞ ", " ◡ ", " ◟ "],
9
- star: "✦",
10
- starDim: "✧",
11
- check: "✓",
12
- cross: "✗",
13
- pending: "⋯",
14
- prompt: "❯",
15
- caret: "›",
16
- bullet: "•",
17
- ellipsis: "…",
18
- middot: "·",
19
- elbow: "⎿",
20
- boxTopLeft: "╭",
21
- boxTopRight: "╮",
22
- boxBottomLeft: "╰",
23
- boxBottomRight: "╯",
24
- boxHorizontal: "─",
25
- boxVertical: "│",
26
- boxTeeLeft: "├",
27
- boxTeeRight: "┤",
28
- boxCross: "┼",
29
- circleEmpty: "○",
30
- circleHalf: "◐",
31
- circleFull: "●",
32
- checkbox: "☐",
33
- checkboxDone: "☑",
34
- paused: "⏸",
35
- cancelled: "⊘",
36
- treeBranch: "├─",
37
- treeLast: "╰─",
38
- treeTrunk: "│ ",
39
- collapsed: "▸",
40
- expanded: "▾",
41
- arrowUp: "↑",
42
- arrowLeft: "←",
43
- arrowRight: "→",
44
- arrowDown: "↓",
45
- plus: "+",
46
- minus: "-",
47
- warning: "!",
48
- effectWorkspace: "✎",
49
- effectExternal: "◎",
50
- sparkLevels: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
51
- };
52
- var ASCII_GLYPHS = {
53
- starGlyphs: [".", ".", "+", "*", "*"],
54
- spinnerFrames: ["-", "\\", "|", "/"],
55
- star: "*",
56
- starDim: "+",
57
- check: "+",
58
- cross: "x",
59
- pending: "~",
60
- prompt: ">",
61
- caret: ">",
62
- bullet: "-",
63
- ellipsis: "...",
64
- middot: "-",
65
- elbow: "\\",
66
- boxTopLeft: "+",
67
- boxTopRight: "+",
68
- boxBottomLeft: "+",
69
- boxBottomRight: "+",
70
- boxHorizontal: "-",
71
- boxVertical: "|",
72
- boxTeeLeft: "+",
73
- boxTeeRight: "+",
74
- boxCross: "+",
75
- circleEmpty: "o",
76
- circleHalf: "*",
77
- circleFull: "*",
78
- checkbox: "[ ]",
79
- checkboxDone: "[x]",
80
- paused: "=",
81
- cancelled: "x",
82
- treeBranch: "|-",
83
- treeLast: "`-",
84
- treeTrunk: "| ",
85
- collapsed: ">",
86
- expanded: "v",
87
- arrowUp: "^",
88
- arrowLeft: "<-",
89
- arrowRight: "->",
90
- arrowDown: "v",
91
- plus: "+",
92
- minus: "-",
93
- warning: "!",
94
- effectWorkspace: "w",
95
- effectExternal: "e",
96
- sparkLevels: [" ", ".", ":", "-", "=", "+", "*", "#"]
17
+ // src/terminal/output.ts
18
+ var terminalStream = {
19
+ write(text) {
20
+ process.stdout.write(text);
21
+ },
22
+ get columns() {
23
+ return process.stdout.columns;
24
+ },
25
+ get rows() {
26
+ return process.stdout.rows;
27
+ }
97
28
  };
98
- var ROUND_BORDER = { topLeft: "╭", topRight: "╮", bottomLeft: "╰", bottomRight: "╯", horizontal: "─", vertical: "│", teeLeft: "├", teeRight: "┤", cross: "┼" };
99
- var SINGLE_BORDER = { topLeft: "┌", topRight: "┐", bottomLeft: "└", bottomRight: "┘", horizontal: "─", vertical: "│", teeLeft: "├", teeRight: "┤", cross: "┼" };
100
- var DOUBLE_BORDER = { topLeft: "╔", topRight: "╗", bottomLeft: "╚", bottomRight: "╝", horizontal: "═", vertical: "║", teeLeft: "╠", teeRight: "╣", cross: "╬" };
101
- var ASCII_BORDER = { topLeft: "+", topRight: "+", bottomLeft: "+", bottomRight: "+", horizontal: "-", vertical: "|", teeLeft: "+", teeRight: "+", cross: "+" };
102
- function borderGlyphsFor(style, glyphs) {
103
- if (glyphs === ASCII_GLYPHS)
104
- return ASCII_BORDER;
105
- if (style === "single")
106
- return SINGLE_BORDER;
107
- if (style === "double")
108
- return DOUBLE_BORDER;
109
- return ROUND_BORDER;
110
- }
111
-
112
- // packages/archymedes-cli/src/markdown.ts
113
- var RESET = "\x1B[0m";
114
- var BOLD = "\x1B[1m";
115
- var DIM = "\x1B[2m";
116
- var ITALIC = "\x1B[3m";
117
- var STRIKE = "\x1B[9m";
118
- var CYAN = "\x1B[36m";
119
- var BLUE = "\x1B[34m";
120
- var GREEN = "\x1B[32m";
121
- var YELLOW = "\x1B[33m";
122
- var ANSI = /\x1b\[[0-9;]*m/g;
123
- function visibleWidth(text) {
124
- let width = 0;
125
- for (const character of text.replace(ANSI, "")) {
126
- const code = character.codePointAt(0) ?? 0;
127
- if (code >= 768 && code <= 879)
128
- continue;
129
- width += isWide(code) ? 2 : 1;
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;
130
48
  }
131
- return width;
132
49
  }
133
- function isWide(code) {
134
- return code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 127744 && code <= 129535;
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) };
135
53
  }
54
+
55
+ // src/render/markdown.ts
56
+ var colour = (role, palette) => palette?.[role] || ANSI_PALETTE[role];
136
57
  var INLINE_PATTERN = /(?<!\\)(`[^`\n]+`)|(?<!\\)(\*\*[^*\n]+\*\*)|(?<!\\)(__[^_\n]+__)|(?<!\\)(~~[^~\n]+~~)|(?<!\\)(\*[^*\n]+\*)|(?<!\\)(_[^_\n]+_)|(!?\[([^\]\n]+)\]\((https?:\/\/[^\s)\n]+)\))|(<(https?:\/\/[^>\s]+)>)/g;
137
58
  var unescapeMarkdown = (text) => text.replace(/\\([\\`*_[\]{}()#+.!~>-])/g, "$1");
138
- function parseInline(line) {
59
+ function parseInline(line, palette) {
139
60
  const tokens = [];
140
61
  let lastIndex = 0;
141
62
  INLINE_PATTERN.lastIndex = 0;
@@ -144,7 +65,7 @@ function parseInline(line) {
144
65
  tokens.push({ text: unescapeMarkdown(line.slice(lastIndex, match.index)), code: "" });
145
66
  const [whole, code, boldStars, boldUnderscores, strike, italicStars, italicUnderscores, link, linkLabel, linkUrl, autolink, autolinkUrl] = match;
146
67
  if (code !== undefined)
147
- tokens.push({ text: code.slice(1, -1), code: CYAN });
68
+ tokens.push({ text: code.slice(1, -1), code: colour("primary", palette) });
148
69
  else if (boldStars !== undefined)
149
70
  tokens.push({ text: boldStars.slice(2, -2), code: BOLD });
150
71
  else if (boldUnderscores !== undefined)
@@ -156,18 +77,15 @@ function parseInline(line) {
156
77
  else if (italicUnderscores !== undefined)
157
78
  tokens.push({ text: italicUnderscores.slice(1, -1), code: ITALIC });
158
79
  else if (link !== undefined)
159
- tokens.push({ text: `${link.startsWith("!") ? `image: ${linkLabel}` : linkLabel} (${linkUrl})`, code: CYAN });
80
+ tokens.push({ text: `${link.startsWith("!") ? `image: ${linkLabel}` : linkLabel} (${linkUrl})`, code: colour("primary", palette) });
160
81
  else if (autolink !== undefined)
161
- tokens.push({ text: autolinkUrl, code: CYAN });
82
+ tokens.push({ text: autolinkUrl, code: colour("primary", palette) });
162
83
  lastIndex = match.index + whole.length;
163
84
  }
164
85
  if (lastIndex < line.length)
165
86
  tokens.push({ text: unescapeMarkdown(line.slice(lastIndex)), code: "" });
166
87
  return tokens.filter((token) => token.text.length > 0);
167
88
  }
168
- function paint(text, code, depth) {
169
- return depth === "none" || code === "" ? text : `${code}${text}${RESET}`;
170
- }
171
89
  function wrapTokens(tokens, width, options) {
172
90
  const firstPrefix = options.firstPrefix ?? "";
173
91
  const continuationPrefix = options.continuationPrefix ?? "";
@@ -263,7 +181,7 @@ function renderTableLine(cells, separator, width, depth, glyphs) {
263
181
  return `${glyphs.boxVertical}${rendered.map((cell) => ` ${cell} `).join(glyphs.boxVertical)}${glyphs.boxVertical}`;
264
182
  }
265
183
  function renderMarkdownLine(line, state, options) {
266
- const { width, depth } = options;
184
+ const { width, depth, palette } = options;
267
185
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
268
186
  const fence = FENCE.exec(line);
269
187
  if (fence) {
@@ -280,7 +198,7 @@ function renderMarkdownLine(line, state, options) {
280
198
  return [paint(` ${glyphs.boxTopLeft}${glyphs.boxHorizontal}${label}${glyphs.boxHorizontal.repeat(Math.max(0, ruleWidth - drawn))}`, DIM, depth)];
281
199
  }
282
200
  if (state.inFence) {
283
- return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${paint(line, GREEN, depth)}`];
201
+ return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${paint(line, colour("success", palette), depth)}`];
284
202
  }
285
203
  const table = tableCells(line);
286
204
  if (table) {
@@ -293,14 +211,14 @@ function renderMarkdownLine(line, state, options) {
293
211
  const heading = HEADING.exec(line);
294
212
  if (heading) {
295
213
  const level = heading[1].length;
296
- const code = level <= 2 ? `${BOLD}${CYAN}` : BOLD;
214
+ const code = level <= 2 ? `${BOLD}${colour("primary", palette)}` : BOLD;
297
215
  const tokens = parseInline(heading[2]).map((token) => ({ text: token.text, code }));
298
216
  return wrapTokens(tokens, width, { depth });
299
217
  }
300
218
  const task = TASK.exec(line);
301
219
  if (task) {
302
- return wrapTokens(parseInline(task[3]), width, {
303
- firstPrefix: `${task[1]}${paint(task[2].trim() ? glyphs.checkboxDone : glyphs.checkbox, task[2].trim() ? GREEN : BLUE, depth)} `,
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)} `,
304
222
  continuationPrefix: `${task[1]} `,
305
223
  depth
306
224
  });
@@ -308,8 +226,8 @@ function renderMarkdownLine(line, state, options) {
308
226
  const bullet = BULLET.exec(line);
309
227
  if (bullet) {
310
228
  const indent = bullet[1];
311
- return wrapTokens(parseInline(bullet[3]), width, {
312
- firstPrefix: `${indent}${paint(glyphs.bullet, BLUE, depth)} `,
229
+ return wrapTokens(parseInline(bullet[3], palette), width, {
230
+ firstPrefix: `${indent}${paint(glyphs.bullet, colour("secondary", palette), depth)} `,
313
231
  continuationPrefix: `${indent} `,
314
232
  depth
315
233
  });
@@ -318,8 +236,8 @@ function renderMarkdownLine(line, state, options) {
318
236
  if (numbered) {
319
237
  const indent = numbered[1];
320
238
  const marker = numbered[2];
321
- return wrapTokens(parseInline(numbered[3]), width, {
322
- firstPrefix: `${indent}${paint(marker, BLUE, depth)} `,
239
+ return wrapTokens(parseInline(numbered[3], palette), width, {
240
+ firstPrefix: `${indent}${paint(marker, colour("secondary", palette), depth)} `,
323
241
  continuationPrefix: `${indent}${" ".repeat(marker.length + 1)}`,
324
242
  depth
325
243
  });
@@ -327,14 +245,14 @@ function renderMarkdownLine(line, state, options) {
327
245
  const quote = BLOCKQUOTE.exec(line);
328
246
  if (quote) {
329
247
  return wrapTokens([{ text: quote[1], code: DIM }], width, {
330
- firstPrefix: paint(`${glyphs.boxVertical} `, YELLOW, depth),
331
- continuationPrefix: paint(`${glyphs.boxVertical} `, YELLOW, depth),
248
+ firstPrefix: paint(`${glyphs.boxVertical} `, colour("accent", palette), depth),
249
+ continuationPrefix: paint(`${glyphs.boxVertical} `, colour("accent", palette), depth),
332
250
  depth
333
251
  });
334
252
  }
335
253
  if (line.trim() === "")
336
254
  return [""];
337
- return wrapTokens(parseInline(line), width, { depth });
255
+ return wrapTokens(parseInline(line, palette), width, { depth });
338
256
  }
339
257
  function renderMarkdown(text, options) {
340
258
  const state = newMarkdownState();
@@ -347,29 +265,7 @@ function renderMarkdown(text, options) {
347
265
  `);
348
266
  }
349
267
 
350
- // packages/archymedes-cli/src/output.ts
351
- var terminalStream = {
352
- write(text) {
353
- process.stdout.write(text);
354
- },
355
- get columns() {
356
- return process.stdout.columns;
357
- },
358
- get rows() {
359
- return process.stdout.rows;
360
- }
361
- };
362
-
363
- // packages/archymedes-cli/src/tui.ts
364
- var RESET2 = "\x1B[0m";
365
- var DIM2 = "\x1B[2m";
366
- var CYAN2 = "\x1B[36m";
367
- var GREEN2 = "\x1B[32m";
368
- var YELLOW2 = "\x1B[33m";
369
- var RED = "\x1B[31m";
370
- function paint2(text, code, depth) {
371
- return depth === "none" ? text : `${code}${text}${RESET2}`;
372
- }
268
+ // src/render/tui.ts
373
269
  function archymedesSpinnerFrame(index, glyphs = UNICODE_GLYPHS) {
374
270
  const frames = glyphs.spinnerFrames;
375
271
  const normalized = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
@@ -541,7 +437,7 @@ function table(columns, rows, options) {
541
437
  const row = (cells) => `${border.vertical} ${widths.map((width, index) => cell(cells[index] ?? "", width)).join(` ${border.vertical} `)} ${border.vertical}`;
542
438
  return [
543
439
  rule(border.topLeft, border.horizontal, border.topRight),
544
- row(columns.map((header) => paint2(header, DIM2, options.depth))),
440
+ row(columns.map((header) => paint(header, DIM, options.depth))),
545
441
  rule(border.teeLeft, border.cross, border.teeRight),
546
442
  ...rows.map((line) => row(line)),
547
443
  rule(border.bottomLeft, border.horizontal, border.bottomRight)
@@ -617,12 +513,12 @@ function stepProgress(done, total, options) {
617
513
  });
618
514
  return `${label}${counted} ${bar}`;
619
515
  }
620
- function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent = CYAN2) {
516
+ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent = ANSI_PALETTE.primary) {
621
517
  const label = activityLabel(fields.phase ?? "thinking", fields.operation, fields.elapsedMs);
622
518
  const ellipsis = glyphs.ellipsis;
623
519
  const separator = ` ${glyphs.middot} `;
624
520
  const left = `${fields.spinnerGlyph} ${label}${ellipsis}`;
625
- const paintedLeft = `${paint2(fields.spinnerGlyph, accent, depth)} ${paint2(`${label}${ellipsis}`, DIM2, depth)}`;
521
+ const paintedLeft = `${paint(fields.spinnerGlyph, accent, depth)} ${paint(`${label}${ellipsis}`, DIM, depth)}`;
626
522
  const optional = [
627
523
  fields.cost,
628
524
  fields.tokens > 0 ? formatTokens(fields.tokens) : "",
@@ -642,36 +538,124 @@ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent
642
538
  return paintedLeft;
643
539
  const compact = `${fields.spinnerGlyph} ${ellipsis}`;
644
540
  if (visibleWidth(compact) <= width)
645
- return `${paint2(fields.spinnerGlyph, accent, depth)} ${paint2(ellipsis, DIM2, depth)}`;
646
- return width > 0 ? paint2(glyphs.star, accent, depth) : "";
541
+ return `${paint(fields.spinnerGlyph, accent, depth)} ${paint(ellipsis, DIM, depth)}`;
542
+ return width > 0 ? paint(glyphs.star, accent, depth) : "";
647
543
  }
648
544
  const gap = Math.max(1, width - visibleWidth(left) - right.length);
649
- return `${paintedLeft}${" ".repeat(gap)}${paint2(right, DIM2, depth)}`;
545
+ return `${paintedLeft}${" ".repeat(gap)}${paint(right, DIM, depth)}`;
650
546
  }
651
547
  kept = kept.slice(0, -1);
652
548
  }
653
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
+ }
654
582
  function rowsOccupied(text, columns) {
655
583
  const width = visibleWidth(text);
656
584
  if (width === 0)
657
585
  return 1;
658
586
  return Math.ceil(width / Math.max(1, columns));
659
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
+
660
642
  class MarkdownStream {
661
643
  stream;
662
644
  depth;
663
645
  columns;
664
646
  live;
665
647
  glyphs;
648
+ palette;
666
649
  pending = "";
667
650
  atLineStart = true;
668
651
  state = newMarkdownState();
669
- 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) {
670
653
  this.stream = stream;
671
654
  this.depth = depth;
672
655
  this.columns = columns;
673
656
  this.live = live;
674
657
  this.glyphs = glyphs;
658
+ this.palette = palette;
675
659
  }
676
660
  get active() {
677
661
  return this.live ? this.pending !== "" : !this.atLineStart;
@@ -708,7 +692,7 @@ class MarkdownStream {
708
692
  if (this.pending !== "")
709
693
  this.finalizeLine();
710
694
  if (this.state.inFence) {
711
- 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 });
712
696
  this.stream.write(`${rendered.join(`
713
697
  `)}
714
698
  `);
@@ -721,7 +705,7 @@ class MarkdownStream {
721
705
  }
722
706
  finalizeLine() {
723
707
  this.erasePending();
724
- 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 });
725
709
  this.stream.write(`${rendered.join(`
726
710
  `)}
727
711
  `);
@@ -739,17 +723,24 @@ class MarkdownStream {
739
723
  function box(lines, options) {
740
724
  const terminalWidth = options.width ?? process.stdout.columns ?? 80;
741
725
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
742
- const border = borderGlyphsFor(options.borderStyle ?? "round", glyphs);
726
+ const border = borderGlyphsFor(options.borderStyle ?? options.palette?.borderStyle ?? "round", glyphs);
743
727
  const titleWidth = options.title ? visibleWidth(options.title) : 0;
744
- const titlePaint = TONE_CODES[options.titleColor ?? "cyan"];
745
- const edge = (text) => options.borderColor === undefined ? text : paint2(text, TONE_CODES[options.borderColor], options.depth);
728
+ const titlePaint = roleCode(TONE_ROLE[options.titleColor ?? "cyan"], options.palette, options.depth);
729
+ const rainbowBorder = options.borderColor === "rainbow";
730
+ const edge = (text, phase = 0) => {
731
+ if (options.borderColor === undefined)
732
+ return text;
733
+ return rainbowBorder ? rainbowText(text, options.depth, phase) : paint(text, roleCode(TONE_ROLE[options.borderColor], options.palette, options.depth), options.depth);
734
+ };
746
735
  const contentWidth = Math.min(Math.max(titleWidth, ...lines.map((line) => visibleWidth(line)), 1), Math.max(1, terminalWidth - 4));
747
736
  const horizontal = border.horizontal.repeat(contentWidth + 2);
748
- const top = options.title ? `${edge(`${border.topLeft}${border.horizontal}`)} ${paint2(options.title, titlePaint, options.depth)} ${edge(`${border.horizontal.repeat(Math.max(0, contentWidth - titleWidth - 1))}${border.topRight}`)}` : edge(`${border.topLeft}${horizontal}${border.topRight}`);
749
- const bottom = edge(`${border.bottomLeft}${horizontal}${border.bottomRight}`);
750
- const body = lines.map((line) => {
737
+ const top = options.title ? `${edge(`${border.topLeft}${border.horizontal}`, 0)} ${paint(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);
738
+ const bottomPhase = (lines.length + 1) * 0.1;
739
+ const bottom = edge(`${border.bottomLeft}${horizontal}${border.bottomRight}`, bottomPhase);
740
+ const body = lines.map((line, index) => {
751
741
  const clipped = visibleWidth(line) > contentWidth ? `${sliceToWidth(line, contentWidth - visibleWidth(glyphs.ellipsis))}${glyphs.ellipsis}` : line;
752
- return `${edge(border.vertical)} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${edge(border.vertical)}`;
742
+ const rowPhase = (index + 1) * 0.1;
743
+ return `${edge(border.vertical, rowPhase)} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${edge(border.vertical, rowPhase)}`;
753
744
  });
754
745
  return [top, ...body, bottom].join(`
755
746
  `);
@@ -785,22 +776,22 @@ function joinHorizontal(left, right, options) {
785
776
  const separator = options.separator ?? " ";
786
777
  return `${padToWidth(left, options.leftWidth, options.leftAlign)}${separator}${padToWidth(right, options.rightWidth, options.rightAlign)}`;
787
778
  }
788
- var TONE_CODES = { cyan: CYAN2, green: GREEN2, yellow: YELLOW2, red: RED };
789
- var MODE_COLORS = { plan: YELLOW2, auto: GREEN2, build: CYAN2, 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" };
790
781
  var PROMPT_CHROME_COLUMNS = 6;
791
782
  function renderPromptBox(options) {
792
783
  const { mode, workspace, depth } = options;
793
784
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
794
785
  const border = borderGlyphsFor(options.palette?.borderStyle ?? options.borderStyle ?? "round", glyphs);
795
786
  const width = Math.max(12, options.width);
796
- const primary = options.palette?.primary ?? CYAN2;
797
- const modeColor = options.palette ? { plan: options.palette.warning, auto: options.palette.success, build: primary, defender: options.palette.error }[mode] ?? primary : MODE_COLORS[mode] ?? CYAN2;
798
- const horizontal = (count) => paint2(border.horizontal.repeat(Math.max(0, count)), primary, depth);
787
+ const primary = roleCode("primary", options.palette, depth);
788
+ const modeColor = roleCode(MODE_ROLE[mode] ?? "primary", options.palette, depth);
789
+ const horizontal = (count) => paint(border.horizontal.repeat(Math.max(0, count)), primary, depth);
799
790
  const CHROME = PROMPT_CHROME_COLUMNS;
800
791
  const status = options.status ?? "";
801
792
  const separator = ` ${glyphs.middot} `;
802
- const full = `${paint2(glyphs.star, primary, depth)} ${paint2("archymedes", primary, depth)}${separator}${paint2(mode, modeColor, depth)}`;
803
- const compact = `${paint2(glyphs.star, primary, depth)} ${paint2(mode, modeColor, depth)}`;
793
+ const full = `${paint(glyphs.star, primary, depth)} ${paint("archymedes", primary, depth)}${separator}${paint(mode, modeColor, depth)}`;
794
+ const compact = `${paint(glyphs.star, primary, depth)} ${paint(mode, modeColor, depth)}`;
804
795
  const statusRoom = status === "" ? 0 : visibleWidth(status) + 2;
805
796
  const showStatus = status !== "" && width - CHROME - 1 - statusRoom - visibleWidth(compact) >= 0;
806
797
  const room = width - CHROME - 1 - (showStatus ? statusRoom : 0);
@@ -808,19 +799,48 @@ function renderPromptBox(options) {
808
799
  let title;
809
800
  if (workspaceRoom >= 4) {
810
801
  const shown = visibleWidth(workspace) <= workspaceRoom ? workspace : `${sliceToWidth(workspace, Math.max(0, workspaceRoom - visibleWidth(glyphs.ellipsis)))}${glyphs.ellipsis}`;
811
- title = `${full}${separator}${paint2(shown, DIM2, depth)}`;
802
+ title = `${full}${separator}${paint(shown, DIM, depth)}`;
812
803
  } else if (visibleWidth(full) <= room)
813
804
  title = full;
814
805
  else if (visibleWidth(compact) <= room)
815
806
  title = compact;
816
807
  else
817
- title = paint2(glyphs.star, primary, depth);
808
+ title = paint(glyphs.star, primary, depth);
818
809
  const tail = showStatus ? ` ${status} ` : "";
819
810
  const filler = Math.max(1, width - visibleWidth(title) - visibleWidth(tail) - CHROME);
820
- const top = `${paint2(`${border.topLeft}${border.horizontal}`, primary, depth)} ${title} ${horizontal(filler)}${tail}${horizontal(1)}${paint2(border.topRight, primary, depth)}`;
821
- const prefix = `${paint2(border.vertical, primary, depth)} ${paint2(glyphs.caret, modeColor, depth)} `;
822
- const bottom = `${paint2(border.bottomLeft, primary, depth)}${horizontal(width - 2)}${paint2(border.bottomRight, primary, depth)}`;
811
+ const top = `${paint(`${border.topLeft}${border.horizontal}`, primary, depth)} ${title} ${horizontal(filler)}${tail}${horizontal(1)}${paint(border.topRight, primary, depth)}`;
812
+ const prefix = `${paint(border.vertical, primary, depth)} ${paint(glyphs.caret, modeColor, depth)} `;
813
+ const bottom = `${paint(border.bottomLeft, primary, depth)}${horizontal(width - 2)}${paint(border.bottomRight, primary, depth)}`;
823
814
  return { top, prefix, bottom };
824
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
+ }
825
845
 
826
- export { UNICODE_GLYPHS, ASCII_GLYPHS, borderGlyphsFor, visibleWidth, newMarkdownState, renderMarkdownLine, renderMarkdown, 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",