MITOpen source, for Claude Code, Codex and Cursor

Your agent animates.demovie makes it true.

Accurate, on-brand motion-graphics videos of your real web app: launch videos, feature clips, changelogs and teasers, made by the coding agent you already use from real captures of your running app, and checked by 30 QA rules before they render.

$npx demovie init
Read the docs
Real captures, not imagination Your brand and your words 16:9, 9:16, 1:1 and 4:5 No telemetry
Imagined one prompt, no captures Real demovie, from captures
0:00 / 0:35

Left: a recreation of what one prompt and no captures typically gives you: an imagined dashboard, invented metrics, HUD labels. Right: demovie, from real captures of the app, in the product's own words, with a cursor that clicks real buttons. Sound is the right-hand video's soundtrack, synthesized by demovie.

Drives the agent you already use, installed and signed in by you

Claude CodeCodex CLICursorAny MCP clientAny skills-aware agent
30QA rules run on every video before the final render, each with a fix.
4 formats16:9, 9:16, 1:1 and 4:5, each laid out on its own, from one composition.
3m 21sof agent time for the Harborly teaser, from one command in a project already set up. QA 0 errors.[1]
0telemetry. Captures and videos stay in your repo's .demovie/ folder.

How it works

Ground. Direct. Check. Render.

demovie gives your agent your real product to work from, a motion runtime to animate it, and a QA engine that checks the result. Your agent does the creative work; demovie keeps it true.

01

Ground

init reads your code and the running app: brand colors and fonts, your product's vocabulary, every route. capture screenshots real states with seeded demo data, a frozen clock and masked personal data, and maps every element.

npx demovie init · npx demovie capture
02

Direct

Your agent follows the demovie skill: brief, capture plan, storyboard, style frames, animation. Product UI only enters through screen() with real captures; cursors, zooms and highlights target element ids.

“Make a 30-second launch video of the Projects board.”
03

Check

demovie qa samples the video and runs 30 rules: reading time, legibility, contrast, safe areas, pacing, truthful clicks, invented words and numbers, determinism, loudness and the “AI look” clichés, with a fix for every failure.

npx demovie qa launch --format all
04

Render

A deterministic frame-stepping renderer (Playwright and your ffmpeg) writes H.264 MP4s in every format, posters and captions, with license-clean music and sound effects mixed to −16 LUFS.

npx demovie render launch
captures/routes/app-projects@desktop
A real capture of Harborly's Projects board
link:new-projectlink:q3-launchdm:health-prj_android-app-v2
.demovie/videos/launch/composition/main.js
// launch/composition/main.js: the New project flow, shot 3
const plan = browser(flow, "flows/create-project@desktop/board", bar(6) - 0.3);
const pointer = cursor(v, { style: "mac" });
pointer.moveTo(plan, "link:new-project", { at: bar(6) + 0.4, duration: 0.85 });
pointer.click({ at: bar(6) + 1.35 });
plan.swap("flows/create-project@desktop/dialog", { at: bar(6) + 1.5, transition: "crossfade" });
pointer.moveTo(plan, "textbox:project-name", { at: bar(6) + 2.2, duration: 0.6 });
typeText(plan, "textbox:project-name", "Q4 Launch", { at: typedAt, cps: 14 });
pointer.moveTo(plan, "button:create-project", { at: bar(6) + 4.0, duration: 0.65 });
pointer.click({ at: bar(6) + 4.75 });
plan.highlight("generic:project-checklist", { at: bar(9) + 1.7, style: "ring" });
~/harborly
$ npx demovie qa launch --format all
◇ QA launch @ 16:9…
◇ QA launch @ 9:16…
16:9: 0 error(s), 0 warning(s), 0 waived
9:16: 0 error(s), 0 warning(s), 0 waived
QA passed with 0 errors (0 warning(s)) → .demovie/videos/launch/qa.json

# qa.json, 16:9
✓ DM-T01 Text stays on screen long enough to read
✓ DM-T02 Minimum text size
✓ DM-T03 No text overflow or clipping
✓ DM-T04 Contrast
✓ DM-T05 Too much text at once
✓ DM-L01 Safe area
✓ DM-L02 Overlaps
✓ DM-L03 Off-stage leftovers
✓ DM-P01 Shot pacing
✓ DM-P02 Duration
✓ DM-P03 End hold
✓ DM-P04 Shots declared
– DM-P05 Seamless loop (hero-loop only) (skipped)
✓ DM-G01 Truthful cursor
✓ DM-G02 Invented vocabulary
✓ DM-G03 Product shots use captures
✓ DM-G04 Invented numbers
✓ DM-G05 Upscaled captures
✓ DM-A01 Fonts
✓ DM-A02 Assets
✓ DM-A03 Network
✓ DM-A04 Audio provenance
✓ DM-R01 Determinism
✓ DM-R02 Blank frames
✓ DM-R03 Stray GSAP tweens
✓ DM-R04 Unregistered CSS animations
✓ DM-R05 Timers after ready
✓ DM-S01 Loudness
– DM-S02 VO overlap (skipped)
✓ DM-V01 "AI look" clichés
.demovie/videos/launch/out/
16:9 poster
16:9
9:16 poster
9:16
1:1 poster
1:1
◇ 16:9: launch-16x9.mp4 (1050 frames, 67.2s, libx264)
◇ 9:16: launch-9x16.mp4 (1050 frames, 77.0s, libx264)
.demovie/videos/launch/out/launch-16x9.mp4 · 1920×1080 · 30 fps · 35.00 s · 5.9 MB · libx264 · 67.2 s
.demovie/videos/launch/out/launch-9x16.mp4 · 1080×1920 · 30 fps · 35.00 s · 4.5 MB · libx264 · 77.0 s
audio: .demovie/videos/launch/audio/mix.wav · total 144.5 s

Captures

Every element has an address.

A capture is more than a screenshot. demovie records every visible element's role, name and box under a stable id, so a cursor click lands exactly on the real button, and QA can check that it did. Hover the board, or pick an id.

harborly.example/app/projects
Harborly's Projects board, captured with seeded demo data

56 visible elements in routes/app-projects@desktop, captured at 1440×900 and device scale 2.

Find an element

    In a composition

    // pick an element

    Flows

    States behind clicks, captured for real.

    Dialogs, filled forms and results come from flows: a few steps of YAML that demovie replays against your running app, capturing each state on the way. The composition then animates between real screenshots instead of drawing them.

    .demovie/flows/create-project.flow.yaml
    1. # create-project: open the Projects board, create "Q4 Launch" through the dialog, land on the new project,
    2. # then return to the board where it now sits in Planning. Demo data is re-seeded before every capture run.
    3. name: create-project
    4. start: /app/projects
    5. viewport: desktop
    6. description: Create a project from the Projects board
    7. steps:
    8. - capture: board
    9. - click: { testId: new-project }
    10. - waitFor: { role: dialog }
    11. - capture: dialog
    12. - fill: { label: Project name, value: Q4 Launch }
    13. - capture: dialog-filled
    14. - click: { role: button, name: Create project }
    15. - waitFor: { url: /app/projects/prj_q4-launch }
    16. - waitFor: { text: Launch checklist }
    17. - capture: created
    18. - hover: { testId: project-checklist }
    19. - capture: checklist-hover
    20. - goto: /app/projects
    21. - waitFor: { text: Q4 Launch }
    22. - capture: board-after
    harborly.example/app/projects
    Flow state boardFlow state dialogFlow state dialog-filledFlow state createdFlow state checklist-hoverFlow state board-after

    Six states of the create-project flow from the Harborly example, re-seeded before every capture run.

    QA

    30 checks before anything renders.

    demovie qa loads the composition exactly as the renderer does, samples it at 10 fps and reports what a careful reviewer would flag, with a fix for each finding. These are real reports on the deliberately broken examples in the repo.

      All 30 rules

      DM-T01Text stays on screen long enough to readerror

      Measures: Each distinct non-UI text block is continuously visible for ≥ 0.5 + words/3 s and ≥ 1.2 s

      Fix: hold the text longer (or cut words): give it at least 0.5 s + 1/3 s per word, and never less than 1.2 s

      DM-T02Minimum text sizeerror

      Measures: Font size ≥ 3.0% of the frame's short side (captions ≥ 3.8%), at scale 1

      Fix: set the font size to at least 3% of the frame's short side (calc(var(--dm-unit) * 3)); captions need 3.8%

      DM-T03No text overflow or clippingerror

      Measures: scrollWidth > clientWidth, or the text box extends beyond its container or the stage

      Fix: shorten the copy, let it wrap (max-width in ch), or enlarge the container so no glyph is cut

      DM-T04Contrasterror

      Measures: WCAG ratio of text color vs. a sampled background ring: < 4.5 warns, < 3.0 errors

      Fix: darken/lighten the text or its background (brand fg on bg), or put the text on a solid panel

      DM-T05Too much text at oncewarn

      Measures: More than 2 non-UI text blocks, or more than 18 words, visible at once

      Fix: show one idea at a time: at most 2 text blocks and 18 words on screen together

      DM-L01Safe areaerror

      Measures: Non-UI text, logos and the CTA stay inside the format's safe area

      Fix: move it inside the safe area: position with var(--dm-safe-top/right/bottom/left), check with preview safe guides

      DM-L02Overlapserror

      Measures: Two visible non-UI text boxes intersect by more than 5% of the smaller one, or text covers a highlighted product element

      Fix: give each text block its own space, and keep text off the element you highlight

      DM-L03Off-stage leftoverswarn

      Measures: Visible elements sit fully outside the stage for more than 1 s

      Fix: hide or remove elements once they leave the stage (set opacity 0 or end their shot)

      DM-P01Shot pacingerror

      Measures: Warn when the average shot length is below the type floor; error when more than 4 consecutive shots are under 0.8 s

      Fix: merge or lengthen shots: keep the average above the type's floor and never cut more than 4 times in a row under 0.8 s

      DM-P02Durationerror

      Measures: Within the type's range, and equal to video.json.duration ± 1 frame

      Fix: set video.json duration inside the type's range, and end the last shot exactly at the duration

      DM-P03End holdwarn

      Measures: The final logo/CTA shot is visible for ≥ 1.5 s

      Fix: end on a logo + CTA shot held for at least 1.5 s

      DM-P04Shots declarederror

      Measures: At least 1 shot, and the shots cover the full duration with no gap over 0.5 s

      Fix: declare shots with v.shot(id, start, end) covering the whole video

      DM-P05Seamless loop (hero-loop only)error

      Measures: First vs. last frame pixel diff < 2%

      Fix: make the end state equal the start state (camera reset, same text and positions) so the loop is invisible

      DM-G01Truthful cursorerror

      Measures: Every cursor click lands inside its target element's rect under the current screen transform

      Fix: move the cursor to the element (cursor.moveTo(screen, id)) so it arrives before the click, and click while the element is on screen

      DM-G02Invented vocabularywarn

      Measures: Capitalized terms in non-UI text that aren't in the glossary, the brief or captured UI text

      Fix: use the product's own words (.demovie/glossary.md), or add the term to the brief/glossary if it is real

      DM-G03Product shots use captureserror

      Measures: Every kind: "product" shot contains at least one screen()

      Fix: show product UI only with screen(v, { capture: "<id>", parent: shot.el }) — never draw or mock it

      DM-G04Invented numberswarn

      Measures: Numbers in non-UI text that aren't in the brief, the glossary or captured UI text

      Fix: only show numbers from the brief, seed data or captures; put the source in the brief if it is real

      DM-G05Upscaled captureserror

      Measures: A screen displayed above 1.0× its native pixel density warns; above 1.5× errors (blurry)

      Fix: zoom less, show the screen smaller, or capture at a higher deviceScaleFactor

      DM-A01Fontserror

      Measures: Declared brand and fallback fonts are loaded; no silent fallback to system fonts

      Fix: check brand.json font files exist under .demovie/brand/fonts and use var(--dm-font-heading/body/mono)

      DM-A02Assetserror

      Measures: Any 4xx/5xx response, or a broken image or video

      Fix: fix the path (composition-relative, /brand/*, /captures/*, /assets/*, /audio/*) or remove the reference

      DM-A03Networkerror

      Measures: Any blocked external request

      Fix: serve everything locally: copy the file into the composition folder or npx demovie add it

      DM-A04Audio provenanceerror

      Measures: An audio file that has no provenance.json entry from a demovie generator, a configured provider, or add --licensed

      Fix: generate audio with npx demovie audio music|voice|mix, or import it with npx demovie add <file> --licensed

      DM-R01Determinismerror

      Measures: 5 sampled frames, rendered at full size on two fresh pages in different seek orders (the second time right after the frame before each), look the same: at most 0.01% of pixels differ visibly (pixelmatch threshold 0.1)

      Fix: derive everything from t: no Date/timers/real randomness, put tweens in v.timeline, make onSeek pure, and avoid will-change on animated elements

      DM-R02Blank frameswarn

      Measures: Frames that are over 98% one color for more than 0.3 s, outside declared transitions

      Fix: start content earlier, overlap shots with a transition, or add a background element

      DM-R03Stray GSAP tweenserror

      Measures: Tweens outside v.timeline

      Fix: add every tween to v.timeline (v.timeline.to/from/fromTo(..., at)), never gsap.to() directly

      DM-R04Unregistered CSS animationswarn

      Measures: Running CSS animations not registered via v.css

      Fix: drive CSS keyframes with v.css(el, keyframes, { start, duration }) so they follow the timeline

      DM-R05Timers after readyerror

      Measures: setTimeout/setInterval called after ready()

      Fix: don't use timers for animation: schedule on v.timeline or compute state in v.onSeek(t)

      DM-S01Loudnesserror

      Measures: Integrated loudness outside target ± 2 LU warns; true peak above −1.0 dBTP errors

      Fix: re-run npx demovie audio mix <slug> (two-pass loudnorm), or lower the music/SFX gains

      DM-S02VO overlapwarn

      Measures: Voice lines overlap each other or run past the end of the video

      Fix: move VO lines apart in voice.json/storyboard (start times) or shorten them

      DM-V01"AI look" clichéswarn

      Measures: Non-UI text that matches HUD, timecode, BPM or "frame N" patterns; generic status chips ("Done", "Saved", "Success") not in the glossary; more than 2 typefaces

      Fix: drop decorative HUD/timecode labels and generic chips; stay with the brand's one or two typefaces

      Rules listed in video.json qa.ignore are reported as waived; --strict turns warnings into errors. Full details in the QA rules reference.

      Formats

      Every format, laid out on its own.

      One composition, separate layouts. The vertical launch video keeps the board legible by framing cards instead of cropping the landscape one; the changelog clip gets its own square cut.

      launch-16x9.mp41920×1080
      9:161080×1920
      1:11080×1080

      Quickstart

      Three commands. Then just ask.

      In your app's folder: set up once, capture, then ask your agent for a video in plain words, or let make start it for you. The status and log below are real: Harborly at grounding level L3, and the headless run that made its teaser.

      ~/your-app
      $ npx demovie init       # detect your app; brand, glossary, routes, login, skill
      $ npx demovie capture    # screenshots + element maps of every route and flow
      $ npx demovie status
      demovie 0.1.0 · Harborly (nextjs)
      captures: 20 (0 stale, updated 2026-10-02T23:02:28.765Z)
      grounding: L3 (flows captured, with multi-step states)
      videos: changelog [rendered, QA 0E/0W], launch [rendered, QA 0E/0W], projects-board-teaser [rendered, QA 0E/0W]
      $ npx demovie make --type teaser --format 16:9 --yes
      [ 2m 15s] → Bash npx demovie stills projects-board-teaser --every 1 --format all --sheet
      [ 2m 35s] → Bash npx demovie qa projects-board-teaser --format all
      [ 3m 11s] Rendered. Writing the share copy.
      Claude Code finished · 3m 21s · 43 turns · $1.06 (as reported by Claude Code)
      videos: .demovie/videos/projects-board-teaser/out/projects-board-teaser-16x9.mp4
      Or, in Claude Code, Codex or Cursor:“Make a 30-second launch video of the Projects board with demovie.”
      12 s teaser, 16:943 turns$1.06 reported by Claude CodeQA 0 errors · 0 warnings
      Node ≥ 20.19, or Bunffmpeg with libx264An app you can run locallyNext.js detected automatically; anything else with init --url

      Agents

      Works with the agent you already use.

      demovie is skill-first: your agent reads the demovie skill and drives the CLI, or calls the MCP server. demovie never logs in to AI services or touches your subscription; it starts the agent you installed and signed in to, or passes through API keys in CI.

      Claude Code

      Install the plugin: it bundles the skill and registers the MCP server. Or let init copy the skill into .claude/skills/demovie.

      in Claude Code
      /plugin marketplace add enszrlu/demovie
      /plugin install demovie@demovie
      bash
      npx demovie make --type launch --about "the Projects board"   # interactive
      npx demovie make --type changelog --yes                        # headless

      Changelog mode

      A fresh clip for every release.

      demovie changes maps commits to the routes they affect through the import graph. The GitHub Action turns that into a clip on every release.

      1. Recaptures only what changed: capture --changed --since <previous tag>
      2. Runs your agent headless with the changelog preset: make --type changelog --yes
      3. Comments on the pull request with the poster, links and the QA summary
      4. Attaches the MP4s and posters to the release
      .github/workflows/demovie.yml (written by npx demovie ci init)
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0                       # tags and history for `demovie changes`
      - uses: enszrlu/demovie/packages/action@v0
        with:
          agent: claude                        # or codex
          type: changelog
          app-url: ${{ github.event.deployment_status.environment_url }}   # or `start: pnpm build && pnpm start`
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}              # an API key, not a subscription

      Features

      What a product video needs. None of what it shouldn't have.

      Real captures, not imagination

      Logged-in states via form login, saved sessions or scripts; multi-step flows in YAML or TypeScript; stable element maps; freshness tracking.

      Your brand and your words

      Colors, fonts and logos extracted from your code, and a glossary of your product's vocabulary that QA enforces.

      A motion runtime

      Device frames, camera moves, a truthful cursor, typing, callouts, captions and transitions, in five style presets.

      QA you can trust

      30 machine-checked rules, a preview player with QA overlays, and contact sheets for review.

      License-clean audio

      A deterministic music synthesizer that follows your storyboard, 12 CC0 sound effects, optional voiceover with your own key, ducking and loudness normalization.

      Changelog mode

      demovie changes maps commits to affected routes through the import graph; a GitHub Action makes a clip for every release.

      Compare

      Honest about the alternatives.

      There are several good ways to make a product video with an agent now. Here is the same app made with one command each: /brag with its defaults, and npx demovie make --type teaser --format 16:9 --yes.

      brag /brag

      39 minagent time
      108turns
      $12.26reported cost

      Started from the repository, nothing set up. 21.5 s, 16:9, music and sound effects. Harborly's real components and demo data, rendered in a harness it built for the run.

      demovie make --yes

      3m 21sagent time
      43turns
      $1.06reported cost

      Started with init and capture already done (a few minutes, once per project). 12 s, 16:9, music. Screenshots of the running app with its demo data. QA: 0 errors, 0 warnings.

      [1] Both runs used Claude Opus 5.5, brag at maximum effort and demovie at the default effort, so read the cost difference as a direction, not a precise ratio. brag's story is the stronger one here; demovie's run was the cheap part of a project that was already set up. Read the full comparison, including four approaches measured on a private production app.

      A quick, fun launch post of a React app you just builtbrag is one command and tells a good story.brag
      Building videos as code yourself, or hundreds of data-driven videosUse a video framework with agent skills.Remotion · HyperFrames
      A showreel or motion art that isn't about your UIA strong model with a good prompt and a render harness is enough.No captures needed
      A video that has to show your real, current productLogged-in screens, your demo data, several formats, checked before it renders, again on every release.demovie
      The full comparison table
      demoviebragRemotion + skillsHyperFrames + skillsA prompt and a render harness
      What it isCaptures + a motion runtime + QA + renderer, driven by your agentA Claude Code skill: reads your code, writes and renders a launch videoReact framework for videoHTML framework for video, built for agentsYour agent writes a seek(t) page and renders it frame by frame
      Where the product UI comes fromScreenshots of your running app, with element mapsYour real components rendered outside the app, or your live siteWhatever you bringWhatever you bringWhat the agent finds: often your marketing site
      Data on screenYour demo dataYour demo data when it can load it outside the app; otherwise sample data the agent writesYoursYoursWhatever it finds
      Pages behind a loginBuilt in: form, recorded session, script, several rolesNot in its docsUp to youUp to youUp to you
      Personal data on screenMasked before each screenshotNot in its docsUp to youUp to youUp to you
      Checks before rendering30 QA rules: legibility, contrast, timing, safe areas, invented words and numbers, loudnessThe agent's judgment (plus HyperFrames' linter in its full mode)Your reviewhyperframes checkThe agent's self-critique
      Formats16:9, 9:16, 1:1, 4:5, each designed separatelyOne per run: landscape, vertical or squareAny you buildAny you buildAny it builds
      A video per releaseGitHub Action: what changed → recapture those pages → a clipNot in its docsBuild it yourselfBuild it yourselfBuild it yourself
      Getting startedinit and capture (minutes)One commandA project to set upAn installA rules file and a script
      LicenseMITMITRemotion's own license: free for individuals and companies of up to 3 people, paid above thatApache-2.0—

      Trust

      Your agent, your keys, your data.

      Never logs in for you

      demovie never logs in to AI services or reads tokens. It starts the agent you installed and signed in to.

      No telemetry

      Captures, videos and caches stay in .demovie/. The only outside calls are the ones you configure.

      MIT, for any company

      No per-company video license. Compositions are plain HTML and GSAP on demovie's own renderer.

      License-clean audio

      Music is synthesized, effects are CC0, voiceover uses your own key. Every audio file has a provenance entry.

      FAQ

      Questions, answered.

      Something else? Troubleshooting covers the common errors, and issues are open.

      Why not just prompt a frontier model for a product video?

      A one-prompt video looks impressive and is wrong: the model has never seen your product, so it redraws a generic dashboard, invents features, metrics and customers, and wraps it in HUD labels and purple gradients. demovie gives the model your real app — screenshots, element maps, your brand and vocabulary — and then checks the result (QA) so it stays true. See the side-by-side at the top of this page.

      Why not Remotion?

      Remotion is free only for individuals, non-profits and companies of up to three people; larger companies need a paid license. demovie is MIT, so anyone can use it. Compositions are plain HTML + GSAP, rendered frame by frame with Playwright and your system ffmpeg. See licensing and terms.

      Does it use my Claude (or ChatGPT, or Cursor) subscription?

      demovie itself never logs in to any AI service and never reads tokens. Your agent — which you installed and signed in to — uses demovie as a tool. demovie make just starts that agent for you. In CI the action passes through API keys.

      Where does my data go?

      Nowhere. Captures, videos and caches stay in your repository's .demovie/ folder (captures, caches and outputs are gitignored). There is no telemetry. The only network calls are to your own app, and — only if you configure them and approve the cost — to ElevenLabs or OpenAI for voiceover or music, and whatever your agent does on its own.

      Which frameworks are supported?

      Next.js (App and Pages Router) is detected automatically, including route groups, dynamic routes, middleware/proxy protection and monorepos. Any other web app works in generic mode: npx demovie init --url http://localhost:5173.

      Can I edit the video by hand?

      Yes. composition/main.js is ordinary JavaScript with GSAP; npx demovie preview <slug> hot-reloads as you edit.

      How is this different from brag, Remotion or HyperFrames?

      brag turns a project into a quick launch post with one command; Remotion and HyperFrames are frameworks for building videos as code. demovie is for videos that must show your real, current product, logins and data included, checked before they render and remade on every release. See how demovie compares, with measurements from one real app made four ways.

      Make your first video.

      A few minutes of your time, then your agent works on it. The MP4s land in .demovie/videos/<slug>/out/.

      demovie is free and MIT licensed. If it saves you time, buy me a coffee.

      ↑ ↓ to move↵ to openesc to close