Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
MobAI-App avatar

Desktop Recorder

  • 16 repo stars
  • Updated June 27, 2026
  • MobAI-App/desktop-recorder-skill

Desktop Recorder is a macOS-only Claude Code agent skill that records polished, reproducible demo videos of desktop and web apps. It explores the app, replays a deterministic JSON screenplay, and exports a screencast wit

About

Desktop Recorder is a macOS-only Claude agent skill that turns a rough idea into a polished product demo video: it explores your desktop or web app, writes a deterministic JSON screenplay, then records and exports a screencast with synthetic cursor, click ripples, follow-cursor zoom, and captions - no manual screen recording.

  • Deterministic JSON-screenplay replay: the agent explores freely, then records a repeatable take
  • Built on deskagent (ScreenCaptureKit + AXPress + Vision OCR) for pixel-accurate macOS capture
  • Synthetic cursor, click ripples, and follow-cursor zoom baked in
  • Auto captions plus ffmpeg/Remotion high-quality HEVC export
  • Drives both native macOS apps and web apps in Chrome/Safari

Desktop Recorder by the numbers

  • Data as of Jul 12, 2026 (Skillselion catalog sync)
npx skills add https://github.com/MobAI-App/desktop-recorder-skill --skill desktop-recorder

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
repo stars16
Last updatedJune 27, 2026
RepositoryMobAI-App/desktop-recorder-skill

What it does

Record a polished, reproducible demo video of a macOS desktop or web app for a launch, landing page, or social post - hands-off.

Who is it for?

Indie developers and founders who need a polished launch or landing-page demo of a macOS desktop or web app without hand-recording it.

Skip if: Windows or Linux users, or anyone needing mobile-app screen recordings (use the sibling mobile-recorder skill).

What you get

A repeatable, on-brand screencast of your macOS or web app, generated hands-off and re-runnable whenever the UI changes.

By the numbers

  • macOS-only
  • Records both native macOS and web apps
  • High-quality HEVC export (~200 Mbps target)

Files

SKILL.mdMarkdownGitHub ↗

Desktop Recorder

Built on deskagent (ScreenCaptureKit + AXPress + Vision OCR). The agent never improvises during the final take - exploration is unconstrained, the recording is a deterministic JSON-script replay.

Pre-recording checklist - ASK, don't pick silently

Use AskUserQuestion (or plain text) before recording. State defaults explicitly so the user can shrug and accept.

DecisionDefault if user shrugs
Background none / dark / light / color:RRGGBB / image:/pathcolor:1a1a2e (dark navy). dark/light render as cached vertical gradients; image: covers the canvas.
Layout for multi-source: auto / side-by-side / grid / stackauto
Padding between elements60
Composition canvasdisplay native (or [1400, 1000] for the typical 1.4 AR)
Supersample on capture1 (device pixels). Bump to 2 for sharper playback when the editor will scale clips up
Cursor spritemacOS system arrow with pointing-hand on click (deskagent cursor-png). Override via screenplay.highlights.cursor.{arrow,pointing,size}
Click rippleProcedural soft expanding white ring. Override color/size/duration via screenplay.highlights.ripple.*, or supply your own .mov/APNG via ripple.sprite
Zoom follow_cursorEnabled - camera tracks the synthetic cursor's eased path (shared with the sprite, no desync)
Final export resolutiondisplay native (so QuickTime plays 1:1 on this machine)
Final qualityhigh (HEVC ~200 Mbps target)
State-verification levelpreflight + fingerprint at dry-run boundary
Which apps may be drivenWhichever the user named; never silently click into others

The 6 rules

1. Explore first

Tools allowed during exploration only:

  • deskagent list --json → window picking
  • deskagent inspect --window $ID → element coords (AX + OCR)
  • deskagent assert --window $ID --label X → cheap yes/no probe
  • deskagent screenshot --window $ID → visual reference; on macOS 26/Tahoe

hangs or capture failures, use node scripts/record-screenshot.js --window $ID --out /tmp/shot.png

  • Trial deskagent control runs against the live UI

Collect: click coords (window-relative), per-step waits, demo data, popups to normalize, the recording's window size (pin BEFORE inspecting), start/end states, captions.

2. The screenplay - single source of truth

One screenplay.json describes the demo end-to-end: scenes of actions to execute, plus top-level composition / zoom / speed / captions / trim directives. deskagent control reads only scenes[].actions[]; editor scripts read the top-level directives.

{
  "schema_version": 2,
  "name": "demo1",
  "coordinate_space": "window",
  "scenes": [
    { "id": "open_settings", "windowId": 245663,
      "actions": [ { "action": "click", "x": 244.5, "y": 54.5 } ] },
    { "id": "wait_load",     "windowId": 245663,
      "actions": [ { "action": "wait", "ms": 4000 } ] }
  ],
  "composition": {
    "canvas":     [2560, 1600],
    "background": "color:0a0a10",
    "layout":     "side-by-side",          // optional; auto-computes slot rects
    "padding":    60,                      // optional gap (used by layout)
    "elements": [
      { "windowId": 245663 }               // auto rect from layout
    ]
  },
  "zoom": [
    { "scale": 2.0, "follow_cursor": true,
      "fromAction": "open_settings/0", "toAction": "wait_load/0" }
  ],
  "speed": [
    { "factor": 2.5,
      "fromAction": "wait_load/0", "toAction": "wait_load/0", "endDelayMs": 4000 }
  ],
  "captions": [
    { "text": "Open Settings",       "fromAction": "open_settings/0", "durationMs": 1500 },
    { "text": "Loading the panel…",  "fromAction": "wait_load/0",     "toAction": "wait_load/0", "endDelayMs": 3000 }
  ],
  "trim": { "beforeScene": "open_settings", "afterScene": "wait_load" }
}

composition drives scripts/stages/compose.js - required for multi-clip recordings, optional when there's a single clip. zoom, speed, and captions are top-level arrays with global fromAction / toAction refs

  • a single entry can span any range of actions across any scenes, with

timing offsets via startDelayMs/endDelayMs. Zoom camera modes: follow_cursor: true tracks the synthetic cursor across click-driven ranges; pan: [...] does cinematic sweeps over no-click ranges (the cursor is hidden during pans). Don't overlap a pan with clicks - use follow_cursor there. See `references/desktop.md` for the full shape.

coordinate_space: "window" keeps screenplays portable across window drags. Per-scene windowId routes that scene's action coords to the correct window in multi-window compositions. No conditionals, no wait_for, no observation loops - every action is deterministic. Recording start/stop is outside the screenplay; the agent owns the lifecycle.

3. Dry-run + state fingerprint

Run the full script with --background against the live UI (no recorder active). Iterate until clean.

Before the final take, prove state matches what the script expects:

1. deskagent screenshot --window $ID → visual record. If screenshot hangs or fails on macOS 26/Tahoe, use the streaming fallback: node scripts/record-screenshot.js --window $ID --out /tmp/shot.png. 2. At least two deskagent assert calls on labels that exist ONLY in the expected sub-state. (Section names usually persist across sub-states - they're too coarse.) 3. Refuse to record if any assert fails. Re-normalize in setup.

Per-step assertions inside recording are NOT supported - the recording is meant to be a deterministic replay, not a probe loop.

4. Pin window info BEFORE recording

Capture id, pid, and x,y for every window during exploration, then drive with --target-pid + --window-frame + --background during the take. No WindowServer lookups during the run, no chance of the wrong window being picked if focus shifted between dry-run and record. Multi-window: collect pid+origin per window in one pre-record list call; route each control invocation via its own flags.

Targeting flag rules are documented in references/deskagent.md. The short form: HID mode needs --target-window (activates the app); background mode needs --target-pid + --window-frame (delivers per-pid, no activation).

5. No live recovery - re-record

Final take failed? Stop, discard, fix the script, re-record. Don't patch live.

6. Don't resize source windows after exploration

Any osascript … set size of window belongs in setup BEFORE exploration, confirmed with the user. Never inside recording. Composition is done in the editor's compose stage; per-clip placement + canvas + background live in the screenplay's composition block, so a mid-take window resize would still mismatch the composition rects.

End-to-end pipeline

1. Explore  -> pin id/pid/origin per window, gather click coords.
2. Author   -> screenplay.json (scenes + actions, top-level composition/zoom/speed/captions/trim).
3. Normalize state (close tabs, log in, hide chrome, theme).
4. Dry-run  -> deskagent control screenplay.json --target-window $ID --timeline /tmp/dry.json
5. Fingerprint state with deskagent assert.
6. Record   -> deskagent record ./demo/raw --window $ID [--window $ID2 ...] --no-cursor --pid-file /tmp/rec.pid [--supersample 2] &
                  (writes per-source ProRes 4444 .mov clips + recording.manifest.json into ./demo/raw/)
7. Drive    -> deskagent control screenplay.json --target-pid $PID --window-frame "$ORIGIN" \
                  --background --no-activate --timeline timeline.json
                  (run in parallel per target window for multi-window demos)
8. Stop     -> kill -INT $(cat /tmp/rec.pid); wait
9. Export   -> node scripts/export.js ./demo/raw screenplay.json timeline.json demo.final.mp4 [format] [--quality high]
                  (one ffmpeg pass: compose -> highlights -> zoom -> captions -> speedups -> encode)
10. Copy    -> node scripts/generate_copy.js timeline.json prompt.txt copy.md

Every editing operation runs in a single ffmpeg invocation off the per-source clips - no intermediate mp4s in the hot path, no generation-loss from per-stage re-encodes. The final encoder choice (HEVC / H.264 / ProRes 422) is the only re-encode in the entire pipeline.

Outputs (one demo folder, default ./demo-out/<name>/)

screenplay.json        single source of truth (scenes + composition + directives)
timeline.json          execution evidence from `deskagent control`
raw/                   directory written by `deskagent record`:
  recording.manifest.json   per-clip paths + host-time alignment anchors
  window-<id>.mov           one ProRes 4444 .mov per source (alpha-preserving)
  display-<id>.mov
demo.final.mp4         the deliverable (single ffmpeg pass via scripts/export.js)
copy.md                upload copy

For per-stage debugging, each editing stage has a CLI that can either emit its filter fragment as JSON (used by the orchestrator) or render it standalone to a ProRes 4444 .mov to inspect just that stage's effect:

node scripts/stages/zoom.js generate <recDir> <screenplay> <timeline>
node scripts/stages/zoom.js generate <recDir> <screenplay> <timeline> --apply input.mov output.mov

References (load on demand)

  • references/deskagent.md - CLI surface, permissions, recording.manifest.json schema
  • references/desktop.md - screenplay schema (scenes + composition + captions + zoom + speed + trim), setup normalization, state-fingerprint recipe
  • references/timeline.md - timeline event schema (scene_start / action / scene_end)
  • references/editing.md - export orchestrator + per-stage modules (compose / highlights / zoom / captions / speedups); cursor track + cursor.hide/show
  • references/web-driver.md - CDP web driver (drive-web.js): drive browser page content focus-free; trajectory/pointer actions; draw on web canvases
  • remotion-template/ - optional Remotion bridge: turn a recording (manifest+timeline+screenplay) into React motion graphics. The bridge (loadRecording + <RecordingCard>/<Cursor>/<ClickRipple>/<Caption>) does the integration; you only write the creative composition. See its README.md.

Authorization rule

deskagent control mutates the user's app state (focus, tabs, open docs). --background minimizes visible impact but still mutates. Confirm before touching any app the user didn't name. Don't silently swap targets or open new tabs / windows / documents.

Quick failure map

StageCauseAction
inspect returns nothingWails/Electron WebViewrely on OCR; AX walk only sees chrome
Capture errors at startTarget window onScreen: falsebring it forward; SCK can't always frame-pump occluded windows
macOS 26/Tahoe: deskagent screenshot hangs or fails while deskagent record --window ID worksscreenshot uses a separate ScreenCaptureKit still-image path that can fail independently from streaming captureUse node scripts/record-screenshot.js --window ID --out /tmp/shot.png for visual fingerprints; keep final takes on deskagent record
macOS 26/Tahoe: deskagent list reports displays: []ScreenCaptureKit display enumeration mismatchTry deskagent record ... --display 1 directly; display recording can still work even when list output omits displays
Web/browser clip frozen, animations stalled, stale contentWindow fully occluded - WebView throttles timers/rAF/repaintskeep the window at least partially visible during the take
screenplay schema_version N not supportedscreenplay was authored for a different deskagent buildre-author to current schema (current: 2)
compose.js errors: "manifest has N clips but screenplay has no \composition\"multi-clip recording lacks compositionadd composition.canvas + composition.elements[] to screenplay
captions[i] overlaps captions[j]two caption entries cover the same time rangeshorten the first with endDelayMs/durationMs or push the second with startDelayMs
Video looks soft in QuickTimeQT renders .mov dimensions as logical points; pixel-doubles on retinarecord with --supersample 2, OR export at display-native size (default), OR open the frame as PNG in Preview
Composition stretches a windowsource pixel size doesn't fit slot AND composition.upscale is onleave upscale unset; clip will sit at native size centered in the slot (downscale-only)
TCC re-prompts every releaseAd-hoc signing - fresh identity per buildre-grant once, or build with a stable self-signed cert

Chromium-based browsers (Chrome, Edge, Brave, Arc)

For page content, prefer the CDP web driver (scripts/drive-web.js) over deskagent control - it injects input at the renderer, so it needs no focus and no foreground (drive an unfocused window while recording), targets the DOM by selector (no pixel-guessing / mis-clicks), and emits the same timeline.json so record + export are unchanged. It replaces control for browser scenes; full setup (launch flags, actions, coordinates) in `references/web-driver.md`. Use deskagent control only for the browser's own UI (omnibox, tabs, extensions, menus) - CDP can't reach those.

Fallback (no debug Chrome): deskagent control HID mode reaches only the browser chrome via --background; for page content record with --no-cursor and drive with --target-window (HID, cursor moves during the take). OCR beats AX for Chrome element discovery; URL-bar nav (cmd+l → type → return) beats chasing SPA links.

Keep the browser window at least partially visible during the take. When fully occluded, the renderer throttles timers / rAF / repaints (Page Visibility), so the clip captures stalled animations or stale content - even though SCK is still pulling frames. (Launch Chrome with the --disable-*backgrounding* / throttling flags - see web-driver.md - to keep an unfocused window compositing.)

Out of scope

Mobile (use mobile-recorder-skill); Linux/Windows; direct upload; AI voiceover / music; GUI video editor.

Related skills

Forks & variants (1)

Desktop Recorder has 1 known copy in the catalog totaling 0 installs. They canonicalize to this original listing.

Generative Mediacontentdistribution

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.