archymedes-cli 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/{archymedes-aj30app4.mjs → archymedes-06fh0z84.mjs} +370 -159
  2. package/dist/{archymedes-d5wqcmdr.mjs → archymedes-0jxzsh4z.mjs} +97 -97
  3. package/dist/archymedes-130jt0wr.mjs +1231 -0
  4. package/dist/{archymedes-twbks7g3.mjs → archymedes-138genyy.mjs} +214 -5
  5. package/dist/{archymedes-h13ap0za.mjs → archymedes-1g1y6q74.mjs} +1 -1
  6. package/dist/{archymedes-dz59nvw7.mjs → archymedes-2kvjaajw.mjs} +21 -11
  7. package/dist/{archymedes-g8y3rt6t.mjs → archymedes-5vhvv8kr.mjs} +332 -9
  8. package/dist/{archymedes-4kx65anz.mjs → archymedes-a3ms7jvr.mjs} +43 -12
  9. package/dist/{archymedes-tsne2m7p.mjs → archymedes-bmtdqqwt.mjs} +2 -2
  10. package/dist/{archymedes-sf9t1hz9.mjs → archymedes-d3azqkay.mjs} +176 -77
  11. package/dist/archymedes-main.mjs +31312 -28352
  12. package/dist/archymedes-w4bjfye7.mjs +280 -0
  13. package/dist/{editor-screen-g08zzt70.mjs → editor-screen-e01mn7dd.mjs} +49 -14
  14. package/dist/{factory-7vtm2fed.mjs → factory-hyv8agr0.mjs} +326 -326
  15. package/dist/{file-screen-ma9yxq2b.mjs → file-screen-stcxznwy.mjs} +11 -9
  16. package/dist/{guide-screen-gd129txd.mjs → guide-screen-w5nb9sry.mjs} +10 -8
  17. package/dist/{index-qvgcwsvm.mjs → index-teq1cta4.mjs} +1 -1
  18. package/dist/{node-mdgzx68h.mjs → node-0fh63p8z.mjs} +7 -7
  19. package/dist/pager-screen-aamt6xv0.mjs +155 -0
  20. package/dist/projection-x6x6hm9t.mjs +248 -0
  21. package/dist/{sandbox-templates-9jcjws23.mjs → sandbox-templates-8sd1psez.mjs} +2 -2
  22. package/dist/workspace-screen-62k6hnq7.mjs +70 -0
  23. package/package.json +2 -2
  24. package/dist/archymedes-a10k8yrk.mjs +0 -464
  25. package/dist/workspace-screen-8cwk6jz9.mjs +0 -241
@@ -7,14 +7,17 @@ import {
7
7
  STRIKE,
8
8
  UNICODE_GLYPHS,
9
9
  borderGlyphsFor,
10
+ codeStyle,
11
+ highlightSyntax,
12
+ newSyntaxState,
10
13
  paint,
11
14
  rainbowText,
12
15
  rgbTo256,
13
16
  roleCode,
14
17
  visibleWidth
15
- } from "./archymedes-a10k8yrk.mjs";
18
+ } from "./archymedes-130jt0wr.mjs";
16
19
 
17
- // src/terminal/output.ts
20
+ // packages/archymedes-cli/src/terminal/output.ts
18
21
  var terminalStream = {
19
22
  write(text) {
20
23
  process.stdout.write(text);
@@ -26,6 +29,90 @@ var terminalStream = {
26
29
  return process.stdout.rows;
27
30
  }
28
31
  };
32
+ var DEFAULT_SCROLLBACK_LINES = 5000;
33
+
34
+ class LineLog {
35
+ limit;
36
+ committed = [];
37
+ partial = "";
38
+ droppedLines = 0;
39
+ constructor(limit = DEFAULT_SCROLLBACK_LINES) {
40
+ this.limit = limit;
41
+ }
42
+ write(text) {
43
+ if (text === "")
44
+ return;
45
+ const parts = (this.partial + text).split(`
46
+ `);
47
+ this.partial = parts.pop() ?? "";
48
+ for (const line of parts)
49
+ this.commit(line);
50
+ }
51
+ commit(line) {
52
+ this.committed.push(line);
53
+ if (this.committed.length > this.limit) {
54
+ this.committed.shift();
55
+ this.droppedLines += 1;
56
+ }
57
+ }
58
+ flush() {
59
+ if (this.partial === "")
60
+ return;
61
+ this.commit(this.partial);
62
+ this.partial = "";
63
+ }
64
+ get lines() {
65
+ return this.committed;
66
+ }
67
+ get pending() {
68
+ return this.partial;
69
+ }
70
+ get size() {
71
+ return this.committed.length;
72
+ }
73
+ get dropped() {
74
+ return this.droppedLines;
75
+ }
76
+ tail(count) {
77
+ return count <= 0 ? [] : this.committed.slice(Math.max(0, this.committed.length - count));
78
+ }
79
+ clear() {
80
+ this.committed.length = 0;
81
+ this.partial = "";
82
+ this.droppedLines = 0;
83
+ }
84
+ }
85
+
86
+ class TabSink {
87
+ downstream;
88
+ log;
89
+ live;
90
+ constructor(downstream = terminalStream, options = {}) {
91
+ this.downstream = downstream;
92
+ this.live = options.live ?? false;
93
+ this.log = new LineLog(options.limit);
94
+ }
95
+ get isLive() {
96
+ return this.live;
97
+ }
98
+ setLive(live) {
99
+ if (!live)
100
+ this.log.flush();
101
+ this.live = live;
102
+ }
103
+ write(text) {
104
+ this.log.write(text);
105
+ if (this.live)
106
+ this.downstream.write(text);
107
+ }
108
+ get columns() {
109
+ return this.downstream.columns;
110
+ }
111
+ get rows() {
112
+ return this.downstream.rows;
113
+ }
114
+ }
115
+
29
116
  class OutputRouter {
30
117
  target;
31
118
  constructor(target = terminalStream) {
@@ -52,7 +139,7 @@ function replayLines(log, count) {
52
139
  return { lines, dropped: log.dropped, omitted: Math.max(0, log.size - lines.length) };
53
140
  }
54
141
 
55
- // src/render/markdown.ts
142
+ // packages/archymedes-cli/src/render/markdown.ts
56
143
  var colour = (role, palette) => palette?.[role] || ANSI_PALETTE[role];
57
144
  var INLINE_PATTERN = /(?<!\\)(`[^`\n]+`)|(?<!\\)(\*\*[^*\n]+\*\*)|(?<!\\)(__[^_\n]+__)|(?<!\\)(~~[^~\n]+~~)|(?<!\\)(\*[^*\n]+\*)|(?<!\\)(_[^_\n]+_)|(!?\[([^\]\n]+)\]\((https?:\/\/[^\s)\n]+)\))|(<(https?:\/\/[^>\s]+)>)/g;
58
145
  var unescapeMarkdown = (text) => text.replace(/\\([\\`*_[\]{}()#+.!~>-])/g, "$1");
@@ -142,7 +229,7 @@ function wrapTokens(tokens, width, options) {
142
229
  return lines;
143
230
  }
144
231
  function newMarkdownState() {
145
- return { inFence: false, fenceLanguage: "" };
232
+ return { inFence: false, fenceLanguage: "", fenceLine: 0, fenceSyntax: newSyntaxState() };
146
233
  }
147
234
  var HEADING = /^(#{1,6})\s+(.*)$/;
148
235
  var BULLET = /^(\s*)([-*+])\s+(.*)$/;
@@ -189,16 +276,24 @@ function renderMarkdownLine(line, state, options) {
189
276
  if (state.inFence) {
190
277
  state.inFence = false;
191
278
  state.fenceLanguage = "";
279
+ state.fenceLine = 0;
280
+ state.fenceSyntax = newSyntaxState();
192
281
  return [paint(` ${glyphs.boxBottomLeft}${glyphs.boxHorizontal.repeat(ruleWidth)}`, DIM, depth)];
193
282
  }
194
283
  state.inFence = true;
195
284
  state.fenceLanguage = fence[1].trim();
285
+ state.fenceLine = 0;
286
+ state.fenceSyntax = newSyntaxState();
196
287
  const label = state.fenceLanguage ? ` ${state.fenceLanguage} ` : "";
197
288
  const drawn = 1 + visibleWidth(label);
198
- return [paint(` ${glyphs.boxTopLeft}${glyphs.boxHorizontal}${label}${glyphs.boxHorizontal.repeat(Math.max(0, ruleWidth - drawn))}`, DIM, depth)];
289
+ return [`${paint(` ${glyphs.boxTopLeft}${glyphs.boxHorizontal}`, DIM, depth)}${label ? paint(label, `${BOLD}${colour("primary", palette)}`, depth) : ""}${paint(glyphs.boxHorizontal.repeat(Math.max(0, ruleWidth - drawn)), DIM, depth)}`];
199
290
  }
200
291
  if (state.inFence) {
201
- return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${paint(line, colour("success", palette), depth)}`];
292
+ state.fenceLine += 1;
293
+ const style = codeStyle();
294
+ const number = style.lineNumbers ? `${paint(String(state.fenceLine).padStart(3), DIM, depth)} ` : "";
295
+ const code = style.colors === "vscode" ? highlightSyntax(line, { language: state.fenceLanguage, depth, palette, state: state.fenceSyntax }) : paint(line, colour("success", palette), depth);
296
+ return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${number}${code}`];
202
297
  }
203
298
  const table = tableCells(line);
204
299
  if (table) {
@@ -265,12 +360,107 @@ function renderMarkdown(text, options) {
265
360
  `);
266
361
  }
267
362
 
268
- // src/render/tui.ts
363
+ // packages/archymedes-cli/src/render/tui.ts
269
364
  function archymedesSpinnerFrame(index, glyphs = UNICODE_GLYPHS) {
270
365
  const frames = glyphs.spinnerFrames;
271
366
  const normalized = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
272
367
  return frames[normalized % frames.length];
273
368
  }
369
+
370
+ class Spinner {
371
+ onTick;
372
+ intervalMs;
373
+ glyphs;
374
+ delayMs;
375
+ frame = 0;
376
+ timer;
377
+ delay;
378
+ constructor(onTick, intervalMs = 120, glyphs = UNICODE_GLYPHS, delayMs = 0) {
379
+ this.onTick = onTick;
380
+ this.intervalMs = intervalMs;
381
+ this.glyphs = glyphs;
382
+ this.delayMs = delayMs;
383
+ }
384
+ start() {
385
+ if (this.timer || this.delay)
386
+ return;
387
+ const begin = () => {
388
+ this.delay = undefined;
389
+ this.onTick();
390
+ this.timer = setInterval(() => {
391
+ this.frame = (this.frame + 1) % this.glyphs.spinnerFrames.length;
392
+ this.onTick();
393
+ }, this.intervalMs);
394
+ };
395
+ if (this.delayMs <= 0)
396
+ begin();
397
+ else
398
+ this.delay = setTimeout(begin, this.delayMs);
399
+ }
400
+ stop() {
401
+ if (this.delay) {
402
+ clearTimeout(this.delay);
403
+ this.delay = undefined;
404
+ }
405
+ if (!this.timer)
406
+ return;
407
+ clearInterval(this.timer);
408
+ this.timer = undefined;
409
+ }
410
+ get glyph() {
411
+ return archymedesSpinnerFrame(this.frame, this.glyphs);
412
+ }
413
+ }
414
+ function formatCountdown(remainingMs) {
415
+ const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000));
416
+ if (totalSeconds < 60)
417
+ return `${totalSeconds}s`;
418
+ const minutes = Math.floor(totalSeconds / 60);
419
+ const seconds = totalSeconds % 60;
420
+ return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
421
+ }
422
+
423
+ class CountdownTimer {
424
+ onTick;
425
+ onDone;
426
+ intervalMs;
427
+ timer;
428
+ endsAt;
429
+ constructor(durationMs, onTick, onDone, intervalMs = 1000) {
430
+ this.onTick = onTick;
431
+ this.onDone = onDone;
432
+ this.intervalMs = intervalMs;
433
+ this.endsAt = Date.now() + Math.max(0, durationMs);
434
+ }
435
+ get remaining() {
436
+ return Math.max(0, this.endsAt - Date.now());
437
+ }
438
+ start() {
439
+ if (this.timer)
440
+ return;
441
+ if (this.remaining <= 0) {
442
+ this.onDone();
443
+ return;
444
+ }
445
+ this.onTick(this.remaining);
446
+ this.timer = setInterval(() => {
447
+ const left = this.remaining;
448
+ if (left <= 0) {
449
+ this.stop();
450
+ this.onDone();
451
+ return;
452
+ }
453
+ this.onTick(left);
454
+ }, this.intervalMs);
455
+ }
456
+ stop() {
457
+ if (!this.timer)
458
+ return;
459
+ clearInterval(this.timer);
460
+ this.timer = undefined;
461
+ }
462
+ }
463
+
274
464
  class Spring {
275
465
  angularFrequency;
276
466
  dampingRatio;
@@ -355,6 +545,21 @@ class SpringAnimator {
355
545
  this.timer = undefined;
356
546
  }
357
547
  }
548
+ function formatHeaderSegments(segments, width, separator = " · ") {
549
+ const limit = Math.max(0, Math.floor(width));
550
+ if (limit === 0 || segments.length === 0)
551
+ return "";
552
+ let kept = [...segments];
553
+ while (kept.length > 0) {
554
+ const rendered = kept.map((segment) => segment.full).join(separator);
555
+ if (visibleWidth(rendered) <= limit)
556
+ return rendered;
557
+ kept = kept.slice(0, -1);
558
+ }
559
+ const first = segments[0];
560
+ const compact = first.compact ?? first.full;
561
+ return visibleWidth(compact) <= limit ? compact : "";
562
+ }
358
563
  function formatElapsed(ms) {
359
564
  const seconds = ms / 1000;
360
565
  return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
@@ -525,6 +730,7 @@ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent
525
730
  fields.toolCalls > 0 ? `${fields.toolCalls} tool${fields.toolCalls === 1 ? "" : "s"}` : "",
526
731
  fields.badge ?? "",
527
732
  fields.steps && fields.steps.total > 0 ? stepProgress(fields.steps.done, fields.steps.total, { label: fields.steps.label ?? "steps", depth: "none" }) : "",
733
+ fields.allowance?.text ?? "",
528
734
  fields.mode,
529
735
  fields.balance ?? ""
530
736
  ].filter((segment) => segment !== "");
@@ -542,7 +748,9 @@ function formatStatusLine(fields, width, depth, glyphs = UNICODE_GLYPHS, accent
542
748
  return width > 0 ? paint(glyphs.star, accent, depth) : "";
543
749
  }
544
750
  const gap = Math.max(1, width - visibleWidth(left) - right.length);
545
- return `${paintedLeft}${" ".repeat(gap)}${paint(right, DIM, depth)}`;
751
+ const warned = fields.allowance?.warning ? fields.allowance.text : undefined;
752
+ const paintedRight = warned && kept.includes(warned) ? [...kept, formatElapsed(fields.elapsedMs)].map((segment) => paint(segment, segment === warned ? roleCode("warning", undefined, depth) : DIM, depth)).join(paint(separator, DIM, depth)) : paint(right, DIM, depth);
753
+ return `${paintedLeft}${" ".repeat(gap)}${paintedRight}`;
546
754
  }
547
755
  kept = kept.slice(0, -1);
548
756
  }
@@ -778,7 +986,12 @@ function joinHorizontal(left, right, options) {
778
986
  }
779
987
  var TONE_ROLE = { cyan: "primary", green: "success", yellow: "warning", red: "error" };
780
988
  var MODE_ROLE = { plan: "warning", auto: "success", build: "primary", defender: "error" };
989
+ var PROMPT_PREFIX_COLUMNS = 4;
781
990
  var PROMPT_CHROME_COLUMNS = 6;
991
+ function promptStatusRoom(mode, workspace, width, glyphs = UNICODE_GLYPHS) {
992
+ const compactTitle = `${glyphs.star} ${mode}`;
993
+ return Math.max(0, Math.max(12, width) - visibleWidth(compactTitle) - PROMPT_CHROME_COLUMNS - 3);
994
+ }
782
995
  function renderPromptBox(options) {
783
996
  const { mode, workspace, depth } = options;
784
997
  const glyphs = options.glyphs ?? UNICODE_GLYPHS;
@@ -813,6 +1026,116 @@ function renderPromptBox(options) {
813
1026
  const bottom = `${paint(border.bottomLeft, primary, depth)}${horizontal(width - 2)}${paint(border.bottomRight, primary, depth)}`;
814
1027
  return { top, prefix, bottom };
815
1028
  }
1029
+ function dropupPaintInPlace(lines) {
1030
+ if (lines.length === 0)
1031
+ return "";
1032
+ const parts = ["\x1B7\x1B[?25l", `\x1B[${lines.length + 1}A`];
1033
+ for (const line of lines)
1034
+ parts.push(`\r\x1B[2K${line}\x1B[1B`);
1035
+ parts.push("\x1B8\x1B[?25h");
1036
+ return parts.join("");
1037
+ }
1038
+ function dropupEraseBlock(currentRows) {
1039
+ return `\x1B[?25l\x1B[${currentRows + 1}A\r\x1B[0J`;
1040
+ }
1041
+
1042
+ class PromptBox {
1043
+ stream;
1044
+ options;
1045
+ drawn = false;
1046
+ borderCleared = false;
1047
+ suggestions = [];
1048
+ constructor(stream = terminalStream, options = { depth: "none" }) {
1049
+ this.stream = stream;
1050
+ this.options = options;
1051
+ }
1052
+ get isDrawn() {
1053
+ return this.drawn;
1054
+ }
1055
+ get width() {
1056
+ return Math.max(1, this.options.columns?.() ?? this.stream.columns ?? 80);
1057
+ }
1058
+ draw(mode, workspace, status) {
1059
+ const { prefix } = this.paintBlock(mode, workspace, status);
1060
+ this.drawn = true;
1061
+ this.borderCleared = false;
1062
+ return prefix;
1063
+ }
1064
+ paintBlock(mode, workspace, status) {
1065
+ const { top, prefix, bottom } = renderPromptBox({
1066
+ mode,
1067
+ workspace,
1068
+ depth: this.options.depth,
1069
+ palette: this.options.palette?.(),
1070
+ width: this.width,
1071
+ ...status === undefined ? {} : { status },
1072
+ ...this.options.glyphs === undefined ? {} : { glyphs: this.options.glyphs },
1073
+ ...this.options.borderStyle === undefined ? {} : { borderStyle: this.options.borderStyle }
1074
+ });
1075
+ for (const line of this.suggestions)
1076
+ this.stream.write(`${line}
1077
+ `);
1078
+ this.stream.write(`${top}
1079
+ `);
1080
+ this.stream.write(`
1081
+ ${bottom}`);
1082
+ this.stream.write("\x1B[1A\r");
1083
+ return { prefix };
1084
+ }
1085
+ get suggestionRows() {
1086
+ return this.suggestions.length;
1087
+ }
1088
+ setSuggestions(lines, bar) {
1089
+ if (!this.drawn)
1090
+ return { moved: false };
1091
+ const next = [...lines];
1092
+ if (next.length === this.suggestions.length) {
1093
+ if (next.every((line, index) => line === this.suggestions[index]))
1094
+ return { moved: false };
1095
+ this.suggestions = next;
1096
+ if (next.length > 0)
1097
+ this.stream.write(dropupPaintInPlace(next));
1098
+ return { moved: false };
1099
+ }
1100
+ this.stream.write(dropupEraseBlock(this.suggestions.length));
1101
+ this.suggestions = next;
1102
+ this.paintBlock(bar.mode, bar.workspace, bar.status);
1103
+ this.stream.write("\x1B[?25h");
1104
+ this.borderCleared = false;
1105
+ return { moved: true };
1106
+ }
1107
+ restoreBottomBorder(mode, workspace, status) {
1108
+ if (!this.drawn || this.borderCleared)
1109
+ return;
1110
+ const { bottom } = renderPromptBox({
1111
+ mode,
1112
+ workspace,
1113
+ depth: this.options.depth,
1114
+ palette: this.options.palette?.(),
1115
+ width: this.width,
1116
+ ...status === undefined ? {} : { status },
1117
+ ...this.options.glyphs === undefined ? {} : { glyphs: this.options.glyphs },
1118
+ ...this.options.borderStyle === undefined ? {} : { borderStyle: this.options.borderStyle }
1119
+ });
1120
+ this.stream.write(`\x1B7\x1B[?25l\x1B[1B\r\x1B[2K${bottom}\x1B8\x1B[?25h`);
1121
+ }
1122
+ erase(submitted) {
1123
+ if (!this.drawn)
1124
+ return;
1125
+ const rows = Math.floor((PROMPT_PREFIX_COLUMNS + visibleWidth(submitted) - 1) / this.width) + 3 + this.suggestions.length;
1126
+ for (let index = 0;index < rows; index += 1)
1127
+ this.stream.write("\x1B[1A\x1B[2K");
1128
+ this.suggestions = [];
1129
+ this.drawn = false;
1130
+ }
1131
+ dropBorder(remainder) {
1132
+ if (!this.drawn || this.borderCleared || remainder === "")
1133
+ return;
1134
+ this.stream.write("\r\x1B[2K");
1135
+ this.stream.write(remainder);
1136
+ this.borderCleared = true;
1137
+ }
1138
+ }
816
1139
  function wrapPlain(text, width) {
817
1140
  const lines = [];
818
1141
  let current = "";
@@ -843,4 +1166,4 @@ function wrapPlain(text, width) {
843
1166
  return lines.length === 0 ? [""] : lines;
844
1167
  }
845
1168
 
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 };
1169
+ export { terminalStream, TabSink, OutputRouter, replayLines, renderMarkdown, archymedesSpinnerFrame, Spinner, formatCountdown, CountdownTimer, SpringAnimator, formatHeaderSegments, sparkline, progressBar, table, scrollPercent, scrollIndicator, paginator, stepProgress, formatStatusLine, StatusBar, rowsOccupied, ReplaceableBlock, MarkdownStream, box, sliceToWidth, padToWidth, joinHorizontal, PROMPT_PREFIX_COLUMNS, promptStatusRoom, renderPromptBox, PromptBox, wrapPlain };
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  SpringAnimator,
3
3
  paginator
4
- } from "./archymedes-g8y3rt6t.mjs";
4
+ } from "./archymedes-5vhvv8kr.mjs";
5
5
  import {
6
6
  UNICODE_GLYPHS,
7
7
  clipTo,
8
8
  visibleWidth
9
- } from "./archymedes-a10k8yrk.mjs";
9
+ } from "./archymedes-130jt0wr.mjs";
10
10
 
11
- // src/ui/chooser.ts
11
+ // packages/archymedes-cli/src/ui/chooser.ts
12
12
  var ALL_CHROME = { title: true, status: true, pagination: true, help: true };
13
13
  function chromeOf(chrome) {
14
14
  return { ...ALL_CHROME, ...chrome };
@@ -50,6 +50,7 @@ function advanceChooser(state, items, input, options = {}) {
50
50
  const last = Math.max(0, visible.length - 1);
51
51
  const page = options.page ?? 8;
52
52
  const clamp = (index) => Math.max(0, Math.min(last, index));
53
+ state = { ...state, selected: clamp(state.selected) };
53
54
  const keep = state.chrome === undefined ? {} : { chrome: state.chrome };
54
55
  if (name === "escape" && options.filter && state.query !== "")
55
56
  return { state: { selected: 0, query: "", ...keep } };
@@ -122,7 +123,7 @@ function renderChooser(state, items, options) {
122
123
  const fadingOut = !active && index === options.transitionFrom;
123
124
  const number = offset < 9 ? `${offset + 1}.` : " ";
124
125
  const furniture = visibleWidth(` ${active ? ">" : " "} ${number} `) + 2;
125
- if (width < furniture) {
126
+ if (width < furniture + 1) {
126
127
  lines.push(active ? paint.green(clip(`${glyphs.prompt}${item.label}`, width)) : clip(item.label, width));
127
128
  continue;
128
129
  }
@@ -142,7 +143,8 @@ function renderChooser(state, items, options) {
142
143
  }
143
144
  const position = visible.length > height && chrome.pagination ? ` ${paginator(state.selected, visible.length)}` : "";
144
145
  if (chrome.help) {
145
- lines.push(paint.dim(clip(` ${options.legend ?? `${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} ${options.filter ? `type to filter ${glyphs.middot} ` : ""}${options.filter ? "Esc clear/cancel" : "Esc cancel"}`}${position}`, width)));
146
+ const legend = options.legend !== undefined && options.filter && state.query !== "" ? options.legend.replace(/Esc\b[^·]*$/, "Esc clear filter") : options.legend;
147
+ lines.push(paint.dim(clip(` ${legend ?? `${glyphs.arrowUp}${glyphs.arrowDown} move ${glyphs.middot} Enter choose ${glyphs.middot} ${options.filter ? `type to filter ${glyphs.middot} ` : ""}${options.filter ? "Esc clear/back" : "Esc back"}`}${position}`, width)));
146
148
  } else if (position) {
147
149
  lines.push(paint.dim(clip(position, width)));
148
150
  }
@@ -158,7 +160,9 @@ async function runChooser(keys, items, paint, options) {
158
160
  const height = Math.max(1, Math.min(options.height ?? 10, size?.height ?? Number.POSITIVE_INFINITY));
159
161
  return { ...options, width: size?.width ?? options.width, height };
160
162
  };
161
- paint(renderChooser(state, items, liveOptions()));
163
+ let list = items;
164
+ let pendingDelete;
165
+ paint(renderChooser(state, list, liveOptions()));
162
166
  let glide;
163
167
  const settleGlide = () => {
164
168
  glide?.stop();
@@ -166,9 +170,36 @@ async function runChooser(keys, items, paint, options) {
166
170
  };
167
171
  try {
168
172
  for await (const input of keys) {
173
+ if (pendingDelete && options.onDelete) {
174
+ const target = pendingDelete;
175
+ pendingDelete = undefined;
176
+ if (input.str === "y" || input.str === "Y") {
177
+ const outcome = await options.onDelete(target);
178
+ if (outcome.deleted) {
179
+ list = list.filter((item) => item !== target);
180
+ const remaining = filterItems(list, state.query).length;
181
+ state = { ...state, selected: Math.max(0, Math.min(state.selected, remaining - 1)), status: `deleted "${target.label}"` };
182
+ } else {
183
+ state = { ...state, status: outcome.reason };
184
+ }
185
+ } else {
186
+ state = { ...state, status: "kept" };
187
+ }
188
+ paint(renderChooser(state, list, liveOptions()));
189
+ continue;
190
+ }
191
+ if (options.onDelete && (input.key.name === "delete" || input.key.ctrl && input.key.name === "d")) {
192
+ const target = filterItems(list, state.query)[state.selected];
193
+ if (target && !target.pinned) {
194
+ pendingDelete = target;
195
+ state = { ...state, status: `Delete "${target.label}"? y/n` };
196
+ paint(renderChooser(state, list, liveOptions()));
197
+ }
198
+ continue;
199
+ }
169
200
  const current = liveOptions();
170
201
  const previousSelected = state.selected;
171
- const step = advanceChooser(state, items, input, {
202
+ const step = advanceChooser(state, list, input, {
172
203
  filter: options.filter ?? false,
173
204
  page: options.page ?? current.height ?? 8,
174
205
  ...current.height === undefined ? {} : { height: current.height }
@@ -178,24 +209,24 @@ async function runChooser(keys, items, paint, options) {
178
209
  settleGlide();
179
210
  if (step.done.index === undefined)
180
211
  return;
181
- return filterItems(items, state.query)[step.done.index]?.value;
212
+ return filterItems(list, state.query)[step.done.index]?.value;
182
213
  }
183
214
  settleGlide();
184
215
  const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
185
216
  const motion = options.motion ?? (process.env.ARCHYMEDES_NO_MOTION !== "1" && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb");
186
217
  if (!motion || !isSingleStep || previousSelected === state.selected) {
187
- paint(renderChooser(state, items, liveOptions()));
218
+ paint(renderChooser(state, list, liveOptions()));
188
219
  continue;
189
220
  }
190
- paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
221
+ paint(renderChooser(state, list, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
191
222
  const animator = new SpringAnimator(0, (value) => {
192
223
  if (value < 0.95) {
193
- paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
224
+ paint(renderChooser(state, list, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
194
225
  return;
195
226
  }
196
227
  animator.stop();
197
228
  glide = undefined;
198
- paint(renderChooser(state, items, liveOptions()));
229
+ paint(renderChooser(state, list, liveOptions()));
199
230
  }, { intervalMs: 30 });
200
231
  glide = animator;
201
232
  animator.retarget(1);
@@ -7,9 +7,9 @@ import {
7
7
  paintAll,
8
8
  roleCode,
9
9
  visibleWidth
10
- } from "./archymedes-a10k8yrk.mjs";
10
+ } from "./archymedes-130jt0wr.mjs";
11
11
 
12
- // src/render/sections.ts
12
+ // packages/archymedes-cli/src/render/sections.ts
13
13
  var TONE_ROLE = {
14
14
  good: "success",
15
15
  bad: "error",