@cfbender/cesium 0.5.2 → 0.6.1

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,10 +1,15 @@
1
1
  // cesium stop — kill the running cesium server cross-process via PID file.
2
2
 
3
- import { parseArgs } from "node:util";
3
+ import { defineCommand } from "citty";
4
4
  import { loadConfig, type CesiumConfig } from "../../config.ts";
5
5
  import { stopServer } from "../../server/stop.ts";
6
6
  import type { StopServerArgs } from "../../server/stop.ts";
7
7
 
8
+ export interface StopArgs {
9
+ force: boolean;
10
+ timeoutMs: number;
11
+ }
12
+
8
13
  export interface StopContext {
9
14
  stdout: { write: (s: string) => void };
10
15
  stderr: { write: (s: string) => void };
@@ -22,109 +27,71 @@ function defaultCtx(): StopContext {
22
27
  };
23
28
  }
24
29
 
25
- export interface StopOptions {
26
- force: boolean;
27
- timeout: number;
28
- }
29
-
30
- /** Parse stop-command argv. Returns null on parse error. */
31
- export function parseStopArgs(
32
- argv: string[],
33
- ctx: Pick<StopContext, "stdout" | "stderr">,
34
- ): StopOptions | null | "help" {
35
- let values: { force: boolean; timeout: string | undefined; help: boolean };
30
+ export async function runStop(args: StopArgs, ctxOverride?: Partial<StopContext>): Promise<number> {
31
+ const ctx: StopContext = { ...defaultCtx(), ...ctxOverride };
36
32
 
37
- try {
38
- const parsed = parseArgs({
39
- args: argv,
40
- options: {
41
- force: { type: "boolean", short: "f", default: false },
42
- timeout: { type: "string" },
43
- help: { type: "boolean", short: "h", default: false },
44
- },
45
- allowPositionals: false,
46
- strict: true,
47
- });
48
- values = parsed.values as typeof values;
49
- } catch (err) {
50
- const e = err as Error;
51
- ctx.stderr.write(`cesium stop: ${e.message}\n`);
52
- ctx.stderr.write(`Usage: cesium stop [--force] [--timeout <ms>]\n`);
53
- return null;
54
- }
55
-
56
- if (values.help) {
57
- ctx.stdout.write(
58
- [
59
- "Usage: cesium stop [options]",
60
- "",
61
- "Options:",
62
- " --force, -f SIGKILL immediately — skip the SIGTERM grace period",
63
- " --timeout <ms> Grace period in ms before SIGKILL (default: 3000)",
64
- " --help, -h Show this help message",
65
- "",
66
- "Stops the running cesium server via its PID file. Idempotent when no",
67
- "server is running.",
68
- "",
69
- ].join("\n"),
70
- );
71
- return "help";
72
- }
73
-
74
- let timeout = 3000;
75
- if (values.timeout !== undefined) {
76
- const t = parseInt(values.timeout, 10);
77
- if (isNaN(t) || t < 0) {
78
- ctx.stderr.write(`cesium stop: --timeout must be a non-negative integer\n`);
79
- return null;
80
- }
81
- timeout = t;
33
+ if (!Number.isInteger(args.timeoutMs) || args.timeoutMs < 0) {
34
+ ctx.stderr.write(`cesium stop: --timeout must be a non-negative integer\n`);
35
+ return 1;
82
36
  }
83
37
 
84
- return { force: values.force, timeout };
85
- }
86
-
87
- export async function stopCommand(argv: string[], ctx?: Partial<StopContext>): Promise<number> {
88
- const resolved: StopContext = { ...defaultCtx(), ...ctx };
89
-
90
- const parseResult = parseStopArgs(argv, resolved);
91
- if (parseResult === null) return 1;
92
- if (parseResult === "help") return 0;
93
-
94
- const opts = parseResult;
95
- const cfg = (resolved.loadConfig ?? loadConfig)();
38
+ const cfg = (ctx.loadConfig ?? loadConfig)();
96
39
 
97
40
  const stopArgs: StopServerArgs = {
98
41
  stateDir: cfg.stateDir,
99
- force: opts.force,
100
- timeoutMs: opts.timeout,
42
+ force: args.force,
43
+ timeoutMs: args.timeoutMs,
101
44
  };
102
- if (resolved.isAlive !== undefined) {
103
- stopArgs.isAlive = resolved.isAlive;
104
- }
105
- if (resolved.killProcess !== undefined) {
106
- stopArgs.killProcess = resolved.killProcess;
107
- }
108
- if (resolved.sleep !== undefined) {
109
- stopArgs.sleep = resolved.sleep;
110
- }
45
+ if (ctx.isAlive !== undefined) stopArgs.isAlive = ctx.isAlive;
46
+ if (ctx.killProcess !== undefined) stopArgs.killProcess = ctx.killProcess;
47
+ if (ctx.sleep !== undefined) stopArgs.sleep = ctx.sleep;
111
48
 
112
49
  const outcome = await stopServer(stopArgs);
113
50
 
114
51
  switch (outcome.kind) {
115
52
  case "not-running":
116
- resolved.stdout.write("no cesium server running\n");
53
+ ctx.stdout.write("no cesium server running\n");
117
54
  return 0;
118
55
  case "stale":
119
- resolved.stdout.write("server not running (stale PID file removed)\n");
56
+ ctx.stdout.write("server not running (stale PID file removed)\n");
120
57
  return 0;
121
58
  case "stopped":
122
- resolved.stdout.write(`stopped cesium server (pid ${outcome.pid}, port ${outcome.port})\n`);
59
+ ctx.stdout.write(`stopped cesium server (pid ${outcome.pid}, port ${outcome.port})\n`);
123
60
  return 0;
124
61
  case "permission-denied":
125
- resolved.stderr.write(
62
+ ctx.stderr.write(
126
63
  `cesium stop: permission denied — process ${outcome.pid} is owned by another user\n`,
127
64
  );
128
65
  return 2;
129
66
  }
130
67
  }
68
+
69
+ export const stopCmd = defineCommand({
70
+ meta: {
71
+ name: "stop",
72
+ description:
73
+ "Stop the running cesium server via its PID file. Idempotent when no server is running.",
74
+ },
75
+ args: {
76
+ force: {
77
+ type: "boolean",
78
+ alias: "f",
79
+ default: false,
80
+ description: "SIGKILL immediately — skip the SIGTERM grace period",
81
+ },
82
+ timeout: {
83
+ type: "string",
84
+ default: "3000",
85
+ description: "Grace period in ms before SIGKILL",
86
+ },
87
+ },
88
+ async run({ args }) {
89
+ const t = parseInt(args.timeout, 10);
90
+ if (isNaN(t) || t < 0) {
91
+ process.stderr.write(`cesium stop: --timeout must be a non-negative integer\n`);
92
+ process.exit(1);
93
+ }
94
+ const code = await runStop({ force: args.force, timeoutMs: t });
95
+ if (code !== 0) process.exit(code);
96
+ },
97
+ });
@@ -1,6 +1,6 @@
1
1
  // cesium theme — show or apply the configured theme.
2
2
 
3
- import { parseArgs } from "node:util";
3
+ import { defineCommand } from "citty";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { loadConfig, type CesiumConfig } from "../../config.ts";
6
6
  import {
@@ -9,9 +9,8 @@ import {
9
9
  type ThemeTokens,
10
10
  type ThemePalette,
11
11
  } from "../../render/theme.ts";
12
- import { writeThemeCss, themeCssPath } from "../../storage/theme-write.ts";
12
+ import { writeThemeCss, themeCssPath, buildThemeCss } from "../../storage/theme-write.ts";
13
13
  import { writeFaviconSvg } from "../../storage/favicon-write.ts";
14
- import { themeTokensCss } from "../../render/theme.ts";
15
14
  import { atomicWrite } from "../../storage/write.ts";
16
15
  import { readdir } from "node:fs/promises";
17
16
  import { join } from "node:path";
@@ -75,7 +74,7 @@ function printTokenTable(
75
74
  }
76
75
 
77
76
  async function isWriteNeeded(cssPath: string, theme: ThemeTokens): Promise<boolean> {
78
- const expected = themeTokensCss(theme) + "\n";
77
+ const expected = buildThemeCss(theme);
79
78
  try {
80
79
  const existing = await readFile(cssPath, "utf8");
81
80
  return existing !== expected;
@@ -205,61 +204,12 @@ async function retrofitAll(
205
204
 
206
205
  // ─── Command ──────────────────────────────────────────────────────────────────
207
206
 
208
- export async function themeCommand(argv: string[], ctx?: Partial<ThemeContext>): Promise<number> {
209
- const resolved: ThemeContext = { ...defaultCtx(), ...ctx };
210
-
211
- const subcommand = argv[0];
212
- const rest = argv.slice(1);
213
-
214
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
215
- resolved.stdout.write(
216
- [
217
- "Usage: cesium theme <subcommand> [options]",
218
- "",
219
- "Subcommands:",
220
- " show Print resolved theme tokens",
221
- " apply [--rewrite-artifacts] Write theme.css from current config",
222
- "",
223
- "Options:",
224
- " --help, -h Show this help message",
225
- "",
226
- ].join("\n"),
227
- );
228
- return subcommand ? 0 : 1;
229
- }
230
-
231
- if (subcommand === "show") {
232
- return themeShowCommand(rest, resolved);
233
- }
234
-
235
- if (subcommand === "apply") {
236
- return themeApplyCommand(rest, resolved);
237
- }
238
-
239
- resolved.stderr.write(`cesium theme: unknown subcommand: ${subcommand}\n`);
240
- return 1;
207
+ export interface ThemeApplyArgs {
208
+ rewriteArtifacts: boolean;
241
209
  }
242
210
 
243
- async function themeShowCommand(argv: string[], ctx: ThemeContext): Promise<number> {
244
- let values: { help: boolean };
245
- try {
246
- const parsed = parseArgs({
247
- args: argv,
248
- options: { help: { type: "boolean", short: "h", default: false } },
249
- allowPositionals: false,
250
- strict: true,
251
- });
252
- values = parsed.values as typeof values;
253
- } catch (err) {
254
- const e = err as Error;
255
- ctx.stderr.write(`cesium theme show: ${e.message}\n`);
256
- return 1;
257
- }
258
-
259
- if (values.help) {
260
- ctx.stdout.write("Usage: cesium theme show\n\nPrint resolved theme tokens.\n\n");
261
- return 0;
262
- }
211
+ export async function runThemeShow(ctxOverride?: Partial<ThemeContext>): Promise<number> {
212
+ const ctx: ThemeContext = { ...defaultCtx(), ...ctxOverride };
263
213
 
264
214
  const cfg = (ctx.loadConfig ?? loadConfig)();
265
215
  const { theme, presetLabel } = resolveTheme(cfg);
@@ -270,47 +220,18 @@ async function themeShowCommand(argv: string[], ctx: ThemeContext): Promise<numb
270
220
  return 0;
271
221
  }
272
222
 
273
- async function themeApplyCommand(argv: string[], ctx: ThemeContext): Promise<number> {
274
- let values: { "rewrite-artifacts": boolean; help: boolean };
275
- try {
276
- const parsed = parseArgs({
277
- args: argv,
278
- options: {
279
- "rewrite-artifacts": { type: "boolean", default: false },
280
- help: { type: "boolean", short: "h", default: false },
281
- },
282
- allowPositionals: false,
283
- strict: true,
284
- });
285
- values = parsed.values as typeof values;
286
- } catch (err) {
287
- const e = err as Error;
288
- ctx.stderr.write(`cesium theme apply: ${e.message}\n`);
289
- return 1;
290
- }
291
-
292
- if (values.help) {
293
- ctx.stdout.write(
294
- [
295
- "Usage: cesium theme apply [--rewrite-artifacts]",
296
- "",
297
- "Write theme.css from the current config.",
298
- "",
299
- "Options:",
300
- " --rewrite-artifacts Retrofit existing artifacts and index pages with the theme link",
301
- " --help, -h Show this help message",
302
- "",
303
- ].join("\n"),
304
- );
305
- return 0;
306
- }
223
+ export async function runThemeApply(
224
+ args: ThemeApplyArgs,
225
+ ctxOverride?: Partial<ThemeContext>,
226
+ ): Promise<number> {
227
+ const ctx: ThemeContext = { ...defaultCtx(), ...ctxOverride };
307
228
 
308
229
  const cfg = (ctx.loadConfig ?? loadConfig)();
309
230
  const { theme, presetLabel } = resolveTheme(cfg);
310
231
  const cssPath = await writeThemeCss(cfg.stateDir, theme);
311
232
  await writeFaviconSvg(cfg.stateDir);
312
233
 
313
- if (values["rewrite-artifacts"]) {
234
+ if (args.rewriteArtifacts) {
314
235
  const { artifacts, indexes } = await retrofitAll(cfg.stateDir, ctx.stdout);
315
236
  ctx.stdout.write(
316
237
  [
@@ -333,3 +254,44 @@ async function themeApplyCommand(argv: string[], ctx: ThemeContext): Promise<num
333
254
 
334
255
  return 0;
335
256
  }
257
+
258
+ const themeShowCmd = defineCommand({
259
+ meta: {
260
+ name: "show",
261
+ description: "Print resolved theme tokens.",
262
+ },
263
+ args: {},
264
+ async run() {
265
+ const code = await runThemeShow();
266
+ if (code !== 0) process.exit(code);
267
+ },
268
+ });
269
+
270
+ const themeApplyCmd = defineCommand({
271
+ meta: {
272
+ name: "apply",
273
+ description: "Write theme.css from the current config.",
274
+ },
275
+ args: {
276
+ "rewrite-artifacts": {
277
+ type: "boolean",
278
+ default: false,
279
+ description: "Retrofit existing artifacts and index pages with the theme link",
280
+ },
281
+ },
282
+ async run({ args }) {
283
+ const code = await runThemeApply({ rewriteArtifacts: args["rewrite-artifacts"] });
284
+ if (code !== 0) process.exit(code);
285
+ },
286
+ });
287
+
288
+ export const themeCmd = defineCommand({
289
+ meta: {
290
+ name: "theme",
291
+ description: "Show or apply the configured theme.",
292
+ },
293
+ subCommands: {
294
+ show: themeShowCmd,
295
+ apply: themeApplyCmd,
296
+ },
297
+ });
package/src/cli/index.ts CHANGED
@@ -1,78 +1,26 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { parseArgs as _parseArgs } from "node:util";
3
+ import { defineCommand, runMain } from "citty";
4
4
  import pkg from "../../package.json" with { type: "json" };
5
- import { lsCommand } from "./commands/ls.ts";
6
- import { openCommand } from "./commands/open.ts";
7
- import { serveCommand } from "./commands/serve.ts";
8
- import { stopCommand } from "./commands/stop.ts";
9
- import { restartCommand } from "./commands/restart.ts";
10
- import { pruneCommand } from "./commands/prune.ts";
11
- import { themeCommand } from "./commands/theme.ts";
12
-
13
- const subcommand = process.argv[2];
14
- const rest = process.argv.slice(3);
15
5
 
16
6
  export const CESIUM_VERSION: string = pkg.version;
17
7
 
18
- const COMMANDS: Record<string, (argv: string[]) => Promise<number>> = {
19
- ls: lsCommand,
20
- open: openCommand,
21
- serve: serveCommand,
22
- stop: stopCommand,
23
- restart: restartCommand,
24
- prune: pruneCommand,
25
- theme: themeCommand,
26
- version: async () => {
27
- process.stdout.write(`cesium ${CESIUM_VERSION}\n`);
28
- return 0;
8
+ const main = defineCommand({
9
+ meta: {
10
+ name: "cesium",
11
+ version: pkg.version,
12
+ description: "artifact manager for opencode sessions",
29
13
  },
30
- };
31
-
32
- function printHelp(): void {
33
- process.stdout.write(
34
- [
35
- "cesium artifact manager for opencode sessions",
36
- "",
37
- "Usage: cesium <command> [options]",
38
- "",
39
- "Commands:",
40
- " ls List artifacts in the current project (or all with --all)",
41
- " open Open an artifact by id prefix in the browser",
42
- " serve Start the local HTTP server in the foreground",
43
- " stop Stop the running cesium server",
44
- " restart Stop and re-start the cesium server",
45
- " prune Delete artifacts older than a given duration",
46
- " theme Show or apply the configured theme",
47
- " version Print the cesium version",
48
- "",
49
- "Options:",
50
- " --help, -h Show this help message",
51
- " --version, -v Print the cesium version",
52
- "",
53
- "Run 'cesium <command> --help' for command-specific options.",
54
- "",
55
- ].join("\n"),
56
- );
57
- }
58
-
59
- async function main(): Promise<void> {
60
- if (subcommand === "--version" || subcommand === "-v") {
61
- process.stdout.write(`cesium ${CESIUM_VERSION}\n`);
62
- process.exit(0);
63
- }
64
- if (!subcommand || subcommand === "--help" || subcommand === "-h") {
65
- printHelp();
66
- process.exit(subcommand ? 0 : 1);
67
- }
68
- const fn = COMMANDS[subcommand];
69
- if (!fn) {
70
- process.stderr.write(`cesium: unknown command: ${subcommand}\n`);
71
- printHelp();
72
- process.exit(1);
73
- }
74
- const code = await fn(rest);
75
- process.exit(code);
76
- }
14
+ subCommands: {
15
+ ls: () => import("./commands/ls.ts").then((m) => m.lsCmd),
16
+ open: () => import("./commands/open.ts").then((m) => m.openCmd),
17
+ export: () => import("./commands/export.ts").then((m) => m.exportCmd),
18
+ serve: () => import("./commands/serve.ts").then((m) => m.serveCmd),
19
+ stop: () => import("./commands/stop.ts").then((m) => m.stopCmd),
20
+ restart: () => import("./commands/restart.ts").then((m) => m.restartCmd),
21
+ prune: () => import("./commands/prune.ts").then((m) => m.pruneCmd),
22
+ theme: () => import("./commands/theme.ts").then((m) => m.themeCmd),
23
+ },
24
+ });
77
25
 
78
- await main();
26
+ await runMain(main);
@@ -318,6 +318,13 @@ h1, h2, h3, h4, h5, h6 {
318
318
  border-radius: 12px;
319
319
  padding: 18px 22px;
320
320
  margin-bottom: 1.5em;
321
+ /* contain wide children (tables, long URLs, code) inside the card.
322
+ * min-width:0 lets the card shrink in grid/flex contexts (.cards-grid)
323
+ * so it actually obeys its track instead of growing to its widest child.
324
+ * overflow-x:auto then scrolls any content that's STILL too wide
325
+ * (e.g. a many-column table) rather than bursting the card border. */
326
+ min-width: 0;
327
+ overflow-x: auto;
321
328
  }
322
329
 
323
330
  /* tldr */
@@ -329,6 +336,8 @@ h1, h2, h3, h4, h5, h6 {
329
336
  margin-bottom: 1.5em;
330
337
  font-size: 1.05rem;
331
338
  color: var(--ink-soft);
339
+ min-width: 0;
340
+ overflow-x: auto;
332
341
  }
333
342
 
334
343
  /* callout */
@@ -340,6 +349,8 @@ h1, h2, h3, h4, h5, h6 {
340
349
  background: var(--surface-2);
341
350
  color: var(--ink-soft);
342
351
  font-size: 0.95rem;
352
+ min-width: 0;
353
+ overflow-x: auto;
343
354
  }
344
355
  .callout.note { border-color: var(--olive); background: color-mix(in srgb, var(--olive) 10%, var(--surface)); }
345
356
  .callout.warn { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
@@ -573,6 +584,11 @@ figure.code figcaption {
573
584
  padding: 10px 14px;
574
585
  text-align: left;
575
586
  vertical-align: top;
587
+ /* let long URLs / identifiers / paths wrap inside the cell instead of
588
+ * pushing the table beyond its container. Many-column tables that are
589
+ * still wider than the card fall through to the card's overflow-x. */
590
+ overflow-wrap: anywhere;
591
+ word-break: break-word;
576
592
  }
577
593
  .compare-table th {
578
594
  background: var(--surface-2);
@@ -594,6 +610,8 @@ figure.code figcaption {
594
610
  padding: 10px 14px;
595
611
  text-align: left;
596
612
  vertical-align: top;
613
+ overflow-wrap: anywhere;
614
+ word-break: break-word;
597
615
  }
598
616
  .risk-table th {
599
617
  background: var(--surface-2);
@@ -1,7 +1,7 @@
1
1
  // Assembles the full <!doctype html> document from a body fragment + metadata.
2
2
 
3
3
  import { type ThemeTokens } from "./theme.ts";
4
- import { fallbackCss } from "./fallback.ts";
4
+ import { buildThemeCss } from "../storage/theme-write.ts";
5
5
  import { renderControl, renderAnswered } from "./controls.ts";
6
6
  import { getClientJs } from "./client-js.ts";
7
7
  import { faviconLinkTag } from "./favicon.ts";
@@ -131,7 +131,7 @@ function renderFooter(meta: ArtifactMeta): string {
131
131
  }
132
132
 
133
133
  export function wrapDocument(opts: WrapOptions): string {
134
- const { body, meta, warnings = [], interactive } = opts;
134
+ const { body, meta, theme, warnings = [], interactive } = opts;
135
135
  // Default href: artifact context (three levels deep from stateDir)
136
136
  const href =
137
137
  opts.themeCssHref === undefined
@@ -142,7 +142,12 @@ export function wrapDocument(opts: WrapOptions): string {
142
142
  // Suppress <link> when null is explicitly passed
143
143
  const suppressLink = opts.themeCssHref === null;
144
144
 
145
- const fallback = fallbackCss();
145
+ // Bake the full theme CSS into every artifact so it's genuinely
146
+ // self-contained when opened standalone. When served by the cesium HTTP
147
+ // server, the <link> below still loads and overrides the inline rules in
148
+ // cascade order — so theme upgrades retroactively apply to served artifacts
149
+ // while standalone copies retain their generation-time look.
150
+ const themeCss = buildThemeCss(theme);
146
151
  // Embed interactive into the cesium-meta JSON block when present
147
152
  const metaPayload: Record<string, unknown> = { ...meta };
148
153
  if (interactive !== undefined) {
@@ -172,8 +177,7 @@ export function wrapDocument(opts: WrapOptions): string {
172
177
  <meta charset="utf-8">
173
178
  <meta name="viewport" content="width=device-width, initial-scale=1">
174
179
  <title>${titleEsc} · cesium</title>
175
- <style>/* fallback — standalone-readable; full styles served from /theme.css */
176
- ${fallback}</style>${linkTag}${faviconTag}
180
+ <style>${themeCss}</style>${linkTag}${faviconTag}
177
181
  <script type="application/json" id="cesium-meta">${metaJson}</script>
178
182
  </head>
179
183
  <body>