CLI reference

Specreel has one default command (render a trace or a directory) plus a few verbs.

Render — specreel <trace|dir> [options]

  • <trace> — a trace.zip (single demo) or a directory to scan (gallery/batch).
  • -o, --out PATH — output directory (default specreel-out).
  • --title TITLE — demo title (single-trace mode).
  • --mp4 — also export demo.mp4 (needs Pillow + ffmpeg). Built from Playwright's own video.webm when one sits next to the trace (sharp, 25fps — see Sharp MP4s); otherwise from the trace's screenshots at their native width. Written with +faststart, so a hosted video starts before it has fully downloaded.
  • --video-width PX — MP4 width (default: the picture source's native width). Trace screenshots are capped at 800px wide, so a bigger number only sharpens when a recording is present — otherwise it enlarges, and enlarged frames are soft. Or video_width: in specreel.yml.
  • --bundle — also emit a single self-contained gallery.html (batch mode).
  • --strict — exit non-zero if any flow's first or last step wasn't captured (a truncated demo). For CI: a demo that never recorded its outcome fails the build, like a red test. Every build already prints a ⚠ capture: warning for such flows; --strict turns the warning into a failure. Fix by adding a brief settle (await page.waitForTimeout(1000)) at the end of the test.
  • --quality high|medium|low — motion quality. Playwright records the screen continuously (~8fps); Specreel plays the frames captured during each step, so typing types and pages load instead of cutting between stills. high (default) embeds up to 14 frames per step — real motion, largest files; medium ~6 frames; low one still per step — smallest files, no motion. Or set quality: in specreel.yml. (The single-file gallery.html bundle always uses one frame per step so it stays email-sized.)
  • --gif — also export demo.gif (needs ffmpeg) — the artifact for READMEs and PRs. Exports are linked from the player (⤓ MP4 / ⤓ GIF) and listed in manifest.json (mp4 / gif per flow).
  • --pace P — how long each step lingers: slow | normal | fast, or a number multiplying every hold (2 = twice as long). Every step already holds at least long enough to read its caption, so a long narration is never cut short in silent playback or in the MP4. Or pace: in specreel.yml. Viewers get their own speed control.
  • --showcase — also emit showcase/, the curated customer-facing gallery: only flows marked public: true in specreel.yml that are passing. Failing and internal flows aren't hidden, their files simply aren't in that directory. Brandable via the showcase_* config keys; details + publish patterns in gallery & publishing. Or set showcase: true in specreel.yml (batch mode).
  • --theme dark|light — color theme (default dark; or set theme: in config).
  • --config PATH — path to specreel.yml (auto-discovered in CWD / the traces dir otherwise).
  • --ai — opt-in AI narration (needs ANTHROPIC_API_KEY).
  • --ai-model MODEL — model for --ai (default claude-opus-5-5; claude-sonnet-5-5 and claude-haiku-5-5 are cheaper).
  • --api-key KEY — Anthropic key (else read from ANTHROPIC_API_KEY).
  • --notify URL — Slack incoming-webhook to post a build summary to (batch).
  • --url URL — public URL the output is served at: sets og:image on every page (a pasted link unfurls with the flow's poster) and links the notification.
  • --voice [NAME] — studio voiceover: pre-render neural TTS narration per step and embed it (BYO OPENAI_API_KEY). Default voice nova (also alloy/echo/fable/onyx/ shimmer/…). The player prefers this audio and falls back to the browser voice per step. A demo built with voiceover defaults its sound toggle on (audio still starts only on the viewer's Play click; their on/off choice is remembered per browser), and --mp4 gets the narration muxed in as a real audio track — steps stretch so a sentence is never cut short.
  • --tts-model MODEL — TTS model for --voice (default gpt-4o-mini-tts; tts-1 cheaper, tts-1-hd higher fidelity).
  • --tts-key KEY — OpenAI key for --voice (else OPENAI_API_KEY).
  • --tts-instructions TEXT — delivery notes for the narrator (gpt-* TTS models only, e.g. "upbeat, brisk"; default: a calm product-demo narrator). Config equivalents: voice: / tts_model: / tts_instructions: in specreel.yml.

Studio voiceover pairs well with --ai (it narrates the friendly prose). It costs a few cents/flow and adds audio bytes to the HTML; the free browser-voice default needs no key and no audio files. Clips are cached in ~/.cache/specreel/tts keyed on (model, voice, instructions, words), so a CI rebuild only pays for steps whose words changed — point SPECREEL_TTS_CACHE at a persisted directory in CI (e.g. via actions/cache), or set it to off to disable. Failed steps are narrated as failures ("This step failed: …, the reason"), and masked secrets (•••) are spoken as "the hidden value", never read out.

specreel trace.zip -o out/ --title "Checkout"
specreel test-results/ -o site/ --bundle --ai --theme light

Sharp MP4s: record video too

Playwright's trace screenshots are capped at 800px wide whatever the viewport (a 1280×800 viewport records 800×500 JPEGs), so an MP4 made from the trace alone can't be sharper than that. Playwright can also record a full-resolution video of each test, and Specreel uses it as the MP4's picture source whenever it finds one next to the trace:

// playwright.config.ts — size matters: without it Playwright scales the video to fit 800×800
use: { trace: 'on', video: { mode: 'on', size: { width: 1280, height: 800 } } }
# Python: record_video_dir + record_video_size on the context
context = browser.new_context(viewport={"width": 1280, "height": 800},
                              record_video_dir="videos/",
                              record_video_size={"width": 1280, "height": 800})

Playwright Test writes video.webm into the same per-test directory as trace.zip; Specreel accepts any single *.webm there (two recordings — a popup — are ambiguous and ignored). The trace stays the source of truth for steps, timing and captions; the video only supplies pixels. The step cuts are mapped from the trace's timestamps onto the recording, which starts with the page's first frame, so tracing must already be on when the page is created — true for trace: 'on' and for tracing.start() before new_page(). When there is no recording, ffmpeg can't read it, or the timeline doesn't line up, the MP4 is built from the trace's frames instead (the build says so).

Side effect worth knowing: with a video size, the trace's own screenshots are recorded at that size too, so demo.html gets larger as well.

Secrets in captions

Typed values into secret fields are masked (••••••••) so they never leak into a shareable demo. Detection is conservative — it keys off the field's selector/label matching a curated keyword set (password, passcode, otp, cvv, ssn, …), so input[name=password], get_by_label("Passcode"), etc. are all covered. A secret field located by a placeholder with no keyword (e.g. get_by_placeholder("at least 8 characters")) can't be detected from the trace alone — locate such fields by type=password (e.g. page.locator("input[type=password]")) so masking applies. Generated sign-in steps (loginsteps, and the sign-in Specreel Cloud prepends to hosted runs) already locate the password field that way: a password placeholder like •••••••• carries no keyword, and a demo that keeps its sign-in steps would otherwise caption the real password.

recommend <url> — suggest + scaffold flows for a new app

Crawl a running app, propose demo-worthy flows, and write a runnable Playwright script. See recommend. - --max N — max pages to crawl (default 12). - --browser — render pages with Playwright first (for client-rendered SPAs). - --wait MS — render wait in --browser mode (default 1200). - --cookie "k=v; ..." — Cookie header for a logged-in crawl (static + browser). It is set as cookies on the app's own host (plus its parent domain for an API subdomain), never sent as a blanket header — so a redirect to an OAuth provider gets nothing. - --storage-state FILE — a Playwright storage-state JSON (cookies + localStorage) of a signed-in session, e.g. from playwright codegen --save-storage; implies --browser. Only the app's own domain's cookies and origins are loaded: the Google / GitHub / SSO session the file also holds is dropped. - --login-url URL — sign in before crawling (implies --browser), so discovery sees the real app instead of its marketing shell. Credentials come from the environment — SPECREEL_LOGIN_USER / SPECREEL_LOGIN_PASSWORD — never from the command line, so they stay out of shell history and ps. Use a dedicated test account: whatever it can see may end up in a shared demo. - --header "K: V" — extra request header (repeatable), e.g. an Authorization bearer. - --keep-banners — leave cookie-consent banners alone. By default the scaffolded flows dismiss one (a visible "Accept all" / "Got it" inside something that talks about cookies), so it doesn't sit in every frame of every demo. - --lang py|js — scaffold language (auto-detected from package.json otherwise). - -o FILE — scaffold path (default specreel_flows.<lang>). - --ai / --api-key — re-rank and retitle the suggestions with AI (crawl titles read like "Open Acme" for every page; curated ones say what the demo shows). Falls back to crawl order on any failure; bounded to ~25s. - --json — emit suggestions + scaffold as JSON on stdout (progress/errors to stderr, no file written). Used by Specreel Cloud's onboarding wizard.

specreel recommend http://localhost:3000 --max 10

scaffold --specs <json> — assemble a script from a wizard spec

Build a Playwright scaffold from a JSON spec {base_url, lang, items[]}, where each item is a discovered flow dict or {"nl_prompt": "log in then…"}. NL items are turned into runnable flows by AI (BYO-key), grounded in the other flows; without a key (or when generation fails) they're left as a TODO stub that fails when run ("not generated yet") until you write its steps — it never passes as a demo. Powers Specreel Cloud's onboarding wizard. - --api-key KEY (or ANTHROPIC_API_KEY) — needed for nl_prompt items. - -o FILE — output (default stdout).

nlflow --desc "…" --url <base> — one plain-English flow → runnable code

Turn a description ("search the docs for 'trace viewer' and open the top result") into a Playwright flow body, grounded in crawled pages (--context, the recommend --json flows) so it only names controls that exist. AI, BYO-key. Prints the flow as JSON. Generated code navigates with BASE (the URL you gave, which may be a deep page) and ORIGIN (its scheme + host) — other pages are ORIGIN + "/path". - --lang py|js, --context FILE, --vars NAME,… (referenced as {{NAME}}). - --verify — run the flow once in a headless browser before returning it. If it throws, the model gets the error, the failing line and what was actually on the page (button/link names, fields, visible text — including things the crawl couldn't see, like an opened search modal) and rewrites it once. If it runs clean, a quick check of the page it ended on asks whether it actually reached the goal (a flow that waits on text that was already there "passes" without ever seeing the result) — a miss gets the same one rewrite. The output says "verified": true|false (+ verify_error). The code runs in a subprocess with a scrubbed environment (no API keys). Python flows need playwright; JS flows need Node + @playwright/test (found via SPECREEL_NODE_MODULES, ./node_modules or npm root -g). - --login-steps FILE — sign-in statements to run before the flow when verifying. - --var-values FILE — JSON {NAME: value} filling {{NAME}} for the verify run only. - --draft FILE — verify/repair an existing flow (a previous nlflow result) instead of generating a new one.

specreel recommend https://app.example.com --browser --json > crawl.json
specreel nlflow --desc "log in and open billing" --url https://app.example.com \
  --context <(jq .flows crawl.json) --verify

curate --flows <json> --instruction "…" — refine flows in plain English

Reorder / rename / drop discovered flows by a plain-English instruction (e.g. "focus on checkout, drop the marketing pages"). AI, BYO-key; never invents flows. Prints the new flow list as JSON.

loginsteps --url <login page> — generate sign-in steps

Render a login page, find the username/password fields, and print Playwright steps that fill them from {{SPECREEL_USER}} / {{SPECREEL_PASSWORD}} placeholders (never real values). The password field is located by input[type=password], so a demo that keeps the sign-in steps captions its value as •••••••• (the username keeps its placeholder/name locator). Specreel Cloud calls this when you save a project sign-in and leave the steps blank. Exits non-zero — with a plain explanation — for a magic-link/passwordless page, which credentials can't automate.

doctor [path] — preflight your traces

Sanity-check a traces dir (or a single trace.zip) before rendering: are traces present, are they valid, do they have screencast frames (so the demo has visuals), does specreel.yml parse? Exits non-zero if something's fatal — handy as a CI gate. - path — a trace.zip or directory (default test-results). - --mp4 — also check MP4 export readiness (Pillow + ffmpeg).

specreel doctor test-results
#   ✓ signup/trace.zip: 5 steps · 12 frames
#   ⚠ checkout/trace.zip: 4 steps but 0 screencast frames — enable screenshots in tracing
#   ready (with warnings) — 0 error(s), 1 warning(s)

init <traces> — scaffold a specreel.yml

Discover traces under a directory and write a starter config whose slugs match the gallery output. - -o FILE — output path (default specreel.yml).

specreel init test-results/

publish <site> --to <target> — deploy a gallery

Deploy an already-generated gallery to a real URL + print an <iframe> embed. See publishing. - --to ghpages — clean single-commit gh-pages force-push → Pages URL (needs a GitHub remote). - --to dir:<path> — copy into a static webroot / synced folder. - --to cloud — upload to Specreel Cloud (--cloud-url/--token/--project, or SPECREEL_CLOUD_URL / SPECREEL_CLOUD_TOKEN). - --message MSG — commit message for ghpages.

summary <site> [--url U] [--since OLD] — markdown build summary

Print a per-flow markdown table from manifest.json (used by the PR-comment workflow; handy in any CI). --url adds links. --since <manifest.json> adds a “Changes vs previous build” block (regressed / recovered / added / removed) — the PR-comment workflow fetches the live manifest and passes it automatically.

notes <site> [--since OLD] — AI release notes (BYO-key)

Draft user-facing release notes from the gallery (and, with --since, from what changed). Opt-in AI, BYO-key (ANTHROPIC_API_KEY / --api-key); degrades to a non-zero exit if no key. Recovered flows read as fixes, regressed as known issues, added as new. - --since <manifest.json> — diff against a previous build. - --product NAME — product name for the notes. - --ai-model MODEL / --api-key KEY — model + key (default claude-opus-5-5). - -o FILE — write to a file instead of stdout.

specreel notes site --since prev/manifest.json --product "Acme" -o RELEASE.md

Environment variables

  • ANTHROPIC_API_KEY — key for --ai (narration, recommend curation) and nlflow.
  • OPENAI_API_KEY — key for --voice (studio neural voiceover).
  • SPECREEL_SLACK_WEBHOOK — default Slack webhook for --notify.
  • BASE_URL — read by scaffolded recommend scripts to override the app URL.
  • SPECREEL_NODE_MODULES — a node_modules dir with @playwright/test, for nlflow --verify on JS flows.