Agent skill

Michel UI Demo Recorder

by PackmindHub in PackmindHub/packmind

Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros.

Apache-2.0Auto-check passedMedia & Creative

Install Michel UI Demo Recorder

skills CLI
$ npx skills add PackmindHub/packmind --skill michel-ui-demo-recorder -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install PackmindHub/packmind michel-ui-demo-recorder --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/PackmindHub/packmind.git skills-src && mkdir -p .claude/skills && cp -r skills-src/scripts/michel/michel-ui-demo-recorder .claude/skills/michel-ui-demo-recorder && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
michel-ui-demo-recorder
GitHub stars
318
Token cost
~6.4k tokens
SKILL.md length
3,221 words
Files
5 (incl. scripts, references)
Skills in repo
35
Repo updated
First seen
Licence
Apache-2.0

At a glance

Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros.

  • Works in 5 steps: Confirm the Playwright MCP has… → Confirm the right tool prefix → Get the app running — from a clean volume → …
  • Asks to record a demo
  • SKILL.md covers When to reach for this skill, Why a skill exists for this, Pre-flight check (do this… and Recording recipe, plus 5 more sections
  • Runs JavaScript and Shell scripts from its folder; calls docker, ffprobe and bash

What it does

Michel UI Demo Recorder is an agent skill from PackmindHub/packmind. Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros. Produces an HD WebM video with chapter markers, a mandatory animated cursor overlay, and a mandatory subtitle bar that narrates each step (positioned deliberately so it never masks the UI being demonstrated), plus full-page screenshots at each step. Use this whenever the user asks to "record a demo", "create a screencast", "make a UI walkthrough…

Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including scripts and reference files (for example `references/playwright-mcp-tools.md`, `scripts/check-mcp-config.sh` and `scripts/inject-cursor.js`).

It sits in Media & Creative, covering Browser testing, Transcription and Video production. It works with Model Context Protocol and Playwright. The repository describes itself as: Packmind seamlessly captures your engineering playbook and turns it into AI context, guardrails, and governance. The licence is Apache-2.0.

When your agent uses it

  • Asks to record a demo
  • Create a screencast
  • Make a UI walkthrough video
  • Document this feature with video

Example prompts

  • “record a demo”
  • “create a screencast”
  • “make a UI walkthrough video”
  • “/michel-ui-demo-recorder”

Requirements

  • Node.js
  • A Bash shell
  • Docker

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. Confirm the Playwright MCP has --caps=devtools
  2. Confirm the right tool prefix
  3. Get the app running — from a clean volume
  4. check raw duration and choose speed multiplier
  5. trim lead-in and apply speedup in one pass

What it can do on your machine

Read from SKILL.md and the folder at commit 67de8a2. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 3 files in scripts/ (JavaScript and Shell), which the agent can run.

    Shell commands in SKILL.md call:

    • docker
    • ffprobe
    • bash
    • ffmpeg

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use docker, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Michel UI Demo Recorder loads about 6.4k tokens when it runs, and up to ~8.9k if it reads all its reference files. Until then it costs about 231 tokens; SKILL.md has 3,221 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~231
When it runs · the whole SKILL.md, loaded when a task matches
~6.4k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~8.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from PackmindHub/packmind at commit 67de8a2, republished under its Apache-2.0 licence (© PackmindHub). 3,221 words, ~6,428 tokens.

Download SKILL.mdSave it as .claude/skills/michel-ui-demo-recorder/SKILL.md (or your agent's skills folder). This skill also uses 4 other files; get the full folder from GitHub.
name
michel-ui-demo-recorder
description
Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros. Produces an HD WebM video with chapter markers, a mandatory animated cursor overlay, and a mandatory subtitle bar that narrates each step (positioned deliberately so it never masks the UI being demonstrated), plus full-page screenshots at each step. Use this whenever the user asks to "record a demo", "create a screencast", "make a UI walkthrough video", "document this feature with video", "show the client how X works", "capture screenshots of the app", or anything similar — even when the user only says "make a video" or "take screenshots" in the context of a running frontend. Also use it when the user wants to demonstrate a workflow, generate marketing-quality footage of an app, or produce repeatable visual documentation.

UI Demo Recorder

Produce client-ready UI documentation (HD video + screenshots) from a running web app using the Playwright MCP video tools.

When to reach for this skill

  • "Record a demo / walkthrough / screencast / video of the app"
  • "Take screenshots of the feature working"
  • "Show the client what X looks like"
  • "Document this flow visually"
  • "Make a GIF/video of clicking through Y"

The deliverable is always one or more of:

  • WebM video (1440×900 HD by default), with optional chapter cards
  • Full-page PNG screenshots at key moments
  • An animated cursor overlay so viewers can follow what's being clicked (always present)
  • A subtitle bar narrating each step (always present)

Non-negotiable: every video this skill produces MUST carry both the cursor overlay and the subtitle bar. They are injected via browser_evaluate (pure DOM), independent of .mcp.json — a run that omits them is a defective deliverable, not a stylistic choice. If you cannot inject them, stop and report why rather than shipping a bare recording.

Why a skill exists for this

The Playwright MCP video tools work, but they have several non-obvious gotchas that waste a lot of time if you discover them mid-recording:

  1. The video tools live behind an opt-in flag (--caps=devtools) and are silently absent if the MCP server wasn't launched with it.
  2. There are usually two different Playwright MCP tool prefixes in the deferred-tools list (a stock one and a plugin one). They use different browser contexts, and only one of them can record video. Mixing them mid-session causes "Browser is already in use" errors.
  3. The recorder doesn't render the OS cursor — videos look like the app is operating itself unless you inject a fake cursor.
  4. Any location.reload() wipes the injected cursor — it must be reinjected.
  5. The output file lands at the project root, not in whatever --output-dir says.

This skill codifies the working recipe so the model doesn't relearn it every time.

Pre-flight check (do this first, every time)

1. Confirm the Playwright MCP has --caps=devtools

Open .mcp.json (project root or ~/.claude/.mcp.json). The Playwright server entry must include --caps=devtools in its args. If it doesn't, video tools won't be exposed.

Working config:

json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--caps=devtools",
        "--output-dir=./videos"
      ]
    }
  }
}

If you have to add the flag, tell the user — they have to reload MCP (/mcp reconnect or restart Claude Code) before browser_start_video becomes callable.

2. Confirm the right tool prefix

Use ToolSearch for mcp__playwright__browser_start_video. The video tools belong to the mcp__playwright__browser_* family. Use the same prefix for every browser interaction in the recording — browser_navigate, browser_click, browser_type, browser_snapshot, browser_wait_for, browser_evaluate, browser_close. Do not mix in mcp__plugin_playwright_playwright__browser_* — that's a different MCP server with a different browser context; mixing causes "Browser is already in use".

3. Get the app running — from a clean volume

See the michel-run-local-dev-stack skill for the full stack lifecycle. Packmind runs as a Docker Compose stack (PostgreSQL, Redis, NestJS API on container port :3000, React/Vite frontend on container port :4200, MCP server). Those are container-internal ports — from the host the API is reached only through the frontend's Vite proxy, and the frontend's host port is edition-dependent (4200 OSS / 4201 proprietary). For recordings, start from wiped volumes so stale Postgres schema/rows from a prior run don't leak into the footage, then confirm it's serving before recording:

bash
export PACKMIND_EDITION="$(bash scripts/michel/resolve-edition.sh)"   # oss | proprietary, from the git remote
docker compose down -v && docker compose up -d --build
PM_WEB="$(docker compose port frontend 4200 | sed 's#.*:##')"   # host port: 4200 oss / 4201 proprietary
# wait for the API via the Vite proxy (NOT :3000 — that container port is not host-exposed)
until curl -sf "localhost:$PM_WEB/api/v0" >/dev/null; do sleep 1; done
# then wait for the frontend you'll actually record
until curl -sf "localhost:$PM_WEB/" >/dev/null; do sleep 1; done

The frontend you record lives at http://localhost:$PM_WEB (4200 OSS / 4201 proprietary) — navigate there, not at the API port.

On a freshly wiped database there is no account, so the first screen is the sign-up / login page. Create an account (or seed via the API) before recording the feature, otherwise the demo is just the auth screen. If a populated workspace matters, seed it via the UI or the API after the stack is up — do not record over stale state.

Recording recipe

The recipe in plain English:

  1. Make sure no browser tab is currently open on the Playwright side, and always call browser_stop_video first (ignore any error it returns) to clear a recorder a prior session may have left open. Delete any stub WebMs it drops at the project root.
  2. Start the video.
  3. Navigate to the app.
  4. Inject BOTH overlays — cursor and subtitle bar — in one browser_evaluate. This step is mandatory, never skip it.
  5. Add a chapter card.
  6. Drive the UI (click, type slowly, wait for visible feedback). Call window.__setSubtitle(...) before every step.
  7. Whenever the page reloads, reinject BOTH overlays (cursor + subtitle bar). A reload wipes them.
  8. Add chapter cards between major sections.
  9. Stop the video.
  10. Move the file from project root into videos/.
  11. Stop the app stack (docker compose down — see the michel-run-local-dev-stack skill).
Starting the video (with the common pitfall)

Always call browser_stop_video before start_video, even on the first recording — wrap it so its error is ignored. An earlier browser session in this run frequently leaves a screencast open, and start_video then fails with Error: Screencast is already started. Stopping first is idempotent: if nothing was recording it's a harmless no-op; if something was, it clears it.

mcp__playwright__browser_stop_video()   # ignore any error; deletes/returns stub WebMs at project root
mcp__playwright__browser_start_video(
  filename="<name>.webm",
  size={"width": 1440, "height": 900}
)

If you still get Error: Screencast is already started after that, the recorder is wedged to a stale browser context that a second stop_video won't clear on its own. Escalate — do not just retry stop+start in a loop:

  1. mcp__playwright__browser_stop_video() again (ignore error).
  2. mcp__playwright__browser_close() — this tears down the wedged context that owns the orphaned screencast.
  3. Delete the stub WebMs it drops at the project root.
  4. Retry start_video. The fresh context starts clean.

This is the common failure mode on the fly worker, where an earlier browser session in the same run left both a screencast and its browser context alive — stopping the screencast alone is not idempotent there, because the next start_video reattaches to the same wedged context. Closing the context is what actually resets it.

If you get Browser is already in use for ... use --isolated, a non-recording Playwright session has the persistent profile locked. Call mcp__playwright__browser_close first, then start_video.

Injecting the cursor overlay

The Playwright video recorder is a CDP screencast of the viewport — the real OS cursor is never in the frame. To make the video readable, inject a DOM cursor that listens to mousemove/mousedown/mouseup (Playwright's pointer events do dispatch these in capture phase).

Run the script in scripts/inject-cursor.js (relative to this skill's directory, not the project repo you're working in — the agent cwd is usually a checked-out repo that has no such file) via browser_evaluate immediately after every navigate or reload. Inject the cursor and the subtitle bar (next section) together in a single browser_evaluate so neither is ever forgotten — call both injector bodies in one function:

mcp__playwright__browser_evaluate(function=`() => {
  (<contents of scripts/inject-cursor.js>)();
  (<contents of scripts/inject-subtitles.js>)({position: 'bottom'});
}`)

The cursor is:

  • A blue radial-gradient disk with a soft glow
  • Animates with a 220ms CSS transition so jumps look smooth
  • Turns red and shrinks on mousedown
  • Emits an expanding ring "ripple" on click

Reinject after every location.reload() — there is no initScript equivalent exposed in MCP, so it has to be a manual step.

Chapter cards

mcp__playwright__browser_video_chapter renders a full-screen card with a blurred backdrop over the page for a configurable duration.

Scale to flow complexity — do not use 3-6 cards by default:

  • ≤5 action steps → 0-1 cards max (one opener only if the flow needs context)
  • 6-12 steps → 2-3 cards (intro + sections)
  • 12+ steps → up to 5 cards

Every card adds dead time. For short demos like "create a standard" or "open a recipe", skip chapter cards entirely or use a single 1000ms opener.

Duration is raw recording time — multiply by your speedup factor to get on-screen time. A duration: 2000 card at 4× speedup shows for only 500 ms after ffmpeg — too fast to read. Formula: duration = desired_screen_ms × speedup. For a 2 s on-screen hold at 4×, set duration: 8000. At 2× use 4000, at 3× use 6000.

Subtitle bar (continuous narration) — mandatory

Chapter cards interrupt the action. For inline narration that doesn't hide the UI — short captions that explain each step while the viewer watches the click happen — inject a subtitle bar via scripts/inject-subtitles.js. This is required on every recording, not optional. Bundle it with the cursor injection so both go in with one browser_evaluate (see the combined snippet above), and reinject both after every reload.

The script exposes window.__setSubtitle(text). Call it before each step:

mcp__playwright__browser_evaluate(function="() => window.__setSubtitle('Type the task name')")

Pass an empty string or null to hide the bar.

Pick the position deliberately. The default is bottom-center. A subtitle that sits over the very area the viewer needs to watch defeats the purpose — they'll either miss the action or miss the caption.

  • If the action happens in the header/top bar (a form, search input, primary CTA at the top), use the bottom position (default).
  • If the action happens in a footer, sticky action bar, or fixed bottom CTA, switch to the top position by passing {position: 'top'} to the injector, or call window.__moveSubtitle('top') mid-recording.
  • If a single screen has critical content at both top and bottom, reposition between steps with window.__moveSubtitle so the bar always sits on the inert side of the UI.

The bar fades + slides on text change (280ms) so swaps look intentional rather than glitchy. Reinject after location.reload() along with the cursor.

Scenario script (written narration for the PR) — mandatory

A video link alone is useless to a reviewer who can't or won't play it — typically the Product Manager the demo is for. Every recording MUST ship a short written scenario so the demo flow is understandable from text alone.

You already have the raw material: the captions you fed to __setSubtitle ARE the per-step narration. After recording, distill that sequence into a concise, bullet-point scenario:

  • Audience: a non-technical reviewer who may never open the video. They should grasp the whole flow from the bullets.
  • Synthetic and essential only — login → key action(s) → observable result. Drop superficial UI steps (scrolling, opening menus, incidental clicks). Keep what proves the feature works.
  • One line per bullet. No screenshots-of-text, no implementation detail.

Write it to a sidecar file with the SAME basename as the final webm, in the artifacts dir Michel passes you:

bash
# video: feature-demo.webm  →  sidecar: feature-demo.scenario.md
cat > "<ARTIFACTS_DIR>/feature-demo.scenario.md" <<'EOF'
- Log in as a Product Manager and open the Standards page
- Create a new standard "React naming conventions" with two rules
- The standard appears in the list; open its detail view
- Edit a rule inline and confirm the change persists after reload
EOF

Michel's publish step finds this sidecar by basename and inlines it directly under the video link in the PR body as What the video shows: — you do NOT edit the PR body yourself. If the sidecar is missing or empty, the PR shows _No scenario available — an error occurred during generation._ under the link instead — a visible defect, so always write it. For uses outside Michel (no automatic PR assembly), hand the same bullets to whoever embeds the video so they go right under the link.

Lead-in dead air

The recorder buffers for a second or two after start_video before useful frames appear. Combined with MCP tool round-trip latency, the first ~10–25 seconds of the WebM can show an empty page or about:blank. Mitigations:

  1. Set the first subtitle and a chapter card before the first wait_for — gives the viewer something to read during the lead-in.
  2. Keep the gap between start_video, navigate, and the inject-overlays browser_evaluate as tight as possible — no intermediate snapshots.
  3. Always trim dead air in post — add -ss 5 before the speedup step (see post-processing section). This cuts the first 5 seconds from the raw file before any speedup is applied, removing blank openers at essentially zero quality cost.
Typing and clicking — make it watchable
  • browser_type(..., slowly=true) types one character at a time. Use it for any text the viewer should read.
  • Before each browser_click, take a fresh browser_snapshot to read current UI state — but click by text/CSS (next section), not by ref=, so a re-render or navigation can't invalidate your target.
  • After actions that change the DOM, call browser_wait_for(text=<expected new content>) instead of arbitrary sleeps — recordings made of wait_for look like a real user; recordings made of fixed sleeps look robotic.
  • wait_for(text=...) must use the label the UI actually renders, not the one you assume. A guessed string that never appears burns the full 30s timeout. Read the real text from a fresh browser_snapshot first, then wait on it. If a step is flaky to wait on by text, drive it via browser_evaluate instead of waiting.
Show full SKILL.md (1,232 more words)Show less
Do not use snapshot ref= tokens as click selectors

browser_snapshot output contains ref=eXX tokens. They look like selectors but are snapshot-internal handles, not selector-engine names — passing one to browser_click fails with Error: Unknown engine ref. Target elements by text or CSS instead: :text('Add task'), input[name="assignee"], etc. — or drive the interaction with browser_evaluate.

Clearing a text input (there is no browser_triple_click)

The Playwright MCP exposes no browser_triple_click, and browser_type appends — it does not replace existing text. The Packmind frontend is React (Chakra UI / @packmind/ui), so its inputs are controlled. To clear a field (e.g. before retyping into an edit drawer), set the value via browser_evaluate and dispatch an input event so React's controlled state updates:

mcp__playwright__browser_evaluate(function=`() => {
  const el = document.querySelector('<selector>');   // e.g. input[name="name"]
  const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
  setter.call(el, '');                                 // native setter so React sees the change
  el.dispatchEvent(new Event('input', { bubbles: true }));
}`)

Using the native value setter (not el.value = '') is required for React-controlled inputs — a plain assignment is silently overwritten on the next render. After clearing, browser_type(slowly=true) the new text so the viewer reads it.

Chakra checkboxes/radios — click the control part, not the native input

Chakra UI v3 (@packmind/ui, on zag-js) renders a checkbox/radio as a composite: a hidden native <input> plus a visible control element carrying data-part="control". The native <input> is not interactable — clicking it (or the row's checkbox role) is a silent no-op, and you burn snapshot/retry cycles wondering why selection never toggles. Click the visible control part instead, via browser_evaluate:

mcp__playwright__browser_evaluate(function=`() => {
  // the visible control toggles selection; the native <input> does not
  document.querySelector('table tbody tr:has(a:text("<name>")) [data-part="control"]').click();
  // select-all lives in the header row:
  // document.querySelector('table thead [data-part="control"]').click();
}`)

This mirrors the e2e Page Objects, which click [data-part="control"] (see apps/e2e-tests/src/infra/pages/StandardsPage.ts). Select/combobox options follow the same idea — pick [data-part="item"] filtered by text.

Waiting on a Chakra dialog/drawer — target the overlay or the toast, never an inner button

After opening or submitting a dialog, do not wait_for an inner button's disabled/hidden state — a condition that never settles burns the full 30s timeout. zag-js carries the open state as data-state on the same node as role="dialog", so:

  • Wait for OPEN with one combined selector (a descendant search finds nothing): [role="dialog"][data-state="open"].
  • Wait for the action to FINISH on the success-toast text the app actually renders (e.g. moved to the selected space), or on the dialog node detaching — not on the button you clicked.

Same patterns the e2e POMs use: AbstractPackmindAppPage.ts for the drawer-open wait, StandardsPage.ts / SkillsPage.ts for toast waits.

When a click doesn't propagate (drag-drop and friends)

The Playwright browser_drag tool dispatches mouse events but does not reliably trigger libraries that use pointer-event sensors (e.g. @dnd-kit's PointerSensor). If you see the drag "land" on the wrong drop zone or no state change happens, don't fight it. Switch to driving state via the app's API:

mcp__playwright__browser_evaluate(function=`async () => {
  // fetch + PATCH the relevant endpoint
  // then location.reload()
}`)

Then reinject the cursor. The video still shows the UI visually changing — viewers don't see the difference.

Screenshots

Create the target dir before the first capture. browser_take_screenshot throws ENOENT if the directory in filePath doesn't exist — it will not create it. Run mkdir -p screenshots once up front:

bash
mkdir -p screenshots

Use browser_take_screenshot(filePath=..., fullPage=true) for full-page PNGs at moments worth capturing as stills. Number them (01-initial.png, 02-typing.png, ...) so they sort correctly. Save them under screenshots/ inside the project.

Wait for real content before the first screenshot. The Packmind frontend is an SPA — on first paint it shows only a loading spinner, so a screenshot taken right after browser_navigate captures the spinner, not the page. Always browser_wait_for(text=<a label the loaded page renders>) (e.g. the sign-in heading) before capturing, exactly as for the video steps above.

If the project doesn't have ffmpeg/ImageMagick installed and you also want a GIF or MP4 from a sequence of stills (for an environment that can't play WebM), use a Python venv with Pillow (GIF) or imageio-ffmpeg (MP4) — both ship a usable binary so brew install isn't needed.

Stopping and packaging

mcp__playwright__browser_stop_video()
mcp__playwright__browser_close()

The WebM lands at the project root, not in videos/, regardless of what --output-dir says. Move it:

bash
mv ./<name>.webm ./videos/

Stop the app stack you started in the background (docker compose down) — don't leave it running. See the michel-run-local-dev-stack skill for the teardown rationale.

Post-processing: trim dead air and speed up

Playwright recordings run at real-time wall-clock speed, which includes MCP round-trip latency. The raw WebM always feels sluggish. Always apply both steps after moving the file:

Step 1: check raw duration and choose speed multiplier
bash
ffprobe -v quiet -show_entries format=duration -of csv=p=0 ./videos/<name>.webm

Pick multiplier based on raw duration:

  • < 60s → 2× (setpts=0.5*PTS)
  • 60–120s → 3× (setpts=0.333*PTS)
  • 120s → 4× (setpts=0.25*PTS)

Step 2: trim lead-in and apply speedup in one pass
bash
# Replace SETPTS with the value chosen above (0.5, 0.333, or 0.25)
ffmpeg -i ./videos/<name>.webm \
  -vf "setpts=SETPTS*PTS,fps=30" \
  -an -c:v libvpx-vp9 -b:v 2M \
  -ss 5 \
  ./videos/<name>-fast.webm && mv ./videos/<name>-fast.webm ./videos/<name>.webm

-ss 5 (placed after -i, output-side) trims the first 5 seconds (blank lead-in) accurately. -an drops the silent audio track.

The fps=30 and explicit -c:v libvpx-vp9 -b:v 2M are mandatory — do not drop them. Playwright's recorder is a CDP screencast: a variable-frame-rate stream that emits a frame only when the page changes, leaving large gaps between PTS timestamps. Applying setpts alone and re-encoding with libvpx defaults keeps those sparse frames, producing a file that reports the full duration in its container metadata but holds almost no real frames — it plays for a few seconds then freezes or won't play at all. (Symptom seen in the wild: a 169s source produced a 256 KB "fast" file that still claimed 169s duration but only showed ~4s of content.) The fps=30 filter resamples the VFR stream to a constant 30 fps, regenerating real frames across the whole timeline; the explicit codec + bitrate guarantee a clean VP9 re-encode instead of a degenerate passthrough.

Verify the output before shipping it. A correct fast file's duration ≈ source duration × SETPTS, and its size is a meaningful fraction of the source. Check:

bash
ffprobe -v error -show_entries format=duration,size -of default=noprint_wrappers=1 ./videos/<name>.webm

If the duration didn't shrink by the multiplier, or the file collapsed to a tiny fraction of the source (e.g. 6 MB source → 256 KB output), the re-encode is corrupt — do not hand it over. Re-run with fps=30 present.

If the recording has a real audio track (rare for app demos), remove -an and add -af "atempo=2.0" (only valid for 2×; higher speedups need chained atempo filters).

Why the subtitle minHold is 2500ms: at 2× speed that becomes ≈1.25 s on screen — long enough to read a short caption comfortably. At 3× it becomes ≈0.83 s — still readable for short captions. Do not lower minHold below 2000ms or subtitles will flash too fast after the speed-up. If you skip the ffmpeg step, the default hold will make subtitles feel unnecessarily long — only skip if ffmpeg is not available.

What "good" looks like

  • Resolution 1440×900 or larger
  • Smooth cursor that's clearly visible against the app's UI — present the whole time (no bare frames)
  • A subtitle caption visible for every step, repositioned so it never masks the active UI
  • 3–6 chapter cards (intro, 2–4 sections, outro)
  • ~15–60 seconds total for focused flows (single feature/action); 60–90 s for multi-section walkthroughs — anything longer should be split or sped up more aggressively
  • File size 4–15 MB for typical demos; if it's bigger, the run was too long
  • No console errors visible in the video (close DevTools if it was open)

What to tell the user at the end

Hand over:

  • The path of the final WebM
  • The path of the <basename>.scenario.md sidecar (the written scenario that lands under the video link in the PR)
  • A one-line summary of the chapters
  • Anything that was faked (e.g. "drag-drop was driven via the API because dnd-kit doesn't respond to MCP drag synth")
  • A reminder that WebM may need a modern browser or VLC to play; offer to also produce an MP4 if the client uses a tool that doesn't accept WebM

Reference material

  • scripts/inject-cursor.js — paste-ready cursor overlay
  • scripts/inject-subtitles.js — paste-ready subtitle bar with __setSubtitle / __moveSubtitle helpers
  • scripts/check-mcp-config.sh — one-liner to confirm --caps=devtools is set
  • references/playwright-mcp-tools.md — table of the relevant tools and their gotchas

© PackmindHub, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 4 other files (scripts, references) in scripts/michel/michel-ui-demo-recorder of PackmindHub/packmind.

  • SKILL.md
  • references/playwright-mcp-tools.md
  • scripts/check-mcp-config.sh
  • scripts/inject-cursor.js
  • scripts/inject-subtitles.js

Open the folder on GitHubat commit 67de8a2

Compare with similar skills

Michel UI Demo Recorder next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Michel UI Demo Recorder compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Michel UI Demo Recorder this skillPackmindHub/packmind318—~6.4kAutomated safety check: PassApache-2.0
Project Docs Maintainerswimmwatch/cloakbrowser-mcp164—~569Automated safety check: PassMIT
Browser Video Recordingnirholas/three.ws2301 repos~1.5kAutomated safety check: PassApache-2.0
Sprint Summaryshopsys/shopsys350—~7kAutomated safety check: PassCustom licence
Cassette Video EditCassette-Editor/oh-my-cassette1191 repos~3.4kAutomated safety check: PassMIT
KinocutKyaniteLabs/kinocut198—~5.7kAutomated safety check: PassApache-2.0

Similar skills

  • Project Docs Maintainer

    swimmwatch/cloakbrowser-mcp

    Maintain, organize, consolidate, or audit the cloakbrowser-mcp documentation set only when the user explicitly requests project documentation maintenance or an authorized public change requires it.

    164 GitHub stars~569 tokensUpdated 2 days ago
    DevelopmentAuto-check passed
  • Browser Video Recording

    nirholas/three.ws

    Create polished 60 fps 4:3 4K browser screen-recording style videos from Codex in-app browser captures, with browser-only crop, natural macOS cursor styling, deliberate click choreography…

    230 GitHub starsUsed in 1 repo~1.5k tokens
    Media & CreativeAuto-check passed
  • Sprint Summary

    shopsys/shopsys

    Generates a Czech sprint summary article from Jira sprint data (Jira MCP first, CSV fallback), publishes it to Confluence with embedded screenshots and MP4 clips, and records the clips with…

    350 GitHub stars~7k tokensUpdated yesterday
    Media & CreativeAuto-check passed
  • Cassette Video Edit

    Cassette-Editor/oh-my-cassette

    Edit, trim, cut, caption, subtitle, reframe, combine, add background music to, or export video, audio, and image files through Cassette.

    119 GitHub starsUsed in 1 repo~3.4k tokens
    Media & CreativeAuto-check passed
  • Kinocut

    KyaniteLabs/kinocut

    Use Kinocut for guarded video editing, source-backed planning, FFmpeg operations, media analysis, subtitles, audio workflows, Hyperframes or Revideo rendering, repurposing packages, and release…

    198 GitHub stars~5.7k tokensUpdated 2 days ago
    Media & CreativeAuto-check passed
  • Kinocut Repurpose

    KyaniteLabs/kinocut

    Use the current Kinocut tools to turn one local video path into a short platform-ready clip package with manifests, review artifacts, and human approval gates.

    198 GitHub stars~1k tokensUpdated 2 days ago
    Media & CreativeAuto-check passed

More from PackmindHub/packmind

All 35 skills in this repo
  • Michel CLI Demo Recorder

    PackmindHub/packmind

    Produce proof-of-execution demos of the Packmind CLI (packmind-cli) as terminal-styled images (colors and formatting preserved exactly), for embedding in a GitHub PR.

    318 GitHub stars~3.4k tokensUpdated 2 days ago
    Auto-check passed
  • Packmind Create Skill

    PackmindHub/packmind

    Guide for creating effective skills. An agent skill from PackmindHub/packmind.

    318 GitHub stars~3.5k tokensUpdated 2 days ago
    Auto-check: notes
  • Doc Audit

    PackmindHub/packmind

    Audit Packmind end-user documentation (apps/doc/) for broken links, outdated CLI references, non-existent concepts, misleading information, and missing coverage.

    318 GitHub stars~2.4k tokensUpdated 2 days ago
    Auto-check passed
  • Feature Sprint

    PackmindHub/packmind

    Execute the implementation plan produced by /feature-spec. An agent skill from PackmindHub/packmind.

    318 GitHub stars~1.9k tokensUpdated 2 days ago
    Auto-check passed
  • Review an implemented GitHub issue the way a senior Packmind engineer would — the human-judgment checks that ESLint, the TypeScript compiler, and e2e tests cannot catch (authorization scoping…

    318 GitHub stars~2.7k tokensUpdated 2 days ago
    Auto-check passed
  • Packmind Update Playbook

    PackmindHub/packmind

    A skill your agent uses when updating, adding, fixing, changing, or deprecating Packmind playbook artifacts (standards, commands, skills).

    318 GitHub stars~1.9k tokensUpdated 2 days ago
    Auto-check passed

Questions about Michel UI Demo Recorder

What does Michel UI Demo Recorder do?

Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros. Michel UI Demo Recorder is an agent skill from PackmindHub/packmind. Record polished UI demo videos and screenshots of a running web app using Playwright MCP — for client deliverables, release notes, feature walkthroughs, or bug repros.

When should I use Michel UI Demo Recorder?

Michel UI Demo Recorder fits situations like: asks to record a demo; create a screencast; make a UI walkthrough video; document this feature with video.

How do I install Michel UI Demo Recorder in Claude Code?

Run `npx skills add PackmindHub/packmind --skill michel-ui-demo-recorder -a claude-code`. Or copy the skill folder (scripts/michel/michel-ui-demo-recorder in PackmindHub/packmind) into .claude/skills/michel-ui-demo-recorder in your project. Claude Code loads it when a task matches its description.

How do I install Michel UI Demo Recorder in Codex?

Run `npx skills add PackmindHub/packmind --skill michel-ui-demo-recorder -a codex`. Or copy the skill folder (scripts/michel/michel-ui-demo-recorder in PackmindHub/packmind) into .agents/skills/michel-ui-demo-recorder in your project. Codex loads it when a task matches its description.

Can I use Michel UI Demo Recorder in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add PackmindHub/packmind --skill michel-ui-demo-recorder -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/michel-ui-demo-recorder, .gemini/skills/michel-ui-demo-recorder, .github/skills/michel-ui-demo-recorder and .opencode/skills/michel-ui-demo-recorder in your project.

What does Michel UI Demo Recorder need to run?

Going by SKILL.md and its folder, Michel UI Demo Recorder needs JavaScript and a shell for the scripts in its folder and the command-line tools its instructions call (docker, ffprobe, bash and ffmpeg). Our summary lists: Node.js; A Bash shell; Docker.

Does Michel UI Demo Recorder access the network?

SKILL.md contains no URLs. Its commands use docker, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Michel UI Demo Recorder safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Michel UI Demo Recorder use?

Michel UI Demo Recorder is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Michel UI Demo Recorder use?

About 6.4k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 2.4k tokens, read only when the agent opens those files.

What are the alternatives to Michel UI Demo Recorder?

Skills that share tags, products or a category with Michel UI Demo Recorder: Project Docs Maintainer (swimmwatch/cloakbrowser-mcp, 164 stars), Browser Video Recording (nirholas/three.ws, 230 stars), Sprint Summary (shopsys/shopsys, 350 stars) and Cassette Video Edit (Cassette-Editor/oh-my-cassette, 119 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Michel UI Demo Recorder?

PackmindHub (a GitHub organization) maintains it in PackmindHub/packmind, which has 318 GitHub stars. The repository holds 35 skills in this directory. The repository was last updated on October 9, 2026.

Source: PackmindHub/packmind on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.