CLI reference
The vanillasky CLI validates, renders, and shares VideoConfig JSON with a local Chrome. No cloud, no credits — the same deterministic render contract as the server export path, driven from your machine.
vanillasky setup [--check]
vanillasky render <config.json> [options]
vanillasky validate <config.json> [--json] [--format <id>]
vanillasky tracks [--json]
vanillasky scope [--json]
vanillasky brand [path] [--json]
vanillasky link <config.json> [--base <url>]setup
Interactively fills in only what's missing — Pexels key, default orientation, music mood (every prompt skippable, merged into ~/.vanillasky/config.json) — and offers to scaffold a DESIGN.md for the current repo.
| Flag | Description |
|---|---|
--check | Print the current setup state (config, Pexels key, default orientation, music mood, DESIGN.md) without prompting. |
validate
Checks structure, template ids (camelCase, with suggestions for near-misses), every scene's variables against the template's variable schema (unknown names, missing required, type mismatches), backgroundEffect placement, and — when the config declares "format": "launch" or --format launch is passed — the slot contract. Exits 1 on errors.
| Flag | Description |
|---|---|
--json | Machine-readable JSON output. |
--format <id> | Enforce a format's slot contract (e.g. launch); defaults to the config's format field when present. |
render
Full render to MP4 (validates first and refuses invalid configs), or cheap inspection renders: a single frame in ~2 seconds, or a contact sheet per scene.
vanillasky render video.json # full render → ./video.mp4
vanillasky render video.json --frame 1.2 --out check.png # one PNG (~2s)
vanillasky render video.json --sheet --out ./sheet # 5 PNGs per scene + sheet.png
vanillasky render video.json --draft # fast in-browser WebCodecs export
vanillasky render video.json --fps 30 --scale 0.5 --pages 4 --out out.mp4| Flag | Description |
|---|---|
--out <path> | Output path (default ./video.mp4; --frame → PNG; --sheet → directory). |
--fps <n> | Frames per second (default 30). |
--scale <0..1> | Resolution scale, 1 = full resolution (default 1; 1080x1920 portrait / 1920x1080 landscape per the config's orientation). |
--frame <sec> | Render a single frame at <sec> as a PNG and exit (~2s). Pick a mid-scene time — scene boundaries catch transition blends. |
--sheet | Contact sheet: 5 evenly-spaced PNGs per scene plus a composited sheet.png. |
--draft | Fast in-browser WebCodecs export (fixed 30fps, full resolution). |
--pages <n> | Parallel browser pages for the full render (default 4). |
--no-validate | Skip config validation before rendering. |
--no-design-md | Don't merge DESIGN.md brand tokens (render and validate). |
--no-pexels | Don't auto-resolve mediaKeyword via the Pexels API. |
--open | Open the finished MP4 with the platform viewer (best-effort). |
--base <url> | Host for the Watch/Studio links in the completion block (default https://vanillasky.ai). |
studio
Opens the bundled visual editor for a config — scene timeline, variables, music, live preview and MP4 export — served from 127.0.0.1. Edits autosave to the file, and the page follows external changes, so your agent can keep editing the same config while you watch. Full detail in The local Studio.
vanillasky studio video.json
vanillasky studio video.json --no-open # print the URL instead of opening a browserdiff
Ejecting a template gives you the code, and the cost is that your copy stops receiving fixes. diff compares each ejected scene against the registry item it came from and tells you when upstream has moved — a changed safe zone, an export constraint, a bug fix. It reports; it never merges, because your copy diverged on purpose.
vanillasky diff video.json ⚠ scene 2 (custom_split) — beforeAfterSplit changed upstream since you ejected
ejected from 9f2c1e04b7a3 on 2026-01-15; upstream is now 5284c267c64e
compare: https://vanillasky.ai/r/beforeAfterSplit.json
✓ scene 4 (custom_stat) — up to date with countUpNumberThis works because an ejected scene records where it came from in scene.origin — the item, the version, and a hash of the upstream source at eject time. A scene ejected without that is reported too, so you know it's unanchored.
tracks
Lists the bundled audio library — ids, moods, energy, durations, descriptions. Use a track via "audio": { "trackId": "<id>" }. --json for machine-readable output.
scope
Prints the exact globals a custom_* scene's componentSource can use — custom source has no imports, everything listed is already in lexical scope. --json for machine-readable output.
brand
vanillasky brand [path] shows which DESIGN.md would apply to a directory and the mapped tokens (--json for machine-readable output).
link
Prints a zero-install browser-render URL for a config: <base>/render#config=<base64url>. Anyone can watch the video without local Chrome.
| Flag | Description |
|---|---|
--base <url> | Host for the render link (default https://vanillasky.ai). |
DESIGN.md brand ingestion
When the repo root above the config file carries a DESIGN.md — discovered with a git-style upward walk — render and validate merge its front-matter tokens into the config:
| DESIGN.md token | Config field |
|---|---|
colors.primary | style.brandKit.accent |
colors.secondary | style.brandKit.secondary |
colors.background / colors.surface | style.brandKit.bg |
first typography fontFamily | style.font |
- Precedence: config-explicit values > DESIGN.md > defaults. Opt out with
--no-design-md. A malformed file warns and is skipped — it never fails a render. - The cwd is only a fallback: it applies only when the config's own directory tree has no DESIGN.md and the config declares no
brandKit, and the merge is logged loudly — an unrelated repo's brand never silently bleeds into a branded config.
Stock footage (Pexels)
With PEXELS_API_KEY set (free at pexels.com/api; env var, or ~/.vanillasky/config.json { "pexelsApiKey": "..." } — env wins), render resolves scenes with a mediaKeyword and an empty mediaUrl against the Pexels API before validating: orientation-matched, video unless the scene pins mediaType: "photo", best-HD file, and the video thumbnail stored as the sibling mediaPoster. Each fill is logged; responses are cached per keyword within the run.
Pexels' API guidelines require a prominent link to Pexels wherever the API-sourced media is shown, and encourage crediting the photographer — the CLI logs the photographer for every filled clip so you can carry the credit into your post.
Requirements
Node 18+, a Chromium-based browser, and ffmpeg (auto-installed via ffmpeg-static) — see the overview for details. Proxy env vars (HTTPS_PROXY/NO_PROXY) are passed into the browser.