archymedes-cli 2.0.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/I18N.md +46 -0
- package/LICENSE +202 -0
- package/NOTICE +12 -0
- package/README.md +309 -0
- package/dist/archymedes-0jxzsh4z.mjs +9004 -0
- package/dist/archymedes-1g1y6q74.mjs +86 -0
- package/dist/archymedes-2tp22t9a.mjs +254 -0
- package/dist/archymedes-44c1mqas.mjs +171 -0
- package/dist/archymedes-brkqgrxv.mjs +212 -0
- package/dist/archymedes-dwvn4c51.mjs +826 -0
- package/dist/archymedes-h2x8ct7s.mjs +8853 -0
- package/dist/archymedes-jecyxv5c.mjs +249 -0
- package/dist/archymedes-k1bx08z9.mjs +589 -0
- package/dist/archymedes-main.mjs +26941 -0
- package/dist/archymedes-x67v3n58.mjs +67 -0
- package/dist/archymedes.mjs +18 -0
- package/dist/defender-knowledge/README.md +15 -0
- package/dist/defender-knowledge/knowledge-v1.jsonl +14 -0
- package/dist/editor-screen-51z7z11j.mjs +400 -0
- package/dist/factory-0z4he0ev.mjs +51462 -0
- package/dist/file-screen-eg28a3ky.mjs +320 -0
- package/dist/guide-screen-mfvwkdhb.mjs +565 -0
- package/dist/index-teq1cta4.mjs +52 -0
- package/dist/node-0fh63p8z.mjs +892 -0
- package/dist/sandbox-templates-8sd1psez.mjs +59 -0
- package/dist/workspace-screen-3zztn4by.mjs +240 -0
- package/package.json +62 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// packages/core/src/sandbox-policy.ts
|
|
2
|
+
var ALLOWED_SANDBOX_PROGRAMS = [
|
|
3
|
+
"bun",
|
|
4
|
+
"npm",
|
|
5
|
+
"git",
|
|
6
|
+
"node",
|
|
7
|
+
"python",
|
|
8
|
+
"python3",
|
|
9
|
+
"pytest",
|
|
10
|
+
"uv",
|
|
11
|
+
"cargo",
|
|
12
|
+
"go",
|
|
13
|
+
"rg",
|
|
14
|
+
"ls",
|
|
15
|
+
"pwd",
|
|
16
|
+
"find"
|
|
17
|
+
];
|
|
18
|
+
var BASE_TEMPLATE_PROGRAMS = ["npm", "git", "node", "python", "python3", "ls", "pwd", "find"];
|
|
19
|
+
function availableSandboxPrograms(templatePrograms) {
|
|
20
|
+
const present = new Set(templatePrograms ?? BASE_TEMPLATE_PROGRAMS);
|
|
21
|
+
return ALLOWED_SANDBOX_PROGRAMS.filter((program) => present.has(program));
|
|
22
|
+
}
|
|
23
|
+
var allowedPrograms = new Set(ALLOWED_SANDBOX_PROGRAMS);
|
|
24
|
+
var blockedArguments = new Set([
|
|
25
|
+
"-c",
|
|
26
|
+
"--config-env",
|
|
27
|
+
"--exec",
|
|
28
|
+
"--exec-path",
|
|
29
|
+
"--pre",
|
|
30
|
+
"--pre-glob",
|
|
31
|
+
"--upload-pack",
|
|
32
|
+
"-delete",
|
|
33
|
+
"-exec",
|
|
34
|
+
"-execdir",
|
|
35
|
+
"-ok",
|
|
36
|
+
"-okdir"
|
|
37
|
+
]);
|
|
38
|
+
var ALLOWED_GIT_SUBCOMMANDS = ["diff", "log", "rev-parse", "show", "status"];
|
|
39
|
+
var SCRIPT_RUNNER_SUBCOMMANDS = ["test", "run"];
|
|
40
|
+
var INLINE_EVAL_FLAGS = ["-e", "--eval", "-p", "--print", "-c"];
|
|
41
|
+
var INLINE_EVAL_PROGRAMS = ["node", "python", "python3"];
|
|
42
|
+
var allowedGitSubcommands = new Set(ALLOWED_GIT_SUBCOMMANDS);
|
|
43
|
+
function assertWorkspacePath(path, label) {
|
|
44
|
+
if (path !== "/workspace" && !path.startsWith("/workspace/") || path.split("/").includes("..")) {
|
|
45
|
+
throw new Error(`${label} must stay inside /workspace`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function validateSandboxCommand(command) {
|
|
49
|
+
if (!allowedPrograms.has(command.program))
|
|
50
|
+
throw new Error(`Program is not allowed in coding sandboxes: ${command.program}`);
|
|
51
|
+
if (!Number.isInteger(command.timeoutMs) || command.timeoutMs < 1 || command.timeoutMs > 15 * 60000) {
|
|
52
|
+
throw new Error("Command timeout must be between 1ms and 15 minutes");
|
|
53
|
+
}
|
|
54
|
+
if (command.args.length > 64 || command.args.some((argument) => argument.length > 4096)) {
|
|
55
|
+
throw new Error("Command arguments exceed the sandbox policy");
|
|
56
|
+
}
|
|
57
|
+
if (command.cwd)
|
|
58
|
+
assertWorkspacePath(command.cwd, "cwd");
|
|
59
|
+
if (command.args.some((argument) => blockedArguments.has(argument))) {
|
|
60
|
+
throw new Error("Command contains an argument blocked by the sandbox policy");
|
|
61
|
+
}
|
|
62
|
+
if (command.args.some((argument) => argument.startsWith("/") && argument !== "/workspace" && !argument.startsWith("/workspace/"))) {
|
|
63
|
+
throw new Error("Absolute command arguments must stay inside /workspace");
|
|
64
|
+
}
|
|
65
|
+
if (INLINE_EVAL_PROGRAMS.includes(command.program) && command.args.some((argument) => INLINE_EVAL_FLAGS.includes(argument))) {
|
|
66
|
+
throw new Error("Inline program evaluation is blocked");
|
|
67
|
+
}
|
|
68
|
+
if (command.program === "git") {
|
|
69
|
+
const subcommand = command.args.find((argument) => !argument.startsWith("-"));
|
|
70
|
+
if (!subcommand || !allowedGitSubcommands.has(subcommand))
|
|
71
|
+
throw new Error("Git command is not read-only");
|
|
72
|
+
}
|
|
73
|
+
if (command.program === "bun" && command.args[0] && !SCRIPT_RUNNER_SUBCOMMANDS.includes(command.args[0])) {
|
|
74
|
+
throw new Error("Bun command must run a declared script or test");
|
|
75
|
+
}
|
|
76
|
+
if (command.program === "npm" && command.args[0] && !SCRIPT_RUNNER_SUBCOMMANDS.includes(command.args[0])) {
|
|
77
|
+
throw new Error("npm command must run a declared script or test");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function validateWorkspaceFile(path, content) {
|
|
81
|
+
assertWorkspacePath(path, "File path");
|
|
82
|
+
if (new TextEncoder().encode(content).byteLength > 1e6)
|
|
83
|
+
throw new Error("Sandbox file exceeds the 1MB limit");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { ALLOWED_SANDBOX_PROGRAMS, availableSandboxPrograms, ALLOWED_GIT_SUBCOMMANDS, SCRIPT_RUNNER_SUBCOMMANDS, INLINE_EVAL_FLAGS, INLINE_EVAL_PROGRAMS, validateSandboxCommand, validateWorkspaceFile };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASCII_GLYPHS,
|
|
3
|
+
UNICODE_GLYPHS,
|
|
4
|
+
padToWidth,
|
|
5
|
+
progressBar,
|
|
6
|
+
sliceToWidth,
|
|
7
|
+
visibleWidth
|
|
8
|
+
} from "./archymedes-dwvn4c51.mjs";
|
|
9
|
+
|
|
10
|
+
// packages/archymedes-cli/src/canvas.ts
|
|
11
|
+
var BRAILLE_DOTS = [
|
|
12
|
+
[1, 2, 4, 64],
|
|
13
|
+
[8, 16, 32, 128]
|
|
14
|
+
];
|
|
15
|
+
var BRAILLE_BLANK = 10240;
|
|
16
|
+
|
|
17
|
+
class BrailleCanvas {
|
|
18
|
+
columns;
|
|
19
|
+
rows;
|
|
20
|
+
cellColumns;
|
|
21
|
+
cellRows;
|
|
22
|
+
bits;
|
|
23
|
+
ascii;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.ascii = (options.glyphs ?? UNICODE_GLYPHS) === ASCII_GLYPHS;
|
|
26
|
+
this.columns = Math.max(1, Math.floor(options.columns));
|
|
27
|
+
this.rows = Math.max(1, Math.floor(options.rows));
|
|
28
|
+
this.cellColumns = this.ascii ? 1 : 2;
|
|
29
|
+
this.cellRows = this.ascii ? 1 : 4;
|
|
30
|
+
this.bits = Array.from({ length: this.rows }, () => new Array(this.columns).fill(0));
|
|
31
|
+
}
|
|
32
|
+
get width() {
|
|
33
|
+
return this.columns * this.cellColumns;
|
|
34
|
+
}
|
|
35
|
+
get height() {
|
|
36
|
+
return this.rows * this.cellRows;
|
|
37
|
+
}
|
|
38
|
+
set(x, y) {
|
|
39
|
+
const dotX = Math.round(x);
|
|
40
|
+
const dotY = Math.round(y);
|
|
41
|
+
if (!Number.isFinite(dotX) || !Number.isFinite(dotY))
|
|
42
|
+
return;
|
|
43
|
+
if (dotX < 0 || dotY < 0 || dotX >= this.width || dotY >= this.height)
|
|
44
|
+
return;
|
|
45
|
+
const cellX = Math.floor(dotX / this.cellColumns);
|
|
46
|
+
const cellY = Math.floor(dotY / this.cellRows);
|
|
47
|
+
if (this.ascii)
|
|
48
|
+
this.bits[cellY][cellX] = 1;
|
|
49
|
+
else
|
|
50
|
+
this.bits[cellY][cellX] |= BRAILLE_DOTS[dotX % this.cellColumns][dotY % this.cellRows];
|
|
51
|
+
}
|
|
52
|
+
line(x0, y0, x1, y1) {
|
|
53
|
+
let x = Math.round(x0);
|
|
54
|
+
let y = Math.round(y0);
|
|
55
|
+
const endX = Math.round(x1);
|
|
56
|
+
const endY = Math.round(y1);
|
|
57
|
+
if (![x, y, endX, endY].every(Number.isFinite))
|
|
58
|
+
return;
|
|
59
|
+
const deltaX = Math.abs(endX - x);
|
|
60
|
+
const deltaY = -Math.abs(endY - y);
|
|
61
|
+
const stepX = x < endX ? 1 : -1;
|
|
62
|
+
const stepY = y < endY ? 1 : -1;
|
|
63
|
+
let error = deltaX + deltaY;
|
|
64
|
+
for (let guard = 0;guard <= this.width + this.height; guard += 1) {
|
|
65
|
+
this.set(x, y);
|
|
66
|
+
if (x === endX && y === endY)
|
|
67
|
+
return;
|
|
68
|
+
const doubled = 2 * error;
|
|
69
|
+
if (doubled >= deltaY) {
|
|
70
|
+
error += deltaY;
|
|
71
|
+
x += stepX;
|
|
72
|
+
}
|
|
73
|
+
if (doubled <= deltaX) {
|
|
74
|
+
error += deltaX;
|
|
75
|
+
y += stepY;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
render() {
|
|
80
|
+
return this.bits.map((row) => row.map((bits) => this.ascii ? bits ? "*" : " " : String.fromCharCode(BRAILLE_BLANK + bits)).join(""));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function dotRowFor(value, low, span, dotRows) {
|
|
84
|
+
const normalized = (value - low) / (span === 0 ? 1 : span);
|
|
85
|
+
return Math.min(dotRows - 1, Math.max(0, Math.round((1 - normalized) * (dotRows - 1))));
|
|
86
|
+
}
|
|
87
|
+
function seriesBounds(values) {
|
|
88
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
89
|
+
if (finite.length === 0)
|
|
90
|
+
return { low: 0, high: 0, span: 1 };
|
|
91
|
+
const low = Math.min(...finite);
|
|
92
|
+
const high = Math.max(...finite);
|
|
93
|
+
return { low, high, span: high - low === 0 ? 1 : high - low };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// packages/archymedes-cli/src/charts.ts
|
|
97
|
+
function barChart(data, options) {
|
|
98
|
+
if (data.length === 0)
|
|
99
|
+
return [];
|
|
100
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
101
|
+
const format = options.format ?? ((value) => `${Math.round(value)}`);
|
|
102
|
+
const width = Math.max(20, Math.floor(options.width));
|
|
103
|
+
const labels = data.map((datum) => datum.label);
|
|
104
|
+
const values = data.map((datum) => format(datum.value));
|
|
105
|
+
const labelWidth = Math.min(Math.max(...labels.map(visibleWidth)), Math.floor(width * 0.35));
|
|
106
|
+
const valueWidth = Math.min(Math.max(...values.map(visibleWidth)), Math.floor(width * 0.2));
|
|
107
|
+
const barWidth = Math.max(1, width - labelWidth - valueWidth - 2);
|
|
108
|
+
const ceiling = options.max ?? Math.max(...data.map((datum) => datum.value));
|
|
109
|
+
return data.map((datum, index) => {
|
|
110
|
+
const fraction = ceiling > 0 ? datum.value / ceiling : 0;
|
|
111
|
+
const bar = progressBar(fraction, barWidth, {
|
|
112
|
+
depth: options.depth,
|
|
113
|
+
glyphs,
|
|
114
|
+
...options.from === undefined ? {} : { from: options.from },
|
|
115
|
+
...options.to === undefined ? {} : { to: options.to }
|
|
116
|
+
});
|
|
117
|
+
return `${padToWidth(sliceToWidth(labels[index], labelWidth), labelWidth)} ${bar} ${padToWidth(values[index], valueWidth, "right")}`;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function lineChart(values, options) {
|
|
121
|
+
return plotSeries([{ values }], options);
|
|
122
|
+
}
|
|
123
|
+
function plotSeries(series, options) {
|
|
124
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
125
|
+
const height = Math.max(1, Math.floor(options.height));
|
|
126
|
+
const format = options.format ?? ((value) => `${Math.round(value)}`);
|
|
127
|
+
const all = series.flatMap((entry) => entry.values.filter((value) => Number.isFinite(value)));
|
|
128
|
+
if (all.length === 0)
|
|
129
|
+
return Array.from({ length: height }, () => "");
|
|
130
|
+
const { low, high, span } = seriesBounds(all);
|
|
131
|
+
const gutter = options.bare ? 0 : Math.max(visibleWidth(format(high)), visibleWidth(format(low))) + 1;
|
|
132
|
+
const plotWidth = Math.max(1, Math.floor(options.width) - gutter);
|
|
133
|
+
const canvas = new BrailleCanvas({ columns: plotWidth, rows: height, glyphs });
|
|
134
|
+
for (const entry of series) {
|
|
135
|
+
const finite = entry.values.filter((value) => Number.isFinite(value));
|
|
136
|
+
if (finite.length === 0)
|
|
137
|
+
continue;
|
|
138
|
+
let previous;
|
|
139
|
+
for (let x = 0;x < canvas.width; x += 1) {
|
|
140
|
+
const position = canvas.width === 1 ? 0 : x / (canvas.width - 1) * (finite.length - 1);
|
|
141
|
+
const left = Math.floor(position);
|
|
142
|
+
const right = Math.min(finite.length - 1, left + 1);
|
|
143
|
+
const value = finite[left] + (finite[right] - finite[left]) * (position - left);
|
|
144
|
+
const y = dotRowFor(value, low, span, canvas.height);
|
|
145
|
+
if (previous)
|
|
146
|
+
canvas.line(previous.x, previous.y, x, y);
|
|
147
|
+
else
|
|
148
|
+
canvas.set(x, y);
|
|
149
|
+
previous = { x, y };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return canvas.render().map((drawn, index) => {
|
|
153
|
+
if (options.bare)
|
|
154
|
+
return drawn;
|
|
155
|
+
const label = index === 0 ? format(high) : index === height - 1 ? format(low) : "";
|
|
156
|
+
return `${padToWidth(label, gutter - 1)} ${drawn}`;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function scatterChart(points, options) {
|
|
160
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
161
|
+
const height = Math.max(1, Math.floor(options.height));
|
|
162
|
+
const format = options.format ?? ((value) => `${Math.round(value)}`);
|
|
163
|
+
const finite = points.filter((point) => Number.isFinite(point.x) && Number.isFinite(point.y));
|
|
164
|
+
if (finite.length === 0)
|
|
165
|
+
return Array.from({ length: height }, () => "");
|
|
166
|
+
const vertical = seriesBounds(finite.map((point) => point.y));
|
|
167
|
+
const horizontal = seriesBounds(finite.map((point) => point.x));
|
|
168
|
+
const gutter = options.bare ? 0 : Math.max(visibleWidth(format(vertical.high)), visibleWidth(format(vertical.low))) + 1;
|
|
169
|
+
const plotWidth = Math.max(1, Math.floor(options.width) - gutter);
|
|
170
|
+
const canvas = new BrailleCanvas({ columns: plotWidth, rows: height, glyphs });
|
|
171
|
+
for (const point of finite) {
|
|
172
|
+
const x = Math.round((point.x - horizontal.low) / horizontal.span * (canvas.width - 1));
|
|
173
|
+
canvas.set(x, dotRowFor(point.y, vertical.low, vertical.span, canvas.height));
|
|
174
|
+
}
|
|
175
|
+
return canvas.render().map((drawn, index) => {
|
|
176
|
+
if (options.bare)
|
|
177
|
+
return drawn;
|
|
178
|
+
const label = index === 0 ? format(vertical.high) : index === height - 1 ? format(vertical.low) : "";
|
|
179
|
+
return `${padToWidth(label, gutter - 1)} ${drawn}`;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function waveLine(values, options) {
|
|
183
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
184
|
+
const width = Math.max(1, Math.floor(options.width));
|
|
185
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
186
|
+
if (finite.length === 0)
|
|
187
|
+
return "";
|
|
188
|
+
const { low, span } = seriesBounds(finite);
|
|
189
|
+
const ascii = glyphs === ASCII_GLYPHS;
|
|
190
|
+
const level = ascii ? ["_", "-", "^"] : ["▁", "─", "▔"];
|
|
191
|
+
const rising = ascii ? "/" : "╱";
|
|
192
|
+
const falling = ascii ? "\\" : "╲";
|
|
193
|
+
const sampled = Array.from({ length: width }, (_, column) => {
|
|
194
|
+
const position = width === 1 ? 0 : column / (width - 1) * (finite.length - 1);
|
|
195
|
+
const left = Math.floor(position);
|
|
196
|
+
const right = Math.min(finite.length - 1, left + 1);
|
|
197
|
+
return finite[left] + (finite[right] - finite[left]) * (position - left);
|
|
198
|
+
});
|
|
199
|
+
const bandOf = (value) => Math.min(2, Math.max(0, Math.round((value - low) / span * 2)));
|
|
200
|
+
return sampled.map((value, column) => {
|
|
201
|
+
const band = bandOf(value);
|
|
202
|
+
if (column === 0)
|
|
203
|
+
return level[band];
|
|
204
|
+
const previous = bandOf(sampled[column - 1]);
|
|
205
|
+
if (band > previous)
|
|
206
|
+
return rising;
|
|
207
|
+
if (band < previous)
|
|
208
|
+
return falling;
|
|
209
|
+
return level[band];
|
|
210
|
+
}).join("");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
class StreamSeries {
|
|
214
|
+
capacity;
|
|
215
|
+
samples = [];
|
|
216
|
+
constructor(capacity) {
|
|
217
|
+
this.capacity = capacity;
|
|
218
|
+
if (!Number.isInteger(capacity) || capacity < 1)
|
|
219
|
+
throw new Error("A stream needs room for at least one sample");
|
|
220
|
+
}
|
|
221
|
+
push(value) {
|
|
222
|
+
if (!Number.isFinite(value))
|
|
223
|
+
return;
|
|
224
|
+
this.samples.push(value);
|
|
225
|
+
if (this.samples.length > this.capacity)
|
|
226
|
+
this.samples.splice(0, this.samples.length - this.capacity);
|
|
227
|
+
}
|
|
228
|
+
values() {
|
|
229
|
+
return [...this.samples];
|
|
230
|
+
}
|
|
231
|
+
get length() {
|
|
232
|
+
return this.samples.length;
|
|
233
|
+
}
|
|
234
|
+
clear() {
|
|
235
|
+
this.samples.length = 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function heatStrip(data, options) {
|
|
239
|
+
if (data.length === 0)
|
|
240
|
+
return [];
|
|
241
|
+
const glyphs = options.glyphs ?? UNICODE_GLYPHS;
|
|
242
|
+
const levels = glyphs.sparkLevels;
|
|
243
|
+
const width = Math.max(10, Math.floor(options.width));
|
|
244
|
+
const labelWidth = Math.min(Math.max(...data.map((datum) => visibleWidth(datum.label))), Math.floor(width * 0.4));
|
|
245
|
+
const cells = Math.max(1, width - labelWidth - 1);
|
|
246
|
+
const ceiling = options.max ?? Math.max(...data.map((datum) => datum.value));
|
|
247
|
+
return data.map((datum) => {
|
|
248
|
+
const fraction = ceiling > 0 ? Math.max(0, Math.min(1, datum.value / ceiling)) : 0;
|
|
249
|
+
const level = levels[Math.min(levels.length - 1, Math.floor(fraction * levels.length))];
|
|
250
|
+
return `${padToWidth(sliceToWidth(datum.label, labelWidth), labelWidth)} ${level.repeat(cells)}`;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { BrailleCanvas, dotRowFor, seriesBounds, barChart, lineChart, plotSeries, scatterChart, waveLine, StreamSeries, heatStrip };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UNICODE_GLYPHS,
|
|
3
|
+
borderGlyphsFor,
|
|
4
|
+
visibleWidth
|
|
5
|
+
} from "./archymedes-dwvn4c51.mjs";
|
|
6
|
+
|
|
7
|
+
// packages/archymedes-cli/src/ansi.ts
|
|
8
|
+
var RESET = "\x1B[0m";
|
|
9
|
+
var BOLD = "\x1B[1m";
|
|
10
|
+
var DIM = "\x1B[2m";
|
|
11
|
+
var REVERSE = "\x1B[7m";
|
|
12
|
+
var RED = "\x1B[31m";
|
|
13
|
+
var GREEN = "\x1B[32m";
|
|
14
|
+
var YELLOW = "\x1B[33m";
|
|
15
|
+
var MAGENTA = "\x1B[35m";
|
|
16
|
+
var CYAN = "\x1B[36m";
|
|
17
|
+
function paint(text, code, depth) {
|
|
18
|
+
return depth === "none" || code === "" ? text : `${code}${text}${RESET}`;
|
|
19
|
+
}
|
|
20
|
+
function paintAll(text, codes, depth) {
|
|
21
|
+
return depth === "none" || codes.length === 0 ? text : `${codes.join("")}${text}${RESET}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// packages/archymedes-cli/src/sections.ts
|
|
25
|
+
var FALLBACK_TONE_CODE = {
|
|
26
|
+
neutral: DIM,
|
|
27
|
+
good: GREEN,
|
|
28
|
+
bad: RED,
|
|
29
|
+
warn: YELLOW,
|
|
30
|
+
accent: CYAN
|
|
31
|
+
};
|
|
32
|
+
function toneCode(tone, style) {
|
|
33
|
+
if (style.depth === "none")
|
|
34
|
+
return "";
|
|
35
|
+
const palette = style.palette;
|
|
36
|
+
if (!palette)
|
|
37
|
+
return FALLBACK_TONE_CODE[tone];
|
|
38
|
+
switch (tone) {
|
|
39
|
+
case "neutral":
|
|
40
|
+
return DIM;
|
|
41
|
+
case "good":
|
|
42
|
+
return palette.success || FALLBACK_TONE_CODE.good;
|
|
43
|
+
case "bad":
|
|
44
|
+
return palette.error || FALLBACK_TONE_CODE.bad;
|
|
45
|
+
case "warn":
|
|
46
|
+
return palette.warning || FALLBACK_TONE_CODE.warn;
|
|
47
|
+
case "accent":
|
|
48
|
+
return palette.primary || FALLBACK_TONE_CODE.accent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var GUTTER = " ";
|
|
52
|
+
function setOf(style) {
|
|
53
|
+
return style.glyphs ?? UNICODE_GLYPHS;
|
|
54
|
+
}
|
|
55
|
+
function rule(style, options = {}) {
|
|
56
|
+
const glyphs = setOf(style);
|
|
57
|
+
const tone = options.tone ?? "neutral";
|
|
58
|
+
const width = Math.max(4, style.width - GUTTER.length);
|
|
59
|
+
const trailing = options.trailing ? clip(` ${options.trailing}`, Math.max(4, Math.floor(width / 2)), glyphs) : "";
|
|
60
|
+
const trailingWidth = visibleWidth(trailing);
|
|
61
|
+
if (!options.label) {
|
|
62
|
+
const bare = Math.max(0, width - trailingWidth);
|
|
63
|
+
return `${GUTTER}${paint(glyphs.boxHorizontal.repeat(bare), DIM, style.depth)}${trailing ? paint(trailing, DIM, style.depth) : ""}`;
|
|
64
|
+
}
|
|
65
|
+
const lead = glyphs.boxHorizontal.repeat(2);
|
|
66
|
+
const label = clip(` ${options.label} `, Math.max(0, width - visibleWidth(lead) - trailingWidth), glyphs);
|
|
67
|
+
const used = visibleWidth(lead) + visibleWidth(label) + trailingWidth;
|
|
68
|
+
const fill = Math.max(0, width - used);
|
|
69
|
+
return [
|
|
70
|
+
GUTTER,
|
|
71
|
+
paint(lead, DIM, style.depth),
|
|
72
|
+
label ? paintAll(label, [toneCode(tone, style), BOLD], style.depth) : "",
|
|
73
|
+
paint(glyphs.boxHorizontal.repeat(fill), DIM, style.depth),
|
|
74
|
+
trailing ? paint(trailing, DIM, style.depth) : ""
|
|
75
|
+
].join("");
|
|
76
|
+
}
|
|
77
|
+
function heading(text, level, style, tone = "accent") {
|
|
78
|
+
if (level === 1) {
|
|
79
|
+
return [
|
|
80
|
+
`${GUTTER}${paintAll(text, [toneCode(tone, style), BOLD], style.depth)}`,
|
|
81
|
+
rule(style, { tone })
|
|
82
|
+
].join(`
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
if (level === 2)
|
|
86
|
+
return `${GUTTER}${paintAll(text, [toneCode(tone, style), BOLD], style.depth)}`;
|
|
87
|
+
return `${GUTTER}${paint(text, DIM, style.depth)}`;
|
|
88
|
+
}
|
|
89
|
+
function note(text, style, tone = "neutral") {
|
|
90
|
+
return `${GUTTER}${GUTTER}${paint(text, toneCode(tone, style), style.depth)}`;
|
|
91
|
+
}
|
|
92
|
+
function panel(lines, style, options = {}) {
|
|
93
|
+
const glyphs = setOf(style);
|
|
94
|
+
const depth = style.depth;
|
|
95
|
+
const tone = options.tone ?? "neutral";
|
|
96
|
+
const available = Math.max(8, style.width - GUTTER.length - 4);
|
|
97
|
+
const border = borderGlyphsFor(style.palette?.borderStyle ?? "round", glyphs);
|
|
98
|
+
if (options.gutterOnly) {
|
|
99
|
+
const edge2 = paint(border.vertical, toneCode(tone, style), depth);
|
|
100
|
+
const head = options.title ? [`${GUTTER}${paintAll(options.title, [toneCode(tone, style), BOLD], depth)}${options.badge ? paint(` ${options.badge}`, DIM, depth) : ""}`] : [];
|
|
101
|
+
return [...head, ...lines.map((line) => `${GUTTER}${edge2} ${clip(line, available, glyphs)}`)].join(`
|
|
102
|
+
`);
|
|
103
|
+
}
|
|
104
|
+
const horizontal = border.horizontal;
|
|
105
|
+
const titleCell = options.title ? ` ${options.title} ` : "";
|
|
106
|
+
const badgeCell = options.badge ? ` ${options.badge} ` : "";
|
|
107
|
+
const contentWidth = Math.min(Math.max(visibleWidth(titleCell) + visibleWidth(badgeCell) - 1, ...lines.map((line) => visibleWidth(line)), 8), available);
|
|
108
|
+
const titleShown = titleCell ? clip(titleCell, Math.max(0, contentWidth + 1 - visibleWidth(badgeCell)), glyphs) : "";
|
|
109
|
+
const fill = Math.max(0, contentWidth + 1 - visibleWidth(titleShown) - visibleWidth(badgeCell));
|
|
110
|
+
const top = [
|
|
111
|
+
GUTTER,
|
|
112
|
+
paint(border.topLeft + horizontal, DIM, depth),
|
|
113
|
+
titleShown ? paintAll(titleShown, [toneCode(tone, style), BOLD], depth) : "",
|
|
114
|
+
paint(horizontal.repeat(fill), DIM, depth),
|
|
115
|
+
badgeCell ? paint(badgeCell, DIM, depth) : "",
|
|
116
|
+
paint(border.topRight, DIM, depth)
|
|
117
|
+
].join("");
|
|
118
|
+
const bottom = `${GUTTER}${paint(border.bottomLeft + horizontal.repeat(contentWidth + 2) + border.bottomRight, DIM, depth)}`;
|
|
119
|
+
const edge = paint(border.vertical, DIM, depth);
|
|
120
|
+
const body = lines.map((line) => {
|
|
121
|
+
const clipped = clip(line, contentWidth, glyphs);
|
|
122
|
+
return `${GUTTER}${edge} ${clipped}${" ".repeat(Math.max(0, contentWidth - visibleWidth(clipped)))} ${edge}`;
|
|
123
|
+
});
|
|
124
|
+
return [top, ...body, bottom].join(`
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
function clip(text, width, glyphs = UNICODE_GLYPHS) {
|
|
128
|
+
if (visibleWidth(text) <= width)
|
|
129
|
+
return text;
|
|
130
|
+
const marker = glyphs.ellipsis;
|
|
131
|
+
const budget = Math.max(0, width - visibleWidth(marker));
|
|
132
|
+
let taken = "";
|
|
133
|
+
let used = 0;
|
|
134
|
+
const parts = text.split(/(\x1b\[[0-9;]*m)/);
|
|
135
|
+
let styled = false;
|
|
136
|
+
for (const part of parts) {
|
|
137
|
+
if (part.startsWith("\x1B[")) {
|
|
138
|
+
taken += part;
|
|
139
|
+
styled = styled || part !== "\x1B[0m";
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
for (const character of part) {
|
|
143
|
+
const next = visibleWidth(character);
|
|
144
|
+
if (used + next > budget)
|
|
145
|
+
return `${taken}${marker}${styled ? "\x1B[0m" : ""}`;
|
|
146
|
+
taken += character;
|
|
147
|
+
used += next;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return `${taken}${marker}${styled ? "\x1B[0m" : ""}`;
|
|
151
|
+
}
|
|
152
|
+
function outcomeMark(outcome, style) {
|
|
153
|
+
const glyphs = setOf(style);
|
|
154
|
+
switch (outcome) {
|
|
155
|
+
case "pass":
|
|
156
|
+
return paint(glyphs.check, GREEN, style.depth);
|
|
157
|
+
case "fail":
|
|
158
|
+
return paint(glyphs.cross, RED, style.depth);
|
|
159
|
+
case "skip":
|
|
160
|
+
return paint(glyphs.circleEmpty, DIM, style.depth);
|
|
161
|
+
default:
|
|
162
|
+
return paint(glyphs.pending, CYAN, style.depth);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function keyValues(rows, style) {
|
|
166
|
+
const width = Math.max(0, ...rows.map(([key]) => visibleWidth(key)));
|
|
167
|
+
return rows.map(([key, value]) => `${GUTTER}${paint(key.padEnd(width + 2), DIM, style.depth)}${value}`).join(`
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { BOLD, DIM, REVERSE, RED, GREEN, YELLOW, MAGENTA, CYAN, paint, paintAll, GUTTER, rule, heading, note, panel, clip, outcomeMark, keyValues };
|