@liustack/pptfast
Stable, editable PPTX generation for AI agents — semantic IR in, native DrawingML out
AI Analysis
核心用途是让智能体生成高质量、可二次编辑的 PPT 演示文稿。适合需要智能体根据大纲自动排版并输出非图片拼接、完全可编辑的 PPTX 文件的用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:liustack/pptfastREADME
Read the full README ↗pptfast
Stable, editable PPTX generation for AI agents — semantic IR in, native DrawingML out.
[English] | 简体中文
Why
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), 17 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.
That editability claim has one honest boundary: shapes and text runs are the native unit pptfast emits, and every one is a real PowerPoint object you can select, restyle, and retype (that includes the shapes and text a chart or data_table component draws). What pptfast does not produce is a native PowerPoint chart part or table object: no embedded chart data, no ``. A chart's bars and a table's cells are geometry and text, not a data-bound object PowerPoint can redraw from new numbers, so change the numbers by editing the IR and re-rendering, not by dragging a bar or typing into a cell. That is a deliberate trade for the deterministic, seed-stable output described above, not an oversight, and it does not weaken the editability claim for every other shape on the slide.
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.
Install
npm install -g @liustack/pptfast
pptfast --help
Node >= 18. Or build from source: git clone https://github.com/liustack/pptfast.git && cd pptfast && pnpm install && pnpm build.
As a Claude Code plugin
The repo doubles as a Claude Code plugin that ships the deck-generation skill:
/plugin marketplace add liustack/pptfast
/plugin install pptfast@pptfast
/reload-plugins
The skill drives the CLI, so install the CLI too (npm install -g @liustack/pptfast).
As a DSH plugin
pptfast is also a DeepSeek Harness (DSH) plugin. One command installs it into a DSH profile:
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/pptfast@0.17.0
Name the version explicitly: dsh installs plugins through pnpm 11, which holds back anything published in the last 24 hours and silently resolves @latest to an older release — for this package that would be 0.16.0, which has no dsh plugin entry at all. A named version is installed as a deliberate exception. npm view @liustack/pptfast version prints the current one.
The plugin card shows up as "pptfast" and registers the same deck-generation skill into DSH's skill system. The skill drives the CLI that ships inside the plugin package itself — no separate CLI install needed there. Uninstalling the plugin removes the skill with no residue.
Other agents (Codex, etc.)
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.
Quick start
Write a minimal deck, then run the validate → render → preview loop:
cat > deck.json -o [--theme ] [--theme-file ] [--style ] [--draft]` | Validate + render to a `.pptx` — `target` is an IR JSON file, a deck project directory, or a bare deck name (see Deck projects) |
| `validate ` | Check the IR, print page-scoped errors and advisory warnings — same `target` forms as `render` |
| `audit [--json] [--pixels]` | Deterministic geometry review (overflow/out-of-bounds/low-contrast/overlap/content-truncated/content-dropped) — same `target` forms as `render`, exits 1 when it finds anything (see Auditing) |
| `asset-brief [--json]` | Image-generation brief for every `image` component — real rendered frame, fit/crop mode, suggested pixel size, theme palette/mood, and a paste-ready prompt (see Asset briefs) |
| `spec validate ` | Check a deck spec against the schema and strategy-aware hard gates (see Deck projects) |
| `assemble [-o ]` | Materialize a deck project directory into a single IR JSON file |
| `disassemble -o ` | Split an IR JSON file into a deck project directory |
| `schema [--style \| --spec]` | Print the IR JSON Schema (or the style-override schema, or the deck spec schema) |
| `themes [--json]` | List the 17 built-in themes |
| `brand extract -o [--id] [--label]` | Extract brand colors/fonts from a `.thmx`/`.potx`/`.pptx` into a theme file, entirely locally (see Your own brand) — load it with `--theme-file` (also on `validate`/`audit`/`preview`/`serve`) or as a deck project's `theme.json` |
| `narratives [--json]` | List named narrative presets (strategy/pacing/audience axes + theme recommendations) |
| `preview -o [--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 |
| `serve [--port 4400] [--no-open]` | Live-preview server: the same review page as `preview --html`, auto-reloading on source changes, with annotations submitting straight back to the deck directory as `revision-request.json` |
| `migrate -o ` | Convert a v3 IR file to v4, or a `deck.plan.json` project directory to `deck.spec.json` — deterministic, no model call (see The IR and Deck projects) |
| `init` | Scaffold `pptfast.config.json` |
| `check-update` / `self-update` | Check npm for a newer release / update the global install |
## The IR
Run `pptfast schema` for the full JSON Schema — feed it to a model before asking it to write IR. A deck (`PptxIR`) has `version` (currently `"4"`, and now the default when omitted), `filename`, an optional `narrative` (a preset id string or a partial axes object — see Narratives 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. `alt`, when set, lands in the exported PPTX's standard accessibility-description slot for that image (what PowerPoint's "Edit Alt Text" reads and writes) — an `image` component with no `alt` on its asset exports unchanged, same as before this field did anything.
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 spec pages and validation error messages reference it by), `placeholder: true` (a slide with no content yet — injected by `assemble` for a spec page nobody has filled in, skipped by the content-quality checks, and blocking `render` unless `--draft`), and an optional `notes` (aliases `note`/`speaker_notes`/`speakerNotes`) that exports as a native PowerPoint speaker note — content for the presenter's own view, never drawn onto the slide canvas and never counted toward any layout capacity. Field names that commonly drift between a model's output and the schema (55 synonym pairs across component types, e.g. kpi `title`→`label`, quote `content`→`text`, swot `strength`→`strengths`, bmc `partners`→`key_partners`) are silently normalized to the canonical name at validate time — `validate`/`render`/`preview` print a note listing what changed, never a hard error. That rescue is scoped to weak-model synonym drift only — it does not cover pre-v4 vocabulary. A v4-labeled document that writes `scenario` instead of `narrative`, `mode`/`delivery` instead of `strategy`/`pacing`, or the old `narrative`/`text`/`presentation` axis values hard-rejects, listing the current names/values, exactly like any other unrecognized field or value. An explicit `version: "3"` (or `"2"`) also hard-rejects, with a migration pointer — see `pptfast migrate` below, the only supported path for old-vocabulary input.
Eight component types are *full-body*: `swot` (strengths/weaknesses/opportunities/threats), `bmc` (the nine-block Business Model Canvas), `waterfall` (a running-total bridge chart), `gantt` (dated bars on a shared numeric axis), `pest` (a political/economic/social/technological macro-environment scan), `five_forces` (Porter's competitive-forces hub-and-spoke), `heatmap` (a value-driven color grid), and `sankey` (a layered, quantity-proportional flow diagram — shipped as native editable vectors, not the rasterized image the type gets elsewhere). Each fills a slide's entire content rect and must be the only component on its slide — mixing one in with anything else fails `validate` instead of silently dropping the sibling.
The v4 IR schema is frozen as of 0.4.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 v3 got. `pptfast migrate -o ` deterministically converts a v3 file to v4 (field renames only — same theme, layout selection, content budgets, and visual output) — see Deck projects below for the sibling `deck.plan.json` → `deck.spec.json` conversion.
## Themes
A theme bundles a style (design tokens), a brand (identity chrome), and a layout set for each page type — the 17 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 norm — none of the 17 narrows anything today (an earlier three-theme exclusion was reverted once every archetype's ink adapted to its actual background). Override the style (`--style`) to re-color a theme.
| id | label |
|---|---|
| `consulting` | Business Consulting |
| `enterprise` | Enterprise |
| `academic` | Academic |
| `insight` | Financial Insight |
| `campaign` | Marketing Campaign |
| `bloom` | Soft Bloom |
| `classroom` | Classroom |
| `ink` | Ink Wash |
| `tech` | Tech |
| `runway` | Fashion Runway |
| `journal` | Editorial Journal |
| `luxe` | Luxe |
| `heritage` | Heritage |
| `pulse` | Health & Life Science |
| `terra` | Sustainability & ESG |
| `ember` | Startup Pitch |
| `vermilion` | Official Report |
### Your own brand
The fastest way to make the output look like *your company* instead of a built-in theme: extract the brand from a template you already have. `pptfast brand extract` reads the colors and fonts out of a `.thmx` theme, `.potx` template, or `.pptx` presentation — **entirely locally, the file never leaves your machine** (verified against all 39 Office themes shipped with a macOS PowerPoint install) — and writes a pptfast theme file:
```bash
pptfast brand extract corp-template.pptx -o my-brand.theme.json
pptfast render deck.json -o deck.pptx --theme-file my-brand.theme.json
--theme-file works on render, validate, audit, preview, and serve. In a deck project directory, drop the file in as theme.json and it auto-loads on every command — reference its id from deck.spec.json, no flag needed. The 12 OOXML color slots map almost 1:1 onto pptfast's tokens (the six accent colors become the chart palette); the one derived token, muted, is stepped toward the background only as far as it can go while still clearing a 4.5:1 contrast ratio. Loading enforces the same contrast floor every registered theme faces: a palette whose text and background are too close is refused with the failing token, the measured ratio, and the background named — never rendered unreadable. A custom theme can never shadow a built-in id.
Narratives
A narrative is three axes, independent of theme (visual style), that set editorial discipline: strategy (how the argument is built — pyramid, storytelling, instructional, showcase, briefing), pacing (how dense the content is — dense, balanced, spacious), and audience (a tone anchor — executive, technical, customer, public, no rendering effect yet). Set the IR's top-level narrative to a named preset string (e.g. "boardroom-report") or a partial axes object (e.g. { "pacing": "spacious" }) — an omitted axis, or an omitted narrative 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.
pacing 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 dense toward spacious, while the body font size grows the other way — density is additionally capped by whichever layout the slide resolves to, whichever ceiling is tighter. These are editorial guidance, not hard limits: validate reports them as warnings and still succeeds — only genuine render-safety ceilings (below) can block generation.
| pacing | body text | components / slide | bullets |
|---|---|---|---|
dense | 20px | 5 | up to 6 items, ~48 characters each |
balanced (the default) | 24px | 4 | up to 5 items, ~40 characters each |
spacious | 32px | 3 | up to 4 items, ~30 characters each |
Bullets shrink below their tier's baseline to fit when need