archymedes-cli 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/archymedes-5zn4zp84.mjs +646 -0
- package/dist/{archymedes-44c1mqas.mjs → archymedes-cdgejs3p.mjs} +1 -1
- package/dist/archymedes-main.mjs +1175 -663
- package/dist/{archymedes-k1bx08z9.mjs → archymedes-npcrm9a0.mjs} +3 -1
- package/dist/{archymedes-2tp22t9a.mjs → archymedes-pg3pvh9r.mjs} +6 -4
- package/dist/{archymedes-dwvn4c51.mjs → archymedes-r54qthg5.mjs} +47 -378
- package/dist/archymedes-sc3mwttb.mjs +284 -0
- package/dist/{archymedes-brkqgrxv.mjs → archymedes-wjaqydyg.mjs} +44 -32
- package/dist/{editor-screen-51z7z11j.mjs → editor-screen-1b3z96tn.mjs} +259 -11
- package/dist/{file-screen-eg28a3ky.mjs → file-screen-m7q1xcer.mjs} +5 -5
- package/dist/{guide-screen-mfvwkdhb.mjs → guide-screen-aj9n9v80.mjs} +15 -11
- package/dist/{workspace-screen-3zztn4by.mjs → workspace-screen-w88mcw4w.mjs} +7 -6
- package/package.json +1 -1
- package/dist/archymedes-jecyxv5c.mjs +0 -249
package/README.md
CHANGED
|
@@ -309,3 +309,18 @@ TUI dependency resolution. The reviewed archive and SHA256 checksum are written
|
|
|
309
309
|
See [the release assessment](https://github.com/chrisnkuno/archymedes-cli/blob/main/docs/RELEASE_ASSESSMENT.md) for scope and remaining release proof.
|
|
310
310
|
|
|
311
311
|
The startup geometry makes a brief rotation on roomy interactive terminals. Set `ARCHYMEDES_NO_MOTION=1` to keep it still. Preview it with `bun run preview:tui archymedes --animate`.
|
|
312
|
+
|
|
313
|
+
## Fixed workspace
|
|
314
|
+
|
|
315
|
+
Start with `archymedes --layout fixed` for a persistent mode bar, an anchored composer, and menus
|
|
316
|
+
that open inside the transcript area. `/layout` switches layouts during a session;
|
|
317
|
+
`ARCHYMEDES_LAYOUT=fixed` makes the fixed workspace your launch preference.
|
|
318
|
+
|
|
319
|
+
Use `/mode` to choose plan, build, auto, or defender with the arrow keys. Page Up / Page Down
|
|
320
|
+
read retained transcript history; Escape returns to live output. Home and End still edit the
|
|
321
|
+
composer. Each tab retains up to 5,000 output lines. Resize reflows the live transcript.
|
|
322
|
+
|
|
323
|
+
The opening geometry rotates while the workspace is idle and settles as soon as you type.
|
|
324
|
+
`ARCHYMEDES_NO_MOTION=1` disables this motion and menu focus animation. `--ascii`, `NO_COLOR`,
|
|
325
|
+
and the existing themes remain supported. Try the real interface offline from the repository
|
|
326
|
+
with `bun run preview:workspace`; the preview uses a temporary project and a local model fixture.
|
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
// packages/archymedes-cli/src/theme.ts
|
|
2
|
+
var BLUEPRINT = `
|
|
3
|
+
/* Blueprint navy, chalk-white ink and an amber pencil — the original drafting table. */
|
|
4
|
+
@theme blueprint {
|
|
5
|
+
--primary: #7cc5ff;
|
|
6
|
+
--secondary: #9fb4e8;
|
|
7
|
+
--accent: #ffcf6b;
|
|
8
|
+
--bg: #0a1524;
|
|
9
|
+
--surface: #122135;
|
|
10
|
+
--text: #e8eefb;
|
|
11
|
+
--text-muted: #6f89b0;
|
|
12
|
+
--success: #79dfa6;
|
|
13
|
+
--warning: #ffcf6b;
|
|
14
|
+
--error: #ff8f7a;
|
|
15
|
+
--border: round;
|
|
16
|
+
--border-color: #24406a;
|
|
17
|
+
--border-focus: #7cc5ff;
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
var PARCHMENT = `
|
|
21
|
+
/* The same drawing inked on paper, for a light terminal. */
|
|
22
|
+
@theme parchment {
|
|
23
|
+
--primary: #2f5fbf;
|
|
24
|
+
--secondary: #5a4a86;
|
|
25
|
+
--accent: #9a6a00;
|
|
26
|
+
--bg: #faf6ec;
|
|
27
|
+
--surface: #f1eadd;
|
|
28
|
+
--text: #1b1a16;
|
|
29
|
+
--text-muted: #6a6252;
|
|
30
|
+
--success: #196a44;
|
|
31
|
+
--warning: #8a5300;
|
|
32
|
+
--error: #b3243b;
|
|
33
|
+
--border: round;
|
|
34
|
+
--border-color: #d8cfb8;
|
|
35
|
+
--border-focus: #2f5fbf;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
var CHALKBOARD = `
|
|
39
|
+
/* A slate: near-black green, chalk white, chalk yellow — for terminals with contrast to spare. */
|
|
40
|
+
@theme chalkboard {
|
|
41
|
+
--primary: #9fe8d8;
|
|
42
|
+
--secondary: #d8d0b0;
|
|
43
|
+
--accent: #ffe066;
|
|
44
|
+
--bg: #0d1a15;
|
|
45
|
+
--surface: #16271f;
|
|
46
|
+
--text: #f2efe4;
|
|
47
|
+
--text-muted: #86987f;
|
|
48
|
+
--success: #7ef2b0;
|
|
49
|
+
--warning: #ffd166;
|
|
50
|
+
--error: #ff7a7a;
|
|
51
|
+
--border: single;
|
|
52
|
+
--border-color: #2c443a;
|
|
53
|
+
--border-focus: #ffe066;
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
var HIGH_CONTRAST = `
|
|
57
|
+
/* Maximum separation, named colours only — for low vision and for terminals with a fixed palette. */
|
|
58
|
+
@theme high-contrast {
|
|
59
|
+
--primary: brightCyan;
|
|
60
|
+
--secondary: brightMagenta;
|
|
61
|
+
--accent: brightYellow;
|
|
62
|
+
--bg: black;
|
|
63
|
+
--surface: black;
|
|
64
|
+
--text: brightWhite;
|
|
65
|
+
--text-muted: white;
|
|
66
|
+
--success: brightGreen;
|
|
67
|
+
--warning: brightYellow;
|
|
68
|
+
--error: brightRed;
|
|
69
|
+
--border: single;
|
|
70
|
+
--border-color: white;
|
|
71
|
+
--border-focus: brightCyan;
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
var ARCHYMEDES = `
|
|
75
|
+
/* Bronze instruments, limestone ink and olive accents on charcoal — Archymedes's own. */
|
|
76
|
+
@theme archymedes {
|
|
77
|
+
--primary: #e7bb78;
|
|
78
|
+
--secondary: #b9c59a;
|
|
79
|
+
--accent: #e5a58c;
|
|
80
|
+
--bg: #191815;
|
|
81
|
+
--surface: #25231e;
|
|
82
|
+
--text: #f2ecdf;
|
|
83
|
+
--text-muted: #aaa18f;
|
|
84
|
+
--success: #b5cc96;
|
|
85
|
+
--warning: #efc778;
|
|
86
|
+
--error: #f29686;
|
|
87
|
+
--border: single;
|
|
88
|
+
--border-color: #736956;
|
|
89
|
+
--border-focus: #e7bb78;
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
var RAINBOW = `
|
|
93
|
+
/* Every hue at once — the opening art and the thinking spinner sweep the full spectrum. */
|
|
94
|
+
@theme rainbow {
|
|
95
|
+
--primary: #ff5f5f;
|
|
96
|
+
--secondary: #5fd7ff;
|
|
97
|
+
--accent: #ffd75f;
|
|
98
|
+
--bg: #0c0c0c;
|
|
99
|
+
--surface: #161616;
|
|
100
|
+
--text: #f2f2f2;
|
|
101
|
+
--text-muted: #9a9a9a;
|
|
102
|
+
--success: #5fff87;
|
|
103
|
+
--warning: #ffaf5f;
|
|
104
|
+
--error: #ff3b3b;
|
|
105
|
+
--border: double;
|
|
106
|
+
--border-color: #8a8a8a;
|
|
107
|
+
--border-focus: #d75fff;
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
var BUILTIN_THEME_SOURCES = {
|
|
111
|
+
archymedes: ARCHYMEDES,
|
|
112
|
+
blueprint: BLUEPRINT,
|
|
113
|
+
parchment: PARCHMENT,
|
|
114
|
+
chalkboard: CHALKBOARD,
|
|
115
|
+
"high-contrast": HIGH_CONTRAST,
|
|
116
|
+
rainbow: RAINBOW
|
|
117
|
+
};
|
|
118
|
+
var DEFAULT_THEME_NAME = "archymedes";
|
|
119
|
+
var NAMED_COLORS = {
|
|
120
|
+
black: 0,
|
|
121
|
+
red: 1,
|
|
122
|
+
green: 2,
|
|
123
|
+
yellow: 3,
|
|
124
|
+
blue: 4,
|
|
125
|
+
magenta: 5,
|
|
126
|
+
cyan: 6,
|
|
127
|
+
white: 7,
|
|
128
|
+
brightblack: 8,
|
|
129
|
+
brightred: 9,
|
|
130
|
+
brightgreen: 10,
|
|
131
|
+
brightyellow: 11,
|
|
132
|
+
brightblue: 12,
|
|
133
|
+
brightmagenta: 13,
|
|
134
|
+
brightcyan: 14,
|
|
135
|
+
brightwhite: 15,
|
|
136
|
+
gray: 8,
|
|
137
|
+
grey: 8
|
|
138
|
+
};
|
|
139
|
+
function parseColor(value) {
|
|
140
|
+
const text = value.trim();
|
|
141
|
+
const named = NAMED_COLORS[text.toLowerCase().replace(/[\s_-]/g, "")];
|
|
142
|
+
if (named !== undefined)
|
|
143
|
+
return named;
|
|
144
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text);
|
|
145
|
+
if (!hex)
|
|
146
|
+
return;
|
|
147
|
+
const digits = hex[1];
|
|
148
|
+
const full = digits.length === 3 ? digits.split("").map((digit) => digit + digit).join("") : digits;
|
|
149
|
+
return {
|
|
150
|
+
r: Number.parseInt(full.slice(0, 2), 16),
|
|
151
|
+
g: Number.parseInt(full.slice(2, 4), 16),
|
|
152
|
+
b: Number.parseInt(full.slice(4, 6), 16)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function rgbTo256({ r, g, b }) {
|
|
156
|
+
const level = (value) => {
|
|
157
|
+
if (value < 48)
|
|
158
|
+
return 0;
|
|
159
|
+
if (value < 115)
|
|
160
|
+
return 1;
|
|
161
|
+
return Math.min(5, Math.round((value - 35) / 40));
|
|
162
|
+
};
|
|
163
|
+
const cubeIndex = 16 + 36 * level(r) + 6 * level(g) + level(b);
|
|
164
|
+
const cubeValue = (step) => step === 0 ? 0 : 55 + step * 40;
|
|
165
|
+
const cube = { r: cubeValue(level(r)), g: cubeValue(level(g)), b: cubeValue(level(b)) };
|
|
166
|
+
const greyAverage = (r + g + b) / 3;
|
|
167
|
+
const greyStep = Math.max(0, Math.min(23, Math.round((greyAverage - 8) / 10)));
|
|
168
|
+
const greyValue = 8 + greyStep * 10;
|
|
169
|
+
const grey = { r: greyValue, g: greyValue, b: greyValue };
|
|
170
|
+
return distance({ r, g, b }, grey) < distance({ r, g, b }, cube) ? 232 + greyStep : cubeIndex;
|
|
171
|
+
}
|
|
172
|
+
function distance(a, b) {
|
|
173
|
+
return (a.r - b.r) ** 2 + (a.g - b.g) ** 2 + (a.b - b.b) ** 2;
|
|
174
|
+
}
|
|
175
|
+
function colorCode(value, depth) {
|
|
176
|
+
if (depth === "none")
|
|
177
|
+
return "";
|
|
178
|
+
const parsed = parseColor(value);
|
|
179
|
+
if (parsed === undefined)
|
|
180
|
+
return "";
|
|
181
|
+
if (typeof parsed === "number")
|
|
182
|
+
return parsed < 8 ? `\x1B[${30 + parsed}m` : `\x1B[${90 + parsed - 8}m`;
|
|
183
|
+
return depth === "truecolor" ? `\x1B[38;2;${parsed.r};${parsed.g};${parsed.b}m` : `\x1B[38;5;${rgbTo256(parsed)}m`;
|
|
184
|
+
}
|
|
185
|
+
function rainbowHex(t) {
|
|
186
|
+
const hue = (t % 1 + 1) % 1 * 360;
|
|
187
|
+
const c = 0.5;
|
|
188
|
+
const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
189
|
+
const m = 0.6 - c / 2;
|
|
190
|
+
const [r1, g1, b1] = hue < 60 ? [c, x, 0] : hue < 120 ? [x, c, 0] : hue < 180 ? [0, c, x] : hue < 240 ? [0, x, c] : hue < 300 ? [x, 0, c] : [c, 0, x];
|
|
191
|
+
const channel = (value) => Math.round((value + m) * 255).toString(16).padStart(2, "0");
|
|
192
|
+
return `#${channel(r1)}${channel(g1)}${channel(b1)}`;
|
|
193
|
+
}
|
|
194
|
+
function rainbowText(text, depth, phase = 0) {
|
|
195
|
+
if (depth === "none")
|
|
196
|
+
return text;
|
|
197
|
+
const characters = [...text];
|
|
198
|
+
let out = "";
|
|
199
|
+
let open;
|
|
200
|
+
characters.forEach((character, index) => {
|
|
201
|
+
if (character === " ") {
|
|
202
|
+
if (open !== undefined) {
|
|
203
|
+
out += "\x1B[0m";
|
|
204
|
+
open = undefined;
|
|
205
|
+
}
|
|
206
|
+
out += character;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const code = colorCode(rainbowHex(phase + index / 12), depth);
|
|
210
|
+
if (code !== open) {
|
|
211
|
+
out += code;
|
|
212
|
+
open = code;
|
|
213
|
+
}
|
|
214
|
+
out += character;
|
|
215
|
+
});
|
|
216
|
+
return open === undefined ? out : `${out}\x1B[0m`;
|
|
217
|
+
}
|
|
218
|
+
var DEFAULT_TOKENS = {
|
|
219
|
+
primary: "cyan",
|
|
220
|
+
secondary: "blue",
|
|
221
|
+
accent: "yellow",
|
|
222
|
+
bg: "black",
|
|
223
|
+
surface: "black",
|
|
224
|
+
text: "white",
|
|
225
|
+
textMuted: "brightBlack",
|
|
226
|
+
success: "green",
|
|
227
|
+
warning: "yellow",
|
|
228
|
+
error: "red",
|
|
229
|
+
border: "round",
|
|
230
|
+
borderColor: "brightBlack",
|
|
231
|
+
borderFocus: "cyan"
|
|
232
|
+
};
|
|
233
|
+
var TOKEN_NAMES = {
|
|
234
|
+
primary: "primary",
|
|
235
|
+
secondary: "secondary",
|
|
236
|
+
accent: "accent",
|
|
237
|
+
bg: "bg",
|
|
238
|
+
background: "bg",
|
|
239
|
+
surface: "surface",
|
|
240
|
+
text: "text",
|
|
241
|
+
fg: "text",
|
|
242
|
+
"text-muted": "textMuted",
|
|
243
|
+
muted: "textMuted",
|
|
244
|
+
success: "success",
|
|
245
|
+
warning: "warning",
|
|
246
|
+
error: "error",
|
|
247
|
+
border: "border",
|
|
248
|
+
"border-color": "borderColor",
|
|
249
|
+
"border-focus": "borderFocus",
|
|
250
|
+
highlight: "secondary"
|
|
251
|
+
};
|
|
252
|
+
function parseThemeSource(source) {
|
|
253
|
+
const themes = [];
|
|
254
|
+
const withoutLineComments = source.replace(/(^|\s)\/\/[^\n]*/g, "$1");
|
|
255
|
+
const blockPattern = /(?:\/\*([\s\S]*?)\*\/\s*)?@theme\s+([A-Za-z0-9_-]+)\s*\{([^}]*)\}/g;
|
|
256
|
+
for (const match of withoutLineComments.matchAll(blockPattern)) {
|
|
257
|
+
const description = (match[1] ?? "").replace(/\s+/g, " ").trim();
|
|
258
|
+
const tokens = { ...DEFAULT_TOKENS };
|
|
259
|
+
for (const declaration of match[3].matchAll(/(?:--|\$)([a-z0-9-]+)\s*:\s*([^;}]+)[;]?/gi)) {
|
|
260
|
+
const key = TOKEN_NAMES[declaration[1].toLowerCase()];
|
|
261
|
+
if (key)
|
|
262
|
+
tokens[key] = declaration[2].trim();
|
|
263
|
+
}
|
|
264
|
+
themes.push({ name: match[2], description, tokens });
|
|
265
|
+
}
|
|
266
|
+
return themes;
|
|
267
|
+
}
|
|
268
|
+
function builtinThemes() {
|
|
269
|
+
return Object.values(BUILTIN_THEME_SOURCES).flatMap((source) => parseThemeSource(source));
|
|
270
|
+
}
|
|
271
|
+
function findBuiltinTheme(name) {
|
|
272
|
+
return builtinThemes().find((theme) => theme.name.toLowerCase() === name.trim().toLowerCase());
|
|
273
|
+
}
|
|
274
|
+
function borderStyleOf(value) {
|
|
275
|
+
const text = value.trim().toLowerCase();
|
|
276
|
+
return text === "single" || text === "double" || text === "none" ? text : "round";
|
|
277
|
+
}
|
|
278
|
+
function buildPalette(theme, depth) {
|
|
279
|
+
const code = (value) => colorCode(value, depth);
|
|
280
|
+
return {
|
|
281
|
+
theme: theme.name,
|
|
282
|
+
depth,
|
|
283
|
+
primary: code(theme.tokens.primary),
|
|
284
|
+
secondary: code(theme.tokens.secondary),
|
|
285
|
+
accent: code(theme.tokens.accent),
|
|
286
|
+
text: code(theme.tokens.text),
|
|
287
|
+
muted: code(theme.tokens.textMuted),
|
|
288
|
+
success: code(theme.tokens.success),
|
|
289
|
+
warning: code(theme.tokens.warning),
|
|
290
|
+
error: code(theme.tokens.error),
|
|
291
|
+
border: code(theme.tokens.borderColor),
|
|
292
|
+
borderFocus: code(theme.tokens.borderFocus),
|
|
293
|
+
borderStyle: borderStyleOf(theme.tokens.border),
|
|
294
|
+
bg: theme.tokens.bg,
|
|
295
|
+
surface: theme.tokens.surface,
|
|
296
|
+
tokens: theme.tokens
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
var NO_COLOR_PALETTE = buildPalette(findBuiltinTheme(DEFAULT_THEME_NAME) ?? { name: DEFAULT_THEME_NAME, description: "", tokens: DEFAULT_TOKENS }, "none");
|
|
300
|
+
|
|
301
|
+
// packages/archymedes-cli/src/glyphs.ts
|
|
302
|
+
var UNICODE_GLYPHS = {
|
|
303
|
+
starGlyphs: [".", "·", "✧", "✦", "✶"],
|
|
304
|
+
spinnerFrames: [" ◜ ", " ◠ ", " ◝ ", " ◞ ", " ◡ ", " ◟ "],
|
|
305
|
+
star: "✦",
|
|
306
|
+
starDim: "✧",
|
|
307
|
+
check: "✓",
|
|
308
|
+
cross: "✗",
|
|
309
|
+
pending: "⋯",
|
|
310
|
+
prompt: "❯",
|
|
311
|
+
caret: "›",
|
|
312
|
+
bullet: "•",
|
|
313
|
+
ellipsis: "…",
|
|
314
|
+
middot: "·",
|
|
315
|
+
elbow: "⎿",
|
|
316
|
+
boxTopLeft: "╭",
|
|
317
|
+
boxTopRight: "╮",
|
|
318
|
+
boxBottomLeft: "╰",
|
|
319
|
+
boxBottomRight: "╯",
|
|
320
|
+
boxHorizontal: "─",
|
|
321
|
+
boxVertical: "│",
|
|
322
|
+
boxTeeLeft: "├",
|
|
323
|
+
boxTeeRight: "┤",
|
|
324
|
+
boxCross: "┼",
|
|
325
|
+
circleEmpty: "○",
|
|
326
|
+
circleHalf: "◐",
|
|
327
|
+
circleFull: "●",
|
|
328
|
+
checkbox: "☐",
|
|
329
|
+
checkboxDone: "☑",
|
|
330
|
+
paused: "⏸",
|
|
331
|
+
cancelled: "⊘",
|
|
332
|
+
treeBranch: "├─",
|
|
333
|
+
treeLast: "╰─",
|
|
334
|
+
treeTrunk: "│ ",
|
|
335
|
+
collapsed: "▸",
|
|
336
|
+
expanded: "▾",
|
|
337
|
+
arrowUp: "↑",
|
|
338
|
+
arrowLeft: "←",
|
|
339
|
+
arrowRight: "→",
|
|
340
|
+
arrowDown: "↓",
|
|
341
|
+
plus: "+",
|
|
342
|
+
minus: "-",
|
|
343
|
+
warning: "!",
|
|
344
|
+
effectWorkspace: "✎",
|
|
345
|
+
effectExternal: "◎",
|
|
346
|
+
sparkLevels: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
|
|
347
|
+
};
|
|
348
|
+
var ASCII_GLYPHS = {
|
|
349
|
+
starGlyphs: [".", ".", "+", "*", "*"],
|
|
350
|
+
spinnerFrames: ["-", "\\", "|", "/"],
|
|
351
|
+
star: "*",
|
|
352
|
+
starDim: "+",
|
|
353
|
+
check: "+",
|
|
354
|
+
cross: "x",
|
|
355
|
+
pending: "~",
|
|
356
|
+
prompt: ">",
|
|
357
|
+
caret: ">",
|
|
358
|
+
bullet: "-",
|
|
359
|
+
ellipsis: "...",
|
|
360
|
+
middot: "-",
|
|
361
|
+
elbow: "\\",
|
|
362
|
+
boxTopLeft: "+",
|
|
363
|
+
boxTopRight: "+",
|
|
364
|
+
boxBottomLeft: "+",
|
|
365
|
+
boxBottomRight: "+",
|
|
366
|
+
boxHorizontal: "-",
|
|
367
|
+
boxVertical: "|",
|
|
368
|
+
boxTeeLeft: "+",
|
|
369
|
+
boxTeeRight: "+",
|
|
370
|
+
boxCross: "+",
|
|
371
|
+
circleEmpty: "o",
|
|
372
|
+
circleHalf: "*",
|
|
373
|
+
circleFull: "*",
|
|
374
|
+
checkbox: "[ ]",
|
|
375
|
+
checkboxDone: "[x]",
|
|
376
|
+
paused: "=",
|
|
377
|
+
cancelled: "x",
|
|
378
|
+
treeBranch: "|-",
|
|
379
|
+
treeLast: "`-",
|
|
380
|
+
treeTrunk: "| ",
|
|
381
|
+
collapsed: ">",
|
|
382
|
+
expanded: "v",
|
|
383
|
+
arrowUp: "^",
|
|
384
|
+
arrowLeft: "<-",
|
|
385
|
+
arrowRight: "->",
|
|
386
|
+
arrowDown: "v",
|
|
387
|
+
plus: "+",
|
|
388
|
+
minus: "-",
|
|
389
|
+
warning: "!",
|
|
390
|
+
effectWorkspace: "w",
|
|
391
|
+
effectExternal: "e",
|
|
392
|
+
sparkLevels: [" ", ".", ":", "-", "=", "+", "*", "#"]
|
|
393
|
+
};
|
|
394
|
+
var ROUND_BORDER = { topLeft: "╭", topRight: "╮", bottomLeft: "╰", bottomRight: "╯", horizontal: "─", vertical: "│", teeLeft: "├", teeRight: "┤", cross: "┼" };
|
|
395
|
+
var SINGLE_BORDER = { topLeft: "┌", topRight: "┐", bottomLeft: "└", bottomRight: "┘", horizontal: "─", vertical: "│", teeLeft: "├", teeRight: "┤", cross: "┼" };
|
|
396
|
+
var DOUBLE_BORDER = { topLeft: "╔", topRight: "╗", bottomLeft: "╚", bottomRight: "╝", horizontal: "═", vertical: "║", teeLeft: "╠", teeRight: "╣", cross: "╬" };
|
|
397
|
+
var ASCII_BORDER = { topLeft: "+", topRight: "+", bottomLeft: "+", bottomRight: "+", horizontal: "-", vertical: "|", teeLeft: "+", teeRight: "+", cross: "+" };
|
|
398
|
+
function borderGlyphsFor(style, glyphs) {
|
|
399
|
+
if (glyphs === ASCII_GLYPHS)
|
|
400
|
+
return ASCII_BORDER;
|
|
401
|
+
if (style === "single")
|
|
402
|
+
return SINGLE_BORDER;
|
|
403
|
+
if (style === "double")
|
|
404
|
+
return DOUBLE_BORDER;
|
|
405
|
+
return ROUND_BORDER;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// packages/archymedes-cli/src/markdown.ts
|
|
409
|
+
var RESET = "\x1B[0m";
|
|
410
|
+
var BOLD = "\x1B[1m";
|
|
411
|
+
var DIM = "\x1B[2m";
|
|
412
|
+
var ITALIC = "\x1B[3m";
|
|
413
|
+
var STRIKE = "\x1B[9m";
|
|
414
|
+
var CYAN = "\x1B[36m";
|
|
415
|
+
var BLUE = "\x1B[34m";
|
|
416
|
+
var GREEN = "\x1B[32m";
|
|
417
|
+
var YELLOW = "\x1B[33m";
|
|
418
|
+
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
419
|
+
function visibleWidth(text) {
|
|
420
|
+
let width = 0;
|
|
421
|
+
for (const character of text.replace(ANSI, "")) {
|
|
422
|
+
const code = character.codePointAt(0) ?? 0;
|
|
423
|
+
if (code >= 768 && code <= 879)
|
|
424
|
+
continue;
|
|
425
|
+
width += isWide(code) ? 2 : 1;
|
|
426
|
+
}
|
|
427
|
+
return width;
|
|
428
|
+
}
|
|
429
|
+
function isWide(code) {
|
|
430
|
+
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;
|
|
431
|
+
}
|
|
432
|
+
var INLINE_PATTERN = /(?<!\\)(`[^`\n]+`)|(?<!\\)(\*\*[^*\n]+\*\*)|(?<!\\)(__[^_\n]+__)|(?<!\\)(~~[^~\n]+~~)|(?<!\\)(\*[^*\n]+\*)|(?<!\\)(_[^_\n]+_)|(!?\[([^\]\n]+)\]\((https?:\/\/[^\s)\n]+)\))|(<(https?:\/\/[^>\s]+)>)/g;
|
|
433
|
+
var unescapeMarkdown = (text) => text.replace(/\\([\\`*_[\]{}()#+.!~>-])/g, "$1");
|
|
434
|
+
function parseInline(line) {
|
|
435
|
+
const tokens = [];
|
|
436
|
+
let lastIndex = 0;
|
|
437
|
+
INLINE_PATTERN.lastIndex = 0;
|
|
438
|
+
for (let match = INLINE_PATTERN.exec(line);match !== null; match = INLINE_PATTERN.exec(line)) {
|
|
439
|
+
if (match.index > lastIndex)
|
|
440
|
+
tokens.push({ text: unescapeMarkdown(line.slice(lastIndex, match.index)), code: "" });
|
|
441
|
+
const [whole, code, boldStars, boldUnderscores, strike, italicStars, italicUnderscores, link, linkLabel, linkUrl, autolink, autolinkUrl] = match;
|
|
442
|
+
if (code !== undefined)
|
|
443
|
+
tokens.push({ text: code.slice(1, -1), code: CYAN });
|
|
444
|
+
else if (boldStars !== undefined)
|
|
445
|
+
tokens.push({ text: boldStars.slice(2, -2), code: BOLD });
|
|
446
|
+
else if (boldUnderscores !== undefined)
|
|
447
|
+
tokens.push({ text: boldUnderscores.slice(2, -2), code: BOLD });
|
|
448
|
+
else if (strike !== undefined)
|
|
449
|
+
tokens.push({ text: strike.slice(2, -2), code: STRIKE });
|
|
450
|
+
else if (italicStars !== undefined)
|
|
451
|
+
tokens.push({ text: italicStars.slice(1, -1), code: ITALIC });
|
|
452
|
+
else if (italicUnderscores !== undefined)
|
|
453
|
+
tokens.push({ text: italicUnderscores.slice(1, -1), code: ITALIC });
|
|
454
|
+
else if (link !== undefined)
|
|
455
|
+
tokens.push({ text: `${link.startsWith("!") ? `image: ${linkLabel}` : linkLabel} (${linkUrl})`, code: CYAN });
|
|
456
|
+
else if (autolink !== undefined)
|
|
457
|
+
tokens.push({ text: autolinkUrl, code: CYAN });
|
|
458
|
+
lastIndex = match.index + whole.length;
|
|
459
|
+
}
|
|
460
|
+
if (lastIndex < line.length)
|
|
461
|
+
tokens.push({ text: unescapeMarkdown(line.slice(lastIndex)), code: "" });
|
|
462
|
+
return tokens.filter((token) => token.text.length > 0);
|
|
463
|
+
}
|
|
464
|
+
function paint(text, code, depth) {
|
|
465
|
+
return depth === "none" || code === "" ? text : `${code}${text}${RESET}`;
|
|
466
|
+
}
|
|
467
|
+
function wrapTokens(tokens, width, options) {
|
|
468
|
+
const firstPrefix = options.firstPrefix ?? "";
|
|
469
|
+
const continuationPrefix = options.continuationPrefix ?? "";
|
|
470
|
+
const lines = [];
|
|
471
|
+
let current = "";
|
|
472
|
+
let currentWidth = 0;
|
|
473
|
+
let prefix = firstPrefix;
|
|
474
|
+
let prefixWidth = visibleWidth(firstPrefix);
|
|
475
|
+
const limit = Math.max(1, width);
|
|
476
|
+
const flush = () => {
|
|
477
|
+
lines.push(`${prefix}${current}`.replace(/[ \t]+$/, ""));
|
|
478
|
+
current = "";
|
|
479
|
+
currentWidth = 0;
|
|
480
|
+
prefix = continuationPrefix;
|
|
481
|
+
prefixWidth = visibleWidth(continuationPrefix);
|
|
482
|
+
};
|
|
483
|
+
for (const token of tokens) {
|
|
484
|
+
for (const word of token.text.split(/(\s+)/)) {
|
|
485
|
+
if (word === "")
|
|
486
|
+
continue;
|
|
487
|
+
const wordWidth = visibleWidth(word);
|
|
488
|
+
if (/^\s+$/.test(word)) {
|
|
489
|
+
if (currentWidth > 0 && prefixWidth + currentWidth + wordWidth <= limit) {
|
|
490
|
+
current += word;
|
|
491
|
+
currentWidth += wordWidth;
|
|
492
|
+
}
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (currentWidth > 0 && prefixWidth + currentWidth + wordWidth > limit)
|
|
496
|
+
flush();
|
|
497
|
+
if (prefixWidth + wordWidth > limit) {
|
|
498
|
+
let remaining = word;
|
|
499
|
+
while (remaining.length > 0) {
|
|
500
|
+
const available = Math.max(1, limit - prefixWidth - currentWidth);
|
|
501
|
+
if (visibleWidth(remaining) <= available)
|
|
502
|
+
break;
|
|
503
|
+
const head = [...remaining].slice(0, available).join("");
|
|
504
|
+
current += paint(head, token.code, options.depth);
|
|
505
|
+
remaining = [...remaining].slice(available).join("");
|
|
506
|
+
flush();
|
|
507
|
+
}
|
|
508
|
+
if (remaining.length > 0) {
|
|
509
|
+
current += paint(remaining, token.code, options.depth);
|
|
510
|
+
currentWidth += visibleWidth(remaining);
|
|
511
|
+
}
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
current += paint(word, token.code, options.depth);
|
|
515
|
+
currentWidth += wordWidth;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (current !== "" || lines.length === 0)
|
|
519
|
+
lines.push(`${prefix}${current}`.replace(/[ \t]+$/, ""));
|
|
520
|
+
return lines;
|
|
521
|
+
}
|
|
522
|
+
function newMarkdownState() {
|
|
523
|
+
return { inFence: false, fenceLanguage: "" };
|
|
524
|
+
}
|
|
525
|
+
var HEADING = /^(#{1,6})\s+(.*)$/;
|
|
526
|
+
var BULLET = /^(\s*)([-*+])\s+(.*)$/;
|
|
527
|
+
var TASK = /^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/;
|
|
528
|
+
var NUMBERED = /^(\s*)(\d+[.)])\s+(.*)$/;
|
|
529
|
+
var BLOCKQUOTE = /^>\s?(.*)$/;
|
|
530
|
+
var RULE = /^\s*([-*_])\s*(?:\1\s*){2,}$/;
|
|
531
|
+
var FENCE = /^\s*```(.*)$/;
|
|
532
|
+
var TABLE_SEPARATOR_CELL = /^:?-{3,}:?$/;
|
|
533
|
+
function tableCells(line) {
|
|
534
|
+
if (!line.includes("|"))
|
|
535
|
+
return null;
|
|
536
|
+
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
537
|
+
const cells = trimmed.split("|").map((cell) => cell.trim());
|
|
538
|
+
return cells.length >= 2 ? cells : null;
|
|
539
|
+
}
|
|
540
|
+
function fitCell(text, width, glyphs = UNICODE_GLYPHS) {
|
|
541
|
+
let result = "";
|
|
542
|
+
for (const character of text) {
|
|
543
|
+
if (visibleWidth(result + character) > width) {
|
|
544
|
+
while (visibleWidth(result) > Math.max(0, width - visibleWidth(glyphs.ellipsis)))
|
|
545
|
+
result = [...result].slice(0, -1).join("");
|
|
546
|
+
result += glyphs.ellipsis;
|
|
547
|
+
return result + " ".repeat(Math.max(0, width - visibleWidth(result)));
|
|
548
|
+
}
|
|
549
|
+
result += character;
|
|
550
|
+
}
|
|
551
|
+
return result + " ".repeat(Math.max(0, width - visibleWidth(result)));
|
|
552
|
+
}
|
|
553
|
+
function renderTableLine(cells, separator, width, depth, glyphs) {
|
|
554
|
+
const cellWidth = Math.max(3, Math.floor((Math.max(width, 12) - cells.length - 1) / cells.length));
|
|
555
|
+
if (separator)
|
|
556
|
+
return paint(`${glyphs.boxTeeLeft}${cells.map(() => glyphs.boxHorizontal.repeat(cellWidth)).join(glyphs.boxCross)}${glyphs.boxTeeRight}`, DIM, depth);
|
|
557
|
+
const contentWidth = Math.max(1, cellWidth - 2);
|
|
558
|
+
const rendered = cells.map((cell) => fitCell(parseInline(cell).map((token) => token.text).join(""), contentWidth, glyphs));
|
|
559
|
+
return `${glyphs.boxVertical}${rendered.map((cell) => ` ${cell} `).join(glyphs.boxVertical)}${glyphs.boxVertical}`;
|
|
560
|
+
}
|
|
561
|
+
function renderMarkdownLine(line, state, options) {
|
|
562
|
+
const { width, depth } = options;
|
|
563
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
564
|
+
const fence = FENCE.exec(line);
|
|
565
|
+
if (fence) {
|
|
566
|
+
const ruleWidth = Math.max(4, Math.min(width - 4, 40));
|
|
567
|
+
if (state.inFence) {
|
|
568
|
+
state.inFence = false;
|
|
569
|
+
state.fenceLanguage = "";
|
|
570
|
+
return [paint(` ${glyphs.boxBottomLeft}${glyphs.boxHorizontal.repeat(ruleWidth)}`, DIM, depth)];
|
|
571
|
+
}
|
|
572
|
+
state.inFence = true;
|
|
573
|
+
state.fenceLanguage = fence[1].trim();
|
|
574
|
+
const label = state.fenceLanguage ? ` ${state.fenceLanguage} ` : "";
|
|
575
|
+
const drawn = 1 + visibleWidth(label);
|
|
576
|
+
return [paint(` ${glyphs.boxTopLeft}${glyphs.boxHorizontal}${label}${glyphs.boxHorizontal.repeat(Math.max(0, ruleWidth - drawn))}`, DIM, depth)];
|
|
577
|
+
}
|
|
578
|
+
if (state.inFence) {
|
|
579
|
+
return [`${paint(` ${glyphs.boxVertical} `, DIM, depth)}${paint(line, GREEN, depth)}`];
|
|
580
|
+
}
|
|
581
|
+
const table = tableCells(line);
|
|
582
|
+
if (table) {
|
|
583
|
+
const separator = table.every((cell) => TABLE_SEPARATOR_CELL.test(cell));
|
|
584
|
+
return [renderTableLine(table, separator, width, depth, glyphs)];
|
|
585
|
+
}
|
|
586
|
+
if (RULE.test(line) && line.trim().length > 0) {
|
|
587
|
+
return [paint(" " + glyphs.boxHorizontal.repeat(Math.max(4, Math.min(width - 4, 40))), DIM, depth)];
|
|
588
|
+
}
|
|
589
|
+
const heading = HEADING.exec(line);
|
|
590
|
+
if (heading) {
|
|
591
|
+
const level = heading[1].length;
|
|
592
|
+
const code = level <= 2 ? `${BOLD}${CYAN}` : BOLD;
|
|
593
|
+
const tokens = parseInline(heading[2]).map((token) => ({ text: token.text, code }));
|
|
594
|
+
return wrapTokens(tokens, width, { depth });
|
|
595
|
+
}
|
|
596
|
+
const task = TASK.exec(line);
|
|
597
|
+
if (task) {
|
|
598
|
+
return wrapTokens(parseInline(task[3]), width, {
|
|
599
|
+
firstPrefix: `${task[1]}${paint(task[2].trim() ? glyphs.checkboxDone : glyphs.checkbox, task[2].trim() ? GREEN : BLUE, depth)} `,
|
|
600
|
+
continuationPrefix: `${task[1]} `,
|
|
601
|
+
depth
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const bullet = BULLET.exec(line);
|
|
605
|
+
if (bullet) {
|
|
606
|
+
const indent = bullet[1];
|
|
607
|
+
return wrapTokens(parseInline(bullet[3]), width, {
|
|
608
|
+
firstPrefix: `${indent}${paint(glyphs.bullet, BLUE, depth)} `,
|
|
609
|
+
continuationPrefix: `${indent} `,
|
|
610
|
+
depth
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
const numbered = NUMBERED.exec(line);
|
|
614
|
+
if (numbered) {
|
|
615
|
+
const indent = numbered[1];
|
|
616
|
+
const marker = numbered[2];
|
|
617
|
+
return wrapTokens(parseInline(numbered[3]), width, {
|
|
618
|
+
firstPrefix: `${indent}${paint(marker, BLUE, depth)} `,
|
|
619
|
+
continuationPrefix: `${indent}${" ".repeat(marker.length + 1)}`,
|
|
620
|
+
depth
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
const quote = BLOCKQUOTE.exec(line);
|
|
624
|
+
if (quote) {
|
|
625
|
+
return wrapTokens([{ text: quote[1], code: DIM }], width, {
|
|
626
|
+
firstPrefix: paint(`${glyphs.boxVertical} `, YELLOW, depth),
|
|
627
|
+
continuationPrefix: paint(`${glyphs.boxVertical} `, YELLOW, depth),
|
|
628
|
+
depth
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (line.trim() === "")
|
|
632
|
+
return [""];
|
|
633
|
+
return wrapTokens(parseInline(line), width, { depth });
|
|
634
|
+
}
|
|
635
|
+
function renderMarkdown(text, options) {
|
|
636
|
+
const state = newMarkdownState();
|
|
637
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
638
|
+
const rendered = text.split(`
|
|
639
|
+
`).flatMap((line) => renderMarkdownLine(line, state, options));
|
|
640
|
+
if (state.inFence)
|
|
641
|
+
rendered.push(paint(` ${glyphs.boxBottomLeft}${glyphs.boxHorizontal.repeat(4)}`, DIM, options.depth));
|
|
642
|
+
return rendered.join(`
|
|
643
|
+
`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export { UNICODE_GLYPHS, ASCII_GLYPHS, borderGlyphsFor, visibleWidth, newMarkdownState, renderMarkdownLine, renderMarkdown, BUILTIN_THEME_SOURCES, rgbTo256, rainbowText, parseThemeSource, NO_COLOR_PALETTE };
|