CLI reference
Specreel has one default command (render a trace or a directory) plus a few verbs.
Render — specreel <trace|dir> [options]
<trace>— atrace.zip(single demo) or a directory to scan (gallery/batch).-o, --out PATH— output directory (defaultspecreel-out).--title TITLE— demo title (single-trace mode).--mp4— also exportdemo.mp4(needs Pillow + ffmpeg). Built from Playwright's ownvideo.webmwhen 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. Orvideo_width:inspecreel.yml.--bundle— also emit a single self-containedgallery.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;--strictturns 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;lowone still per step — smallest files, no motion. Or setquality:inspecreel.yml. (The single-filegallery.htmlbundle always uses one frame per step so it stays email-sized.)--gif— also exportdemo.gif(needsffmpeg) — the artifact for READMEs and PRs. Exports are linked from the player (⤓ MP4 / ⤓ GIF) and listed inmanifest.json(mp4/gifper 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. Orpace:inspecreel.yml. Viewers get their own speed control.--showcase— also emitshowcase/, the curated customer-facing gallery: only flows markedpublic: trueinspecreel.ymlthat are passing. Failing and internal flows aren't hidden, their files simply aren't in that directory. Brandable via theshowcase_*config keys; details + publish patterns in gallery & publishing. Or setshowcase: trueinspecreel.yml(batch mode).--theme dark|light— color theme (defaultdark; or settheme:in config).--config PATH— path tospecreel.yml(auto-discovered in CWD / the traces dir otherwise).--ai— opt-in AI narration (needsANTHROPIC_API_KEY).--ai-model MODEL— model for--ai(defaultclaude-opus-5-5;claude-sonnet-5-5andclaude-haiku-5-5are cheaper).--api-key KEY— Anthropic key (else read fromANTHROPIC_API_KEY).--notify URL— Slack incoming-webhook to post a build summary to (batch).--url URL— public URL the output is served at: setsog:imageon 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 (BYOOPENAI_API_KEY). Default voicenova(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--mp4gets 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(defaultgpt-4o-mini-tts;tts-1cheaper,tts-1-hdhigher fidelity).--tts-key KEY— OpenAI key for--voice(elseOPENAI_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:inspecreel.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/ttskeyed on (model, voice, instructions, words), so a CI rebuild only pays for steps whose words changed — pointSPECREEL_TTS_CACHEat a persisted directory in CI (e.g. viaactions/cache), or set it tooffto 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) andnlflow.OPENAI_API_KEY— key for--voice(studio neural voiceover).SPECREEL_SLACK_WEBHOOK— default Slack webhook for--notify.BASE_URL— read by scaffoldedrecommendscripts to override the app URL.SPECREEL_NODE_MODULES— anode_modulesdir with@playwright/test, fornlflow --verifyon JS flows.