@liustack/pptfast 0.1.0 → 0.3.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/README.md CHANGED
@@ -7,7 +7,7 @@ DrawingML out.
7
7
 
8
8
  ## Why
9
9
 
10
- Freeform SVG/HTML-to-PPTX pipelines have a high ceiling but an unstable floor — a weak model (or a strong one having an off turn) produces a deck that's broken, off-brand, or unreadable. pptfast trades freeform drawing for a controlled vocabulary: a semantic IR (zod schema), 13 built-in themes driven by design tokens, an archetype/block layout library with seeded variety, and native DrawingML output where every shape stays editable — not a picture pasted onto a slide.
10
+ Freeform SVG/HTML-to-PPTX pipelines have a high ceiling but an unstable floor — a weak model (or a strong one having an off turn) produces a deck that's broken, off-brand, or unreadable. pptfast trades freeform drawing for a controlled vocabulary: a semantic IR (zod schema), 13 built-in themes bundling a style (design tokens) and a brand (identity chrome), a layout-and-component library with seeded variety, and native DrawingML output where every shape stays editable — not a picture pasted onto a slide.
11
11
 
12
12
  A deck is really five things: a content model, a 2D layout, a visual style, motion, and a narrative. pptfast owns the last four — you (or your agent) own the content model by writing the IR.
13
13
 
@@ -20,6 +20,22 @@ pptfast --help
20
20
 
21
21
  Node >= 18. Or build from source: `git clone https://github.com/liustack/pptfast.git && cd pptfast && pnpm install && pnpm build`.
22
22
 
23
+ ### As a Claude Code plugin
24
+
25
+ The repo doubles as a Claude Code plugin that ships the deck-generation skill:
26
+
27
+ ```
28
+ /plugin marketplace add liustack/pptfast
29
+ /plugin install pptfast@pptfast
30
+ /reload-plugins
31
+ ```
32
+
33
+ The skill drives the CLI, so install the CLI too (`npm install -g @liustack/pptfast`).
34
+
35
+ ### Other agents (Codex, etc.)
36
+
37
+ [`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md) is a self-contained Markdown playbook — reference it from your agent's context (e.g. `AGENTS.md`) and it teaches the same schema → outline → validate → render loop.
38
+
23
39
  ## Quick start
24
40
 
25
41
  ```bash
@@ -46,19 +62,30 @@ const bytes = await generatePptx(ir) // Uint8Array, ready to write to a .pptx
46
62
 
47
63
  | Command | Does |
48
64
  |---|---|
49
- | `render <ir.json> -o <out.pptx> [--theme <id>]` | Validate + render to a `.pptx` |
50
- | `validate <ir.json>` | Check the IR, print page-scoped errors |
51
- | `schema` | Print the IR JSON Schema |
65
+ | `render <target> -o <out.pptx> [--theme <id>] [--style <file>] [--draft]` | Validate + render to a `.pptx` — `target` is an IR JSON file, a deck project directory, or a bare deck name (see Deck projects) |
66
+ | `validate <target>` | Check the IR, print page-scoped errors — same `target` forms as `render` |
67
+ | `audit <target> [--json]` | Deterministic geometry review (overflow/out-of-bounds/low-contrast/overlap) same `target` forms as `render`, exits 1 when it finds anything (see Auditing) |
68
+ | `plan validate <plan.json>` | Check a deck plan against the schema and mode-aware hard gates (see Deck projects) |
69
+ | `assemble <dir\|name> [-o <file>]` | Materialize a deck project directory into a single IR JSON file |
70
+ | `disassemble <ir.json> -o <dir>` | Split an IR JSON file into a deck project directory |
71
+ | `schema [--style \| --plan]` | Print the IR JSON Schema (or the style-override schema, or the deck plan schema) |
52
72
  | `themes [--json]` | List the 13 built-in themes |
53
- | `preview <ir.json> -o <dir>` | Render each slide to a standalone SVG |
73
+ | `scenarios [--json]` | List named scenario presets (mode/delivery/audience axes + theme recommendations) |
74
+ | `preview <target> -o <dir> [--html]` | Render each slide to a standalone SVG (`--html` also writes a self-contained `preview.html`) — same `target` forms as `render`, never gated on placeholder pages |
75
+ | `init` | Scaffold `pptfast.config.json` |
76
+ | `check-update` / `self-update` | Check npm for a newer release / update the global install |
54
77
 
55
78
  ## The IR
56
79
 
57
- Run `node dist/cli.js schema` for the full JSON Schema — feed it to a model before asking it to write IR. A deck (`PptxIR`) is a required `filename`, `version`, `theme`, `meta`, `assets`, an optional `brand`, and an ordered list of `slides`; each slide has a `type` (`cover`, `chapter`, `content`, `ending`) and, for content slides, a list of typed `blocks` (`bullets`, `kpi_cards`, `image`, `chart`, …). `assets` is `{ images: { [id]: { src, alt? } } }` — blocks reference images by `asset_id`, so the same image can be reused across slides without duplication.
80
+ Run `node dist/cli.js schema` for the full JSON Schema — feed it to a model before asking it to write IR. A deck (`PptxIR`) has `version` (currently `"3"`), `filename`, an optional `scenario` (a preset id string or a partial axes object — see Scenarios below), `theme` (`id` plus optional `style`/`brand` overrides), `meta`, and `assets` all optional with sane defaults — plus a separate optional `brand` (logo placement) and a required ordered list of `slides`. Each slide has a `type` (`cover`, `chapter`, `content`, `ending`), an optional `layout` (an explicit page-layout id that always wins over auto-selection — omit it and pptfast auto-selects one, see Layout selection below), an optional `arrangement` (how a content slide's body is laid out, e.g. `two_column`, `kpi_focus`), and a list of typed `components` (`bullets`, `kpi_cards`, `image`, `chart`, …). `assets` is `{ images: { [id]: { src, alt? } } }` — components reference images by `asset_id`, so the same image can be reused across slides without duplication.
81
+
82
+ A deck also carries an optional `seed` (an integer that keeps auto-selected layouts stable across revisions — see Layout selection below for how it's derived when omitted). Any slide may set a stable `id` (what plan pages and validation error messages reference it by) and `placeholder: true` (a slide with no content yet — injected by `assemble` for a plan page nobody has filled in, skipped by the content-quality checks, and blocking `render` unless `--draft`). Field names that commonly drift between a model's output and the schema (25 synonym pairs across component types, e.g. kpi `title`→`label`, quote `content`→`text`) are silently normalized to the canonical name at validate time — `validate`/`render`/`preview` print a note listing what changed, never a hard error.
83
+
84
+ The v3 IR schema is frozen as of 0.3.0 — future evolution is additive only (new optional fields, new enum members), and any breaking change ships under a new top-level `version` value with the same hard-reject-and-migration treatment v2 got.
58
85
 
59
86
  ## Themes
60
87
 
61
- Each theme is a token set (color, type, motif) plus a manifest of which archetypes it allowsthemes never touch layout code.
88
+ A theme bundles a style (design tokens), a brand (identity chrome), and a layout set for each page type — the 13 built-ins below. Every built-in defaults to the *full* set of registered layouts for each page type (every archetype adapts its text color to the theme's actual background, so the full set stays readable everywhere). Narrowing it is a deliberate theme-author choice, not the normonly 3 of the 13 exclude a single chapter layout (a runway-native design whose contrast doesn't clear those themes' accent color). Override the style (`--style`) to re-color a theme.
62
89
 
63
90
  | id | label |
64
91
  |---|---|
@@ -76,15 +103,85 @@ Each theme is a token set (color, type, motif) plus a manifest of which archetyp
76
103
  | `luxe` | Luxe |
77
104
  | `heritage` | Heritage |
78
105
 
106
+ ## Scenarios
107
+
108
+ A scenario is three narrative axes, independent of theme (visual style), that set editorial discipline: `mode` (how the argument is built — `pyramid`, `narrative`, `instructional`, `showcase`, `briefing`), `delivery` (how dense the content is — `text`, `balanced`, `presentation`), and `audience` (a tone anchor — `executive`, `technical`, `customer`, `public`, no rendering effect yet). Set the IR's top-level `scenario` to a named preset string (e.g. `"boardroom-report"`) or a partial axes object (e.g. `{ "delivery": "presentation" }`) — an omitted axis, or an omitted `scenario` field entirely, falls back to `general` (`briefing` × `balanced` × `public`). An unknown preset name or axis value is a hard validate error listing what's available.
109
+
110
+ `delivery` drives the content-quality gate and the body-text baseline (paragraph/bullets/callout only — every other component's own type scale and the heading system are unaffected): the per-slide component budget and the bullets budget (item count and per-item length) both tighten from `text` toward `presentation`, while the body font size grows the other way — density is additionally capped by whichever layout the slide resolves to, whichever ceiling is tighter.
111
+
112
+ | delivery | body text | components / slide | bullets |
113
+ |---|---|---|---|
114
+ | `text` | 20px | 5 | up to 6 items, ~48 characters each |
115
+ | `balanced` (the default) | 24px | 4 | up to 5 items, ~40 characters each |
116
+ | `presentation` | 32px | 3 | up to 4 items, ~30 characters each |
117
+
118
+ Bullets shrink below their tier's baseline to fit when needed, down to a 14px floor, before any overflow handling kicks in. `pptfast validate` reports the exact numbers that applied to each slide.
119
+
120
+ Run `pptfast scenarios [--json]` to list the named presets (each carries soft theme recommendations — a starting suggestion, never a constraint) plus the raw axes tables.
121
+
122
+ ## Layout selection
123
+
124
+ When a slide omits `layout`, pptfast resolves one automatically in four deterministic steps: the page type's full registry pool → the theme's layout set for that page type (full by default, see Themes above) → the scenario's `mode` softly upweights (×3) a handful of content-layout ids that suit that mode, everything else stays at a ×1 floor (cover/chapter/ending are never weighted — their character comes from the theme, not the mode) → a seeded weighted pick, swapped deterministically to the runner-up when it would repeat the immediately preceding slide's layout. An explicit `layout` always wins and skips every step above. Whether the content actually fits is enforced separately by `validate`'s density gate, never by selection — so editing a page's content cannot silently flip its layout.
125
+
126
+ The pick is fully deterministic — the same IR always resolves the same way, so preview and the final render never disagree. Staying stable *across revisions* (editing one page without reshuffling every other page's auto-picked layout) additionally needs a persisted `seed`, resolved in this order:
127
+
128
+ 1. An explicit `ir.seed` — full revision stability, always wins.
129
+ 2. A deck project's own seed: `pptfast assemble` derives one from the plan's filename and page ids the first time a plan omits `seed`, and prints a note with the value — copy it into `deck.plan.json`'s `seed` field to persist it.
130
+ 3. Neither set: a content hash of `filename` + every slide's `heading` (legacy-compatible) — editing any heading reshuffles every auto-picked layout deck-wide.
131
+
132
+ `pptfast assemble` also writes every auto-picked `layout` back into the assembled `deck.json` (a page file's own explicit `layout` is left untouched) — the CLI notes how many pages it filled in.
133
+
134
+ ## Style overrides & project config
135
+
136
+ Override the built-in palette without forking a theme: write a style JSON
137
+ (schema: `pptfast schema --style`) and pass it per-render
138
+ (`--style brand.json`), or pin it project-wide in a `pptfast.config.json`
139
+ (found by walking up from cwd, scaffold one with `pptfast init`).
140
+ Precedence: CLI flag > project config file > user config file > IR (Deck
141
+ projects below has the full four-layer chain). The IR itself can carry the
142
+ same override in `theme.style` for fully self-contained decks.
143
+
144
+ ```json
145
+ { "theme": "consulting", "style": { "colors": { "primary": "#0B5FFF", "accent": "#FF6A00" } } }
146
+ ```
147
+
148
+ ## Deck projects
149
+
150
+ A deck can be authored two ways, and every command that takes IR accepts either: a single **IR JSON file** (everything above), or a **deck project directory** — the same content split across files so an agent can plan a deck's structure first, then write and revise it page by page instead of holding one growing JSON blob in context.
151
+
152
+ ```
153
+ my-deck/
154
+ deck.plan.json the locked plan: page order, type, and heading for every page
155
+ pages/<page-id>.json one file per filled page (components/layout/arrangement/background/image_side/footnote)
156
+ assets/ local images, auto-registered by filename (image id = filename without extension)
157
+ ```
158
+
159
+ `deck.plan.json` validates on its own, before any page exists: `pptfast plan validate deck.plan.json` checks the schema plus mode-aware hard gates (boundary pages, heading length, rhythm rotation, page count vs. delivery). A plan page with no matching `pages/<id>.json` becomes a **placeholder** slide — heading only, not missing — so a partially-written deck always assembles and previews. `pptfast render` refuses to export a deck with unfilled placeholders unless you pass `--draft`. `pptfast preview` never gates on them.
160
+
161
+ `pptfast assemble <dir>` materializes plan + pages + assets into a single IR JSON file (`deck.json` by default). `pptfast disassemble <ir.json> -o <dir>` does the reverse (documented-lossy — plan-only fields like `rhythm`/`focus` have no IR-side home to recover). `render`/`validate`/`preview` accept a directory directly too, assembling in memory first.
162
+
163
+ Deck project directories can be referenced by a bare name instead of a path — `pptfast render my-deck -o out.pptx` resolves `my-deck` under `$PPTFAST_HOME/decks` (`$PPTFAST_HOME` defaults to `~/.pptfast`) when no local file or directory of that name exists. All deck defaults resolve in four layers, highest wins: CLI flag > project `pptfast.config.json` > user `~/.pptfast/config.json` > the deck's own values. Both config layers can set `decksDir` to redirect where bare names resolve — the project layer's value resolves against that config file's own directory (for a team that wants deck projects checked into the repo), the user layer's against `$PPTFAST_HOME`. Project wins when both are set.
164
+
165
+ ## Auditing
166
+
167
+ `pptfast audit <target> [--json]` renders every page off-screen and runs a deterministic geometry review — no LLM screenshot squinting, no variance. Four checks: **overflow** (text past its own box or column), **out-of-bounds** (past the page edge), **low-contrast** (WCAG relative-luminance ratio between text and its resolved background), and **overlap** (two components' regions substantially colliding). Advisory, not a hard gate — `validate` already rejects structurally invalid or over-dense decks. Audit catches what a valid deck can still get wrong at render time (an author-chosen near-background text color, two components whose combined content collides).
168
+
169
+ Run it once every page is filled, on the same `target` forms as `validate`/`render` (file, deck project directory, or bare name). Human output groups findings by page (`page 3 (p-kpi): [low-contrast] …`, each message carries a fix suggestion) plus a summary line. `--json` prints the full machine-readable report. The exit code alone is agent-judgeable: `0` clean, `1` when it finds anything — fix the flagged page and re-run `audit` alone, no need to re-render. Skipped placeholder pages are noted, the same "not missing, just not written yet" treatment used everywhere else.
170
+
171
+ ```bash
172
+ pptfast audit examples/basic.json
173
+ # → audited 5 pages, 0 skipped, 0 findings
174
+ ```
175
+
79
176
  ## For AI agents
80
177
 
81
- The recommended loop for an agent generating a deck: read `pptfast schema` to learn the vocabulary, write an IR JSON, run `pptfast validate` and fix whatever it reports (errors carry a page number and a fixable-in-place message — the point is to close this loop without a human), then `pptfast render`. `pptfast preview` gives the agent SVG files it can look at to self-check layout before committing to a render. A Claude Code plugin that wraps this loop as a skill is planned for v0.2 (see Roadmap).
178
+ The recommended loop for an agent generating a deck: read `pptfast schema` to learn the vocabulary, write an IR JSON, run `pptfast validate` and fix whatever it reports (errors carry a page number and a fixable-in-place message — the point is to close this loop without a human), then `pptfast audit` for the same kind of fixable-in-place feedback on what a *valid* deck can still get wrong at render time (overflow, low-contrast, overlap — exit code alone says whether it's clean), then `pptfast render`. `pptfast preview` gives the agent SVG files it can look at to self-check layout before committing to a render. Add `--html` to also write a self-contained `preview.html` for a human to review (keyboard nav, placeholder badges — a remote-URL image asset stays remote, the one self-containment gap). The Claude Code plugin above wraps this loop as a skill ([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md)).
82
179
 
83
180
  ## Roadmap
84
181
 
85
- - **v0.2** — Claude Code plugin + skills wrapping the render loop, design token overrides (`--tokens`), `init`/self-update commands.
86
- - **v0.3** — theme-customization skill (brand colors tokens), custom manifest slots, 1.0.
87
- - **v0.4** — richer motion (more entrance animations), Office real-device testing, web playground.
182
+ - **v0.2** — Claude Code plugin + skill wrapping the render loop (shipped), design token overrides (`--style`), `init`/self-update commands.
183
+ - **v0.3** — scenario-driven narrative axes (mode/delivery/audience), an explicit layout + component registry with weighted seeded selection, the deck plan/assemble workflow, a deterministic geometry audit, a self-contained HTML preview, and the six-phase skill (shipped, schema frozen — see The IR above).
184
+ - **v0.4** — theme ecosystem (distributable theme registry, theme-customization skill, custom brand slots), richer motion (more entrance animations), Office real-device testing, web playground, 1.0.
88
185
 
89
186
  ## Credits
90
187
 
package/README.zh-CN.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 为什么
8
8
 
9
- 自由绘制 SVG/HTML 再转 PPTX 的链路上限很高,但下限不稳定——弱模型(或强模型状态不好时)画出来的往往是版式错乱、脱离品牌规范、甚至无法阅读的产物。pptfast 用受控词汇取代自由绘制:一份语义化 IR(zod schema)、由 design tokens 驱动的 13 个内置主题、带 seed 多样性的 archetype/block 版式库,以及每个图形都保持可编辑的原生 DrawingML 输出——不是贴上去的一张图。
9
+ 自由绘制 SVG/HTML 再转 PPTX 的链路上限很高,但下限不稳定——弱模型(或强模型状态不好时)画出来的往往是版式错乱、脱离品牌规范、甚至无法阅读的产物。pptfast 用受控词汇取代自由绘制:一份语义化 IR(zod schema)、13 个内置主题(各自打包一套 style 设计 tokens brand 品牌标识元素)、带 seed 多样性的 layout/component 版式库,以及每个图形都保持可编辑的原生 DrawingML 输出——不是贴上去的一张图。
10
10
 
11
11
  一份 PPT 本质上是五件事:内容模型、二维布局、视觉样式、动效、叙事。pptfast 负责后四项,内容模型交给你(或你的 agent)通过写 IR 来掌控。
12
12
 
@@ -19,6 +19,22 @@ pptfast --help
19
19
 
20
20
  需要 Node >= 18。也可从源码构建:`git clone https://github.com/liustack/pptfast.git && cd pptfast && pnpm install && pnpm build`。
21
21
 
22
+ ### 作为 Claude Code 插件
23
+
24
+ 本仓库同时是一个 Claude Code 插件,内置整套生成流程的 skill:
25
+
26
+ ```
27
+ /plugin marketplace add liustack/pptfast
28
+ /plugin install pptfast@pptfast
29
+ /reload-plugins
30
+ ```
31
+
32
+ skill 依赖 CLI 驱动,请一并安装 CLI(`npm install -g @liustack/pptfast`)。
33
+
34
+ ### 其他 agent(Codex 等)
35
+
36
+ [`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md) 是一份自包含的 Markdown 操作手册——把它引入你的 agent 上下文(例如在 `AGENTS.md` 里引用),即可复用同一套 schema → 大纲 → validate → render 回路。
37
+
22
38
  ## 快速开始
23
39
 
24
40
  ```bash
@@ -44,19 +60,30 @@ const bytes = await generatePptx(ir) // Uint8Array,可直接写成 .pptx 文
44
60
 
45
61
  | 命令 | 作用 |
46
62
  |---|---|
47
- | `render <ir.json> -o <out.pptx> [--theme <id>]` | 校验并渲染成 `.pptx` |
48
- | `validate <ir.json>` | 校验 IR,输出带页码的错误信息 |
49
- | `schema` | 输出 IR JSON Schema |
63
+ | `render <target> -o <out.pptx> [--theme <id>] [--style <file>] [--draft]` | 校验并渲染成 `.pptx`——`target` 可以是 IR JSON 文件、deck 项目目录,或裸名(见「Deck 项目」) |
64
+ | `validate <target>` | 校验 IR,输出带页码的错误信息——`target` 形式同 `render` |
65
+ | `audit <target> [--json]` | 确定性几何审查(溢出/越界/低对比度/重叠)——`target` 形式同 `render`,一旦发现问题 exit 1(见「审查」) |
66
+ | `plan validate <plan.json>` | 校验 deck plan 是否符合 schema 与随 mode 变化的硬门(见「Deck 项目」) |
67
+ | `assemble <dir\|name> [-o <file>]` | 把 deck 项目目录合并成单个 IR JSON 文件 |
68
+ | `disassemble <ir.json> -o <dir>` | 把 IR JSON 文件拆成 deck 项目目录 |
69
+ | `schema [--style \| --plan]` | 输出 IR 的 JSON Schema(或 style 覆盖 schema,或 deck plan schema) |
50
70
  | `themes [--json]` | 列出 13 个内置主题 |
51
- | `preview <ir.json> -o <dir>` | 逐页渲染为独立 SVG |
71
+ | `scenarios [--json]` | 列出具名场景预设(mode/delivery/audience + theme 推荐) |
72
+ | `preview <target> -o <dir> [--html]` | 逐页渲染为独立 SVG(`--html` 额外写出一个自包含的 `preview.html`)——`target` 形式同 `render`,永远不受占位页拦截 |
73
+ | `init` | 生成 `pptfast.config.json` 模板 |
74
+ | `check-update` / `self-update` | 检查 npm 上的新版本 / 更新全局安装 |
52
75
 
53
76
  ## IR
54
77
 
55
- 运行 `node dist/cli.js schema` 获取完整 JSON Schema——让模型写 IR 之前先读它。一份 deck(`PptxIR`)由必填的 `filename`、`version`、`theme`、`meta`、`assets`、可选的 `brand`,和一个有序的 `slides` 列表组成。每张 slide 有一个 `type`(`cover`、`chapter`、`content`、`ending`),content 类型的 slide 还带一组带类型的 `blocks`(`bullets`、`kpi_cards`、`image`、`chart` 等)。`assets` 的形状是 `{ images: { [id]: { src, alt? } } }`,block 通过 `asset_id` 引用图片,同一张图可以在多页复用而不必重复内嵌。
78
+ 运行 `node dist/cli.js schema` 获取完整 JSON Schema——让模型写 IR 之前先读它。一份 deck(`PptxIR`)包含 `version`(现为 `"3"`)、`filename`、一个可选的 `scenario`(预设 id 字符串,或部分轴对象——详见下文「场景」一节)、`theme`(`id` 加可选的 `style`/`brand` 覆盖)、`meta`、`assets`——均可省略、有默认值——另外还有一个独立的可选 `brand`(logo 位置)字段,以及必填的有序 `slides` 列表。每张 slide 有一个 `type`(`cover`、`chapter`、`content`、`ending`)、一个可选的 `layout`(显式指定页面版式 id,一经设置恒生效、优先于自动选型——省略则由 pptfast 自动选型,详见下文「版式选型」)、一个可选的 `arrangement`(content slide 正文的排布方式,如 `two_column`、`kpi_focus`),以及一组带类型的 `components`(`bullets`、`kpi_cards`、`image`、`chart` 等)。`assets` 的形状是 `{ images: { [id]: { src, alt? } } }`,component 通过 `asset_id` 引用图片,同一张图可以在多页复用而不必重复内嵌。
79
+
80
+ 一份 deck 还可以携带一个可选的 `seed`(整数,让自动选型的版式在多次修订之间保持稳定——省略时如何生成,详见下文「版式选型」)。任意 slide 都可以设置一个稳定的 `id`(plan 的页面和校验报错都靠它引用)以及 `placeholder: true`(还没有内容的占位 slide——由 `assemble` 为 plan 里没人填写的页面注入,内容质量检查会跳过它,`render` 也会因它拒绝导出,除非加 `--draft`)。模型输出里容易和 schema 对不上的字段名(跨 component 类型共 25 组同义词,例如 kpi 的 `title`→`label`、quote 的 `content`→`text`)会在校验时静默改写成规范名——`validate`/`render`/`preview` 会打印一条改了什么的提示,从不因此报错。
81
+
82
+ v3 IR schema 自 0.3.0 起冻结——后续演进只走加法(新增可选字段、新增枚举值),任何破坏性变更都会启用新的顶层 `version` 值,并沿用 v2 那套硬拒绝 + 迁移提示的处理方式。
56
83
 
57
84
  ## 主题
58
85
 
59
- 每个主题是一组 token(配色、字体、motif)加一份 manifest(声明允许使用哪些 archetype)——主题本身从不涉及布局代码。
86
+ 主题(theme)打包了 style(设计 tokens)、brand(品牌标识元素)与每个页型各自的版式(layout)集合——以下是 13 个内置主题。每个内置主题默认对每个页型都开放全部已注册版式(每个 archetype 都会按主题的实际背景色自适应取色,所以全集在任何主题下都保持可读)。收窄集合是主题作者的主动选择,不是常态——13 个主题里只有 3 个排除了单个 chapter 版式(一款 runway 专属设计,在这三个主题的强调色上对比度不够)。覆盖 style(`--style`)即可为某个主题重新配色。
60
87
 
61
88
  | id | label |
62
89
  |---|---|
@@ -74,15 +101,79 @@ const bytes = await generatePptx(ir) // Uint8Array,可直接写成 .pptx 文
74
101
  | `luxe` | Luxe |
75
102
  | `heritage` | Heritage |
76
103
 
104
+ ## 场景
105
+
106
+ 场景(scenario)是三条独立于主题(视觉风格)之外的叙事轴,用来定编辑纪律:`mode`(论证方式——`pyramid`、`narrative`、`instructional`、`showcase`、`briefing`)、`delivery`(内容密度——`text`、`balanced`、`presentation`)、`audience`(语气锚点——`executive`、`technical`、`customer`、`public`,目前无渲染效果)。把 IR 顶层的 `scenario` 设为具名预设字符串(如 `"boardroom-report"`),或部分轴对象(如 `{ "delivery": "presentation" }`)——省略任意一轴、或整个省略 `scenario` 字段,均回落到 `general` 预设(`briefing` × `balanced` × `public`)。未知的预设名或轴值会硬报错并列出可用项。
107
+
108
+ `delivery` 驱动内容质量门,也驱动正文字号基线(仅 paragraph/bullets/callout 三件套——其余组件各自的字阶与标题体系不受影响):每页的 component 数预算与 bullets 预算(条目数与单条长度上限)都随 `delivery` 从 `text` 向 `presentation` 收紧,正文字号则反向增长——密度上限还会再叠加所选 layout 的容量,取两者中更紧的一个。
109
+
110
+ | delivery | 正文字号 | 每页 component 数 | bullets |
111
+ |---|---|---|---|
112
+ | `text` | 20px | 5 | 至多 6 条,每条约 48 字 |
113
+ | `balanced`(默认) | 24px | 4 | 至多 5 条,每条约 40 字 |
114
+ | `presentation` | 32px | 3 | 至多 4 条,每条约 30 字 |
115
+
116
+ bullets 需要时会在各自档位基线之下收缩以适配空间,最低到 14px 地板,再触发溢出处理。`pptfast validate` 会报出每页实际生效的具体数值。
117
+
118
+ 运行 `pptfast scenarios [--json]` 查看全部具名预设(各自带一份软性 theme 推荐表——仅供参考,从不构成约束)及三轴的原始数据表。
119
+
120
+ ## 版式选型
121
+
122
+ 当某页省略 `layout` 时,pptfast 按四个确定性步骤自动选型:该页型的全部注册版式 → 主题该页型的版式集合(默认全集,见上文「主题」)→ 场景的 `mode` 对一小撮适配该 mode 的 content 版式做 ×3 软加权,其余维持 ×1 底权(cover/chapter/ending 三个页型永不加权——它们的个性来自主题,不来自 mode)→ 按 seed 加权取样,若命中结果与紧邻的上一页版式相同则确定性地换成次优候选。显式 `layout` 恒优先,跳过以上全部步骤。内容装不装得下由 `validate` 的密度门单独把关,从不参与选型——因此改一页的内容不会悄悄翻转它的版式。
123
+
124
+ 选型本身完全确定——同一份 IR 永远选出同一个结果,预览与最终渲染绝不会不一致。但要在**多次修订之间**保持稳定(改一页不搅动其余页的自动选型),还需要一个持久化的 `seed`,按以下顺序解析:
125
+
126
+ 1. 显式 `ir.seed`——完全修订稳定,恒优先
127
+ 2. deck 项目自己的 seed:plan 省略 `seed` 时,`pptfast assemble` 首次运行会用 plan 的 filename + 页面 id 列表派生一个,并打印提示——把这个值写进 `deck.plan.json` 的 `seed` 字段即可固化
128
+ 3. 以上均未设置:回落到 `filename` + 每页 `heading` 的内容哈希(向后兼容旧行为)——改动任何一页标题都会重排全 deck 的自动选型
129
+
130
+ `pptfast assemble` 还会把每一页的自动选型结果写回合并后的 `deck.json`(页面文件里已显式指定的 `layout` 不受影响)——CLI 会提示本次填写了多少页。
131
+
132
+ ## Style 覆盖与项目配置
133
+
134
+ 不必分叉主题即可覆盖内置色板:写一份 style JSON(结构见 `pptfast schema --style`),按次渲染传入(`--style brand.json`),或固化在项目级 `pptfast.config.json` 里(自 cwd 向上查找,用 `pptfast init` 生成模板)。优先级:CLI flag > 项目配置文件 > 用户配置文件 > IR(完整的四层链见下文「Deck 项目」)。IR 自身也可以在 `theme.style` 携带同样的覆盖,做到单文件自包含。
135
+
136
+ ```json
137
+ { "theme": "consulting", "style": { "colors": { "primary": "#0B5FFF", "accent": "#FF6A00" } } }
138
+ ```
139
+
140
+ ## Deck 项目
141
+
142
+ 一份 deck 有两种写法,接受 IR 的每个命令两种都认:单个 **IR JSON 文件**(如上文所述),或者一个 **deck 项目目录**——把同样的内容拆到多个文件里,方便 agent 先规划整体结构,再逐页撰写和修订,而不必把一份不断增长的 JSON 塞进上下文。
143
+
144
+ ```
145
+ my-deck/
146
+ deck.plan.json 锁定的 plan:每一页的顺序、type、heading
147
+ pages/<page-id>.json 每个已填页面一个文件(components/layout/arrangement/background/image_side/footnote)
148
+ assets/ 本地图片,按文件名自动注册(图片 id = 去掉扩展名的文件名)
149
+ ```
150
+
151
+ `deck.plan.json` 可以在任何页面填写之前单独校验:`pptfast plan validate deck.plan.json` 会检查 schema,以及一组随 mode 变化的硬门(边界页类型、标题长度、节奏轮换、页数是否匹配 delivery)。plan 里某一页如果没有对应的 `pages/<id>.json`,会成为一个**占位页**——只有标题、不算缺失——所以写到一半的 deck 也能正常 assemble 和预览。`pptfast render` 遇到未填的占位页会拒绝导出,除非加 `--draft`。`pptfast preview` 则永远不会因占位页被拦。
152
+
153
+ `pptfast assemble <dir>` 把 plan + pages + assets 合并成一个 IR JSON 文件(默认写到 `deck.json`)。`pptfast disassemble <ir.json> -o <dir>` 做反向操作(有据可查的有损转换——`rhythm`/`focus` 这类只属于 plan 的字段在 IR 里没有对应位置,无法还原)。`render`/`validate`/`preview` 也都能直接接受一个目录,会先在内存里 assemble 一遍。
154
+
155
+ Deck 项目目录可以用裸名代替路径引用——`pptfast render my-deck -o out.pptx` 在本地找不到同名文件或目录时,会到 `$PPTFAST_HOME/decks` 下找 `my-deck`(`$PPTFAST_HOME` 缺省是 `~/.pptfast`)。所有 deck 默认值按四层优先级解析,从高到低:CLI flag > 项目级 `pptfast.config.json` > 用户级 `~/.pptfast/config.json` > deck 自身的值。两个配置层都可以设置 `decksDir` 来重定向裸名的解析位置——项目层的值相对该配置文件自身所在目录解析(给想把 deck 项目入库的团队用),用户层的值相对 `$PPTFAST_HOME` 解析,两者都设置时项目层优先。
156
+
157
+ ## 审查
158
+
159
+ `pptfast audit <target> [--json]` 会离屏渲染每一页,跑一遍确定性几何审查——不靠 LLM 截图目检,零方差。四类检查:**溢出**(文字超出自己的框或列)、**越界**(超出页面边缘)、**低对比度**(文字与其所在背景的 WCAG 相对亮度对比度不达标)、**重叠**(两个组件的区域大面积相交)。这是建议性工具,不是硬门——`validate` 已经拦住了结构非法或密度超标的 deck,audit 抓的是一份合法 deck 在渲染层仍可能出现的问题(作者选了一个贴近背景色的文字颜色、两个组件的内容恰好撞在一起)。
160
+
161
+ 建议在所有页面填完之后跑一遍,`target` 形式同 `validate`/`render`(文件、deck 项目目录或裸名)。人读输出按页分组报错(`page 3 (p-kpi): [low-contrast] …`,每条消息都带修正建议),末尾附一行汇总。`--json` 输出完整的机器可读报告。exit code 本身即可供 agent 判断:干净是 `0`,发现问题是 `1`——按报错修那一页,再单独重跑一次 `audit` 即可,不必重新渲染。被跳过的占位页会在汇总里注明,和别处「不算缺失、只是还没写」的处理方式一致。
162
+
163
+ ```bash
164
+ pptfast audit examples/basic.json
165
+ # → audited 5 pages, 0 skipped, 0 findings
166
+ ```
167
+
77
168
  ## 面向 AI agent
78
169
 
79
- 推荐给 agent 的生成回路:先读 `pptfast schema` 学词汇表,写出 IR JSON,跑 `pptfast validate` 并根据报错自纠(错误信息带页码和可直接照抄的修正方式,目的就是让这个回路不必依赖人工介入),再执行 `pptfast render`。`pptfast preview` 能让 agent 在正式渲染前先看一遍 SVG,自查版式是否合理。把这套回路封装成 skill Claude Code plugin 计划在 v0.2 落地(见 Roadmap)。
170
+ 推荐给 agent 的生成回路:先读 `pptfast schema` 学词汇表,写出 IR JSON,跑 `pptfast validate` 并根据报错自纠(错误信息带页码和可直接照抄的修正方式,目的就是让这个回路不必依赖人工介入),再跑 `pptfast audit`——同样是可直接照抄的修正反馈,只是针对一份*合法* deck 在渲染层仍可能出现的问题(溢出、低对比度、重叠——exit code 本身就说明干不干净),最后执行 `pptfast render`。`pptfast preview` 能让 agent 在正式渲染前先看一遍 SVG,自查版式是否合理。加上 `--html` 还会额外写出一个自包含的 `preview.html`,供人工审查(键盘翻页、占位页角标——远程 URL 的图片资产仍是远程链接,这是自包含性上唯一的缺口)。上文的 Claude Code 插件已把这套回路封装成 skill([`skills/pptfast/SKILL.md`](./skills/pptfast/SKILL.md))。
80
171
 
81
172
  ## 路线图
82
173
 
83
- - **v0.2**——封装该回路的 Claude Code plugin + skills、design token 覆盖(`--tokens`)、`init`/自更新命令。
84
- - **v0.3**——主题定制 skill(品牌色 tokens)、自定义 manifest 插槽、1.0 版本。
85
- - **v0.4**——更丰富的动效(更多入场动画)、Office 真机实测、web playground
174
+ - **v0.2**——封装该回路的 Claude Code plugin + skill(已落地)、design token 覆盖(`--style`)、`init`/自更新命令。
175
+ - **v0.3**——场景化叙事轴(mode/delivery/audience)、显式 layout + component 注册表与加权 seed 选型、deck plan/assemble 工作流、确定性几何审查、自包含 HTML 预览、六阶段 skill(已落地,schema 已冻结——见上文「IR」一节)。
176
+ - **v0.4**——主题生态(可分发主题注册表、主题定制 skill、自定义品牌插槽)、更丰富的动效(更多入场动画)、Office 真机实测、web playground、1.0 版本。
86
177
 
87
178
  ## 致谢
88
179
 
@@ -31,4 +31,4 @@ function installNodePlatform() {
31
31
  export {
32
32
  installNodePlatform
33
33
  };
34
- //# sourceMappingURL=chunk-4WISUDXE.js.map
34
+ //# sourceMappingURL=chunk-SROEW6BH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/node.ts"],"sourcesContent":["import { DOMParser as LinkedomDOMParser } from \"linkedom\"\nimport { installPlatform } from \"./registry\"\nimport type * as Sharp from \"sharp\"\n\nasync function recodeWithSharp(dataUrl: string): Promise<string> {\n let sharpMod: typeof Sharp.default\n try {\n sharpMod = (await import(\"sharp\")).default as unknown as typeof Sharp.default\n } catch (e) {\n const err = e as NodeJS.ErrnoException\n if (err?.code === \"ERR_MODULE_NOT_FOUND\" || /Cannot find/.test(err?.message ?? \"\")) {\n throw new Error(\n 'Re-encoding non-PNG/JPEG/GIF images requires the optional dependency \"sharp\" (npm i sharp), or convert the image beforehand'\n )\n }\n throw e\n }\n const base64 = dataUrl.slice(dataUrl.indexOf(\",\") + 1)\n const png = await sharpMod(Buffer.from(base64, \"base64\")).png().toBuffer()\n return `data:image/png;base64,${png.toString(\"base64\")}`\n}\n\n/** Wire Node implementations (linkedom DOM, sharp image re-encode) into the SDK. */\nexport function installNodePlatform(): void {\n installPlatform({\n domParser: LinkedomDOMParser as unknown as typeof DOMParser,\n recodeImageToPng: recodeWithSharp,\n })\n}\n"],"mappings":";;;;;AAAA,SAAS,aAAa,yBAAyB;AAI/C,eAAe,gBAAgB,SAAkC;AAC/D,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,OAAO,OAAO,GAAG;AAAA,EACrC,SAAS,GAAG;AACV,UAAM,MAAM;AACZ,QAAI,KAAK,SAAS,0BAA0B,cAAc,KAAK,KAAK,WAAW,EAAE,GAAG;AAClF,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,QAAM,SAAS,QAAQ,MAAM,QAAQ,QAAQ,GAAG,IAAI,CAAC;AACrD,QAAM,MAAM,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS;AACzE,SAAO,yBAAyB,IAAI,SAAS,QAAQ,CAAC;AACxD;AAGO,SAAS,sBAA4B;AAC1C,kBAAgB;AAAA,IACd,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB,CAAC;AACH;","names":[]}