We use anonymous analytics to improve VanillaSky. No personal data is collected. Learn more

    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.

    usage
    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.

    FlagDescription
    --checkPrint 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.

    FlagDescription
    --jsonMachine-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.

    bash
    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
    FlagDescription
    --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.
    --sheetContact sheet: 5 evenly-spaced PNGs per scene plus a composited sheet.png.
    --draftFast in-browser WebCodecs export (fixed 30fps, full resolution).
    --pages <n>Parallel browser pages for the full render (default 4).
    --no-validateSkip config validation before rendering.
    --no-design-mdDon't merge DESIGN.md brand tokens (render and validate).
    --no-pexelsDon't auto-resolve mediaKeyword via the Pexels API.
    --openOpen 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.

    bash
    vanillasky studio video.json
    vanillasky studio video.json --no-open   # print the URL instead of opening a browser

    diff

    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.

    bash
    vanillasky diff video.json
    output
      ⚠ 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 countUpNumber

    This 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.

    FlagDescription
    --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 tokenConfig field
    colors.primarystyle.brandKit.accent
    colors.secondarystyle.brandKit.secondary
    colors.background / colors.surfacestyle.brandKit.bg
    first typography fontFamilystyle.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.