Skills / Demo Recorder

Demo Recorder

Polished GIF and MP4 demos of your real, running app, with a smooth cursor and captions, scripted and checked by your agent.

examples/flow.json recorded against the bundled demo app: 14.8s, 890 KB MP4.

Examples

Each example is a real run of the skill on a fixture in examples/. Nothing here is mocked up.

Example 1

A todo app tour with zoom, a ⌘K palette and dark mode

YouMake a short demo of adding and completing a task in Orbit, then switching to dark mode with ⌘K. I need a GIF for the README and an MP4 for the changelog.

The bundled Orbit app recorded in one take: a task is typed in, the camera zooms in while another is ticked off, then the ⌘K palette switches the app to dark mode. One run writes both the MP4 and a full-palette GIF, plus the contact sheet the agent reads to check the take.

MP4, 1024×680 at 2x, with captions, zoom, the ⌘K keystroke HUD and a window frame.
The same Orbit demo as a looping GIF.
GIF from the same take (960 px wide, 15 fps, one palette for the whole clip).
Twelve frames from the Orbit clip in a grid, each labelled with its time and the step that was running.
Contact sheet: what the agent reviews before handing the clip over.
Result
14.8 s clip: MP4 1.26 MB and GIF 2.08 MB from one take
Command
node scripts/record.mjs examples/flow.json
Input
app.html flow.json
◆ [8/12] caption "⌘K runs any command"
◆ [9/12] press "Meta+K"
◆ [10/12] type "dark"
◆ [11/12] press "Enter"
◆ [12/12] highlight "#progress"
◆ Captured 444 frames (15.0s of timeline) in 20.5s, 109 unchanged frames reused
◆ Encoding MP4, GIF…
◆ Encoded in 4.1s
✓ MP4 → .knack/demo-recorder/orbit-demo.mp4 (1.26 MB, 14.8s)
✓ GIF → .knack/demo-recorder/orbit-demo.gif (2.08 MB, 14.8s)
✓ Legibility: typical text is 15px; shown ~11.6px at README width (880px), ~5.1px on a phone.
✓ Contact sheet (open this to review) → .knack/demo-recorder/orbit-demo-sheet.png

Example 3

A signup form as a small, framed README GIF

YouRecord a GIF of the signup flow for the README.

A SaaS signup screen filled with fake data: fields are tabbed through (the keystroke HUD shows each Tab), a team size is picked, the annual-billing switch updates the price, and the submit lands on the success state. Recorded in a window frame on the neutral mono background. The first GIF showed banding rings in the panel's orange radial gradient, so the fixture was changed to flat fills and the clip was recorded again.

Marigold signup form in a macOS window on a light grey background: name, email and studio name are typed with Tab between fields, the 2–5 team size and yearly billing are selected, the price drops to $23, and submitting shows a 'You're in, Ada!' success screen.
GIF with the keystroke HUD and a window frame on the neutral mono background.
Twelve frames from the signup clip, from the empty form through the filled form to the success screen.
Contact sheet of the final take.
Result
14.8 s GIF, 406 KB, framed for a README
Command
node scripts/record.mjs examples/signup.flow.json
Input
signup.html signup.flow.json
◆ [5/10] type "Fernhill Studio"
◆ [6/10] click "role=radio[name="2–5"]"
◆ [7/10] click "#annual"
◆ [8/10] click "#submit"
◆ [9/10] wait "text=You're in, Ada!"
◆ [10/10] cursor [330,615]
◆ Captured 443 frames (14.9s of timeline) in 16.5s, 145 unchanged frames reused
◆ Encoding GIF…
◆ Encoded in 2.0s
✓ GIF → .knack/demo-recorder/signup.gif (406 KB, 14.8s)
⚠ Legibility: typical text is 14px; shown ~10.8px at README width (880px), ~4.8px on a phone. Small: use a smaller viewport (e.g. 960x600), zoom on the detail or drop the window frame (--no-frame).
✓ Contact sheet (open this to review) → .knack/demo-recorder/signup-sheet.png

About this skill

Ask your agent for "a GIF of the signup flow for the README" and get a crisp, smooth clip of your real, running app: an eased cursor that glides to each button, click ripples, keystrokes shown on screen, optional zoom and captions, wrapped in a tidy window frame.

Demo Recorder preview

Why

Screen-recording a feature by hand means re-takes, a jittery mouse, a messy desktop and a 40 MB GIF. Tools like Remotion make authored videos; they do not record your actual app. This skill lets the agent script the flow, check every step, record, and review the result itself before handing you a file that fits GitHub's limits.

  • Retina-sharp and smooth. Time is virtualised, so every frame is captured at 2x with exact timing, even on a slow machine. No dropped frames, no soft VP8 blur.
  • A cursor people can follow. Headless browsers have no cursor; the recorder draws one at a legible size with a human velocity profile (minimum-jerk, gentle landing), motion blur while it travels, hand/I-beam shapes, press feedback and ripples.
  • Calm, framed zooms. Zoom fits the target, scales straight in about a fixed point (no sideways slide) and settles softly. Captions and the keystroke HUD stay in screen space, so a zoom never crops them. --no-zoom gives a reduced-motion take.
  • Self-checking. A dry run screenshots every step; failures list similar elements with ready-to-paste selectors; every recording ends with a contact sheet the agent reads (1600 px, 256-colour PNG, about 100 to 150 KB for the bundled examples).
  • Built for where demos go. MP4/WebM with BT.709 colour tags so brand colours match the page (mean ΔE 0.3 in Chrome, was 5.6), GIF with a full-clip palette (no speckled chrome or banded gradients), size warnings at 10 MB, a legibility estimate at README and phone width. Idle-trimming, speed-up and length caps included.

Example prompts

  • "Record a GIF of the signup flow for the README."
  • "Make a short MP4 showing the new ⌘K command palette for our changelog post on X."
  • "Add a before/after clip of the settings page fix to this PR."
  • "Capture a walkthrough of creating and sharing a dashboard, with captions, in dark mode."
  • "Re-record docs/demos/invoice.flow.json, the button labels changed."

Try it

cd skills/demo-recorder
bash scripts/setup.sh                          # Playwright + Chromium, checks ffmpeg
node scripts/record.mjs examples/flow.json     # records the bundled Orbit demo app

You get .knack/demo-recorder/orbit-demo.mp4, orbit-demo.gif and orbit-demo-sheet.png (a 15-second clip: typing a task, zooming in to complete one, opening the ⌘K palette and switching to dark mode). About 30 seconds on an M-series Mac; MP4 ≈ 1.3 MB, GIF ≈ 2.1 MB.

Other entry points:

node scripts/record.mjs examples/flow.json --dry-run     # per-step screenshots, no video
node scripts/inspect.mjs --find-server                   # which localhost ports are serving
node scripts/inspect.mjs http://localhost:3000           # selectors + annotated screenshot
node scripts/record.mjs --help                           # every flag

Examples

Three real recordings, with their fixtures, flows and contact sheets, in examples/ (outputs under examples/output/, manifest in examples/examples.json):

  • Orbit todo app: typing, zoom, ⌘K palette and dark mode; one take to MP4 + GIF (examples/flow.json).
  • Docs search: ⌘K search on a dark docs site, zoomed in on the palette; 1280×800 MP4 for a changelog post (examples/docs-search.flow.json).
  • Signup form: tabbing through fields with the keystroke HUD, a switch and a success state; 406 KB framed GIF for a README (examples/signup.flow.json).

How a flow looks

{
  "url": "http://localhost:3000",
  "viewport": "1024x680",
  "format": ["gif", "mp4"],
  "frame": true,
  "steps": [
    { "click": "role=button[name=\"New project\"]" },
    { "type": "Q4 launch plan", "into": "#project-name" },
    { "press": "Enter" },
    { "wait": "text=Project created" },
    { "highlight": ".project-card", "duration": 1200 }
  ]
}

Full reference: references/flow-format.md.

Requirements

  • Node.js 18+
  • ffmpeg (brew install ffmpeg, apt-get install ffmpeg, winget install Gyan.FFmpeg)
  • Chromium via Playwright (installed by setup.sh; falls back to installed Chrome or Edge)
  • A running web app (any framework, or a static HTML file)

No network calls beyond loading the page you record. Output stays in ./.knack/demo-recorder/.

Limitations

  • Pages driven by <video> or real-time WebGL can misbehave under virtual time; use --realtime for those.
  • Native <select> menus, browser dialogs and file pickers are not drawn.
  • Chromium only; one tab per recording.