Retake
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.
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:
npm install -g retake-demos(Node 20+; ffmpeg is bundled, Chromium downloads once).Make a workspace and set up from inside it:
mkdir -p ~/retake-demos && cd ~/retake-demos && retake install. This createsdemos/andoutputs/, 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.Your tools arrive at your next session. For this one, drive Retake from the CLI exactly as
AGENTS.mddescribes (validate → dry → run → check). Tell the person: "Restart me after this and I'll have Retake's tools directly."Start the window for them —
retake uiin the workspace — and give them the link: http://localhost:4310. That is where the video, stills and proof log appear.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.
Read
skill/SKILL.mdonce: 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.
Related MCP server: chromium-bridge
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 installretake install makes the folder a workspace (demos/, outputs/, .env, and the .gitignore lines), downloads Chromium, registers the tools with Claude Code, installs its recording-product-demos skill, and prints the config for Codex. Restart your agent afterwards because agents load tools at the start of a session.
Start retake ui and open http://localhost:4310. On an empty workspace, Retake asks only for:
the public or localhost URL;
the result the viewer should see;
the final sound: silent, Edge, local Aiden, or local Kokoro;
confirmation of the request.
The first draft is always silent. Choosing a local voice plays a packaged sample and asks for consent, but it does not download a model or generate speech. Narration starts only after you approve the recording and its continuous script.
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 another MCP client.
If you already have demos, ask your agent directly:
Record a demo of my app at localhost:3000 showing a new user creating their first project.
and in another terminal, retake ui → http://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.
Not sure what to request? Use How to prompt an agent to use Retake to choose the recording type, format, voice, music, camera, branding, privacy rules, and delivery checks.
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 readsIn 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 golden approve finished.mp4 --id short-landscape --title "Short landscape" --approved-by "Your name" --rubric story=pass,framing=pass,timing=pass,text=pass,motion=pass # interactive human approval
retake golden add finished.mp4 --approval-receipt goldens/.approvals/short-landscape-<hash>.json # store evidence; never copies the MP4
retake golden verify # integrity plus the required 5–8 visual families
retake golden score candidate.mp4 --against short-landscape # cheap deterministic media scorecard
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 browserThe 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.
To join separate recordings, use retake stitch. Supply existing output folders or video files, a new MP4 path, and one transition style.
The recording environment — what has to be true before the camera
Six takes in one session were lost to the environment and not one to the recorder: an app pointed at the owner's real cloud store, a browser tab still attached that pushed the owner's board back into the "isolated" instance, a dev server caching its board so the seed only applied at boot, and two runs racing one output folder. Every one is cheap to assert and expensive to read off a frame.
retake env up # restart it clean, clear its state, then PROVE it
retake env check # prove the running one without restarting
retake env downdemos/environment.yaml declares it:
name: capture-local
start:
command: npm run dev -- --port 3179
cwd: ~/capture-local
port: 3179
env:
# Blanked, not unset: .env files are read AFTER the process environment,
# so "" is what stops a real token reaching a real account.
UPSTASH_REDIS_REST_URL: ""
APP_PASSWORD: ""
reset:
restart: true # the app caches state; without this, take two
clear: [".retake/sync"] # inherits whatever take one left behind
expect:
path: /app
present: ["button.tab:has-text('Intentions')"]
absent: ["text=a real note of mine"] # name what must NOT be thereabsent is the one that earns its place. A present-only check cannot see real
data leaking in, and you cannot assert the absence of something you did not
think of — so name it. retake routine run picks the file up automatically and
refuses to record if the environment is not what it claims.
Routines — the settings nobody should decide twice
demos/style.md is taste an agent reads while drafting, so every draft is a
fresh interpretation of it. That is how one video comes back narrated too fast,
the next too slow, and a third in a voice nobody asked for.
A routine is the other half: format, captions, voice, speaking rate and music held as data and stamped onto the manifest after it is drafted. The model writes the story; the routine writes the settings; they never argue.
A new product in one step. Point at the app and at one video you love:
retake routine setup http://localhost:3000/app --like "~/Videos/the-one-we-liked.mp4"Everything that can be read off those two arrives decided: the shape, the
cards and their colours from the video; the brand's mark from the page; an
environment that starts your app on its own port with every token in .env
blanked (names only — a value never leaves its file); and a brief with the
selectors on the first screen, so the next agent starts informed. What cannot
be read — the closing card's words, the one real thing that must never appear —
is asked once, at the end. A live site works too; there is just nothing to start.
Start by reading, not by guessing. The first routine used to be Retake's
defaults — an opinion wearing the clothes of a standard, which apply then
stamps over everything with confidence. Read it off demos you already shipped:
retake routine learn # what your existing manifests actually agree on
retake routine learn --write # make that the standardIt reports what did not agree rather than quietly picking, so a split shows up as a question instead of a silent decision.
retake routine init # or start from Retake's defaults instead
retake routine voices # hear the named voices once, then stop discussing speed
retake routine set voice neutral
retake routine set rate -8 # too fast, once, for every video that follows
retake routine # what every recording here will look and sound likeThree styles, because a style is a shape and not a theme:
Style | Output | Captions |
| 1920×1080 | the app inset on your own colour, with a shadow |
| 1920×1080 | the app fills the frame, nothing around it |
| 1080×1920 | inset the same way, for shorts and reels |
Proving they match. A standard nobody measures is a hope.
retake routine check # measures the finished files, not the manifestsIt reads back size, frame rate, whether there is a voice and how loud it is, and judges each video against the standard and against the others — because two videos can agree with each other and both be wrong (a batch whose final render failed leaves matching previews at the wrong frame rate).
Three things it cannot see, which the earlier steps catch instead: a key Retake
does not know (strict manifests reject it), a script copied from another demo
(validate compares the narration against that demo's own captions), and data
the app repopulated mid-take (stub the sync so nothing outside the demo can
change what is on screen).
Start from a north star. One approved video that every cut is held to:
retake routine reference "~/Videos/the-one-we-liked.mp4"Before anything records, it says what this routine cannot reproduce — "the
reference is narrated, the routine is silent", "the routine uses Retake's own
card, not a branded one" — so a take is never the thing that discovers it.
After, retake routine check scores each cut against it: the opening card, the
closing card and the frame around the app, as structural similarity, plus
size, frame rate and how loud the voice is.
Two pieces exist so a north star can be reproduced: a branded card (the
product's mark, typeface, ground, ink and corner circles, drawn by Retake
instead of added afterwards), and narration with align: scenes — the script
read once so it sounds like a person, then each paragraph placed on its
narrated scene. A line that would run into the next scene is reported, never
sped up.
Recording a batch. demos/plan.json is the queue — names and one-sentence
stories, written by whoever has the ideas. A routine never invents one.
retake routine run # everything not done yet
retake routine run -n 3 # three videos, then stop
retake routine run --only hero # re-record oneLetting it run itself. Once a routine has a plan and a north star, nobody needs to be at the keyboard:
retake routine schedule weekdays 09:00 # the next drafted video, every weekday morning
retake routine schedule # what is scheduled, and whether the machine holds it
retake routine schedule offEach run brings the app up isolated, records one video, judges it against the north star and delivers it only if it matches — then a notification says how it went. launchd wakes it on a Mac, cron on Linux; nothing of Retake's stays running. A story with no manifest yet waits instead of failing, so an undrafted idea never costs a morning.
Each item is stamped, dry-run, recorded as a preview and finished at full
quality, with progress written to plan.json as it goes — so an interrupted
batch resumes where it stopped, and one broken selector does not cost the other
four videos. Two failures in a row stops the batch and asks whether the app is
actually running.
Two standards, two products. retake routine new techtutor-vn, then
retake routine set voice vi-VN-HoaiMyNeural --as techtutor-vn. The language
rides on the voice, so there is no second setting that could disagree with the
first. retake routine use <name> switches; --as <name> is a one-off.
What a routine will not do: turn narration on. That needs a script and a person who has heard it — the routine only says which voice reads it, and how fast, once somebody has written one.
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 }On a new workspace, retake ui opens the first-demo flow and writes the manifest from your answers.
Steps — click, 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 unique — thumbnail, 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). Local voices run on an Apple Silicon Mac and send nothing anywhere: retake routine set voice heart (Kokoro, female, ~300 MB), michael (Kokoro, male) or aiden (Qwen3, ~1.8 GB). They need uv (brew install uv); the model downloads once, the first time a video is narrated. Kokoro reads demo words the demo way ("press re-CORD", "deh-mohz") and every local voice is levelled to Edge's loudness; 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 |
| 960×540 | 960×464 | iterating — the finals' layout at a quarter of the pixels, fastest |
| 1920×1080 | 1920×930 | checking timing and framing, hardware-encoded |
| 1920×1080 | 1920×930 | the general-purpose demo, plus a CRF-14 master |
| 1080×1080 | 1080×960 | feeds |
| 1080×1920 | 1080×1800 | shorts and reels |
| 1440×900 | 1440×800 | README and docs GIFs |
| 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.
Save a login once per site. When the agent sees the app needs a login, a small form appears in the window: Sign in to app.example.com. Type a demo account's username and password; Retake keeps them in your Mac's Keychain (the system keyring on Linux), never in a file the agent can read, and never sends them anywhere. Every demo of that site — in any workspace — signs in with it from then on, and signs in again by itself whenever its session runs out.
Without the window, the agent relays one line — run it and type the login (hidden):
retake login https://app.example.comretake logins lists the sites with a saved login (names only); retake login <url> --forget removes one.
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. If the site asks for an authenticator code, choose It asks for a code and paste the setup key; Retake computes the current code when it fills the field. SMS codes, SSO, captchas — anything only a person can do — choose Sign in by hand: a real browser opens, you sign in, press Done, and Retake keeps the session (not the password). From a terminal: retake login <url> --by-hand.
What the agent writes:
auth:
storageState: ../.auth/app.example.com.json # the site's session, reused until stale
setup:
- { action: fill, selector: "#user", text: "${LOGIN_USER}", secret: true }
- { action: fill, selector: "#password", text: "${LOGIN_PASSWORD}", secret: true }
- { action: fill, selector: "#code", text: "${TOTP:LOGIN_TOTP}", secret: true } # only if asked
- { action: click, selector: "button[type=submit]" }A value of the same name in the workspace .env still wins, so demos written with ${APP_USER}-style names and retake secret keep working.
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) foldswindow.openandtarget="_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.
validatewarns, the recorder detects it,checkfails. Long stories still want chapters of 30–45 seconds — for the viewer, not the tool.macOS is where it's used daily.
videotoolboxhardware 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 landRecording 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 typechecknpm 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.
This server cannot be deployed
Maintenance
Related MCP Connectors
Captured web interfaces, screenshots, flows and structured evidence for AI coding agents.
- mcpOAuthcom.screenshotink
Screenshot, diff, audit and sitemap-capture any web page — 5 MCP tools for AI agents.
Live browser debugging for AI assistants — DOM, console, network via MCP.
Comment on AI-generated webpages; feedback flows back to your coding agent. Free, MIT, local-first.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceMCP server that turns any running web app into demo videos, interactive walkthroughs, and marketing captures via one command. Enables AI agents to show their work with regenerated demos on every PR.4 npm6MIT
- AlicenseAqualityCmaintenanceMCP bridge between Chromium browsers (Arc, Vivaldi, Brave) and Claude Code. 18 browser automation tools (navigate, click, forms, screenshots, GIF recording, console, network) plus a popup chat panel powered by the Claude Agent SDK.1810MIT
- AlicenseAqualityAmaintenanceRecords narrated walkthroughs of any webapp by orchestrating Playwright, speech-mcp TTS, and FFmpeg composition.62MIT
- AlicenseNot gradedqualityBmaintenanceRecords a browser window while Claude drives it and converts the recording into labelled contact sheets, key frames, and an action-correlated timeline for evidence-based evaluation. macOS 15+ only.432 npmMIT