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,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
windowStart
|
|
3
|
+
} from "./archymedes-brkqgrxv.mjs";
|
|
4
|
+
import {
|
|
5
|
+
UNICODE_GLYPHS,
|
|
6
|
+
joinHorizontal,
|
|
7
|
+
padToWidth,
|
|
8
|
+
paginator
|
|
9
|
+
} from "./archymedes-dwvn4c51.mjs";
|
|
10
|
+
import {
|
|
11
|
+
NO_COLOR_PALETTE
|
|
12
|
+
} from "./archymedes-jecyxv5c.mjs";
|
|
13
|
+
import {
|
|
14
|
+
__require
|
|
15
|
+
} from "./archymedes-x67v3n58.mjs";
|
|
16
|
+
|
|
17
|
+
// packages/archymedes-cli/src/file-screen.tsx
|
|
18
|
+
import { useAsync, useInput, useState } from "@termuijs/jsx";
|
|
19
|
+
import { Box, Text } from "@termuijs/widgets";
|
|
20
|
+
|
|
21
|
+
// packages/core/src/cli/file-tree.ts
|
|
22
|
+
function buildFileTree(paths) {
|
|
23
|
+
const root = { name: "", path: "", kind: "dir", children: [] };
|
|
24
|
+
for (const raw of paths) {
|
|
25
|
+
const parts = raw.split("/").filter((part) => part.length > 0);
|
|
26
|
+
let node = root;
|
|
27
|
+
let built = "";
|
|
28
|
+
for (const [index, part] of parts.entries()) {
|
|
29
|
+
built = built ? `${built}/${part}` : part;
|
|
30
|
+
const isFile = index === parts.length - 1;
|
|
31
|
+
let child = node.children.find((candidate) => candidate.name === part);
|
|
32
|
+
if (!child) {
|
|
33
|
+
child = { name: part, path: built, kind: isFile ? "file" : "dir", children: [] };
|
|
34
|
+
node.children.push(child);
|
|
35
|
+
}
|
|
36
|
+
node = child;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
sortTree(root);
|
|
40
|
+
return root.children;
|
|
41
|
+
}
|
|
42
|
+
function sortTree(node) {
|
|
43
|
+
node.children.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "dir" ? -1 : 1);
|
|
44
|
+
for (const child of node.children)
|
|
45
|
+
sortTree(child);
|
|
46
|
+
}
|
|
47
|
+
function directorySummary(node) {
|
|
48
|
+
let files = 0;
|
|
49
|
+
let dirs = 0;
|
|
50
|
+
for (const child of node.children) {
|
|
51
|
+
if (child.kind === "file")
|
|
52
|
+
files += 1;
|
|
53
|
+
else {
|
|
54
|
+
dirs += 1;
|
|
55
|
+
const nested = directorySummary(child);
|
|
56
|
+
files += nested.files;
|
|
57
|
+
dirs += nested.dirs;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { files, dirs };
|
|
61
|
+
}
|
|
62
|
+
// packages/archymedes-cli/src/file-browser.ts
|
|
63
|
+
function flattenTree(tree, expanded, depth = 0, trunks = []) {
|
|
64
|
+
const rows = [];
|
|
65
|
+
for (const [index, node] of tree.entries()) {
|
|
66
|
+
const isLast = index === tree.length - 1;
|
|
67
|
+
rows.push({ node, depth, isLast, trunks });
|
|
68
|
+
if (node.kind === "dir" && expanded.has(node.path)) {
|
|
69
|
+
rows.push(...flattenTree(node.children, expanded, depth + 1, [...trunks, !isLast]));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
74
|
+
function filterTree(tree, query) {
|
|
75
|
+
const needle = query.toLowerCase();
|
|
76
|
+
const rows = [];
|
|
77
|
+
const walk = (nodes) => {
|
|
78
|
+
for (const node of nodes) {
|
|
79
|
+
if (node.kind === "file") {
|
|
80
|
+
if (node.path.toLowerCase().includes(needle))
|
|
81
|
+
rows.push({ node, depth: 0, isLast: false, trunks: [] });
|
|
82
|
+
} else
|
|
83
|
+
walk(node.children);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk(tree);
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
function initialFileBrowserState(paths, columns, rows, palette = NO_COLOR_PALETTE) {
|
|
90
|
+
return { tree: buildFileTree(paths), expanded: new Set, selected: 0, query: "", searching: false, columns, rows, palette };
|
|
91
|
+
}
|
|
92
|
+
function visibleRows(state) {
|
|
93
|
+
return state.query ? filterTree(state.tree, state.query) : flattenTree(state.tree, state.expanded);
|
|
94
|
+
}
|
|
95
|
+
function currentRow(state) {
|
|
96
|
+
const rows = visibleRows(state);
|
|
97
|
+
if (rows.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
return rows[Math.max(0, Math.min(state.selected, rows.length - 1))];
|
|
100
|
+
}
|
|
101
|
+
function keyToFileAction(key, character, searching) {
|
|
102
|
+
const name = key.name ?? "";
|
|
103
|
+
if (key.ctrl && name === "c")
|
|
104
|
+
return { kind: "exit" };
|
|
105
|
+
const isEnter = name === "return" || name === "enter" || name === "\r" || name === `
|
|
106
|
+
`;
|
|
107
|
+
const isEscape = name === "escape" || name === "\x1B";
|
|
108
|
+
if (searching) {
|
|
109
|
+
if (isEscape)
|
|
110
|
+
return { kind: "commit" };
|
|
111
|
+
if (isEnter)
|
|
112
|
+
return { kind: "pick" };
|
|
113
|
+
if (name === "backspace")
|
|
114
|
+
return { kind: "backspace" };
|
|
115
|
+
if (name === "up")
|
|
116
|
+
return { kind: "move", step: -1 };
|
|
117
|
+
if (name === "down")
|
|
118
|
+
return { kind: "move", step: 1 };
|
|
119
|
+
if (character && character.length === 1 && character >= " ")
|
|
120
|
+
return { kind: "type", character };
|
|
121
|
+
return { kind: "none" };
|
|
122
|
+
}
|
|
123
|
+
if (isEscape || name === "q")
|
|
124
|
+
return { kind: "exit" };
|
|
125
|
+
if (character === "/")
|
|
126
|
+
return { kind: "search" };
|
|
127
|
+
if (isEnter)
|
|
128
|
+
return { kind: "pick" };
|
|
129
|
+
if (character === "e")
|
|
130
|
+
return { kind: "edit" };
|
|
131
|
+
if (name === "up" || name === "k")
|
|
132
|
+
return { kind: "move", step: -1 };
|
|
133
|
+
if (name === "down" || name === "j")
|
|
134
|
+
return { kind: "move", step: 1 };
|
|
135
|
+
if (name === "right" || name === "l")
|
|
136
|
+
return { kind: "expand" };
|
|
137
|
+
if (name === "left" || name === "h")
|
|
138
|
+
return { kind: "collapse" };
|
|
139
|
+
return { kind: "none" };
|
|
140
|
+
}
|
|
141
|
+
function applyFileAction(state, action) {
|
|
142
|
+
const rows = visibleRows(state);
|
|
143
|
+
switch (action.kind) {
|
|
144
|
+
case "move": {
|
|
145
|
+
if (rows.length === 0)
|
|
146
|
+
return state;
|
|
147
|
+
const next = ((state.selected + action.step) % rows.length + rows.length) % rows.length;
|
|
148
|
+
return { ...state, selected: next };
|
|
149
|
+
}
|
|
150
|
+
case "expand": {
|
|
151
|
+
const row = currentRow(state);
|
|
152
|
+
if (!row || row.node.kind !== "dir" || state.expanded.has(row.node.path))
|
|
153
|
+
return state;
|
|
154
|
+
return { ...state, expanded: new Set([...state.expanded, row.node.path]) };
|
|
155
|
+
}
|
|
156
|
+
case "collapse": {
|
|
157
|
+
const row = currentRow(state);
|
|
158
|
+
if (!row || row.node.kind !== "dir" || !state.expanded.has(row.node.path))
|
|
159
|
+
return state;
|
|
160
|
+
const expanded = new Set(state.expanded);
|
|
161
|
+
expanded.delete(row.node.path);
|
|
162
|
+
return { ...state, expanded };
|
|
163
|
+
}
|
|
164
|
+
case "search":
|
|
165
|
+
return { ...state, searching: true };
|
|
166
|
+
case "commit":
|
|
167
|
+
return { ...state, searching: false };
|
|
168
|
+
case "backspace":
|
|
169
|
+
return { ...state, query: state.query.slice(0, -1), selected: 0 };
|
|
170
|
+
case "type":
|
|
171
|
+
return { ...state, query: state.query + action.character, selected: 0 };
|
|
172
|
+
default:
|
|
173
|
+
return state;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function toggleDirectory(state) {
|
|
177
|
+
const row = currentRow(state);
|
|
178
|
+
if (!row || row.node.kind !== "dir")
|
|
179
|
+
return state;
|
|
180
|
+
return applyFileAction(state, state.expanded.has(row.node.path) ? { kind: "collapse" } : { kind: "expand" });
|
|
181
|
+
}
|
|
182
|
+
function treeWidth(columns) {
|
|
183
|
+
const width = Math.max(1, Math.floor(columns));
|
|
184
|
+
return width < 40 ? width : Math.max(20, Math.floor(width * 0.4));
|
|
185
|
+
}
|
|
186
|
+
function previewWidth(columns) {
|
|
187
|
+
const width = Math.max(1, Math.floor(columns));
|
|
188
|
+
return width < 40 ? 0 : Math.max(1, width - treeWidth(width) - 3);
|
|
189
|
+
}
|
|
190
|
+
function bodyHeight(rows) {
|
|
191
|
+
return Math.max(0, Math.floor(rows) - 2);
|
|
192
|
+
}
|
|
193
|
+
function treeLabel(row, expanded, glyphs = UNICODE_GLYPHS) {
|
|
194
|
+
const indent = row.trunks.map((carries) => carries ? glyphs.treeTrunk : " ").join("");
|
|
195
|
+
const connector = row.isLast ? glyphs.treeLast : glyphs.treeBranch;
|
|
196
|
+
if (row.node.kind === "file")
|
|
197
|
+
return `${indent}${connector} ${row.node.name}`;
|
|
198
|
+
const triangle = expanded.has(row.node.path) ? glyphs.expanded : glyphs.collapsed;
|
|
199
|
+
return `${indent}${connector}${triangle} ${row.node.name}/`;
|
|
200
|
+
}
|
|
201
|
+
function composeFileFrame(state, preview, glyphs = UNICODE_GLYPHS) {
|
|
202
|
+
const columns = Math.max(1, Math.floor(state.columns));
|
|
203
|
+
const rowCount = Math.max(1, Math.floor(state.rows));
|
|
204
|
+
const height = bodyHeight(rowCount);
|
|
205
|
+
const rows = visibleRows(state);
|
|
206
|
+
const selectedIndex = rows.length === 0 ? -1 : Math.max(0, Math.min(state.selected, rows.length - 1));
|
|
207
|
+
const showPreview = previewWidth(columns) > 0;
|
|
208
|
+
const tree = showPreview ? treeWidth(columns) : columns;
|
|
209
|
+
const body = previewWidth(columns);
|
|
210
|
+
const theme = state.palette.tokens;
|
|
211
|
+
const out = [];
|
|
212
|
+
const current = selectedIndex === -1 ? undefined : rows[selectedIndex];
|
|
213
|
+
out.push({ text: padToWidth(` archymedes files · ${current ? current.node.path : "no matches"}`, columns), bold: true, color: theme.primary });
|
|
214
|
+
if (rowCount === 1)
|
|
215
|
+
return out;
|
|
216
|
+
const start = windowStart(selectedIndex < 0 ? 0 : selectedIndex, rows.length, height);
|
|
217
|
+
const previewLines = previewToLines(preview, body);
|
|
218
|
+
for (let offset = 0;offset < height; offset += 1) {
|
|
219
|
+
const row = rows[start + offset];
|
|
220
|
+
const chosen = row !== undefined && start + offset === selectedIndex;
|
|
221
|
+
const label = row ? treeLabel(row, state.expanded, glyphs) : "";
|
|
222
|
+
const treeCell = padToWidth(chosen ? `${glyphs.prompt} ${label}` : ` ${label}`, tree);
|
|
223
|
+
out.push({
|
|
224
|
+
text: showPreview ? joinHorizontal(treeCell, previewLines[offset] ?? "", { leftWidth: tree, rightWidth: body, separator: ` ${glyphs.boxVertical} ` }) : treeCell,
|
|
225
|
+
bold: chosen,
|
|
226
|
+
dim: row?.node.kind === "dir" && !chosen,
|
|
227
|
+
color: chosen ? theme.accent : undefined
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const overflow = rows.length > height ? ` ${paginator(selectedIndex < 0 ? 0 : selectedIndex, rows.length)}` : "";
|
|
231
|
+
const footer = state.searching ? `search: ${state.query}▏ Enter open · Esc done` : `↑↓ move · →/l open · ←/h close · Enter pick a file · / search · q leave${state.query ? ` filter: ${state.query}` : ""}${overflow}`;
|
|
232
|
+
out.push({ text: padToWidth(` ${footer}`, columns), dim: true, color: theme.textMuted });
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
function previewToLines(preview, width) {
|
|
236
|
+
if (width <= 0)
|
|
237
|
+
return [];
|
|
238
|
+
switch (preview.kind) {
|
|
239
|
+
case "empty":
|
|
240
|
+
return ["", " (nothing to preview)"];
|
|
241
|
+
case "error":
|
|
242
|
+
return ["", ` ${preview.message}`];
|
|
243
|
+
case "directory":
|
|
244
|
+
return ["", ` ${preview.dirs} folder${preview.dirs === 1 ? "" : "s"}, ${preview.files} file${preview.files === 1 ? "" : "s"}`];
|
|
245
|
+
case "file": {
|
|
246
|
+
const lines = preview.lines.map((line) => ` ${line}`);
|
|
247
|
+
if (preview.truncated)
|
|
248
|
+
lines.push("", ` … ${preview.totalLines} lines total`);
|
|
249
|
+
return lines;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// packages/archymedes-cli/src/file-screen.tsx
|
|
255
|
+
import { jsxDEV } from "@termuijs/jsx/jsx-dev-runtime";
|
|
256
|
+
var Panel = Box;
|
|
257
|
+
var Line = Text;
|
|
258
|
+
function FileScreen({ columns, rows, paths, onExit, palette = NO_COLOR_PALETTE, readFile }) {
|
|
259
|
+
const [state, setState] = useState(() => initialFileBrowserState(paths, columns, rows, palette));
|
|
260
|
+
const selected = currentRow(state);
|
|
261
|
+
const previewPath = selected?.node.kind === "file" ? selected.node.path : undefined;
|
|
262
|
+
const { data, error } = useAsync(async () => previewPath ? readFile(previewPath) : null, [previewPath]);
|
|
263
|
+
const preview = !selected ? { kind: "empty" } : selected.node.kind === "dir" ? { kind: "directory", ...directorySummary(selected.node) } : error ? { kind: "error", message: error.message } : data ? { kind: "file", lines: data.content.split(`
|
|
264
|
+
`), totalLines: data.totalLines, truncated: data.truncated } : { kind: "empty" };
|
|
265
|
+
useInput((input, key) => {
|
|
266
|
+
const action = keyToFileAction({ name: key?.key, ctrl: key?.ctrl, shift: key?.shift }, input, state.searching);
|
|
267
|
+
if (action.kind === "exit") {
|
|
268
|
+
onExit(undefined);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (action.kind === "pick" || action.kind === "edit") {
|
|
272
|
+
const current = currentRow(state);
|
|
273
|
+
if (current?.node.kind === "file") {
|
|
274
|
+
onExit({ path: current.node.path, intent: action.kind === "edit" ? "edit" : "mention" });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
setState((value) => toggleDirectory(value));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
setState((current) => applyFileAction(current, action));
|
|
281
|
+
});
|
|
282
|
+
const frame = composeFileFrame(state, preview);
|
|
283
|
+
return /* @__PURE__ */ jsxDEV(Panel, {
|
|
284
|
+
flexDirection: "column",
|
|
285
|
+
width: state.columns,
|
|
286
|
+
height: state.rows,
|
|
287
|
+
children: frame.map((line, index) => /* @__PURE__ */ jsxDEV(Line, {
|
|
288
|
+
bold: line.bold,
|
|
289
|
+
dimColor: line.dim,
|
|
290
|
+
inverse: line.inverse,
|
|
291
|
+
color: line.color,
|
|
292
|
+
children: line.text
|
|
293
|
+
}, `row-${index}`, false, undefined, this))
|
|
294
|
+
}, undefined, false, undefined, this);
|
|
295
|
+
}
|
|
296
|
+
async function runFileScreen(options) {
|
|
297
|
+
const { renderApp } = await import("@termuijs/jsx");
|
|
298
|
+
return new Promise((resolve) => {
|
|
299
|
+
let settled = false;
|
|
300
|
+
const finish = (picked) => {
|
|
301
|
+
if (settled)
|
|
302
|
+
return;
|
|
303
|
+
settled = true;
|
|
304
|
+
resolve(picked);
|
|
305
|
+
};
|
|
306
|
+
renderApp(FileScreen, {
|
|
307
|
+
columns: options.columns,
|
|
308
|
+
rows: options.rows,
|
|
309
|
+
paths: options.paths,
|
|
310
|
+
palette: options.palette,
|
|
311
|
+
readFile: options.readFile,
|
|
312
|
+
onExit: finish,
|
|
313
|
+
fullscreen: true
|
|
314
|
+
}).catch(() => finish(undefined));
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
export {
|
|
318
|
+
runFileScreen,
|
|
319
|
+
FileScreen
|
|
320
|
+
};
|