Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Retake Demos

FreeMaintained

Demo-as-code for coding agents: a YAML manifest in, a product walkthrough video out. Rerun the demo instead of re-recording it.

GitHubEmbed

About

Demo-as-code for coding agents: a YAML manifest in, a product walkthrough video out. Rerun the demo instead of re-recording it.

README

Retake — rerun the demo, don't re-record it

Rerun the demo. Don't re-record it.
A product walkthrough as a small YAML file. Your coding agent writes it, Retake records it, you watch and ship it.

npm MIT ci


Retake drives a real browser through a web app, records it, and hands back an MP4 with burned-in captions, a still for every scene, and a proof log of exactly what happened. The demo is a file, so when a button moves you change a selector instead of re-shooting — and when the app changes, you rerun the same file.

It is built for the way people work now: you ask your coding agent for a demo in one sentence, the agent drafts the file, dry-runs every selector, records a preview, reads the proof log, fixes what failed, and records the real one. Claude Code, Codex, Cursor and anything else that speaks MCP get Retake's tools; retake ui is the window you watch it in.

The Retake window: your demos on the left, the video and its scenes in the middle

If you are a coding agent reading this

Someone pasted this link and wants demos recorded. Do this, in order, and say what you are doing as you go — assume they have never seen Retake:

  1. npm install -g retake-demos (Node 20+; ffmpeg is bundled, Chromium downloads once).
  2. Make a workspace and set up from inside it: mkdir -p ~/retake-demos && cd ~/retake-demos && retake install. This creates demos/ and outputs/, registers Retake's tools with Claude Code and installs its skill, and prints the config for Codex/Cursor — apply it if that is what you are.
  3. Your tools arrive at your next session. For this one, drive Retake from the CLI exactly as AGENTS.md describes (validate → dry → run → check). Tell the person: "Restart me after this and I'll have Retake's tools directly."
  4. Start the window for them — retake ui in the workspace — and give them the link: http://localhost:4310. That is where the video, stills and proof log appear.
  5. If they did not say what to record, ask one question: which app (URL) and what to show. If the app needs a login, read the "Behind a login" section below — you never handle the password yourself.
  6. Read skill/SKILL.md once: it is the method (order of operations, failures as text, when to stop) and it is what Claude Code loads automatically after step 2.

Everything below is for the person; it is accurate for you too.

Install

Needs Node 20+. ffmpeg ships with the package; Chromium is downloaded once.

npm install -g retake-demos
mkdir my-demos && cd my-demos
retake install

retake install makes the folder a workspace (demos/, outputs/, .env, the .gitignore lines), downloads Chromium, registers the tools with Claude Code and installs its recording-product-demos skill, and prints the config for Codex. Restart your agent afterwards — agents load tools at the start of a session.

Prefer not to install globally? npx retake-demos install works the same. Don't use Claude Code? retake agent prints the MCP config for Codex, Cursor, or anything else.

Then, in your agent:

Record a demo of my app at localhost:3000 showing a new user creating their first project.

and in another terminal, retake uihttp://localhost:4310 to watch it happen. The agent reports its plan and progress there; the video, stills and receipts land there when it's done.

retake doctor tells you if anything is missing.

What you get

outputs/<name>/
  demo.mp4        the shareable — captions burned in
  master.mp4      the CRF-14 keeper (post presets only; --no-master skips it,
                  which is ~3x faster on a long take and, because the master
                  path re-encodes an already-encoded file, marginally better)
  stills/         one PNG per scene, mid-scene and at its last moment
  thumbnail.png   the poster — pick any frame, a cover, or your own image in the window
  cover.png       launch mode: the title card, settled
  cover-titled.png launch mode: a real frame with the title over it
  proof-log.md    result, shot list, every step's timing, pass/fail, what was on screen when it failed
  contact.png     launch cuts: one timestamped grid of the whole video (`retake contact`)
  take.json       the raw timeline the renderer reads

In the window: play it, switch speed (0.75× to 1.3×, re-rendered in seconds), download, or open its folder — stills, proof log, manifest. The poster is chosen, not guessed: scrub to a frame and press Use this frame, click any scene still, take one of the generated covers, or upload your own. It rewrites thumbnail.png, so it costs no re-render — and on a feed the poster decides whether anyone presses play. Demos group by the app they point at; archive what you're done with.

The loop

Everything an agent does, you can do from the terminal:

retake validate demos/x.yaml   # schema + warnings, instant
retake dry demos/x.yaml        # every selector and wait, no camera (~30s)
retake run demos/x.yaml --preset draft   # first take, quarter-size, fast
retake check outputs/x         # resolution, fps, duration, files: pass/fail
retake verify outputs/x        # did it LOOK right: each scene's `expect`, judged (exit 3 on failure)
retake run demos/x.yaml --brisk  # the steps without the pacing — for iterating, not for keeping
retake intent                 # what your product IS — context every visual check needs
retake sweep outputs/x        # every frame as a whole: the ten ways a picture can be wrong
retake flag demos/x.yaml --scene s --expect "…"   # this one is real — watch it from now on
retake fixed x                # did what you flagged get fixed, with the clip that shows it
retake heal                   # demo files back for recordings whose manifest went missing
retake sense outputs/x        # does the run ADD UP — what went in against what came out
retake destroy demos/x.yaml   # the flows nobody wrote down: nine ways to abuse this demo
retake notes                  # read every take back: what keeps going wrong, and what it costs
retake run demos/x.yaml        # the real one
retake render outputs/x        # captions/camera/speed changed? re-render, no browser

The expensive step comes last on purpose. dry catches most failures in seconds and prints the step, the error, and the text that was on screen; a failed run costs a minute and a confusing video. When a step fails during run, Retake stops, keeps what it has, and the proof log says which step and why — an agent fixes it from that text, never from watching the video.

Exit codes: 0 good · 2 a step failed or the video is a raw fallback · 3 dry/check failed.

Demo-as-code

name: first-project
title: "Creating your first project"
url: http://localhost:3000
preset: post-landscape            # `retake presets` — 1920×1080 page + caption band
viewport: { width: 1440, height: 1080 }
scale: 1.8                        # page drawn at 1.8× so text stays crisp
reducedMotion: true
camera: static                    # still (the default); `auto` eases toward the last thing touched

seed: []                          # put the app in a known state first
setup: []                         # runs before the camera; trimmed off the video

steps:
  - { action: scene, label: start, caption: "An empty workspace.", camera: static }
  - { action: wait, ms: 1500 }
  - { action: click, selector: "button:has-text('New project')", pauseAfter: 800 }
  - { action: type, selector: "input[name=title]", text: "Q3 launch plan", delay: 40 }
  - { action: click, selector: "button[type=submit]" }
  - { action: waitFor, selector: ".project-header" }
  - { action: scene, label: result, caption: "And it exists." }
  - { action: wait, ms: 2500 }

outputs:
  thumbnail: { scene: result }

The full template is demos/example.yaml; retake init copies it into a new workspace.

Stepsclick, type, fill, select, hover, drag, scroll, keyboard, navigate, wait, waitFor, evaluate, upload, download, screenshot, scene. Every step takes pauseAfter, waitFor, timeout, secret. Targets are a selector, or a point — {x, y} or {selector, dx, dy} — for canvases and SVG editors. ${VAR} expands from .env and fails loudly when unset.

Waits take three modifiers, because a bare selector resolves the moment the element exists: gone: true waits for it to leave (the previous action's banner), minChars: 40 waits for it to hold real text (a streamed reply), stableMs: 500 waits for its subtree to stop changing.

Scenes are the spine: { action: scene, label, caption } at each beat. They take their timestamps from the actual run, so captions land on the frames they describe even when the app took 3 seconds this time and 11 the last. Labels must be uniquethumbnail, until, the stills and the proof log all address scenes by label, so two scenes called the same thing make every one of those ambiguous. validate rejects it.

Two kinds of take. mode: demo (the default) proves how something works — a client walkthrough, a PR clip, a lesson — and keeps everything but the product out of the frame. mode: launch presents the product in public and may carry a title card, music, emphasis and a branded ending. validate warns if a demo is carrying launch furniture. The launch cut. compressIdle: true shows the app's long waits as ~1.5 s each (your own pacing waits are never touched); voiceover: true reads the captions aloud (edge-tts, pipx install edge-tts once); typing: brisk makes typed text land fast with the pauses carrying the meaning. All render-time except typing. Music. music: track.mp3 (or { file, gainDb, fadeOutMs }) mixes a bed under the video at render time, looped or trimmed to fit and faded out at the end. Bring your own track — it ships inside your video, so it must be one you may use (CC0 or licensed). Cards and callouts. intro:/outro: add a title card in Retake's own look — rendered at render time, so changing a word is a re-render, and the intro's settled frame is written as cover.png, the poster. { action: callout, selector, label, ms } draws an animated ring and label around an element while the take holds — recorded coordinates, drawn at render, moving with the camera. Sparingly: the captions carry the story.

Seeds put the app in a known state before the camera: write a JSON file, run JS in the page, or run a shell command. Setup is the stuff that runs before recording and is trimmed off the front — logins live there. tempo speeds the finished video up or down at render time; lock names a shared resource so two demos that touch the same backend don't record over each other.

Quality

Think in the publishing format, not the browser window.

Preset Video Page area For
draft 960×540 960×464 iterating — the finals' layout at a quarter of the pixels, fastest
preview-fast 1920×1080 1920×930 checking timing and framing, hardware-encoded
post-landscape (default) 1920×1080 1920×930 the general-purpose demo, plus a CRF-14 master
post-square 1080×1080 1080×960 feeds
post-vertical 1080×1920 1080×1800 shorts and reels
docs-gif 1440×900 1440×800 README and docs GIFs
master 1920×1080 1920×930 archive, CRF 12

One preset, one size — and size is not recorded. The caption band sits inside the canvas, so post-landscape is always exactly 1920×1080 and two demos of the same app are never different shapes. Better still, the shape is decided at render: the take is fitted into whichever canvas you ask for, so retake render outputs/x --preset post-vertical gives you a 9:16 cut of a demo you recorded landscape — seconds, no browser, no re-record. Setting viewport in a manifest overrides the page and validate warns, because that is how one demo ends up a different shape from the rest.

Page scale is what makes it crisp: the page lays out as if the viewport were smaller and every glyph is drawn at 2×, so 1080p text reads on a phone. Camera is still by default — a calm demo reads as real. camera: auto eases toward whatever the demo just touched, at render time, clamped so it can never crop the thing it points at; per scene: camera: { focus: ".result", zoom: 1.3 }. Zoom is something you ask for after seeing a take, not something that happens to you. Captions, camera, speed, trim, format and layout are all render-time: change them and retake render takes seconds, never touches the browser.

Behind a login

Most real demos start on the far side of a sign-in. You never put a password anywhere an agent can read it.

With the window open (retake ui): when the agent sees the app needs a login, a small form appears in the window asking for a demo account by name — APP_USER, APP_PASSWORD. You type them; they are written to one local file, the workspace .env (readable by you only), and never sent to the agent, to any server, or to anyone. The agent learns only that the names are set.

Without the window: the agent relays one line — run this in your workspace and type the values (hidden):

retake secret APP_USER APP_PASSWORD

Then: the login runs before the camera and is trimmed off the front; the video starts signed in. The session is saved to .auth/, so later takes skip the login. A wrong password stops the take instead of recording a logged-out one.

Two-factor. Authenticator apps: add the enrolment secret as APP_TOTP_SECRET and the manifest uses ${TOTP:APP_TOTP_SECRET} — Retake computes the current code when it fills the field. SMS codes, SSO, captchas — anything only a person can do:

retake signin demos/<name>.yaml

opens a real browser; you log in by hand, press Enter, and Retake keeps the session (not the password) for every later take.

What the agent writes:

auth:
  storageState: .auth/myapp.json   # session saved here, reused until stale
  maxAgeHours: 8
  setup:
    - { action: fill, selector: "#user", text: "${APP_USER}" }
    - { action: fill, selector: "#password", text: "${APP_PASSWORD}", secret: true }
    - { action: fill, selector: "#code", text: "${TOTP:APP_TOTP_SECRET}", secret: true }   # only if asked
    - { action: click, selector: "button[type=submit]" }

secret: true keeps a value out of the terminal, the window and the proof log — but not out of the video, which is why validate warns when a secret step is in steps rather than auth.setup. Use a demo account: the output is a video you may publish. Apps that keep their session in IndexedDB (Firebase) can't be restored from a saved session; put the login under plain setup instead.

Limits worth knowing

  • One page. Retake records one tab. keepInTab (on by default) folds window.open and target="_blank" back into it, so popup logins and "preview" buttons stay on camera; flows that genuinely need a second window do not.
  • ~180 cursor moves per take. Retake flattens testreel's cursor expressions at install time (the stock ones hit ffmpeg's nesting limit at ~45 moves); the remaining ceiling is the filter's size as one process argument. validate warns, the recorder detects it, check fails. Long stories still want chapters of 30–45 seconds — for the viewer, not the tool.
  • macOS is where it's used daily. videotoolbox hardware encoding falls back to libx264 elsewhere; Linux is untested beyond CI.

How it's built

src/
  cli.ts          install · init · doctor · ui · run · dry · render · check · validate · describe · ideas
  manifest.ts     zod schema, presets, warnings
  record.ts       Playwright + testreel → take.json (seeds, auth, locks, stop-on-fail, drag)
  dryrun.ts       every selector and wait, no camera
  render.ts       ffmpeg: camera → layout → captions → demo/master, stills, check
  edits.ts        structural edits agents make to a manifest (comments preserved)
  operator/       the MCP server: 22 tools, from `scout` and `draft` to `run`, `look` and `done`
  ui/             one http server, three pages, no framework, no build step
skill/SKILL.md    what the agent is taught: order of operations, failures as text, when to stop
demos/            manifests; outputs/ is where takes land

Recording and the cursor come from testreel; GIFs from gifski when installed; ffmpeg from ffmpeg-static. Retake adds what the job needs on top: waiting on selectors, seeds, scenes with real timestamps, camera and captions at render time, presets, credentials, locks, structural edits, and the receipts.

Developing

git clone https://github.com/glebbogachev00/retake && cd retake
npm install                      # also builds dist/
npx playwright install chromium
npm run retake -- run demos/todo-basics.yaml --preset draft   # a public app, no setup
npm test && npm run typecheck

npm run retake -- … runs the TypeScript source; retake … is the built package — same code. See CONTRIBUTING.md, BACKLOG.md, and AGENTS.md (what coding agents read when pointed at this folder). Found something off? The Bug? button in the window pre-fills an issue with the receipts.

License

MIT — Gleb Bogachev.

from github.com/glebbogachev00/retake

Install Retake Demos in Claude Desktop, Claude Code & Cursor

Recommended · one command, every IDE
unyly install retake-demos

Installs into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.

First time? Get the CLI: curl -fsSL https://unyly.org/install | sh

Or configure manually

Run in your terminal:

claude mcp add retake-demos -- npx -y retake-demos

Step-by-step: how to install Retake Demos

FAQ

Is Retake Demos MCP free?

Yes, Retake Demos MCP is free — one-click install via Unyly at no cost.

Does Retake Demos need an API key?

No, Retake Demos runs without API keys or environment variables.

Is Retake Demos hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Retake Demos in Claude Desktop, Claude Code or Cursor?

Open Retake Demos on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Retake Demos with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All development MCPs