@bytesbybrandon/living-docs 0.3.0 → 0.3.2
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/README.md +2 -2
- package/docs/guide/cli.md +62 -0
- package/docs/guide/document-model.md +117 -0
- package/docs/guide/guarantees.md +31 -0
- package/docs/guide/mcp.md +47 -0
- package/docs/guide/pipeline.md +36 -0
- package/docs/guide/reactive-workflow.md +21 -0
- package/docs/guide/style-tokens.md +60 -0
- package/docs/guide/timeline.md +75 -0
- package/package.json +2 -1
- package/src/axi/cli.js +6 -2
- package/src/export/video.js +1 -1
- package/src/mcp/server.js +2 -1
- package/src/model/schema.js +24 -12
- package/src/version.js +5 -0
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ Install the package globally or run it without installing.
|
|
|
50
50
|
|
|
51
51
|
1. **Install from npm:**
|
|
52
52
|
```bash
|
|
53
|
-
npm install -g
|
|
53
|
+
npm install -g living-docs-axi
|
|
54
54
|
```
|
|
55
55
|
2. **Or clone the repository:**
|
|
56
56
|
```bash
|
|
@@ -85,7 +85,7 @@ node bin/living-docs-axi.js render docs/note.model.json --theme paper-dark -o do
|
|
|
85
85
|
node bin/living-docs-axi.js preview docs
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
With the published package, use `npx -y
|
|
88
|
+
With the published package, use `npx -y living-docs-axi` for each command.
|
|
89
89
|
|
|
90
90
|
Detailed references:
|
|
91
91
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
The `living-docs-axi` command follows the AXI principles: compact TOON-style output, a content-first home view, and `help[]` next-step lines.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
living-docs-axi home: themes, built docs, next steps
|
|
7
|
+
living-docs-axi themes theme list with when to use each
|
|
8
|
+
living-docs-axi version print the version only, also --version or -v
|
|
9
|
+
living-docs-axi schema [--block figure] block fields and types
|
|
10
|
+
living-docs-axi ingest <path|url|-> outline, or --section <n|id> for one section
|
|
11
|
+
living-docs-axi ingest <path> --draft write a model, with a drafted timeline, from the source
|
|
12
|
+
living-docs-axi render <model.json> validate and write HTML, or --format mp4|webm|gif for video
|
|
13
|
+
living-docs-axi validate <model.json> validation only
|
|
14
|
+
living-docs-axi preview [dir] serve built docs with an index
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Flags
|
|
18
|
+
|
|
19
|
+
| Flag | Purpose |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `--theme <name>` | `paper-light`, `paper-dark`, `hud`, or a curated look: `field-notes`, `editorial`, `blueprint`. With `--draft`, omitted means the source picks |
|
|
22
|
+
| `--seed <hex>` | with `--draft`: accent seed, overriding the one derived from the title |
|
|
23
|
+
| `-o`, `--out <file>` | output path |
|
|
24
|
+
| `--section <n\|id>` | ingest one section |
|
|
25
|
+
| `--max-chars <n>` | cap section text, default 12000 |
|
|
26
|
+
| `--full` | disable truncation |
|
|
27
|
+
| `--stdout` | print HTML instead of writing a file |
|
|
28
|
+
| `--url <url>` | ingest a URL |
|
|
29
|
+
| `--text <text>` | ingest inline text |
|
|
30
|
+
| `--port <n>` | preview port, default 4600 |
|
|
31
|
+
| `--no-open` | do not open a browser |
|
|
32
|
+
| `--static` | draft without timeline cues |
|
|
33
|
+
| `--format <f>` | `html` (default), `mp4`, `webm`, or `gif` |
|
|
34
|
+
| `--size <WxH>` | video stage, default `1080x1350` |
|
|
35
|
+
| `--fps <n>` | video frames per second, default 30 |
|
|
36
|
+
| `--scale <n>` | device pixel ratio for the stage, default 1 |
|
|
37
|
+
|
|
38
|
+
## Exit codes
|
|
39
|
+
|
|
40
|
+
| Code | Meaning |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| 0 | success |
|
|
43
|
+
| 1 | invalid model or runtime failure |
|
|
44
|
+
| 2 | usage error, including unknown flags |
|
|
45
|
+
|
|
46
|
+
Errors are structured on standard output.
|
|
47
|
+
Unknown flags fail loud instead of being ignored.
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx -y living-docs-axi themes
|
|
53
|
+
npx -y living-docs-axi ingest samples/source/harness-notes.md --draft -o docs/draft.model.json
|
|
54
|
+
npx -y living-docs-axi render docs/draft.model.json --theme paper-dark -o docs/draft.html
|
|
55
|
+
npx -y living-docs-axi preview docs --no-open
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## See also
|
|
59
|
+
|
|
60
|
+
- [Pipeline](pipeline.md)
|
|
61
|
+
- [Document model](document-model.md)
|
|
62
|
+
- [Timeline and video](timeline.md)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Document model
|
|
2
|
+
|
|
3
|
+
A model is `{ schemaVersion: 1, theme?, meta, blocks }`.
|
|
4
|
+
Validation is strict: unknown fields, unknown block types, and unresolved references fail with an exact path.
|
|
5
|
+
|
|
6
|
+
## Meta
|
|
7
|
+
|
|
8
|
+
`meta` accepts `title`, `subtitle`, `kicker`, `runningHead`, `author`, `affiliation`, `date`, `stamps`, `abstract`, `indexTerms`, `footer`, `accent`, and `style`.
|
|
9
|
+
`style` carries the [style tokens](style-tokens.md).
|
|
10
|
+
|
|
11
|
+
## Blocks
|
|
12
|
+
|
|
13
|
+
| Block | Purpose |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `prose` | body text with inline markdown and `[[1]]` footnote refs |
|
|
16
|
+
| `heading` | numbered section (`level: 2`) or subsection (`level: 3`) |
|
|
17
|
+
| `figure` | generated diagram or chart, auto-numbered |
|
|
18
|
+
| `table` | rule-based table with small-caps head |
|
|
19
|
+
| `algorithm` | numbered pseudocode with an optional highlighted line |
|
|
20
|
+
| `callout` | `takeaway`, `note`, or `warn` aside |
|
|
21
|
+
| `stats` | row of headline numbers |
|
|
22
|
+
| `list`, `quote`, `code` | supporting text blocks |
|
|
23
|
+
| `image` | supplied file, inlined as a data URI |
|
|
24
|
+
| `footnotes` | footnote list printed at the foot |
|
|
25
|
+
| `ticker` | one-line console status strip |
|
|
26
|
+
| `graphScene` | console hero: tabs, prompt, graph, active-node card |
|
|
27
|
+
| `panels` | console grid with 13 panel kinds |
|
|
28
|
+
|
|
29
|
+
## Figures
|
|
30
|
+
|
|
31
|
+
A figure takes either a `diagram` spec or a `chart` spec.
|
|
32
|
+
Diagrams place nodes and edges on a 1000-unit canvas, with optional groups and notes.
|
|
33
|
+
Charts accept `line`, `scatter`, `bar`, and `steps` series, with ticks, bounds, legend, and annotations.
|
|
34
|
+
Both render as inline SVG, so no image assets are required.
|
|
35
|
+
|
|
36
|
+
`ingest --draft` never writes figures.
|
|
37
|
+
It drafts text, lists, stats, and a timeline from the source, so every figure is authored into the model by hand or by an agent.
|
|
38
|
+
Run `living-docs-axi schema --block figure` for every nested field.
|
|
39
|
+
|
|
40
|
+
### Diagram
|
|
41
|
+
|
|
42
|
+
Nodes sit at fixed `x` and `y` positions on a canvas that is 1000 units wide by default.
|
|
43
|
+
There is no auto layout, so choose positions that leave room for labels.
|
|
44
|
+
Node ids must be unique, and every edge and group member must name an existing node id.
|
|
45
|
+
Node `kind` is `box`, `accent`, `plain`, `dashed`, or `stack`.
|
|
46
|
+
Edge `route` is `straight` or `elbow`.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"type": "figure",
|
|
51
|
+
"id": "segments",
|
|
52
|
+
"caption": "Appends go to the active segment. Sealed segments stay read-only.",
|
|
53
|
+
"spec": "diagram",
|
|
54
|
+
"span": "page",
|
|
55
|
+
"motion": "draw",
|
|
56
|
+
"diagram": {
|
|
57
|
+
"width": 1000,
|
|
58
|
+
"height": 360,
|
|
59
|
+
"nodes": [
|
|
60
|
+
{ "id": "writer", "label": "writer", "sub": "append", "x": 40, "y": 150, "w": 160, "h": 64 },
|
|
61
|
+
{ "id": "dir", "label": "segment directory", "x": 280, "y": 150, "w": 200, "h": 64, "kind": "accent" },
|
|
62
|
+
{ "id": "hot", "label": "active segment", "x": 580, "y": 60, "w": 180, "h": 64 },
|
|
63
|
+
{ "id": "cold", "label": "sealed segments", "x": 580, "y": 240, "w": 180, "h": 64, "kind": "dashed" }
|
|
64
|
+
],
|
|
65
|
+
"edges": [
|
|
66
|
+
{ "from": "writer", "to": "dir", "label": "publish" },
|
|
67
|
+
{ "from": "dir", "to": "hot", "route": "elbow" },
|
|
68
|
+
{ "from": "dir", "to": "cold", "route": "elbow" }
|
|
69
|
+
],
|
|
70
|
+
"groups": [{ "label": "segment store", "nodes": ["hot", "cold"] }],
|
|
71
|
+
"notes": ["Sealing copies no payload."]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Chart
|
|
77
|
+
|
|
78
|
+
Chart `kind` is `line`, `scatter`, `bar`, or `steps`.
|
|
79
|
+
A series carries `points` of `{ x, y }`, or `values` and `labels` for bars.
|
|
80
|
+
Series `tone` is `accent`, `ink`, `muted`, `kept`, or `rejected`.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"type": "figure",
|
|
85
|
+
"caption": "Lookup probes grow with segment count.",
|
|
86
|
+
"spec": "chart",
|
|
87
|
+
"chart": {
|
|
88
|
+
"kind": "line",
|
|
89
|
+
"xLabel": "segments",
|
|
90
|
+
"yLabel": "probes",
|
|
91
|
+
"series": [
|
|
92
|
+
{ "name": "nacre", "tone": "accent", "points": [{ "x": 1, "y": 1 }, { "x": 32, "y": 32 }, { "x": 128, "y": 128 }] }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Use an `image` block instead when the graphic already exists as a raster or SVG file.
|
|
99
|
+
|
|
100
|
+
### Motion
|
|
101
|
+
|
|
102
|
+
`motion` is optional and accepts `none`, `draw`, `reveal`, or `steps`.
|
|
103
|
+
Motion is scoped CSS on the generated SVG: edges draw, marks fade in, and bars grow.
|
|
104
|
+
Print and `prefers-reduced-motion` freeze the final frame, and content stays visible while the animation runs.
|
|
105
|
+
|
|
106
|
+
## Console blocks
|
|
107
|
+
|
|
108
|
+
`graphScene` accepts a title, meta chips, tabs, a prompt line, a graph payload, an action strip, and a status line.
|
|
109
|
+
The graph layout is seeded, so the same model produces the same scene.
|
|
110
|
+
`panels` holds numbered panels with one of 13 kinds: `repo`, `bars`, `list`, `radar`, `tree`, `log`, `heat`, `big`, `spark`, `code`, `hist`, `metrics`, and `diff`.
|
|
111
|
+
|
|
112
|
+
Other themes render console blocks as annotated cards, so a model can switch themes without losing content.
|
|
113
|
+
|
|
114
|
+
## See also
|
|
115
|
+
|
|
116
|
+
- [Pipeline](pipeline.md)
|
|
117
|
+
- [Style tokens](style-tokens.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Guarantees
|
|
2
|
+
|
|
3
|
+
## One file per document
|
|
4
|
+
|
|
5
|
+
CSS, SVG, and the console runtime are inlined.
|
|
6
|
+
No CDN, fonts, or remote images are loaded.
|
|
7
|
+
|
|
8
|
+
## Deterministic output
|
|
9
|
+
|
|
10
|
+
Identical model and theme produce identical bytes, including generated SVG ids, graph layout, and derived palette values.
|
|
11
|
+
|
|
12
|
+
## Curated styling
|
|
13
|
+
|
|
14
|
+
`meta.style` only accepts known palettes, type pairings, densities, and figure treatments.
|
|
15
|
+
Accent colors are contrast corrected.
|
|
16
|
+
No arbitrary CSS is injected.
|
|
17
|
+
|
|
18
|
+
## Escaped content
|
|
19
|
+
|
|
20
|
+
Document text is HTML escaped.
|
|
21
|
+
Only `http(s)`, `mailto:`, `#`, and relative URLs survive as links.
|
|
22
|
+
|
|
23
|
+
## Print ready
|
|
24
|
+
|
|
25
|
+
Paper themes ship `@page` rules and keep figures, tables, algorithms, and callouts unbroken across pages.
|
|
26
|
+
Figure motion freezes to its final frame in print and under reduced-motion preferences.
|
|
27
|
+
|
|
28
|
+
## Known limits
|
|
29
|
+
|
|
30
|
+
PDF ingestion depends on the optional `unpdf` package and reports a typed error when it is unavailable.
|
|
31
|
+
There is no visual regression suite and no per-theme versioning.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# MCP server
|
|
2
|
+
|
|
3
|
+
The package ships a stdio MCP server over the same core as the CLI.
|
|
4
|
+
|
|
5
|
+
## Published package
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"living-docs": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": ["-y", "-p", "living-docs-axi", "living-docs-mcp"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Local checkout
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"living-docs": {
|
|
24
|
+
"command": "node",
|
|
25
|
+
"args": ["<absolute-checkout>/bin/living-docs-mcp.js"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Tools
|
|
32
|
+
|
|
33
|
+
| Tool | Purpose |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `living_docs_themes` | list the themes (three structural bases plus curated looks) and what each is best for |
|
|
36
|
+
| `living_docs_schema` | block fields, or one block in detail |
|
|
37
|
+
| `living_docs_ingest` | outline a source, optionally draft a model with timeline cues (`static` skips them) |
|
|
38
|
+
| `living_docs_render` | validate and write HTML, or `format` mp4, webm, or gif, from a model, a model path, or a source |
|
|
39
|
+
| `living_docs_preview` | serve built docs and return the URL |
|
|
40
|
+
|
|
41
|
+
`living_docs_render` accepts a model object, a model path, or a source to draft from.
|
|
42
|
+
Tools return compact text, and errors are marked with `isError`.
|
|
43
|
+
|
|
44
|
+
## See also
|
|
45
|
+
|
|
46
|
+
- [CLI reference](cli.md)
|
|
47
|
+
- [Document model](document-model.md)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Pipeline
|
|
2
|
+
|
|
3
|
+
The engine turns one source into one self-contained HTML document.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
source -> ingest -> outline + normalized blocks
|
|
7
|
+
|
|
|
8
|
+
+-> --draft -> valid document model
|
|
9
|
+
|
|
|
10
|
+
+-> author a richer model against the schema
|
|
11
|
+
|
|
|
12
|
+
render --theme -> single HTML file
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Ingest
|
|
16
|
+
|
|
17
|
+
`ingest` accepts a file path (Markdown, plain text, HTML, or PDF), a URL, inline text, or standard input.
|
|
18
|
+
It returns an outline with section ids, titles, and word counts.
|
|
19
|
+
Large sources stay bounded: read the outline, then pull one section at a time with `--section <n|id>`.
|
|
20
|
+
|
|
21
|
+
## Draft models
|
|
22
|
+
|
|
23
|
+
`ingest --draft` is the zero-judgment path.
|
|
24
|
+
It writes a document model that validates and renders immediately.
|
|
25
|
+
An author then adds figures, charts, callouts, stats, and console panels against the schema.
|
|
26
|
+
|
|
27
|
+
## Render
|
|
28
|
+
|
|
29
|
+
`render` validates the model, resolves style tokens, and writes one file with inline CSS, inline SVG, and the console runtime.
|
|
30
|
+
The file loads no fonts, images, or scripts from the network.
|
|
31
|
+
|
|
32
|
+
## See also
|
|
33
|
+
|
|
34
|
+
- [CLI reference](cli.md)
|
|
35
|
+
- [Document model](document-model.md)
|
|
36
|
+
- [Style tokens](style-tokens.md)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Reactive workflow
|
|
2
|
+
|
|
3
|
+
An optional reactive skill wraps the same pipeline: intake, outline, theme and style plan, model authoring, validation, render, human review, and finalize.
|
|
4
|
+
It calls the CLI and does not reimplement rendering.
|
|
5
|
+
|
|
6
|
+
## Boundary
|
|
7
|
+
|
|
8
|
+
The skill is maintained separately from this package.
|
|
9
|
+
`src/model/schema.js` and the AXI commands are the stable engine surface.
|
|
10
|
+
|
|
11
|
+
## Plan and review
|
|
12
|
+
|
|
13
|
+
The workflow holds two human gates.
|
|
14
|
+
The first approves the theme, style tokens, sections, and figures.
|
|
15
|
+
The second reviews the rendered page.
|
|
16
|
+
Model revisions are bounded at four attempts before the run ends in an error state.
|
|
17
|
+
|
|
18
|
+
## See also
|
|
19
|
+
|
|
20
|
+
- [CLI reference](cli.md)
|
|
21
|
+
- [Style tokens](style-tokens.md)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Style tokens
|
|
2
|
+
|
|
3
|
+
`meta.style` adapts a theme to its source without accepting arbitrary CSS.
|
|
4
|
+
Every choice is curated and validated, so the page stays coherent, printable, and deterministic.
|
|
5
|
+
|
|
6
|
+
| Token | Values | Effect |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| `palette` | `default`, `ember`, `ocean`, `moss`, `graphite`, `ink`, `plum` | curated accent family |
|
|
9
|
+
| `seed` | hex color, for example `#2f6f8f` | derives the accent, tinted surfaces, and figure fills; lightness is corrected until the accent clears WCAG contrast against the page |
|
|
10
|
+
| `type` | papers: `classic`, `editorial`, `technical`; console: `mono`, `hybrid`, `terminal` | curated font stacks, no web fonts |
|
|
11
|
+
| `density` | `compact`, `normal`, `roomy` | body size, leading, figure margins, panel height |
|
|
12
|
+
| `figure` | `outline`, `solid`, `minimal` | diagram fill and stroke; `minimal` also drops chart gridlines |
|
|
13
|
+
| `layout` | `columns`, `single` | paper themes only: two columns by default, or one reading column for code and wide tables |
|
|
14
|
+
|
|
15
|
+
`meta.accent` remains available as a direct accent override.
|
|
16
|
+
Invalid tokens fail validation at `meta.style`.
|
|
17
|
+
|
|
18
|
+
## Looks and designed drafts
|
|
19
|
+
|
|
20
|
+
Three themes set the structure: `paper-light`, `paper-dark`, and `hud`.
|
|
21
|
+
Curated looks sit on a structure with their own palette and type: `field-notes` and `editorial` on `paper-light`, `blueprint` on `paper-dark`.
|
|
22
|
+
A look is only a palette and a short stylesheet, so blocks, figures, timelines, and print behave exactly like the base theme.
|
|
23
|
+
|
|
24
|
+
`ingest --draft` designs the doc from its source.
|
|
25
|
+
Code-heavy sources get `blueprint`, papers get `paper-light`, long prose gets `editorial`, short notes get `field-notes`, and the rest get `paper-dark`.
|
|
26
|
+
Paper themes also get an accent seed derived from the title, and density follows length.
|
|
27
|
+
The draft writes these as ordinary `meta.style` tokens, so edit them freely.
|
|
28
|
+
Pass `--theme` or `--seed` to override the choice.
|
|
29
|
+
|
|
30
|
+
## Motion
|
|
31
|
+
|
|
32
|
+
Motion has two layers.
|
|
33
|
+
|
|
34
|
+
`figure.motion` animates one generated figure with `draw`, `reveal`, or `steps`.
|
|
35
|
+
It is scoped CSS on the SVG, freezes for print, and disables under `prefers-reduced-motion`.
|
|
36
|
+
The content stays visible while the animation runs.
|
|
37
|
+
|
|
38
|
+
The console theme also animates its graph, pulse, and clock.
|
|
39
|
+
Those animations are theme owned, not tokens.
|
|
40
|
+
|
|
41
|
+
## Example
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"meta": {
|
|
46
|
+
"title": "Example",
|
|
47
|
+
"style": {
|
|
48
|
+
"seed": "#2f6f8f",
|
|
49
|
+
"type": "editorial",
|
|
50
|
+
"density": "roomy",
|
|
51
|
+
"figure": "minimal"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## See also
|
|
58
|
+
|
|
59
|
+
- [Document model](document-model.md)
|
|
60
|
+
- [Guarantees](guarantees.md)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Timeline and video
|
|
2
|
+
|
|
3
|
+
A timeline makes a doc move in step with its own text.
|
|
4
|
+
Cues highlight a source sentence, light up the figure part it describes, count numbers up, type text out, and scroll the video frame to a block.
|
|
5
|
+
The same timeline plays on the live page and records to MP4, WebM, or GIF.
|
|
6
|
+
|
|
7
|
+
## Marks and ids
|
|
8
|
+
|
|
9
|
+
Wrap a sentence in any prose string, the abstract included, with `[[mark:id|text]]`.
|
|
10
|
+
Give any block an `id` so cues can point at it.
|
|
11
|
+
Ids use lowercase letters, digits, and hyphens, and must be unique.
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "type": "figure", "id": "loop", "caption": "...", "spec": "diagram", "diagram": { "nodes": [{ "id": "editor", "label": "4B editor", "x": 552, "y": 28 }] } }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Cues
|
|
18
|
+
|
|
19
|
+
`timeline.cues` is a list of `{ at, dur, do, target }`.
|
|
20
|
+
`at` and `dur` are seconds; `dur` defaults to 1.2.
|
|
21
|
+
|
|
22
|
+
| `do` | What it does |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `highlight` | Sweeps a highlighter across a mark or block, then leaves a faint underline. |
|
|
25
|
+
| `focus` | Outlines a block or part and dims its siblings. The glow stays inside the block. |
|
|
26
|
+
| `reveal` | Fades a block or part in at the cue time, without moving it. |
|
|
27
|
+
| `count` | Counts the numbers inside the target up from zero. |
|
|
28
|
+
| `type` | Types the target's text out. The full text keeps its space, so nothing reflows. |
|
|
29
|
+
| `camera` | Video only: scrolls the frame to a block or part, or back to `page`. The page stays flat at 100%; `zoom` is an opt-in that scales it. |
|
|
30
|
+
| `activate` | HUD only: glides the scene crosshair to a label. |
|
|
31
|
+
|
|
32
|
+
Targets are `mark:<id>`, `<blockId>`, `<blockId>/<part>`, or `page` for the camera.
|
|
33
|
+
A part is a diagram node id, a panel index such as `08`, or a scene label.
|
|
34
|
+
Validation rejects a cue whose target does not exist.
|
|
35
|
+
|
|
36
|
+
`timeline.duration` defaults to the last cue end plus 1.5 seconds.
|
|
37
|
+
`timeline.loop` restarts the live page when it ends; reveals, counts, and typing stay settled after the first pass.
|
|
38
|
+
On the live page, a block whose cue comes up while it is still below the fold waits, with its cues in order, until the reader scrolls to it.
|
|
39
|
+
|
|
40
|
+
## Drafted cues
|
|
41
|
+
|
|
42
|
+
`ingest --draft` writes a timeline from the source: up to four abstract sentences and five section leads are marked and highlighted in reading order, the frame scrolls to each lead, and stats count up.
|
|
43
|
+
Pass `--static` for a draft without cues.
|
|
44
|
+
Edit or replace the drafted cues like any other part of the model.
|
|
45
|
+
|
|
46
|
+
## Video
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
living-docs-axi render docs/notes.model.json --format mp4 -o docs/notes.mp4
|
|
50
|
+
living-docs-axi render docs/notes.model.json --format gif --size 1080x1350 --fps 30
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The renderer lays the doc out on a fixed stage, seeks the timeline frame by frame in headless Chromium, and pipes the frames to ffmpeg.
|
|
54
|
+
Every frame is a pure function of time, so two renders on one machine produce the same frames.
|
|
55
|
+
MP4 is the default for sharing; GIF is 720 wide at 15 fps for READMEs.
|
|
56
|
+
Video needs the optional `playwright` package with Chromium, and `ffmpeg` on `PATH` or in `LIVING_DOCS_FFMPEG`.
|
|
57
|
+
In a project, run `npm i -D playwright`; with npx, add it to the same call:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx -y -p living-docs-axi -p playwright living-docs-axi render notes.model.json --format mp4
|
|
61
|
+
npx playwright install chromium
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Driving a live doc from another page
|
|
65
|
+
|
|
66
|
+
A same-origin page that embeds a timed doc in an iframe can take over its clock through `window.__ld`.
|
|
67
|
+
`pause()` hands the clock to the host, and `seek(t)` then renders the frame at `t` seconds.
|
|
68
|
+
`play()` resumes from the last shown time, `replay()` restarts from zero, and `time()` reads the current time.
|
|
69
|
+
The home page of this site pins one doc and scrubs it as the reader scrolls.
|
|
70
|
+
The controls exist only on a live page; under reduced motion, in print, and in video capture the doc shows its final state or seeks per frame, so a host should check that `pause` is a function first.
|
|
71
|
+
|
|
72
|
+
## Accessibility and print
|
|
73
|
+
|
|
74
|
+
Under `prefers-reduced-motion`, print, or with scripts off, the page shows its final state: everything revealed, counted, and typed, with narrated sentences underlined.
|
|
75
|
+
Docs without a timeline ship no runtime.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytesbybrandon/living-docs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Turn a source document into a designed, self-contained living doc: academic paper, dark reading notes, or telemetry console. Ships an AXI CLI and an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"bin",
|
|
41
41
|
"src",
|
|
42
42
|
"samples",
|
|
43
|
+
"docs/guide",
|
|
43
44
|
"README.md",
|
|
44
45
|
"LICENSE"
|
|
45
46
|
],
|
package/src/axi/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { validateDoc, formatErrors } from '../model/validate.js';
|
|
|
9
9
|
import { BLOCKS, THEMES, META, STYLE, blockFieldSummary } from '../model/schema.js';
|
|
10
10
|
import { createPreviewServer } from '../server/preview.js';
|
|
11
11
|
import { exportVideo, FORMATS } from '../export/video.js';
|
|
12
|
+
import { VERSION } from '../version.js';
|
|
12
13
|
|
|
13
14
|
const HOME = os.homedir();
|
|
14
15
|
const BIN = fileURLToPath(new URL('../../bin/living-docs-axi.js', import.meta.url));
|
|
@@ -36,13 +37,14 @@ const FLAGS = {
|
|
|
36
37
|
'--size': 'value',
|
|
37
38
|
'--fps': 'value',
|
|
38
39
|
'--scale': 'value',
|
|
39
|
-
'--help': 'bool', '-h': 'bool'
|
|
40
|
+
'--help': 'bool', '-h': 'bool',
|
|
41
|
+
'--version': 'bool', '-v': 'bool'
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
function parseArgs(argv) {
|
|
43
45
|
const positionals = [];
|
|
44
46
|
const flags = {};
|
|
45
|
-
const canonical = { '-o': 'out', '-h': 'help' };
|
|
47
|
+
const canonical = { '-o': 'out', '-h': 'help', '-v': 'version' };
|
|
46
48
|
for (let i = 0; i < argv.length; i++) {
|
|
47
49
|
const token = argv[i];
|
|
48
50
|
if (!token.startsWith('-') || token === '-') { positionals.push(token); continue; }
|
|
@@ -394,6 +396,7 @@ const COMMAND_HELP = {
|
|
|
394
396
|
validate: 'validate <model.json|->',
|
|
395
397
|
preview: 'preview [file|dir] [--port <n>] [--no-open]',
|
|
396
398
|
themes: 'themes',
|
|
399
|
+
version: 'version',
|
|
397
400
|
schema: 'schema [--block <type>]'
|
|
398
401
|
};
|
|
399
402
|
|
|
@@ -417,6 +420,7 @@ export async function main(argv) {
|
|
|
417
420
|
return [`error: ${error.message}`, usage()].join('\n');
|
|
418
421
|
}
|
|
419
422
|
const { command, args, flags } = parsed;
|
|
423
|
+
if (flags.version || command === 'version') return VERSION;
|
|
420
424
|
if (flags.help || flags.h || command === 'help') return usage(command === 'help' ? args[0] : command);
|
|
421
425
|
|
|
422
426
|
try {
|
package/src/export/video.js
CHANGED
|
@@ -33,7 +33,7 @@ async function loadChromium() {
|
|
|
33
33
|
} catch {
|
|
34
34
|
throw new ExportError('video export needs the optional playwright package', {
|
|
35
35
|
code: 'missing-playwright',
|
|
36
|
-
hint: 'install playwright beside living-docs (npm i -D playwright, or npx -p
|
|
36
|
+
hint: 'install playwright beside living-docs (npm i -D playwright, or npx -y -p living-docs-axi -p playwright living-docs-axi ...), then npx playwright install chromium'
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/mcp/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { validateDoc, formatErrors } from '../model/validate.js';
|
|
|
9
9
|
import { BLOCKS, THEMES, STYLE, blockFieldSummary } from '../model/schema.js';
|
|
10
10
|
import { createPreviewServer } from '../server/preview.js';
|
|
11
11
|
import { exportVideo, FORMATS } from '../export/video.js';
|
|
12
|
+
import { VERSION } from '../version.js';
|
|
12
13
|
|
|
13
14
|
const quoteCell = value => {
|
|
14
15
|
const text = String(value ?? '');
|
|
@@ -256,7 +257,7 @@ async function handle(name, args = {}) {
|
|
|
256
257
|
|
|
257
258
|
export function createServer() {
|
|
258
259
|
const server = new Server(
|
|
259
|
-
{ name: 'living-docs', version:
|
|
260
|
+
{ name: 'living-docs', version: VERSION },
|
|
260
261
|
{
|
|
261
262
|
capabilities: { tools: {} },
|
|
262
263
|
instructions: 'Turn source documents into designed living docs. Ingest to get an outline, author a document model against the schema, then render with a theme from living_docs_themes. A draft without a theme picks a look from the source.'
|
package/src/model/schema.js
CHANGED
|
@@ -382,19 +382,31 @@ export const THEMES = {
|
|
|
382
382
|
}
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
function describeField(name, spec) {
|
|
386
|
+
return {
|
|
387
|
+
name,
|
|
388
|
+
kind: spec.kind,
|
|
389
|
+
required: !spec.optional,
|
|
390
|
+
desc: spec.desc,
|
|
391
|
+
...(spec.values ? { values: spec.values } : {}),
|
|
392
|
+
...(spec.of ? { of: spec.of.kind } : {})
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Nested object fields are listed under dotted paths and array items under `[]`,
|
|
397
|
+
// so `diagram.nodes[].x` is discoverable without reading the source.
|
|
398
|
+
function flattenFields(fields, prefix = '') {
|
|
399
|
+
return Object.entries(fields).flatMap(([name, spec]) => {
|
|
400
|
+
const path = `${prefix}${name}`;
|
|
401
|
+
const row = describeField(path, spec);
|
|
402
|
+
const inner = spec.kind === 'object' ? spec : spec.kind === 'array' && spec.of?.kind === 'object' ? spec.of : null;
|
|
403
|
+
if (!inner) return [row];
|
|
404
|
+
return [row, ...flattenFields(inner.fields, `${path}${spec.kind === 'array' ? '[].' : '.'}`)];
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
385
408
|
export function blockFieldSummary(type) {
|
|
386
409
|
const block = BLOCKS[type];
|
|
387
410
|
if (!block) return null;
|
|
388
|
-
return {
|
|
389
|
-
type,
|
|
390
|
-
summary: block.summary,
|
|
391
|
-
fields: Object.entries(block.fields).map(([name, spec]) => ({
|
|
392
|
-
name,
|
|
393
|
-
kind: spec.kind,
|
|
394
|
-
required: !spec.optional,
|
|
395
|
-
desc: spec.desc,
|
|
396
|
-
...(spec.values ? { values: spec.values } : {}),
|
|
397
|
-
...(spec.of ? { of: spec.of.kind } : {})
|
|
398
|
-
}))
|
|
399
|
-
};
|
|
411
|
+
return { type, summary: block.summary, fields: flattenFields(block.fields) };
|
|
400
412
|
}
|