archymedes-cli 2.2.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,20 @@
1
1
  import {
2
2
  padToWidth,
3
3
  progressBar,
4
+ scrollIndicator,
5
+ scrollPercent,
4
6
  sliceToWidth
5
- } from "./archymedes-r54qthg5.mjs";
7
+ } from "./archymedes-5vhvv8kr.mjs";
8
+ import {
9
+ clip
10
+ } from "./archymedes-bmtdqqwt.mjs";
6
11
  import {
7
12
  ASCII_GLYPHS,
8
13
  UNICODE_GLYPHS,
9
14
  visibleWidth
10
- } from "./archymedes-5zn4zp84.mjs";
15
+ } from "./archymedes-130jt0wr.mjs";
11
16
 
12
- // packages/archymedes-cli/src/canvas.ts
17
+ // packages/archymedes-cli/src/render/canvas.ts
13
18
  var BRAILLE_DOTS = [
14
19
  [1, 2, 4, 64],
15
20
  [8, 16, 32, 128]
@@ -95,7 +100,7 @@ function seriesBounds(values) {
95
100
  return { low, high, span: high - low === 0 ? 1 : high - low };
96
101
  }
97
102
 
98
- // packages/archymedes-cli/src/charts.ts
103
+ // packages/archymedes-cli/src/render/charts.ts
99
104
  function barChart(data, options) {
100
105
  if (data.length === 0)
101
106
  return [];
@@ -253,4 +258,208 @@ function heatStrip(data, options) {
253
258
  });
254
259
  }
255
260
 
256
- export { BrailleCanvas, dotRowFor, seriesBounds, barChart, lineChart, plotSeries, scatterChart, waveLine, StreamSeries, heatStrip };
261
+ // packages/archymedes-cli/src/ui/workspace-model.ts
262
+ function paneHeight(rows) {
263
+ return Math.max(1, rows - 4);
264
+ }
265
+ function selectedPane(snapshot) {
266
+ if (snapshot.panes.length === 0)
267
+ return;
268
+ const index = Math.max(0, Math.min(snapshot.selected, snapshot.panes.length - 1));
269
+ return snapshot.panes[index];
270
+ }
271
+ function visibleLines(pane, height, scroll) {
272
+ const clamped = Math.max(0, Math.min(scroll, Math.max(0, pane.lines.length - height)));
273
+ const end = pane.lines.length - clamped;
274
+ return pane.lines.slice(Math.max(0, end - height), end);
275
+ }
276
+ function atLiveEdge(pane, height, scroll) {
277
+ return Math.min(scroll, Math.max(0, pane.lines.length - height)) === 0;
278
+ }
279
+ function keyToAction(key, character) {
280
+ const name = key.name ?? "";
281
+ const isEscape = name === "escape" || name === "\x1B";
282
+ const isEnter = name === "return" || name === "enter" || name === "\r" || name === `
283
+ `;
284
+ if (isEscape || name === "q" && !key.ctrl || key.ctrl && name === "c")
285
+ return { kind: "exit" };
286
+ if (isEnter)
287
+ return { kind: "focus" };
288
+ if (name === "tab")
289
+ return { kind: "cycle", step: key.shift ? -1 : 1 };
290
+ if (name === "left")
291
+ return { kind: "cycle", step: -1 };
292
+ if (name === "right")
293
+ return { kind: "cycle", step: 1 };
294
+ if (name === "up" || name === "k")
295
+ return { kind: "scroll", lines: 1 };
296
+ if (name === "down" || name === "j")
297
+ return { kind: "scroll", lines: -1 };
298
+ if (name === "pageup")
299
+ return { kind: "scroll", lines: 10 };
300
+ if (name === "pagedown")
301
+ return { kind: "scroll", lines: -10 };
302
+ if (name === "home" || character === "g")
303
+ return { kind: "top" };
304
+ if (name === "end" || character === "G")
305
+ return { kind: "bottom" };
306
+ const digit = character && /^[1-9]$/.test(character) ? Number(character) : undefined;
307
+ if (digit !== undefined)
308
+ return { kind: "select", index: digit - 1 };
309
+ return { kind: "none" };
310
+ }
311
+ function applyAction(snapshot, action) {
312
+ const count = snapshot.panes.length;
313
+ if (count === 0 || action.kind === "none" || action.kind === "exit" || action.kind === "focus")
314
+ return snapshot;
315
+ if (action.kind === "select") {
316
+ if (action.index < 0 || action.index >= count)
317
+ return snapshot;
318
+ return { ...snapshot, selected: action.index, scroll: 0 };
319
+ }
320
+ if (action.kind === "cycle") {
321
+ const next = ((snapshot.selected + action.step) % count + count) % count;
322
+ return { ...snapshot, selected: next, scroll: 0 };
323
+ }
324
+ const pane = selectedPane(snapshot);
325
+ const height = paneHeight(snapshot.rows);
326
+ const furthest = Math.max(0, (pane?.lines.length ?? 0) - height);
327
+ if (action.kind === "bottom")
328
+ return { ...snapshot, scroll: 0 };
329
+ if (action.kind === "top")
330
+ return { ...snapshot, scroll: furthest };
331
+ return { ...snapshot, scroll: Math.max(0, Math.min(snapshot.scroll + action.lines, furthest)) };
332
+ }
333
+ function paneTabs(snapshot) {
334
+ const selected = Math.max(0, Math.min(snapshot.selected, snapshot.panes.length - 1));
335
+ return snapshot.panes.map((pane, index) => ({
336
+ label: `${index + 1} ${pane.title}`,
337
+ active: index === selected,
338
+ status: pane.status
339
+ }));
340
+ }
341
+
342
+ class PaneActivity {
343
+ capacity;
344
+ series = new Map;
345
+ seen = new Map;
346
+ constructor(capacity = 48) {
347
+ this.capacity = capacity;
348
+ }
349
+ sample(panes) {
350
+ const live = new Set(panes.map((pane) => pane.key));
351
+ for (const key of [...this.series.keys()]) {
352
+ if (!live.has(key)) {
353
+ this.series.delete(key);
354
+ this.seen.delete(key);
355
+ }
356
+ }
357
+ const result = new Map;
358
+ for (const pane of panes) {
359
+ const produced = pane.lines.length + pane.dropped;
360
+ const previous = this.seen.get(pane.key);
361
+ this.seen.set(pane.key, produced);
362
+ const stream = this.series.get(pane.key) ?? new StreamSeries(this.capacity);
363
+ this.series.set(pane.key, stream);
364
+ if (previous !== undefined)
365
+ stream.push(Math.max(0, produced - previous));
366
+ result.set(pane.key, stream.values());
367
+ }
368
+ return result;
369
+ }
370
+ }
371
+ function tabPanes(views, linesFor) {
372
+ return views.map((view) => {
373
+ const record = linesFor(view.id);
374
+ const where = view.backend && view.backend !== "local" ? ` · ${view.backend}` : "";
375
+ return {
376
+ kind: "tab",
377
+ key: `${view.id}`,
378
+ title: view.title,
379
+ subtitle: `${view.model ?? "model unknown"}${where}${view.cost ? ` · ${view.cost}` : ""}`,
380
+ status: view.status,
381
+ lines: record.lines,
382
+ dropped: record.dropped
383
+ };
384
+ });
385
+ }
386
+ var STATUS_MARK = {
387
+ running: "●",
388
+ failed: "✕",
389
+ done: "✓",
390
+ idle: " "
391
+ };
392
+ var WORKSPACE_LEGEND = "1-9 pane · ←→ move · ↑↓ scroll · g/G ends · Esc back";
393
+ function paneBar(snapshot, columns) {
394
+ const tabs = paneTabs(snapshot);
395
+ const cells = tabs.map((tab) => ` ${STATUS_MARK[tab.status] ?? " "}${tab.label} `);
396
+ const full = cells.join("");
397
+ if (full.length <= columns)
398
+ return full || " no panes ";
399
+ const selected = Math.max(0, Math.min(snapshot.selected, tabs.length - 1));
400
+ return `${selected > 0 ? "‹ " : ""}${cells[selected] ?? " no panes "}${selected < tabs.length - 1 ? " ›" : ""}`;
401
+ }
402
+ function composeFrame(snapshot) {
403
+ const rows = [];
404
+ const columns = Math.max(1, Math.floor(snapshot.columns));
405
+ const rowCount = Math.max(1, Math.floor(snapshot.rows));
406
+ const pane = selectedPane(snapshot);
407
+ const height = paneHeight(rowCount);
408
+ const theme = snapshot.palette.tokens;
409
+ const bar = paneBar(snapshot, columns);
410
+ rows.push({ text: bar || " no panes ", bold: true, color: theme.primary });
411
+ const scrolled = pane && !atLiveEdge(pane, height, snapshot.scroll);
412
+ const position = (() => {
413
+ if (!pane || !scrolled)
414
+ return "";
415
+ const maxOffset = Math.max(0, pane.lines.length - height);
416
+ const offsetFromTop = maxOffset - Math.min(snapshot.scroll, maxOffset);
417
+ return scrollIndicator(scrollPercent(offsetFromTop, pane.lines.length, height));
418
+ })();
419
+ const dropped = pane && pane.dropped > 0 ? ` (${pane.dropped} earlier lines dropped)` : "";
420
+ const header = pane ? `${pane.title} ${pane.subtitle}${dropped}` : "nothing open";
421
+ const activityWidth = Math.min(24, Math.max(0, columns - visibleWidth(header) - 3));
422
+ const activity = pane?.activity && pane.activity.length > 1 && activityWidth >= 8 ? ` ${waveLine(pane.activity, { width: activityWidth })}` : "";
423
+ rows.push({
424
+ text: pane ? `${header}${activity}` : header,
425
+ bold: true,
426
+ color: pane?.status === "failed" ? theme.error : pane?.status === "running" ? theme.success : theme.accent
427
+ });
428
+ const body = pane ? visibleLines(pane, height, snapshot.scroll) : [];
429
+ for (let index = 0;index < height; index += 1)
430
+ rows.push({ text: body[index] ?? "" });
431
+ while (rows.length < rowCount - 1)
432
+ rows.push({ text: "" });
433
+ rows.length = Math.max(0, rowCount - 1);
434
+ rows.push({
435
+ text: `${WORKSPACE_LEGEND}${scrolled ? ` ▲ scrolled back ${position}` : ""}`,
436
+ dim: true,
437
+ color: theme.textMuted
438
+ });
439
+ return rows.map((row) => ({ ...row, text: clip(row.text, columns) }));
440
+ }
441
+ function buildWorkspaceSnapshot(input) {
442
+ const panes = [
443
+ ...tabPanes(input.views, input.linesFor),
444
+ ...input.jobs.map((job) => ({
445
+ kind: "job",
446
+ key: job.id,
447
+ title: `job ${job.id.slice(-6)}`,
448
+ subtitle: job.objective,
449
+ status: job.done ? "done" : "running",
450
+ lines: job.lines,
451
+ dropped: job.dropped
452
+ }))
453
+ ];
454
+ const activity = input.activity.sample(panes);
455
+ return {
456
+ panes: panes.map((pane) => ({ ...pane, activity: activity.get(pane.key) })),
457
+ selected: Math.max(0, input.views.findIndex((view) => view.active)),
458
+ scroll: 0,
459
+ palette: input.palette,
460
+ columns: input.columns,
461
+ rows: input.rows
462
+ };
463
+ }
464
+
465
+ export { barChart, lineChart, plotSeries, scatterChart, waveLine, heatStrip, keyToAction, applyAction, PaneActivity, composeFrame, buildWorkspaceSnapshot };
@@ -1,18 +1,22 @@
1
1
  import {
2
- CYAN,
3
- DIM,
4
- GREEN,
5
2
  GUTTER,
6
- MAGENTA,
7
- RED,
8
3
  clip,
9
- paint
10
- } from "./archymedes-cdgejs3p.mjs";
4
+ panel
5
+ } from "./archymedes-bmtdqqwt.mjs";
11
6
  import {
12
- UNICODE_GLYPHS
13
- } from "./archymedes-5zn4zp84.mjs";
7
+ BOLD,
8
+ DIM,
9
+ UNICODE_GLYPHS,
10
+ codeStyle,
11
+ highlightSyntax,
12
+ newSyntaxState,
13
+ paint,
14
+ paintAll,
15
+ roleCode,
16
+ visibleWidth
17
+ } from "./archymedes-130jt0wr.mjs";
14
18
 
15
- // packages/archymedes-cli/src/code-view.ts
19
+ // packages/archymedes-cli/src/render/code-view.ts
16
20
  var MAX_DIFF_CELLS = 250000;
17
21
  function diffLines(before, after) {
18
22
  const left = before === "" ? [] : before.split(`
@@ -57,6 +61,12 @@ function diffLines(before, after) {
57
61
  }
58
62
  return result;
59
63
  }
64
+ function diffStat(diff) {
65
+ return {
66
+ added: diff.filter((line) => line.kind === "add").length,
67
+ removed: diff.filter((line) => line.kind === "remove").length
68
+ };
69
+ }
60
70
  function condenseDiff(diff, context = 2) {
61
71
  const keep = new Array(diff.length).fill(false);
62
72
  diff.forEach((line, index) => {
@@ -186,7 +196,9 @@ var KEYWORDS = new Set([
186
196
  "yield"
187
197
  ]);
188
198
  var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
189
- function highlightCode(line, depth) {
199
+ function highlightCode(line, depth, palette, language, state) {
200
+ if (codeStyle().colors === "vscode")
201
+ return highlightSyntax(line, { depth, palette, ...language ? { language } : {}, ...state ? { state } : {} });
190
202
  if (depth === "none")
191
203
  return line;
192
204
  const commentAt = findCommentStart(line);
@@ -194,13 +206,13 @@ function highlightCode(line, depth) {
194
206
  const comment = commentAt === -1 ? "" : paint(line.slice(commentAt), DIM, depth);
195
207
  const painted = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_$][\w$]*)\b/g, (whole, text, numeric, word) => {
196
208
  if (text !== undefined)
197
- return paint(text, GREEN, depth);
209
+ return paint(text, roleCode("success", palette, depth), depth);
198
210
  if (numeric !== undefined)
199
- return paint(numeric, MAGENTA, depth);
211
+ return paint(numeric, roleCode("accent", palette, depth), depth);
200
212
  if (word !== undefined && KEYWORDS.has(word))
201
- return paint(word, CYAN, depth);
213
+ return paint(word, roleCode("primary", palette, depth), depth);
202
214
  if (word !== undefined && LITERALS.has(word))
203
- return paint(word, MAGENTA, depth);
215
+ return paint(word, roleCode("accent", palette, depth), depth);
204
216
  return whole;
205
217
  });
206
218
  return `${painted}${comment}`;
@@ -239,11 +251,14 @@ function renderCode(content, style, options = {}) {
239
251
  const start = options.startLine ?? 1;
240
252
  const numberWidth = String(start + lines.length - 1).length;
241
253
  const bodyWidth = Math.max(8, style.width - GUTTER.length - numberWidth - 4);
242
- const render = (from, to) => lines.slice(from, to).map((line, index) => {
243
- const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
244
- const painted = options.highlight === false ? line : highlightCode(line, style.depth);
245
- return `${number} ${clip(painted, bodyWidth, glyphs)}`;
246
- });
254
+ const render = (from, to) => {
255
+ const syntax = newSyntaxState();
256
+ return lines.slice(from, to).map((line, index) => {
257
+ const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
258
+ const painted = options.highlight === false ? line : highlightCode(line, style.depth, style.palette, options.language, syntax);
259
+ return `${number} ${clip(painted, bodyWidth, glyphs)}`;
260
+ });
261
+ };
247
262
  const hidden = Math.max(0, lines.length - maxLines);
248
263
  const shown = render(0, hidden > 0 ? maxLines : lines.length);
249
264
  return {
@@ -259,12 +274,14 @@ function renderDiff(diff, style, options = {}) {
259
274
  const maxLines = options.maxLines ?? 16;
260
275
  const condensed = condenseDiff(diff);
261
276
  const bodyWidth = Math.max(8, style.width - GUTTER.length - 4);
277
+ const vscode = codeStyle().colors === "vscode" && style.depth !== "none";
278
+ const bandWidth = Math.min(bodyWidth, Math.max(0, ...diff.map((line) => [...line.text].length)));
262
279
  const row = (line) => {
263
280
  if (line.kind === "gap")
264
281
  return paint(`${glyphs.middot.repeat(3)} ${line.text}`, DIM, style.depth);
265
282
  const mark = line.kind === "add" ? glyphs.plus : line.kind === "remove" ? glyphs.minus : " ";
266
- const code = line.kind === "add" ? GREEN : line.kind === "remove" ? RED : DIM;
267
- const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth);
283
+ const code = line.kind === "add" ? roleCode("success", style.palette, style.depth) : line.kind === "remove" ? roleCode("error", style.palette, style.depth) : DIM;
284
+ const body = line.kind === "context" ? vscode ? highlightCode(line.text, style.depth, style.palette, options.language) : paint(line.text, DIM, style.depth) : vscode ? highlightSyntax(line.text, { depth: style.depth, palette: style.palette, band: line.kind, padTo: bandWidth, ...options.language ? { language: options.language } : {} }) : highlightCode(line.text, style.depth, style.palette);
268
285
  return `${paint(mark, code, style.depth)} ${clip(body, bodyWidth, glyphs)}`;
269
286
  };
270
287
  const rows = condensed.map(row);
@@ -277,8 +294,32 @@ function renderDiff(diff, style, options = {}) {
277
294
  `)
278
295
  };
279
296
  }
280
- function describeChange(stat, depth) {
281
- return `${paint(`+${stat.added}`, GREEN, depth)} ${paint(`-${stat.removed}`, RED, depth)}`;
297
+ function renderFileChange(change, style, options = {}) {
298
+ const language = languageOf(change.path);
299
+ const block = change.kind === "edit" ? renderDiff(diffLines(change.before ?? "", change.after ?? ""), style, { maxLines: options.maxLines, language }) : renderCode(change.content ?? "", style, { maxLines: options.maxLines, language });
300
+ const badge = change.kind === "edit" ? (() => {
301
+ const stat = diffStat(diffLines(change.before ?? "", change.after ?? ""));
302
+ return `+${stat.added} -${stat.removed}`;
303
+ })() : `${(change.content ?? "").split(`
304
+ `).length} lines · ${language}`;
305
+ const body = block.text.split(`
306
+ `);
307
+ 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)] : [];
308
+ return {
309
+ text: panel([...body, ...foot], style, { title: `${change.kind === "edit" ? "diff" : "new file"} · ${change.path}`, badge, tone: change.kind === "edit" ? "accent" : "good" }),
310
+ hidden: block.hidden,
311
+ full: panel(block.full.split(`
312
+ `), style, { title: `${change.kind === "edit" ? "diff" : "new file"} · ${change.path}`, badge, tone: change.kind === "edit" ? "accent" : "good" })
313
+ };
314
+ }
315
+ function fenceHeader(language, style) {
316
+ const glyphs = style.glyphs ?? UNICODE_GLYPHS;
317
+ const label = language ? ` ${language} ` : glyphs.boxHorizontal.repeat(4);
318
+ const width = Math.max(0, style.width - GUTTER.length - visibleWidth(label) - 3);
319
+ 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)}`;
320
+ }
321
+ function describeChange(stat, depth, palette) {
322
+ return `${paint(`+${stat.added}`, roleCode("success", palette, depth), depth)} ${paint(`-${stat.removed}`, roleCode("error", palette, depth), depth)}`;
282
323
  }
283
324
 
284
- export { diffLines, languageOf, highlightCode, renderCode, renderDiff, describeChange };
325
+ export { diffLines, diffStat, languageOf, highlightCode, renderCode, renderDiff, renderFileChange, fenceHeader, describeChange };