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.
- package/README.md +17 -7
- package/dist/{archymedes-wjaqydyg.mjs → archymedes-4kx65anz.mjs} +5 -20
- package/dist/{archymedes-5zn4zp84.mjs → archymedes-a10k8yrk.mjs} +43 -225
- package/dist/{archymedes-76z6n643.mjs → archymedes-aj30app4.mjs} +160 -150
- package/dist/{archymedes-0jxzsh4z.mjs → archymedes-d5wqcmdr.mjs} +97 -97
- package/dist/{archymedes-sc3mwttb.mjs → archymedes-dz59nvw7.mjs} +52 -21
- package/dist/{archymedes-r54qthg5.mjs → archymedes-g8y3rt6t.mjs} +376 -25
- package/dist/{archymedes-1g1y6q74.mjs → archymedes-h13ap0za.mjs} +1 -1
- package/dist/archymedes-main.mjs +17452 -14872
- package/dist/{archymedes-npcrm9a0.mjs → archymedes-sf9t1hz9.mjs} +479 -50
- package/dist/{archymedes-cdgejs3p.mjs → archymedes-tsne2m7p.mjs} +17 -44
- package/dist/{archymedes-pg3pvh9r.mjs → archymedes-twbks7g3.mjs} +5 -5
- package/dist/{editor-screen-1b3z96tn.mjs → editor-screen-g08zzt70.mjs} +20 -19
- package/dist/{factory-96yy6mxr.mjs → factory-7vtm2fed.mjs} +326 -326
- package/dist/{file-screen-m7q1xcer.mjs → file-screen-ma9yxq2b.mjs} +7 -7
- package/dist/guide-screen-gd129txd.mjs +271 -0
- package/dist/{index-teq1cta4.mjs → index-qvgcwsvm.mjs} +1 -1
- package/dist/{node-0fh63p8z.mjs → node-mdgzx68h.mjs} +7 -7
- package/dist/{sandbox-templates-8sd1psez.mjs → sandbox-templates-9jcjws23.mjs} +2 -2
- package/dist/{workspace-screen-w88mcw4w.mjs → workspace-screen-8cwk6jz9.mjs} +7 -7
- package/package.json +2 -2
- package/dist/guide-screen-aj9n9v80.mjs +0 -569
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CYAN,
|
|
3
|
-
DIM,
|
|
4
|
-
GREEN,
|
|
5
2
|
GUTTER,
|
|
6
|
-
MAGENTA,
|
|
7
|
-
RED,
|
|
8
3
|
clip,
|
|
9
|
-
|
|
10
|
-
} from "./archymedes-
|
|
4
|
+
panel
|
|
5
|
+
} from "./archymedes-tsne2m7p.mjs";
|
|
11
6
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
BOLD,
|
|
8
|
+
DIM,
|
|
9
|
+
UNICODE_GLYPHS,
|
|
10
|
+
paint,
|
|
11
|
+
paintAll,
|
|
12
|
+
roleCode,
|
|
13
|
+
visibleWidth
|
|
14
|
+
} from "./archymedes-a10k8yrk.mjs";
|
|
14
15
|
|
|
15
|
-
//
|
|
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,
|
|
204
|
+
return paint(text, roleCode("success", palette, depth), depth);
|
|
198
205
|
if (numeric !== undefined)
|
|
199
|
-
return paint(numeric,
|
|
206
|
+
return paint(numeric, roleCode("accent", palette, depth), depth);
|
|
200
207
|
if (word !== undefined && KEYWORDS.has(word))
|
|
201
|
-
return paint(word,
|
|
208
|
+
return paint(word, roleCode("primary", palette, depth), depth);
|
|
202
209
|
if (word !== undefined && LITERALS.has(word))
|
|
203
|
-
return paint(word,
|
|
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" ?
|
|
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
|
|
281
|
-
|
|
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
|
-
|
|
10
|
+
paint,
|
|
6
11
|
rainbowText,
|
|
7
|
-
renderMarkdownLine,
|
|
8
12
|
rgbTo256,
|
|
13
|
+
roleCode,
|
|
9
14
|
visibleWidth
|
|
10
|
-
} from "./archymedes-
|
|
15
|
+
} from "./archymedes-a10k8yrk.mjs";
|
|
11
16
|
|
|
12
|
-
//
|
|
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
|
-
//
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
|
458
|
-
var
|
|
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
|
|
466
|
-
const modeColor =
|
|
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 };
|