@cfbender/cesium 0.3.6 → 0.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.
- package/CHANGELOG.md +53 -0
- package/README.md +28 -14
- package/assets/styleguide.html +149 -0
- package/package.json +1 -1
- package/src/cli/commands/serve.ts +3 -0
- package/src/index.ts +4 -1
- package/src/prompt/field-reference.ts +94 -0
- package/src/prompt/system-fragment.md +56 -65
- package/src/render/blocks/catalog.ts +39 -0
- package/src/render/blocks/escape.ts +27 -0
- package/src/render/blocks/index.ts +6 -0
- package/src/render/blocks/markdown.ts +217 -0
- package/src/render/blocks/render.ts +96 -0
- package/src/render/blocks/renderers/callout.ts +38 -0
- package/src/render/blocks/renderers/code.ts +44 -0
- package/src/render/blocks/renderers/compare-table.ts +56 -0
- package/src/render/blocks/renderers/diagram.ts +48 -0
- package/src/render/blocks/renderers/divider.ts +31 -0
- package/src/render/blocks/renderers/hero.ts +66 -0
- package/src/render/blocks/renderers/kv.ts +45 -0
- package/src/render/blocks/renderers/list.ts +51 -0
- package/src/render/blocks/renderers/pill-row.ts +45 -0
- package/src/render/blocks/renderers/prose.ts +29 -0
- package/src/render/blocks/renderers/raw-html.ts +32 -0
- package/src/render/blocks/renderers/risk-table.ts +76 -0
- package/src/render/blocks/renderers/section.ts +95 -0
- package/src/render/blocks/renderers/timeline.ts +58 -0
- package/src/render/blocks/renderers/tldr.ts +30 -0
- package/src/render/blocks/types.ts +127 -0
- package/src/render/blocks/validate-block.ts +202 -0
- package/src/render/critique.ts +410 -10
- package/src/render/fallback.ts +18 -0
- package/src/render/theme.ts +235 -0
- package/src/render/validate.ts +282 -17
- package/src/render/wrap.ts +7 -7
- package/src/server/lifecycle.ts +7 -1
- package/src/storage/assets.ts +66 -0
- package/src/storage/index-cache.ts +1 -0
- package/src/storage/index-gen.ts +13 -14
- package/src/tools/ask.ts +5 -3
- package/src/tools/critique.ts +41 -6
- package/src/tools/publish.ts +39 -12
- package/src/tools/styleguide.ts +109 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.5.0 — 2026-05-12
|
|
4
|
+
|
|
5
|
+
Block-mode refactor — `cesium_publish` now accepts a structured `blocks` array
|
|
6
|
+
alongside the legacy `html` field. The server templates 15 block types from
|
|
7
|
+
JSON; raw HTML stays available as a per-block escape hatch (`raw_html`,
|
|
8
|
+
`diagram`). Framework CSS moves out of every artifact and into a single served
|
|
9
|
+
`/theme.css`. Styleguide is generated from a catalog at request time.
|
|
10
|
+
Critique is mode-aware. Expected savings on a balanced doc: roughly 2× output
|
|
11
|
+
tokens, more on heavily structured artifacts.
|
|
12
|
+
|
|
13
|
+
- **feat:** `cesium_publish({ blocks: Block[] })` — closed discriminated union
|
|
14
|
+
of 15 block types: `hero`, `tldr`, `section`, `prose`, `list`, `callout`,
|
|
15
|
+
`code`, `timeline`, `compare_table`, `risk_table`, `kv`, `pill_row`,
|
|
16
|
+
`divider`, `diagram`, `raw_html`. Mutually exclusive with `html`.
|
|
17
|
+
- **feat:** Owned markdown subset (~80 lines, no dependency) for `prose`,
|
|
18
|
+
`tldr`, `callout`, list items, and table cells. Supports paragraphs, lists,
|
|
19
|
+
blockquotes, hr, hard breaks, `**bold**`, `*italic*`, `` `code` ``, local
|
|
20
|
+
links, and the safelisted inline tags `<kbd>`, `<span class="pill">`,
|
|
21
|
+
`<span class="tag">`.
|
|
22
|
+
- **feat:** Sections recurse to depth 3; non-section children get auto-wrapped
|
|
23
|
+
in `<div class="card">` for visual consistency.
|
|
24
|
+
- **feat:** `theme.css` served from `<state-dir>/theme.css` with a small
|
|
25
|
+
inline fallback (~8 lines) so standalone-opened `.html` files remain
|
|
26
|
+
readable. Existing artifacts (with full CSS inlined) are never rewritten and
|
|
27
|
+
stay self-contained.
|
|
28
|
+
- **feat:** `cesium_styleguide` returns a markdown reference generated from
|
|
29
|
+
the block catalog at request time — schema, examples, and renderer can no
|
|
30
|
+
longer drift.
|
|
31
|
+
- **feat:** `cesium_critique` is mode-aware. `html` mode adds a soft
|
|
32
|
+
`prefer-blocks` nag; `blocks` mode focuses on quality (raw-html overuse,
|
|
33
|
+
prose walls, missing tldr on long docs, table-shape, redundant raw_html,
|
|
34
|
+
nesting depth). Findings carry path tags like `blocks[2].children[1]`.
|
|
35
|
+
- **feat:** Deep block validation walks the catalog schema per type. Returns
|
|
36
|
+
path-tagged errors with "did you mean" suggestions for common drift
|
|
37
|
+
(`label`→`k`, `value`→`v`, `description`→`text`, `med`→`medium`, etc.).
|
|
38
|
+
- **feat:** System prompt fragment generated from the block catalog at plugin
|
|
39
|
+
load time — drift between schema and prompt is now physically impossible.
|
|
40
|
+
- **feat:** `inputMode: "html" | "blocks"` recorded in artifact metadata and
|
|
41
|
+
surfaced as a small badge on index cards.
|
|
42
|
+
- **feat:** Framework CSS extended with rules for every block-renderer
|
|
43
|
+
pattern: `dl.kv` (2-column grid), `.pill-row`, `.check-list`, `<hr
|
|
44
|
+
data-label>`, `figure.code`, timeline-item internals, `.lede`, plus
|
|
45
|
+
`.diagram svg text { fill: currentColor }` so SVGs inherit theme color.
|
|
46
|
+
- **feat:** `escapeHtml` and `escapeAttr` throw a clear error on non-string
|
|
47
|
+
input instead of crashing inside `.replace()`.
|
|
48
|
+
- **fix:** `ensureThemeCss` respects the configured `themePreset` (regression
|
|
49
|
+
introduced when the framework CSS was first extracted to a served file).
|
|
50
|
+
- **fix:** `wait` test fixtures use relative timestamps so they don't go
|
|
51
|
+
stale.
|
|
52
|
+
- **docs:** `AGENTS.md` updated with the new project layout, two-input-modes
|
|
53
|
+
architecture, catalog-as-source-of-truth, and softened CSS portability
|
|
54
|
+
invariant ("no external network resources; local `/theme.css` is allowed").
|
|
55
|
+
|
|
3
56
|
## v0.3.6 — 2026-05-11
|
|
4
57
|
|
|
5
58
|
Adds a periodic-table-themed favicon for the cesium HTTP server.
|
package/README.md
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
<h1>
|
|
2
|
+
<img src="assets/favicon.svg" alt="" width="48" height="48" align="left" style="margin-right: 12px; vertical-align: middle;">
|
|
3
|
+
Cesium
|
|
4
|
+
</h1>
|
|
2
5
|
|
|
3
6
|
Cesium publishes substantive opencode agent responses — plans, code reviews,
|
|
4
7
|
comparisons, explainers, audits, RFCs — as self-contained beautiful HTML artifacts
|
|
@@ -6,14 +9,7 @@ on disk, instead of dumping markdown into the terminal. The browser becomes the
|
|
|
6
9
|
reading surface; the terminal stays the control surface. Each artifact is a single
|
|
7
10
|
`.html` file: portable, archivable, viewable offline, shareable as a URL over SSH.
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
form, wait for the user to answer in their browser, and receive the structured
|
|
11
|
-
responses before continuing work.
|
|
12
|
-
|
|
13
|
-
<video src="assets/cesium.mp4" autoplay loop muted playsinline width="720">
|
|
14
|
-
Demo video — see <a href="assets/cesium.mp4">assets/cesium.mp4</a> if it
|
|
15
|
-
doesn't play inline (some markdown viewers strip <code><video></code>).
|
|
16
|
-
</video>
|
|
12
|
+
https://github.com/user-attachments/assets/03fdf32a-c4d5-4819-84eb-d272178d35cb
|
|
17
13
|
|
|
18
14
|
## Examples
|
|
19
15
|
|
|
@@ -51,19 +47,37 @@ unreleased changes).
|
|
|
51
47
|
|
|
52
48
|
### CLI
|
|
53
49
|
|
|
50
|
+
The CLI puts a `cesium` binary on your `PATH` for browsing, opening, and
|
|
51
|
+
managing artifacts (`cesium ls`, `cesium open`, `cesium serve`, `cesium prune`,
|
|
52
|
+
`cesium theme`).
|
|
53
|
+
|
|
54
|
+
**Recommended: install with [mise](https://mise.jdx.dev/)** so cesium is pinned
|
|
55
|
+
in your config and tracks with the rest of your toolchain. Add to your
|
|
56
|
+
`~/.config/mise/config.toml` (or a project-local `mise.toml`):
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
[tools]
|
|
60
|
+
"npm:@cfbender/cesium" = "latest"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then run `mise install` (or `mise use -g npm:@cfbender/cesium@latest` for the
|
|
64
|
+
one-liner equivalent). Pin to a specific release with `"0.3.6"` instead of
|
|
65
|
+
`"latest"`. Upgrade with `mise upgrade npm:@cfbender/cesium`.
|
|
66
|
+
|
|
67
|
+
**Alternative: install with bun directly:**
|
|
68
|
+
|
|
54
69
|
```bash
|
|
55
70
|
bun install -g @cfbender/cesium
|
|
56
71
|
```
|
|
57
72
|
|
|
58
|
-
This puts
|
|
59
|
-
|
|
73
|
+
This puts the binary at `~/.bun/bin/cesium`. If `which cesium` returns nothing,
|
|
74
|
+
add `~/.bun/bin` to your shell rc:
|
|
60
75
|
|
|
61
76
|
```bash
|
|
62
77
|
export PATH="$HOME/.bun/bin:$PATH"
|
|
63
78
|
```
|
|
64
79
|
|
|
65
|
-
Upgrade
|
|
66
|
-
manager — e.g. `mise use -g npm:@cfbender/cesium@latest`). To uninstall:
|
|
80
|
+
Upgrade with `bun update -g @cfbender/cesium`. Uninstall with
|
|
67
81
|
`bun remove -g @cfbender/cesium`.
|
|
68
82
|
|
|
69
83
|
### Developing on cesium itself
|
|
@@ -471,7 +485,7 @@ Cesium took inspiration from:
|
|
|
471
485
|
- [@trq212's tweet](https://x.com/trq212/status/2052809885763747935) on
|
|
472
486
|
letting agents respond with HTML instead of dumping markdown into the
|
|
473
487
|
terminal — the seed idea for the whole project.
|
|
474
|
-
-
|
|
488
|
+
- [Octto](https://github.com/vtemian/octto) — for the model of an agent that publishes a live, browser-served
|
|
475
489
|
surface alongside the terminal, rather than replacing it.
|
|
476
490
|
|
|
477
491
|
## License
|
package/assets/styleguide.html
CHANGED
|
@@ -329,6 +329,89 @@
|
|
|
329
329
|
color: var(--muted);
|
|
330
330
|
text-transform: lowercase;
|
|
331
331
|
}
|
|
332
|
+
.pill.accent {
|
|
333
|
+
background: color-mix(in srgb, var(--accent) 18%, var(--surface));
|
|
334
|
+
color: var(--accent);
|
|
335
|
+
font-weight: 600;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* ranked list */
|
|
339
|
+
.ranked-list {
|
|
340
|
+
display: flex;
|
|
341
|
+
flex-direction: column;
|
|
342
|
+
gap: 1em;
|
|
343
|
+
margin: 0 0 1.5em;
|
|
344
|
+
padding: 0;
|
|
345
|
+
list-style: none;
|
|
346
|
+
}
|
|
347
|
+
.ranked-item {
|
|
348
|
+
background: var(--surface);
|
|
349
|
+
border: 1.5px solid var(--rule);
|
|
350
|
+
border-radius: 12px;
|
|
351
|
+
padding: 22px 26px;
|
|
352
|
+
display: grid;
|
|
353
|
+
grid-template-columns: 64px 1fr;
|
|
354
|
+
gap: 6px 24px;
|
|
355
|
+
align-items: start;
|
|
356
|
+
}
|
|
357
|
+
.ranked-item .rank-num {
|
|
358
|
+
font-family: var(--serif);
|
|
359
|
+
font-size: 2.4rem;
|
|
360
|
+
font-weight: 500;
|
|
361
|
+
color: var(--oat);
|
|
362
|
+
line-height: 1;
|
|
363
|
+
letter-spacing: -0.02em;
|
|
364
|
+
padding-top: 2px;
|
|
365
|
+
}
|
|
366
|
+
.ranked-item .rank-title {
|
|
367
|
+
font-family: var(--serif);
|
|
368
|
+
font-size: 1.2rem;
|
|
369
|
+
font-weight: 600;
|
|
370
|
+
color: var(--ink);
|
|
371
|
+
margin: 0 0 6px;
|
|
372
|
+
line-height: 1.3;
|
|
373
|
+
}
|
|
374
|
+
.ranked-item .rank-meta {
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: center;
|
|
377
|
+
gap: 8px;
|
|
378
|
+
flex-wrap: wrap;
|
|
379
|
+
margin-bottom: 14px;
|
|
380
|
+
}
|
|
381
|
+
.ranked-item .rank-body > p {
|
|
382
|
+
color: var(--ink-soft);
|
|
383
|
+
font-size: 0.95rem;
|
|
384
|
+
line-height: 1.65;
|
|
385
|
+
margin: 0 0 0.85em;
|
|
386
|
+
}
|
|
387
|
+
.ranked-item .rank-body > p:last-child {
|
|
388
|
+
margin-bottom: 0;
|
|
389
|
+
}
|
|
390
|
+
.ranked-item .rank-body > ul,
|
|
391
|
+
.ranked-item .rank-body > ol {
|
|
392
|
+
color: var(--ink-soft);
|
|
393
|
+
font-size: 0.95rem;
|
|
394
|
+
line-height: 1.65;
|
|
395
|
+
margin: 0 0 0.85em;
|
|
396
|
+
padding-left: 1.2em;
|
|
397
|
+
}
|
|
398
|
+
.ranked-item .rank-aside {
|
|
399
|
+
color: var(--muted);
|
|
400
|
+
font-size: 0.9rem;
|
|
401
|
+
line-height: 1.6;
|
|
402
|
+
padding-left: 14px;
|
|
403
|
+
border-left: 2px solid var(--rule);
|
|
404
|
+
margin: 0;
|
|
405
|
+
}
|
|
406
|
+
.ranked-item .rank-aside-label {
|
|
407
|
+
font-family: var(--mono);
|
|
408
|
+
font-size: 0.7rem;
|
|
409
|
+
font-weight: 600;
|
|
410
|
+
letter-spacing: 0.1em;
|
|
411
|
+
text-transform: uppercase;
|
|
412
|
+
color: var(--accent);
|
|
413
|
+
margin-right: 6px;
|
|
414
|
+
}
|
|
332
415
|
|
|
333
416
|
/* byline */
|
|
334
417
|
.byline {
|
|
@@ -845,6 +928,72 @@
|
|
|
845
928
|
</div>
|
|
846
929
|
</section>
|
|
847
930
|
|
|
931
|
+
<!-- ============================================================
|
|
932
|
+
Section 13 · Ranked list
|
|
933
|
+
============================================================ -->
|
|
934
|
+
<section>
|
|
935
|
+
<p class="eyebrow">13 · ranked-list</p>
|
|
936
|
+
<h2 class="h-section"><span class="section-num">13</span>Ranked list</h2>
|
|
937
|
+
<p>
|
|
938
|
+
Use <code>.ranked-list</code> + <code>.ranked-item</code> for ordered recommendations,
|
|
939
|
+
findings, or rankings — anything that would otherwise be a heavy numbered
|
|
940
|
+
<code><h3></code> sequence. Each item gets a soft serif numeral on the left and a
|
|
941
|
+
calm title + meta + body on the right. Pair <code>.pill.accent</code> +
|
|
942
|
+
<code>.tag</code> in the meta row for impact and savings; use <code>.rank-aside</code> for
|
|
943
|
+
a quieter "bonus" addendum.
|
|
944
|
+
</p>
|
|
945
|
+
|
|
946
|
+
<ol class="ranked-list">
|
|
947
|
+
<li class="ranked-item">
|
|
948
|
+
<div class="rank-num">01</div>
|
|
949
|
+
<div class="rank-body">
|
|
950
|
+
<h3 class="rank-title">Replace the styleguide payload with a compact catalog</h3>
|
|
951
|
+
<div class="rank-meta">
|
|
952
|
+
<span class="pill accent">High impact</span>
|
|
953
|
+
<span class="tag">~6–8k tokens / call</span>
|
|
954
|
+
</div>
|
|
955
|
+
<p>
|
|
956
|
+
Return a structured Markdown reference of <em>class → purpose → minimal example</em>
|
|
957
|
+
instead of the full HTML framework. Realistic target: 3–5 KB / ~1k tokens, down from
|
|
958
|
+
~7–9k.
|
|
959
|
+
</p>
|
|
960
|
+
<p class="rank-aside">
|
|
961
|
+
<span class="rank-aside-label">Bonus</span>
|
|
962
|
+
Derive the catalog from a single source so it can't drift from the framework.
|
|
963
|
+
</p>
|
|
964
|
+
</div>
|
|
965
|
+
</li>
|
|
966
|
+
<li class="ranked-item">
|
|
967
|
+
<div class="rank-num">02</div>
|
|
968
|
+
<div class="rank-body">
|
|
969
|
+
<h3 class="rank-title">Trim the system prompt fragment</h3>
|
|
970
|
+
<div class="rank-meta">
|
|
971
|
+
<span class="pill">Low impact</span>
|
|
972
|
+
<span class="tag">~400 tokens / session</span>
|
|
973
|
+
</div>
|
|
974
|
+
<p>
|
|
975
|
+
Remove redundancy with in-tool <code>description</code> strings. Nice but not
|
|
976
|
+
transformative.
|
|
977
|
+
</p>
|
|
978
|
+
</div>
|
|
979
|
+
</li>
|
|
980
|
+
<li class="ranked-item">
|
|
981
|
+
<div class="rank-num">03</div>
|
|
982
|
+
<div class="rank-body">
|
|
983
|
+
<h3 class="rank-title">Stop double-emitting CSS in <code>wrapDocument()</code></h3>
|
|
984
|
+
<div class="rank-meta">
|
|
985
|
+
<span class="pill">Disk only</span>
|
|
986
|
+
<span class="tag">~17 KB / artifact</span>
|
|
987
|
+
</div>
|
|
988
|
+
<p>
|
|
989
|
+
Inline the framework only when the linked stylesheet is unavailable; emit tokens
|
|
990
|
+
inline always.
|
|
991
|
+
</p>
|
|
992
|
+
</div>
|
|
993
|
+
</li>
|
|
994
|
+
</ol>
|
|
995
|
+
</section>
|
|
996
|
+
|
|
848
997
|
<!-- ============================================================
|
|
849
998
|
Footer / byline
|
|
850
999
|
============================================================ -->
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
import { loadConfig, type CesiumConfig } from "../../config.ts";
|
|
5
5
|
import { ensureRunning, stopRunning } from "../../server/lifecycle.ts";
|
|
6
6
|
import { resolveDisplayHost } from "../../tools/publish.ts";
|
|
7
|
+
import { themeFromPreset, mergeTheme } from "../../render/theme.ts";
|
|
7
8
|
|
|
8
9
|
export interface ServeContext {
|
|
9
10
|
stdout: { write: (s: string) => void };
|
|
@@ -160,12 +161,14 @@ export async function serveCommand(argv: string[], ctx?: Partial<ServeContext>):
|
|
|
160
161
|
|
|
161
162
|
let serverInfo: { port: number; url: string };
|
|
162
163
|
try {
|
|
164
|
+
const theme = mergeTheme(themeFromPreset(effectiveCfg.themePreset), effectiveCfg.theme);
|
|
163
165
|
serverInfo = await ensureRunning({
|
|
164
166
|
stateDir: effectiveCfg.stateDir,
|
|
165
167
|
port: effectiveCfg.port,
|
|
166
168
|
portMax: effectiveCfg.portMax,
|
|
167
169
|
idleTimeoutMs: effectiveCfg.idleTimeoutMs,
|
|
168
170
|
hostname: effectiveCfg.hostname,
|
|
171
|
+
theme,
|
|
169
172
|
});
|
|
170
173
|
} catch (err) {
|
|
171
174
|
const e = err as Error;
|
package/src/index.ts
CHANGED
|
@@ -10,12 +10,15 @@ import { createWaitTool } from "./tools/wait.ts";
|
|
|
10
10
|
import { createStyleguideTool } from "./tools/styleguide.ts";
|
|
11
11
|
import { createCritiqueTool } from "./tools/critique.ts";
|
|
12
12
|
import { createStopTool } from "./tools/stop.ts";
|
|
13
|
+
import { generateBlockFieldReference } from "./prompt/field-reference.ts";
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const rawFragment = await readFile(
|
|
15
16
|
join(dirname(fileURLToPath(import.meta.url)), "prompt/system-fragment.md"),
|
|
16
17
|
"utf8",
|
|
17
18
|
);
|
|
18
19
|
|
|
20
|
+
const PROMPT_FRAGMENT = rawFragment.replace("{{BLOCK_FIELD_REFERENCE}}", generateBlockFieldReference());
|
|
21
|
+
|
|
19
22
|
export const CesiumPlugin: Plugin = async (ctx): Promise<Hooks> => {
|
|
20
23
|
return {
|
|
21
24
|
tool: {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Generates a compact block field reference from the catalog.
|
|
2
|
+
// Injected into the system-fragment at plugin load time via placeholder replacement.
|
|
3
|
+
// src/prompt/field-reference.ts
|
|
4
|
+
|
|
5
|
+
import { blockCatalog } from "../render/blocks/catalog.ts";
|
|
6
|
+
import type { Block } from "../render/blocks/types.ts";
|
|
7
|
+
|
|
8
|
+
// ─── Schema → compact field description ──────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
type SchemaNode = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
function formatFieldType(node: SchemaNode): string {
|
|
13
|
+
if ("const" in node) return `"${String(node["const"])}"`;
|
|
14
|
+
const type = node["type"] as string | undefined;
|
|
15
|
+
if (type === undefined) return "unknown";
|
|
16
|
+
if (type === "string") {
|
|
17
|
+
const enumVals = node["enum"] as string[] | undefined;
|
|
18
|
+
if (enumVals !== undefined) return enumVals.map((e) => `"${e}"`).join(" | ");
|
|
19
|
+
return "string";
|
|
20
|
+
}
|
|
21
|
+
if (type === "number") return "number";
|
|
22
|
+
if (type === "boolean") return "boolean";
|
|
23
|
+
if (type === "array") {
|
|
24
|
+
const items = node["items"] as SchemaNode | undefined;
|
|
25
|
+
if (items !== undefined) {
|
|
26
|
+
const innerType = items["type"] as string | undefined;
|
|
27
|
+
if (innerType === "object") {
|
|
28
|
+
const props = items["properties"] as Record<string, SchemaNode> | undefined;
|
|
29
|
+
const req = items["required"] as string[] | undefined;
|
|
30
|
+
if (props !== undefined) {
|
|
31
|
+
const fields = Object.entries(props)
|
|
32
|
+
.filter(([k]) => k !== "type")
|
|
33
|
+
.map(([k, v]) => {
|
|
34
|
+
const isRequired = req !== undefined && req.includes(k);
|
|
35
|
+
return `${k}${isRequired ? "" : "?"}: ${formatFieldType(v)}`;
|
|
36
|
+
})
|
|
37
|
+
.join(", ");
|
|
38
|
+
return `[{ ${fields} }]`;
|
|
39
|
+
}
|
|
40
|
+
return "object[]";
|
|
41
|
+
}
|
|
42
|
+
if (innerType === "string") return "string[]";
|
|
43
|
+
return `${innerType ?? "unknown"}[]`;
|
|
44
|
+
}
|
|
45
|
+
return "array";
|
|
46
|
+
}
|
|
47
|
+
if (type === "object") return "object";
|
|
48
|
+
return type;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatBlockLine(type: Block["type"]): string {
|
|
52
|
+
const entry = blockCatalog[type];
|
|
53
|
+
const schema = entry.schema as SchemaNode;
|
|
54
|
+
const props = schema["properties"] as Record<string, SchemaNode> | undefined;
|
|
55
|
+
const required = schema["required"] as string[] | undefined;
|
|
56
|
+
|
|
57
|
+
if (props === undefined) return `- \`${type}\``;
|
|
58
|
+
|
|
59
|
+
const fields = Object.entries(props)
|
|
60
|
+
.filter(([k]) => k !== "type")
|
|
61
|
+
.map(([k, v]) => {
|
|
62
|
+
const isRequired = required !== undefined && required.includes(k);
|
|
63
|
+
return `${k}${isRequired ? "" : "?"}: ${formatFieldType(v)}`;
|
|
64
|
+
})
|
|
65
|
+
.join(", ");
|
|
66
|
+
|
|
67
|
+
return `- \`${type}\` — ${fields}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generates a compact markdown block field reference from the catalog.
|
|
72
|
+
* This is injected into system-fragment.md at the {{BLOCK_FIELD_REFERENCE}} placeholder.
|
|
73
|
+
*/
|
|
74
|
+
export function generateBlockFieldReference(): string {
|
|
75
|
+
const lines = [
|
|
76
|
+
"## Block field reference",
|
|
77
|
+
"",
|
|
78
|
+
"For full schemas with rendered examples, call `cesium_styleguide`. Exact field names:",
|
|
79
|
+
"",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const type of Object.keys(blockCatalog) as Block["type"][]) {
|
|
83
|
+
lines.push(formatBlockLine(type));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push(
|
|
88
|
+
"All `markdown` fields support `**bold**`, `*italic*`, `` `code` ``, lists, blockquotes, " +
|
|
89
|
+
"and the safelisted inline tags `<kbd>`, `<span class=\"pill\">`, `<span class=\"tag\">`. " +
|
|
90
|
+
"External URLs in links render as plain text.",
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
@@ -1,97 +1,88 @@
|
|
|
1
1
|
# Cesium — beautiful HTML artifacts
|
|
2
2
|
|
|
3
|
+
Cesium publishes beautiful self-contained artifacts to a local server you can open in a browser.
|
|
4
|
+
|
|
3
5
|
You have access to six tools:
|
|
4
6
|
|
|
5
7
|
- `cesium_publish` — write a substantive response as a self-contained HTML document
|
|
6
8
|
- `cesium_ask` — publish an interactive Q&A artifact; returns `{ id, httpUrl, ... }`
|
|
7
9
|
- `cesium_wait` — block until the user completes a `cesium_ask` artifact (polls disk)
|
|
8
|
-
- `cesium_styleguide` — fetch the full
|
|
9
|
-
- `cesium_critique` — analyze a draft
|
|
10
|
+
- `cesium_styleguide` — fetch the full block reference (call before writing anything complex)
|
|
11
|
+
- `cesium_critique` — analyze a draft artifact; returns a 0-100 score and findings
|
|
10
12
|
- `cesium_stop` — stop the running cesium HTTP server
|
|
11
13
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
## Two input modes
|
|
15
|
+
|
|
16
|
+
`cesium_publish` accepts either `blocks: Block[]` (preferred) or `html: string` (escape valve). Provide exactly one.
|
|
17
|
+
|
|
18
|
+
**Prefer `blocks`** for plans, reviews, reports, explainers, comparisons, audits, design docs. Blocks are token-efficient (no structural boilerplate), server-templated, and machine-checkable. Use `html` only when the whole document needs bespoke art-direction. For isolated bespoke regions, use `raw_html` or `diagram` blocks.
|
|
19
|
+
|
|
20
|
+
### Example
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{ "title": "Migration Guide", "kind": "plan", "blocks": [
|
|
24
|
+
{ "type": "hero", "eyebrow": "v2", "title": "Migration Guide",
|
|
25
|
+
"meta": [{ "k": "Status", "v": "Draft" }, { "k": "Owner", "v": "platform" }] },
|
|
26
|
+
{ "type": "tldr", "markdown": "**Summary:** Update one import path and bump the SDK." },
|
|
27
|
+
{ "type": "section", "title": "What Changed", "children": [
|
|
28
|
+
{ "type": "prose", "markdown": "The `auth` module is now a standalone package." },
|
|
29
|
+
{ "type": "callout", "variant": "warn", "markdown": "Change `sdk/auth` imports before upgrading." }
|
|
30
|
+
]},
|
|
31
|
+
{ "type": "risk_table", "rows": [
|
|
32
|
+
{ "risk": "Missed imports", "likelihood": "medium", "impact": "high", "mitigation": "Run codemods." }
|
|
33
|
+
]},
|
|
34
|
+
{ "type": "timeline", "items": [
|
|
35
|
+
{ "label": "Phase 1", "text": "Audit existing imports", "date": "2026-06-01" },
|
|
36
|
+
{ "label": "Phase 2", "text": "Run migration script" }
|
|
37
|
+
]}
|
|
38
|
+
]}
|
|
39
|
+
```
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
- "in terminal", "just tell me", "don't make a doc" → don't publish
|
|
41
|
+
## Quick block reference
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
Call `cesium_styleguide` for full schemas and rendered examples.
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
- `hero` — page-title header (eyebrow, subtitle, meta pairs)
|
|
46
|
+
- `tldr` — summary box; at most one per document
|
|
47
|
+
- `section` — numbered section with child blocks (depth ≤ 3)
|
|
48
|
+
- `prose` — free-form markdown; `list` — bullet/numbered/checklist
|
|
49
|
+
- `callout` — aside with variant: note/warn/risk; `divider` — rule
|
|
50
|
+
- `code` — fenced code with lang; `timeline` — milestone list
|
|
51
|
+
- `compare_table` — comparison grid; `risk_table` — risk grid
|
|
52
|
+
- `kv` — key-value pairs; `pill_row` — pill/tag chips
|
|
53
|
+
- `diagram` — SVG/HTML visual (scrubbed)
|
|
54
|
+
- `raw_html` — custom HTML escape hatch (scrubbed; add `purpose`)
|
|
38
55
|
|
|
39
|
-
|
|
56
|
+
{{BLOCK_FIELD_REFERENCE}}
|
|
40
57
|
|
|
41
|
-
|
|
58
|
+
## When to use raw_html / diagram
|
|
42
59
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- `.code` (with `.kw` `.str` `.cm` `.fn` highlights)
|
|
46
|
-
- `.timeline` `.diagram` `.compare-table` `.risk-table`
|
|
47
|
-
- `.kbd` `.pill` `.tag`
|
|
60
|
+
- `diagram` — SVG visualizations, bespoke layouts.
|
|
61
|
+
- `raw_html` — anything no typed block covers. Critique flags overuse (>2 blocks or >30% of body characters).
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
## When to publish (vs. reply in terminal)
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
Publish when: ≥ 400 words; comparison/matrix/plan/PRD/RFC; code review with >3 findings; design proposal/audit/explainer; or the user will re-read or share it. Stay in terminal for short answers and status updates.
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
User overrides: "/cesium" or "publish this" → publish; "in terminal" → don't.
|
|
54
68
|
|
|
55
69
|
## Self-check before publishing
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
call `cesium_critique` with your draft body BEFORE calling `cesium_publish`. Act
|
|
59
|
-
on warn-level findings; consider suggest-level. info-level is FYI.
|
|
60
|
-
|
|
61
|
-
If critique reports score < 70, revise the body before publishing.
|
|
71
|
+
Call `cesium_critique` before `cesium_publish` on substantive artifacts. Mode is auto-detected (pass `html` or `blocks`). Act on warn-level findings; consider suggest-level. If score < 70, revise.
|
|
62
72
|
|
|
63
73
|
## After publishing
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Cesium · <Title> (<kind>)
|
|
69
|
-
http://localhost:3030/projects/.../...
|
|
70
|
-
file:///.../...html
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Do not paste the full document content into the terminal after publishing.
|
|
74
|
-
|
|
75
|
-
## Stopping the server
|
|
76
|
-
|
|
77
|
-
If the user asks to stop, restart, or recycle the cesium server (e.g. after a
|
|
78
|
-
config change), call `cesium_stop`. The next `cesium_publish` will lazy-start
|
|
79
|
-
a fresh server with the latest config.
|
|
75
|
+
Print a 2-line terminal summary: `Cesium · <Title> (<kind>)` + the HTTP URL. Do not paste the full document content into the terminal.
|
|
80
76
|
|
|
81
77
|
## Interactive Q&A: cesium_ask + cesium_wait
|
|
82
78
|
|
|
83
|
-
When you need structured user input before producing a final artifact (design tradeoffs,
|
|
84
|
-
plan branches, confirmation gates), publish an interactive artifact:
|
|
85
|
-
|
|
86
79
|
1. `cesium_ask({ title, body, questions: [...] })` → returns `{ id, httpUrl, ... }`
|
|
87
80
|
2. Print the terminalSummary so the user knows where to click.
|
|
88
81
|
3. `cesium_wait({ id })` → blocks until user finishes (or 10-min timeout).
|
|
89
|
-
4. Decide next step from `result.answers
|
|
82
|
+
4. Decide next step from `result.answers`.
|
|
90
83
|
|
|
91
|
-
Question types: pick_one, pick_many, confirm, ask_text, slider, react.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
to add a Skip button (useful for "anything else?"-type follow-ups).
|
|
84
|
+
Question types: pick_one, pick_many, confirm, ask_text, slider, react. Set `optional: true` on an `ask_text` question to add a Skip button. Don't use cesium_ask for trivial yes/no questions — use it when the question deserves to live on disk as a decision record.
|
|
85
|
+
|
|
86
|
+
## Stopping the server
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
when the question deserves to live on disk as a decision record.
|
|
88
|
+
Call `cesium_stop` to stop or restart. The next `cesium_publish` will lazy-start a fresh server.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Catalog — source of truth: aggregates meta from every renderer module.
|
|
2
|
+
// src/render/blocks/catalog.ts
|
|
3
|
+
|
|
4
|
+
import type { Block, BlockMeta } from "./types.ts";
|
|
5
|
+
import { meta as heroMeta } from "./renderers/hero.ts";
|
|
6
|
+
import { meta as tldrMeta } from "./renderers/tldr.ts";
|
|
7
|
+
import { meta as sectionMeta } from "./renderers/section.ts";
|
|
8
|
+
import { meta as proseMeta } from "./renderers/prose.ts";
|
|
9
|
+
import { meta as listMeta } from "./renderers/list.ts";
|
|
10
|
+
import { meta as calloutMeta } from "./renderers/callout.ts";
|
|
11
|
+
import { meta as codeMeta } from "./renderers/code.ts";
|
|
12
|
+
import { meta as timelineMeta } from "./renderers/timeline.ts";
|
|
13
|
+
import { meta as compareTableMeta } from "./renderers/compare-table.ts";
|
|
14
|
+
import { meta as riskTableMeta } from "./renderers/risk-table.ts";
|
|
15
|
+
import { meta as kvMeta } from "./renderers/kv.ts";
|
|
16
|
+
import { meta as pillRowMeta } from "./renderers/pill-row.ts";
|
|
17
|
+
import { meta as dividerMeta } from "./renderers/divider.ts";
|
|
18
|
+
import { meta as diagramMeta } from "./renderers/diagram.ts";
|
|
19
|
+
import { meta as rawHtmlMeta } from "./renderers/raw-html.ts";
|
|
20
|
+
|
|
21
|
+
export const blockCatalog: Record<Block["type"], BlockMeta> = {
|
|
22
|
+
hero: heroMeta,
|
|
23
|
+
tldr: tldrMeta,
|
|
24
|
+
section: sectionMeta,
|
|
25
|
+
prose: proseMeta,
|
|
26
|
+
list: listMeta,
|
|
27
|
+
callout: calloutMeta,
|
|
28
|
+
code: codeMeta,
|
|
29
|
+
timeline: timelineMeta,
|
|
30
|
+
compare_table: compareTableMeta,
|
|
31
|
+
risk_table: riskTableMeta,
|
|
32
|
+
kv: kvMeta,
|
|
33
|
+
pill_row: pillRowMeta,
|
|
34
|
+
divider: dividerMeta,
|
|
35
|
+
diagram: diagramMeta,
|
|
36
|
+
raw_html: rawHtmlMeta,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const blockTypes = Object.keys(blockCatalog) as Array<Block["type"]>;
|