@cfbender/cesium 0.5.1 → 0.6.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/CHANGELOG.md +97 -3
- package/README.md +8 -8
- package/package.json +19 -17
- package/src/cli/commands/ls.ts +62 -65
- package/src/cli/commands/open.ts +47 -62
- package/src/cli/commands/prune.ts +59 -71
- package/src/cli/commands/restart.ts +100 -12
- package/src/cli/commands/serve.ts +119 -116
- package/src/cli/commands/stop.ts +51 -84
- package/src/cli/commands/theme.ts +54 -92
- package/src/cli/index.ts +17 -70
- package/src/index.ts +4 -1
- package/src/prompt/field-reference.ts +2 -2
- package/src/prompt/system-fragment.md +46 -16
- package/src/render/blocks/catalog.ts +2 -0
- package/src/render/blocks/diff/myers.ts +221 -0
- package/src/render/blocks/diff/parse-unified.ts +101 -0
- package/src/render/blocks/highlight.ts +8 -11
- package/src/render/blocks/markdown.ts +28 -7
- package/src/render/blocks/render.ts +3 -0
- package/src/render/blocks/renderers/code.ts +1 -3
- package/src/render/blocks/renderers/compare-table.ts +3 -4
- package/src/render/blocks/renderers/diagram.ts +2 -5
- package/src/render/blocks/renderers/diff.ts +378 -0
- package/src/render/blocks/renderers/prose.ts +1 -2
- package/src/render/blocks/renderers/timeline.ts +2 -1
- package/src/render/blocks/themes/claret-dark.ts +1 -6
- package/src/render/blocks/themes/claret-light.ts +1 -6
- package/src/render/blocks/types.ts +13 -1
- package/src/render/blocks/validate-block.ts +19 -9
- package/src/render/theme.ts +149 -0
- package/src/render/validate.ts +53 -9
- package/src/server/api.ts +112 -124
- package/src/server/favicon.ts +8 -16
- package/src/server/http.ts +101 -106
- package/src/server/lifecycle.ts +12 -6
- package/src/storage/assets.ts +8 -10
- package/src/storage/index-gen.ts +2 -3
- package/src/storage/theme-write.ts +17 -3
- package/src/tools/publish.ts +1 -3
- package/src/tools/styleguide.ts +3 -7
- package/src/tools/wait.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.6.0 — 2026-05-13
|
|
4
|
+
|
|
5
|
+
Internal cleanup release. Two hand-rolled server and CLI layers swapped
|
|
6
|
+
for small, well-fit libraries (Hono and Citty) that delete the regex and
|
|
7
|
+
dispatcher boilerplate without changing user-visible behavior. No new
|
|
8
|
+
features and no protocol changes — artifacts written by older versions
|
|
9
|
+
continue rendering unchanged.
|
|
10
|
+
|
|
11
|
+
- **refactor:** Migrate the cesium HTTP server from a hand-rolled
|
|
12
|
+
`Bun.serve` fetch handler with a custom `preHandlers` middleware chain
|
|
13
|
+
to a Hono app fronted by Bun.serve. Routes for `/api/sessions/*` and
|
|
14
|
+
`/favicon.ico` now use Hono's typed `:param` syntax instead of regex
|
|
15
|
+
match + manual narrowing. The static file handler is preserved verbatim
|
|
16
|
+
and mounted as `app.notFound` so all 11 existing static-serve tests
|
|
17
|
+
pass unchanged. `ServerHandle.app: Hono` replaces `addHandler` for
|
|
18
|
+
callers that need to register additional routes.
|
|
19
|
+
- **refactor:** Migrate the `cesium` CLI from `node:util parseArgs` +
|
|
20
|
+
a hand-rolled subcommand dispatcher to Citty. Each command file now
|
|
21
|
+
exports a typed `runX(args, ctx)` inner function (kept directly
|
|
22
|
+
testable with injected `{stdout, stderr, loadConfig}`) plus an outer
|
|
23
|
+
`xxxCmd = defineCommand(...)` that Citty consumes. Subcommands load
|
|
24
|
+
lazily via dynamic `import()` so cold-start cost is paid only for the
|
|
25
|
+
command the user actually invoked. `cesium theme show|apply` is now
|
|
26
|
+
expressed as native Citty sub-subcommands rather than the previous
|
|
27
|
+
manual routing.
|
|
28
|
+
- **fix:** `cesium restart` no longer fails on serve-only flags. The
|
|
29
|
+
previous implementation passed argv through to both `stopCommand` and
|
|
30
|
+
`serveCommand` under `strict: true`, so `cesium restart --port 4000`
|
|
31
|
+
would have errored. Restart now defines its own arg schema covering
|
|
32
|
+
both stop and serve options.
|
|
33
|
+
- **chore:** Clean up all 25 oxlint warnings:
|
|
34
|
+
- Restructure `src/server/api.ts` regex matching to remove four
|
|
35
|
+
`!` non-null assertions.
|
|
36
|
+
- Replace `match![1]!` patterns in tests with a local `unwrap(value,
|
|
37
|
+
name)` helper for proper type narrowing.
|
|
38
|
+
- Replace `handle!.url` in tests with an explicit null check.
|
|
39
|
+
- Add targeted `eslint-disable-next-line no-await-in-loop` comments
|
|
40
|
+
(with `--` reason annotations matching the existing repo convention)
|
|
41
|
+
in places where sequential execution is required: middleware chain,
|
|
42
|
+
poll-with-backoff retry, short-circuit search.
|
|
43
|
+
- **chore:** Add `hono@4.12.18` and `citty@0.2.2` to dependencies.
|
|
44
|
+
Removes a meaningful amount of hand-rolled routing / arg-parsing code
|
|
45
|
+
for ~50KB total install footprint.
|
|
46
|
+
- **chore:** `cesium --version` output format changes from
|
|
47
|
+
`cesium 0.6.0` to plain `0.6.0` (Citty's default). The `cesium version`
|
|
48
|
+
subcommand is removed — use `cesium --version` or `cesium -v`. Auto-
|
|
49
|
+
generated help text for each command also follows Citty's formatting
|
|
50
|
+
(uppercase USAGE/OPTIONS, color codes, default annotations) rather
|
|
51
|
+
than the previous hand-aligned `Usage: cesium ls` strings.
|
|
52
|
+
|
|
53
|
+
## v0.5.2 — 2026-05-12
|
|
54
|
+
|
|
55
|
+
A 16th block type — `diff` — that renders a beautiful side-by-side
|
|
56
|
+
before/after code diff with curved SVG bezier connectors between the two
|
|
57
|
+
columns. JetBrains diff-viewer aesthetic. Plus tooling improvements for
|
|
58
|
+
hot-reload visual iteration during plugin development.
|
|
59
|
+
|
|
60
|
+
- **feat:** New `diff` block type. Two input arms (XOR):
|
|
61
|
+
- `patch`: literal unified diff string (e.g. from `git diff` output)
|
|
62
|
+
- `before`/`after`: paired text strings; server runs Myers O(ND) line
|
|
63
|
+
diff to compute the change set
|
|
64
|
+
- **feat:** Per-line shiki syntax highlighting is preserved through the
|
|
65
|
+
diff. The renderer recomposes before-side and after-side text, runs each
|
|
66
|
+
through `highlightCode`, then zips the styled line spans back into the
|
|
67
|
+
diff line list — so multi-line constructs (template strings, block
|
|
68
|
+
comments) tokenize correctly.
|
|
69
|
+
- **feat:** Visual rendering:
|
|
70
|
+
- Three-column grid (1fr | 60px | 1fr) with line numbers per side
|
|
71
|
+
- Subtle red/green line-tint backgrounds on remove/add lines
|
|
72
|
+
- 60px-wide SVG connector column draws semi-transparent cubic-bezier
|
|
73
|
+
ribbons connecting each remove region on the left to the corresponding
|
|
74
|
+
add region on the right; pure adds collapse to a teardrop pointing
|
|
75
|
+
into the left, pure removes mirror
|
|
76
|
+
- Optional file-header strip with filename + `+N -M` stats
|
|
77
|
+
- Optional caption strip below
|
|
78
|
+
- 720px breakpoint collapses to single-column with connector hidden
|
|
79
|
+
- **feat:** Theme tokens `--diff-add`, `--diff-remove`, `--diff-change`
|
|
80
|
+
added to all seven palette presets so colors fit each preset's character
|
|
81
|
+
(claret rose, warm clay, cool blue, etc.).
|
|
82
|
+
- **fix:** Diff connector SVG now matches the `padding: 8px 0` of the
|
|
83
|
+
side columns so the bezier paths line up exactly with their target
|
|
84
|
+
regions instead of sitting 8px high.
|
|
85
|
+
- **chore:** Project-local opencode config tracked: `.opencode/opencode.json`,
|
|
86
|
+
`.opencode/plugins/cesium.ts` (dev-loop shim that loads the working
|
|
87
|
+
tree's source instead of the published npm package), and
|
|
88
|
+
`.opencode/skills/cesium-preview/SKILL.md` (hot-reload visual iteration
|
|
89
|
+
workflow that bypasses the stale plugin host by importing render code
|
|
90
|
+
directly via bun and writing to /tmp).
|
|
91
|
+
- **chore:** `scripts/dogfood-diff.ts` reference preview script for the
|
|
92
|
+
diff block. Useful template for previewing other block work.
|
|
93
|
+
- **chore:** Apply oxfmt to 30 files that drifted out of format compliance
|
|
94
|
+
in v0.5.1 (no CI lint gate caught it). Pure cosmetic — line-wrapping,
|
|
95
|
+
italic style normalization, key ordering. No behavior changes.
|
|
96
|
+
|
|
3
97
|
## v0.5.1 — 2026-05-12
|
|
4
98
|
|
|
5
99
|
Server-side syntax highlighting for `code` blocks via shiki, custom claret
|
|
@@ -10,11 +104,11 @@ longer kill the plugin host process.
|
|
|
10
104
|
- **fix (critical):** Lazy-started cesium server now runs as a detached
|
|
11
105
|
subprocess. Previously `ensureRunning` (called from publish/ask plugin
|
|
12
106
|
paths) ran `Bun.serve()` in-process and wrote `pid: process.pid` to the PID
|
|
13
|
-
file — meaning that PID was the
|
|
107
|
+
file — meaning that PID was the _plugin host_ (e.g. opencode). Any
|
|
14
108
|
invocation of `cesium stop` (CLI, tool, or test) would signal the host
|
|
15
109
|
process and kill it. Now lazy-start spawns `bun run cli serve` as a
|
|
16
110
|
detached child; the PID file points at that child. Foreground `cesium
|
|
17
|
-
|
|
111
|
+
serve` still runs in-process (correct for its semantics).
|
|
18
112
|
- **api:** Split `ensureRunning` into `runServerForeground` (in-process, for
|
|
19
113
|
the foreground CLI) and `ensureServerRunning` (detached subprocess, for
|
|
20
114
|
plugins). `ensureRunning` is kept as a backward-compat alias for
|
|
@@ -82,7 +176,7 @@ tokens, more on heavily structured artifacts.
|
|
|
82
176
|
surfaced as a small badge on index cards.
|
|
83
177
|
- **feat:** Framework CSS extended with rules for every block-renderer
|
|
84
178
|
pattern: `dl.kv` (2-column grid), `.pill-row`, `.check-list`, `<hr
|
|
85
|
-
|
|
179
|
+
data-label>`, `figure.code`, timeline-item internals, `.lede`, plus
|
|
86
180
|
`.diagram svg text { fill: currentColor }` so SVGs inherit theme color.
|
|
87
181
|
- **feat:** `escapeHtml` and `escapeAttr` throw a clear error on non-string
|
|
88
182
|
input instead of crashing inside `.replace()`.
|
package/README.md
CHANGED
|
@@ -382,15 +382,15 @@ state directory, hostname, and theme settings flow through.
|
|
|
382
382
|
|
|
383
383
|
Optional `~/.config/opencode/cesium.json`:
|
|
384
384
|
|
|
385
|
-
| Key | Type | Default | Description
|
|
386
|
-
| --------------- | ------ | ----------------------- |
|
|
387
|
-
| `stateDir` | string | `~/.local/state/cesium` | Where artifacts and indexes live
|
|
388
|
-
| `port` | number | `3030` | First port to try for the local HTTP server
|
|
389
|
-
| `portMax` | number | `3050` | Upper bound when scanning for free ports
|
|
390
|
-
| `hostname` | string | `127.0.0.1` | Bind address. Use `0.0.0.0` to expose on the LAN
|
|
385
|
+
| Key | Type | Default | Description |
|
|
386
|
+
| --------------- | ------ | ----------------------- | ------------------------------------------------------------------------------------------- |
|
|
387
|
+
| `stateDir` | string | `~/.local/state/cesium` | Where artifacts and indexes live |
|
|
388
|
+
| `port` | number | `3030` | First port to try for the local HTTP server |
|
|
389
|
+
| `portMax` | number | `3050` | Upper bound when scanning for free ports |
|
|
390
|
+
| `hostname` | string | `127.0.0.1` | Bind address. Use `0.0.0.0` to expose on the LAN |
|
|
391
391
|
| `idleTimeoutMs` | number | `1800000` | Plugin server idle-shutdown threshold (30 min). Does not apply to foreground `cesium serve` |
|
|
392
|
-
| `themePreset` | string | `"claret-dark"` | Named color palette (`claret-dark`/`claret-light`/`claret`/`warm`/`cool`/`mono`/`paper`)
|
|
393
|
-
| `theme` | object | (claret-dark palette) | Per-token color overrides (stacked on preset)
|
|
392
|
+
| `themePreset` | string | `"claret-dark"` | Named color palette (`claret-dark`/`claret-light`/`claret`/`warm`/`cool`/`mono`/`paper`) |
|
|
393
|
+
| `theme` | object | (claret-dark palette) | Per-token color overrides (stacked on preset) |
|
|
394
394
|
|
|
395
395
|
Environment overrides: `CESIUM_PORT`, `CESIUM_STATE_DIR`, `CESIUM_HOSTNAME`, `CESIUM_THEME_PRESET`.
|
|
396
396
|
|
package/package.json
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfbender/cesium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Beautiful self-contained HTML artifacts from your opencode agent.",
|
|
5
|
-
"
|
|
6
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"artifact",
|
|
8
|
+
"claret",
|
|
9
|
+
"cli",
|
|
10
|
+
"html",
|
|
11
|
+
"opencode",
|
|
12
|
+
"plugin"
|
|
13
|
+
],
|
|
7
14
|
"homepage": "https://github.com/cfbender/cesium#readme",
|
|
8
15
|
"bugs": "https://github.com/cfbender/cesium/issues",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Cody Bender",
|
|
9
18
|
"repository": {
|
|
10
19
|
"type": "git",
|
|
11
20
|
"url": "git+https://github.com/cfbender/cesium.git"
|
|
12
21
|
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"opencode",
|
|
15
|
-
"agent",
|
|
16
|
-
"html",
|
|
17
|
-
"artifact",
|
|
18
|
-
"plugin",
|
|
19
|
-
"cli",
|
|
20
|
-
"claret"
|
|
21
|
-
],
|
|
22
22
|
"bin": {
|
|
23
23
|
"cesium": "./src/cli/index.ts"
|
|
24
24
|
},
|
|
25
|
-
"type": "module",
|
|
26
|
-
"main": "src/index.ts",
|
|
27
25
|
"files": [
|
|
28
26
|
"src",
|
|
29
27
|
"assets/styleguide.html",
|
|
@@ -31,12 +29,11 @@
|
|
|
31
29
|
"ARCHITECTURE.md",
|
|
32
30
|
"CHANGELOG.md"
|
|
33
31
|
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "src/index.ts",
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"engines": {
|
|
38
|
-
"bun": ">=1.0.0"
|
|
39
|
-
},
|
|
40
37
|
"scripts": {
|
|
41
38
|
"test": "bun test",
|
|
42
39
|
"typecheck": "tsc --noEmit",
|
|
@@ -49,6 +46,8 @@
|
|
|
49
46
|
},
|
|
50
47
|
"dependencies": {
|
|
51
48
|
"@opencode-ai/plugin": "latest",
|
|
49
|
+
"citty": "^0.2.2",
|
|
50
|
+
"hono": "^4.12.18",
|
|
52
51
|
"nanoid": "^5.0.0",
|
|
53
52
|
"parse5": "^7.1.0",
|
|
54
53
|
"shiki": "^4.0.2"
|
|
@@ -58,5 +57,8 @@
|
|
|
58
57
|
"oxfmt": "^0.48.0",
|
|
59
58
|
"oxlint": "^1.63.0",
|
|
60
59
|
"typescript": "^5.4.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"bun": ">=1.0.0"
|
|
61
63
|
}
|
|
62
64
|
}
|
package/src/cli/commands/ls.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
// cesium ls — list artifacts for current project (or all).
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { execSync } from "node:child_process";
|
|
6
6
|
import { loadConfig, type CesiumConfig } from "../../config.ts";
|
|
7
7
|
import { loadIndex, type IndexEntry } from "../../storage/index-cache.ts";
|
|
8
8
|
import { deriveProjectIdentity } from "../../storage/paths.ts";
|
|
9
9
|
|
|
10
|
+
export interface LsArgs {
|
|
11
|
+
all: boolean;
|
|
12
|
+
json: boolean;
|
|
13
|
+
limit: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export interface LsContext {
|
|
11
17
|
stdout: { write: (s: string) => void };
|
|
12
18
|
stderr: { write: (s: string) => void };
|
|
@@ -58,69 +64,27 @@ function getGitRemote(cwd: string): string | null {
|
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
limit: string | undefined;
|
|
68
|
-
help: boolean;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const parsed = parseArgs({
|
|
73
|
-
args: argv,
|
|
74
|
-
options: {
|
|
75
|
-
all: { type: "boolean", short: "a", default: false },
|
|
76
|
-
json: { type: "boolean", default: false },
|
|
77
|
-
limit: { type: "string", short: "n" },
|
|
78
|
-
help: { type: "boolean", short: "h", default: false },
|
|
79
|
-
},
|
|
80
|
-
allowPositionals: false,
|
|
81
|
-
strict: true,
|
|
82
|
-
});
|
|
83
|
-
values = parsed.values as typeof values;
|
|
84
|
-
} catch (err) {
|
|
85
|
-
const e = err as Error;
|
|
86
|
-
resolved.stderr.write(`cesium ls: ${e.message}\n`);
|
|
87
|
-
resolved.stderr.write(`Usage: cesium ls [--all] [--json] [--limit N]\n`);
|
|
88
|
-
return 1;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (values.help) {
|
|
92
|
-
resolved.stdout.write(
|
|
93
|
-
[
|
|
94
|
-
"Usage: cesium ls [options]",
|
|
95
|
-
"",
|
|
96
|
-
"Options:",
|
|
97
|
-
" --all, -a Show artifacts for all projects (default: current project only)",
|
|
98
|
-
" --json Output as JSON array",
|
|
99
|
-
" --limit, -n N Show at most N most recent artifacts (default: 50)",
|
|
100
|
-
" --help, -h Show this help message",
|
|
101
|
-
"",
|
|
102
|
-
].join("\n"),
|
|
103
|
-
);
|
|
104
|
-
return 0;
|
|
105
|
-
}
|
|
67
|
+
/**
|
|
68
|
+
* Inner command logic. Tests call this directly with typed args + injected
|
|
69
|
+
* context for fast feedback; the Citty wrapper handles argv parsing.
|
|
70
|
+
*/
|
|
71
|
+
export async function runLs(args: LsArgs, ctxOverride?: Partial<LsContext>): Promise<number> {
|
|
72
|
+
const ctx: LsContext = { ...defaultCtx(), ...ctxOverride };
|
|
106
73
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
resolved.stderr.write(`cesium ls: --limit must be a positive integer\n`);
|
|
74
|
+
if (args.limit < 1) {
|
|
75
|
+
ctx.stderr.write(`cesium ls: --limit must be a positive integer\n`);
|
|
110
76
|
return 1;
|
|
111
77
|
}
|
|
112
|
-
const limit = limitRaw;
|
|
113
78
|
|
|
114
|
-
|
|
115
|
-
const cfg = (resolved.loadConfig ?? loadConfig)();
|
|
79
|
+
const cfg = (ctx.loadConfig ?? loadConfig)();
|
|
116
80
|
|
|
117
81
|
// Determine which index.json to read
|
|
118
82
|
let jsonPath: string;
|
|
119
|
-
if (
|
|
83
|
+
if (args.all) {
|
|
120
84
|
jsonPath = join(cfg.stateDir, "index.json");
|
|
121
85
|
} else {
|
|
122
|
-
const gitRemote = getGitRemote(
|
|
123
|
-
const identity = deriveProjectIdentity({ cwd:
|
|
86
|
+
const gitRemote = getGitRemote(ctx.cwd);
|
|
87
|
+
const identity = deriveProjectIdentity({ cwd: ctx.cwd, gitRemote });
|
|
124
88
|
jsonPath = join(cfg.stateDir, "projects", identity.slug, "index.json");
|
|
125
89
|
}
|
|
126
90
|
|
|
@@ -129,21 +93,19 @@ export async function lsCommand(argv: string[], ctx?: Partial<LsContext>): Promi
|
|
|
129
93
|
entries = await loadIndex(jsonPath);
|
|
130
94
|
} catch (err) {
|
|
131
95
|
const e = err as Error;
|
|
132
|
-
|
|
96
|
+
ctx.stderr.write(`cesium ls: failed to read index: ${e.message}\n`);
|
|
133
97
|
return 1;
|
|
134
98
|
}
|
|
135
99
|
|
|
136
|
-
|
|
137
|
-
const limited = entries.slice(0, limit);
|
|
100
|
+
const limited = entries.slice(0, args.limit);
|
|
138
101
|
|
|
139
|
-
if (
|
|
140
|
-
|
|
102
|
+
if (args.json) {
|
|
103
|
+
ctx.stdout.write(JSON.stringify(limited, null, 2) + "\n");
|
|
141
104
|
return 0;
|
|
142
105
|
}
|
|
143
106
|
|
|
144
|
-
// Table output
|
|
145
107
|
if (limited.length === 0) {
|
|
146
|
-
|
|
108
|
+
ctx.stdout.write("No artifacts found.\n");
|
|
147
109
|
return 0;
|
|
148
110
|
}
|
|
149
111
|
|
|
@@ -165,8 +127,8 @@ export async function lsCommand(argv: string[], ctx?: Partial<LsContext>): Promi
|
|
|
165
127
|
|
|
166
128
|
const sep = "─".repeat(header.length);
|
|
167
129
|
|
|
168
|
-
|
|
169
|
-
|
|
130
|
+
ctx.stdout.write(header + "\n");
|
|
131
|
+
ctx.stdout.write(sep + "\n");
|
|
170
132
|
|
|
171
133
|
for (const e of limited) {
|
|
172
134
|
const row =
|
|
@@ -179,8 +141,43 @@ export async function lsCommand(argv: string[], ctx?: Partial<LsContext>): Promi
|
|
|
179
141
|
fmtDate(e.createdAt).padEnd(COL_DATE) +
|
|
180
142
|
" " +
|
|
181
143
|
superCol(e);
|
|
182
|
-
|
|
144
|
+
ctx.stdout.write(row + "\n");
|
|
183
145
|
}
|
|
184
146
|
|
|
185
147
|
return 0;
|
|
186
148
|
}
|
|
149
|
+
|
|
150
|
+
export const lsCmd = defineCommand({
|
|
151
|
+
meta: {
|
|
152
|
+
name: "ls",
|
|
153
|
+
description: "List artifacts in the current project (or all with --all).",
|
|
154
|
+
},
|
|
155
|
+
args: {
|
|
156
|
+
all: {
|
|
157
|
+
type: "boolean",
|
|
158
|
+
alias: "a",
|
|
159
|
+
default: false,
|
|
160
|
+
description: "Show artifacts for all projects (default: current project only)",
|
|
161
|
+
},
|
|
162
|
+
json: {
|
|
163
|
+
type: "boolean",
|
|
164
|
+
default: false,
|
|
165
|
+
description: "Output as JSON array",
|
|
166
|
+
},
|
|
167
|
+
limit: {
|
|
168
|
+
type: "string",
|
|
169
|
+
alias: "n",
|
|
170
|
+
default: "50",
|
|
171
|
+
description: "Show at most N most recent artifacts",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
async run({ args }) {
|
|
175
|
+
const limit = parseInt(args.limit, 10);
|
|
176
|
+
if (isNaN(limit) || limit < 1) {
|
|
177
|
+
process.stderr.write(`cesium ls: --limit must be a positive integer\n`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
const code = await runLs({ all: args.all, json: args.json, limit });
|
|
181
|
+
if (code !== 0) process.exit(code);
|
|
182
|
+
},
|
|
183
|
+
});
|
package/src/cli/commands/open.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cesium open — find an artifact by id prefix and open it in the browser.
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { platform } from "node:os";
|
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
} from "../../server/lifecycle.ts";
|
|
16
16
|
import { resolveDisplayHost } from "../../tools/publish.ts";
|
|
17
17
|
|
|
18
|
+
export interface OpenArgs {
|
|
19
|
+
idPrefix: string;
|
|
20
|
+
print: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
export interface OpenContext {
|
|
19
24
|
stdout: { write: (s: string) => void };
|
|
20
25
|
stderr: { write: (s: string) => void };
|
|
@@ -90,59 +95,16 @@ async function tryGetHttpUrl(
|
|
|
90
95
|
return null;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
export async function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
let values: { print: boolean; help: boolean };
|
|
97
|
-
let positionals: string[];
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const parsed = parseArgs({
|
|
101
|
-
args: argv,
|
|
102
|
-
options: {
|
|
103
|
-
print: { type: "boolean", default: false },
|
|
104
|
-
help: { type: "boolean", short: "h", default: false },
|
|
105
|
-
},
|
|
106
|
-
allowPositionals: true,
|
|
107
|
-
strict: true,
|
|
108
|
-
});
|
|
109
|
-
values = parsed.values as typeof values;
|
|
110
|
-
positionals = parsed.positionals;
|
|
111
|
-
} catch (err) {
|
|
112
|
-
const e = err as Error;
|
|
113
|
-
resolved.stderr.write(`cesium open: ${e.message}\n`);
|
|
114
|
-
resolved.stderr.write(`Usage: cesium open <id-prefix> [--print]\n`);
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (values.help) {
|
|
119
|
-
resolved.stdout.write(
|
|
120
|
-
[
|
|
121
|
-
"Usage: cesium open <id-prefix> [options]",
|
|
122
|
-
"",
|
|
123
|
-
"Options:",
|
|
124
|
-
" --print Print the URL instead of opening in the browser",
|
|
125
|
-
" --help, -h Show this help message",
|
|
126
|
-
"",
|
|
127
|
-
"Notes:",
|
|
128
|
-
" Browser launch is supported on macOS (open) and Linux (xdg-open).",
|
|
129
|
-
" On Windows, use --print to get the URL.",
|
|
130
|
-
"",
|
|
131
|
-
].join("\n"),
|
|
132
|
-
);
|
|
133
|
-
return 0;
|
|
134
|
-
}
|
|
98
|
+
export async function runOpen(args: OpenArgs, ctxOverride?: Partial<OpenContext>): Promise<number> {
|
|
99
|
+
const ctx: OpenContext = { ...defaultCtx(), ...ctxOverride };
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
resolved.stderr.write(`cesium open: missing required argument <id-prefix>\n`);
|
|
139
|
-
resolved.stderr.write(`Usage: cesium open <id-prefix> [--print]\n`);
|
|
101
|
+
if (args.idPrefix.length === 0) {
|
|
102
|
+
ctx.stderr.write(`cesium open: missing required argument <id-prefix>\n`);
|
|
140
103
|
return 1;
|
|
141
104
|
}
|
|
142
105
|
|
|
143
|
-
const prefixLower = idPrefix.toLowerCase();
|
|
144
|
-
|
|
145
|
-
const cfg = (resolved.loadConfig ?? loadConfig)();
|
|
106
|
+
const prefixLower = args.idPrefix.toLowerCase();
|
|
107
|
+
const cfg = (ctx.loadConfig ?? loadConfig)();
|
|
146
108
|
|
|
147
109
|
// Search global index for matches
|
|
148
110
|
const globalJsonPath = join(cfg.stateDir, "index.json");
|
|
@@ -151,23 +113,23 @@ export async function openCommand(argv: string[], ctx?: Partial<OpenContext>): P
|
|
|
151
113
|
allEntries = await loadIndex(globalJsonPath);
|
|
152
114
|
} catch (err) {
|
|
153
115
|
const e = err as Error;
|
|
154
|
-
|
|
116
|
+
ctx.stderr.write(`cesium open: failed to read index: ${e.message}\n`);
|
|
155
117
|
return 1;
|
|
156
118
|
}
|
|
157
119
|
|
|
158
120
|
const matches = allEntries.filter((e) => e.id.toLowerCase().startsWith(prefixLower));
|
|
159
121
|
|
|
160
122
|
if (matches.length === 0) {
|
|
161
|
-
|
|
123
|
+
ctx.stderr.write(`cesium open: no artifact found with id prefix "${args.idPrefix}"\n`);
|
|
162
124
|
return 1;
|
|
163
125
|
}
|
|
164
126
|
|
|
165
127
|
if (matches.length > 1) {
|
|
166
|
-
|
|
167
|
-
`cesium open: ambiguous prefix "${idPrefix}" — ${matches.length} matches:\n`,
|
|
128
|
+
ctx.stderr.write(
|
|
129
|
+
`cesium open: ambiguous prefix "${args.idPrefix}" — ${matches.length} matches:\n`,
|
|
168
130
|
);
|
|
169
131
|
for (const m of matches) {
|
|
170
|
-
|
|
132
|
+
ctx.stderr.write(` ${m.id} ${m.title} (${m.kind})\n`);
|
|
171
133
|
}
|
|
172
134
|
return 2;
|
|
173
135
|
}
|
|
@@ -175,7 +137,7 @@ export async function openCommand(argv: string[], ctx?: Partial<OpenContext>): P
|
|
|
175
137
|
const entry = matches[0];
|
|
176
138
|
if (entry === undefined) {
|
|
177
139
|
// Unreachable: guarded by matches.length === 1, but satisfies the type checker
|
|
178
|
-
|
|
140
|
+
ctx.stderr.write(`cesium open: internal error — no match\n`);
|
|
179
141
|
return 1;
|
|
180
142
|
}
|
|
181
143
|
const paths = pathsFor({
|
|
@@ -185,24 +147,47 @@ export async function openCommand(argv: string[], ctx?: Partial<OpenContext>): P
|
|
|
185
147
|
});
|
|
186
148
|
|
|
187
149
|
// Resolve URL
|
|
188
|
-
const runEnsureRunning =
|
|
150
|
+
const runEnsureRunning = ctx.ensureRunning ?? ensureRunning;
|
|
189
151
|
const httpUrl = await tryGetHttpUrl(cfg, paths.serverPath, runEnsureRunning);
|
|
190
152
|
const url = httpUrl ?? paths.fileUrl;
|
|
191
153
|
|
|
192
|
-
if (
|
|
193
|
-
|
|
154
|
+
if (args.print) {
|
|
155
|
+
ctx.stdout.write(url + "\n");
|
|
194
156
|
return 0;
|
|
195
157
|
}
|
|
196
158
|
|
|
197
|
-
const open =
|
|
159
|
+
const open = ctx.opener ?? defaultOpener;
|
|
198
160
|
try {
|
|
199
161
|
await open(url);
|
|
200
162
|
} catch (err) {
|
|
201
163
|
const e = err as Error;
|
|
202
|
-
|
|
203
|
-
|
|
164
|
+
ctx.stderr.write(`cesium open: ${e.message}\n`);
|
|
165
|
+
ctx.stdout.write(`URL: ${url}\n`);
|
|
204
166
|
return 1;
|
|
205
167
|
}
|
|
206
168
|
|
|
207
169
|
return 0;
|
|
208
170
|
}
|
|
171
|
+
|
|
172
|
+
export const openCmd = defineCommand({
|
|
173
|
+
meta: {
|
|
174
|
+
name: "open",
|
|
175
|
+
description: "Open an artifact by id prefix in the browser.",
|
|
176
|
+
},
|
|
177
|
+
args: {
|
|
178
|
+
idPrefix: {
|
|
179
|
+
type: "positional",
|
|
180
|
+
description: "Artifact id prefix (any unique substring of the id)",
|
|
181
|
+
required: true,
|
|
182
|
+
},
|
|
183
|
+
print: {
|
|
184
|
+
type: "boolean",
|
|
185
|
+
default: false,
|
|
186
|
+
description: "Print the URL instead of opening in the browser",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
async run({ args }) {
|
|
190
|
+
const code = await runOpen({ idPrefix: args.idPrefix, print: args.print });
|
|
191
|
+
if (code !== 0) process.exit(code);
|
|
192
|
+
},
|
|
193
|
+
});
|