The demotake CLI
A thin command-line client for the DemoTake app on the same Mac. Validate demotake.json specs, run them as tests without video, re-record only the scenes that changed, diff takes and export packages, from a terminal or CI.
Install
The CLI ships inside DemoTake. Put it on your PATH once:
mkdir -p ~/.local/bin
ln -sf /Applications/DemoTake.app/Contents/MacOS/demotake-cli ~/.local/bin/demotake
demotake statusThis adds a link in your home folder only; no administrator password needed. If demotake isn't found afterwards, add export PATH="$HOME/.local/bin:$PATH" to ~/.zshrc and open a new terminal window. You can also run the CLI by its full path, /Applications/DemoTake.app/Contents/MacOS/demotake-cli.
The file is called demotake-cli inside the app bundle because macOS volumes are usually case-insensitive, and
demotake would clash with the app's own DemoTake executable.
demotake talks to the running DemoTake app over its local MCP endpoint, so DemoTake must be installed and running
(except for validate and schema, which need no app). Running specs operates real apps, so it needs
DemoTake Agent with Screen Recording and Accessibility granted. demotake status shows what's missing.
- demotakeyour terminal or CI
- 127.0.0.1:47823/mcpbearer token
- DemoTake Agentoperates the app, records
Commands
| Command | What it does |
|---|---|
demotake validate <spec> |
Schema and semantic checks of a demotake.json. No app needed. |
demotake test <spec> |
DemoTest: runs every scene and assertion without recording video. Exits 1 on failures. |
demotake run <spec> |
Runs the spec and records it (Rehearse → Take, per-scene retry), then renders the outputs. |
demotake diff <take_id> |
Which scenes changed compared with the previous run. |
demotake export <take_id> |
Renders videos (optionally under a file size), guides and interactive demos, or writes a portable .take.zip package. |
demotake share <take_id> --interactive |
Shares the take's interactive demo as a DemoTake Cloud link and prints its embed code. |
demotake presenter <take_id> |
Shows the presenter camera as a bubble, side by side or full frame, or imports a presenter video. |
demotake screenshots run <spec> |
Regenerates the spec's store screenshots (App Store, Google Play, web) into --out. |
demotake runner |
Serves scheduled re-recording: claims queued runs from DemoTake Cloud and records, exports and uploads them on this Mac (Team plan). |
demotake schema |
Prints the JSON Schema for demotake.json (for editor completion). |
demotake status |
Whether DemoTake is reachable, which permissions are missing, and whether the licence allows recording and exporting. |
validate
demotake validate demotake.jsonChecks the spec's structure and meaning (unknown fields, missing scenes, bad element queries) and prints errors and warnings with their JSON paths. Nothing runs.
test
demotake test demotake.json --junit reports/demotake.xml --commit "$GITHUB_SHA"Runs the steps and assertions scene by scene with no video. It reuses one "(DemoTest)" take per spec take, so
repeated runs don't flood your library. Failure evidence (a blurred snapshot and a UI excerpt) goes into the JUnit
report and the take's runs/ folder.
run
demotake run demotake.json --only-changed --outputs 16:9,9:16,guide --json > reports/demotake-run.jsonRecords the spec and renders its outputs. With --only-changed, DemoTake compares each scene's checkpoint
(accessibility state plus a perceptual diff of the checkpoint snapshot, ignoring redacted regions) with the last
recording of the same spec take, re-records only the scenes that changed and keeps the rest, footage and edits
included. If nothing changed, existing exports stay current and no render runs.
$ demotake run demotake.json --only-changed --outputs 16:9,guide Checkout Take: PASS · 8 scene(s) · 3 of 8 scene(s) changed · 3 regenerated, 5 kept
diff
demotake diff take_01j…Lists the scenes whose UI changed between a take and its baseline.
export
demotake export take_01j… --format packageWrites a portable .take.zip (take, media, pointer tracks, snapshots) you can import on another Mac or share.
demotake export take_01j… --format 16:9,9:16 --quality upload --max-mb 45Renders videos. --quality upload makes small H.264 files at 30 fps for upload forms and review portals (the
default is high). --max-mb keeps each MP4 under that size, in MB of 1,000,000 bytes, encoding again at a lower
bitrate if needed. --resolution 2160p (or 4k) renders 3840×2160 for 16:9, native the recording's own pixels (1080p up
to 4K, never upscaled); --codec h264|hevc overrides the automatic choice (HEVC for high-quality 4K, H.264 otherwise).
--format app_preview --device iphone_6_9 renders an App Store app preview (--orientation portrait|landscape,
--poster 5, --fit speed|trim to land within 15–30 s); the line says whether it meets Apple's specification and the
command exits 1 when it doesn't. Each line shows the file size; a file still over --max-mb makes the command exit 1.
--no-wait returns once the renders are queued.
demotake export take_01j… --format 16:9 --subtitles srt,vtt --embed-subtitles
demotake export take_01j… --format srt,vtt,transcriptSubtitles and transcripts, timed to the edited video. --subtitles srt,vtt,md writes them next to each video
(md is a Markdown transcript); --embed-subtitles also adds a subtitle track viewers can switch on in their player.
With --format srt,vtt,transcript only the files are written, no video. They say what the voiceover says, or the
captions when there's no voiceover; --subtitle-source captions uses the captions either way.
demotake export take_01j… --format 16:9,guide --languages source,de,sv --subtitles srt,vtt
demotake export take_01j… --format 16:9 --embed-subtitles --subtitle-languages all
demotake export take_01j… --format srt,vtt --languages deLocalized takes (languages added and translated in the app or by an agent): --languages renders each format once per
language, with the language at the end of the file name (-de.mp4, -sv.mp4) and that language's subtitles,
transcript, guide and Reviewer Pack; source is the take's own language and all is every language.
--subtitle-languages all with --embed-subtitles puts one subtitle track per language into each MP4.
demotake export take_01j… --format interactive --languages source,deAn interactive (click-through) demo: a folder whose index.html opens in any browser without a server (step pictures,
a hotspot where each click happened, tooltips, the voiceover when there is one) and a ZIP of it. With --languages
every language is in the same demo, with a language switch. The steps are the ones edited in the app (or by an agent
with interactive_update); without edits they come straight from the recording.
share
demotake share take_01j… --interactive --visibility code --expires 14Uploads the latest interactive demo export (or makes one) to DemoTake Cloud and prints the link and an <iframe>
embed code for websites and docs. --visibility public|code|invite, --code for your own access code,
--expires DAYS or never (subscriptions), --no-download to hide the offline ZIP. The app must be signed in.
presenter
demotake presenter take_01j… --layout bubble --corner bottom_left --background blur
demotake presenter take_01j… --scene Intro --layout full
demotake presenter take_01j… --import founder.mov --offset 1.5Styles the presenter camera: --layout bubble|split|full, --corner, --size small|medium|large,
--shape circle|rounded, --border none|white|accent, --side left|right (split), --mirror on|off,
--background none|blur|remove with --quality fast|balanced|accurate (drawn when the Mac renders), --off to hide it.
--scene S --layout hidden|bubble|split|full|inherit sets one scene. --import FILE copies a video of the presenter into
the take and plays it from --offset seconds of the cut (--no-audio without its sound, --volume 0…1).
screenshots
demotake screenshots run demotake.json --out store-screenshots --only-changed --junit reports/screenshots.xml
demotake screenshots validate demotake.json
demotake screenshots formatsRuns the spec's screenshots section: reaches each screen with its steps, captures it, and renders every format at
its exact size into --out (default screenshots), in folders per store and device with a manifest.json. With
--only-changed, shots whose screen didn't change keep their files byte for byte. --shot, --formats and
--file-type png|jpeg run part of the spec. See Screenshots from agents and CI.
runner
export DEMOTAKE_RUNNER_TOKEN=dtr_… # from DemoTake Web › Re-record on a Schedule › Runners › Add Runner
demotake runner # keep serving runs (Ctrl-C to stop)
demotake runner --once --wait 600 # in CI: wait up to 10 minutes for one run, run it, exitMakes this Mac a runner for scheduled re-recording (Team plan). Each
run re-records the take's scenes whose UI changed, keeps your edits, exports, compares the scenes with the last run and
uploads the exports, posters and scene pictures (all rendered from the edited take, so redactions apply). DemoTake
Agent does the work, so it must be running and allowed to record and operate the app. --runner-token or
DEMOTAKE_RUNNER_TOKEN is the runner token (shown once when you add the runner); --once stops after one run (or
none), --wait SECONDS keeps polling until a run arrives, --max-runs N, --poll SECONDS (default 30), --json.
secret_env steps read their values from this process's environment. Exits 1 when a run failed.
schema
demotake schema > demotake.schema.jsonCommon options
| Option | Meaning |
|---|---|
--junit <file> |
Write a JUnit XML report (test, run). |
--json |
Print the machine-readable result instead of the summary. |
--commit <sha> |
The source commit being demoed or tested; it's recorded in the proof manifest. |
--outputs <list> |
Comma-separated output formats for run: 16:9, 9:16, 4:3, 1:1, gif, screenshots, reviewer_pack, guide, interactive. |
--only-changed |
run: re-record only the scenes whose UI changed. |
--url, --token |
Connect to a specific endpoint instead of the discovered one. |
Connection and environment
The endpoint and token are read from ~/Library/Application Support/DemoTake/ (mcp-endpoint.json and
mcp-token). Override them with --url / --token or these environment variables:
| Variable | Meaning |
|---|---|
DEMOTAKE_MCP_URL |
Full endpoint URL, e.g. http://127.0.0.1:47823/mcp |
DEMOTAKE_MCP_PORT |
Port on 127.0.0.1 |
DEMOTAKE_MCP_TOKEN |
Bearer token |
Secrets
Specs never contain secrets. A step like this reads DEMO_PASSWORD from the environment of the demotake
process, which forwards it to the app for that run only. It is never logged, stored in the take or shown in videos,
and secure fields are masked.
{ "action": "type", "secret_env": "DEMO_PASSWORD", "element": { "role": "securetextfield" } }Or use {"keychain": {"service": "com.example.demo", "account": "ci"}} for a generic password in the runner's
login Keychain.
Exit codes
| Code | Meaning |
|---|---|
0 |
OK |
1 |
Failed scenes or assertions, an invalid spec, or an export over --max-mb |
2 |
Usage error |
3 |
DemoTake unreachable |
4 |
DemoTake reported an error |
In CI
A demotake.json in your repo is a demo and an end-to-end test. Run demotake test on pull requests and
demotake run --only-changed on release tags, on a self-hosted macOS runner with a logged-in GUI session and
DemoTake Agent installed. The full GitHub Actions setup is in Demo specs and CI.
On the Team plan you can also let DemoTake Cloud schedule the runs: a schedule
re-records on a timetable or after each deploy (a webhook from GitHub, Vercel or Netlify), updates the take's share
links in place, and notifies Slack. The recording still happens on a Mac: the one that runs demotake runner, or a
Mac with DemoTake open and Use this Mac as a runner turned on.