vesely/skills · Archived

screencast

>- Turn an agent-browser session into a polished product-demo video — fully local, free, no cloud upload. Records the browser to WebM while logging every action, then composites an MP4 with automatic zoom-to-click, a smooth animated cursor with trail, click ripples, a keystroke overlay (keycast), idle trimming, chapters (burned-in lower-thirds plus embedded MP4 chapters), and a gradient background with a rounded, shadowed browser card. Use this whenever the user wants to record or generate a de…

First seen Aug 11, 2026

Installation

$ npx skills add vesely/skills --skill screencast

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Also in this package

Other skills from vesely/skills · top by installs.

npx skills add vesely/skills

Browse all from vesely/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 27
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 14,967 B
  • docs SUMMARY.md 1,042 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

screencast

Produce a polished product-demo video from a browser flow that the agent drives with agent-browser. The agent performs the demo (clicks, typing, navigation); this skill records it and post-processes the raw capture into a cinematic MP4 — locally, with no paid tool and no upload.

What it renders

  • Punch-in / pull-back zoom + pan — the camera zooms to an element only while something happens

there, then pulls straight back to 1x so the whole app, and what the action just changed, stays visible between beats. Element-fit sizing (small controls get a closer zoom, wide ones barely any), pan clamped so the page always fills the card. Back-to-back actions keep the zoom instead of strobing once per click

  • Animated cursor with a subtle trail and a click "pop" (synthesized — the raw capture has no cursor)
  • Click ripples on every click
  • Keystroke overlay (keycast) showing typed text and pressed keys
  • Element highlight — spotlight (dim everything else) or a glowing ring around an element
  • Annotations — labelled callouts with a connector pointing at an element or point
  • Idle trimming — dead time freezes then hard-cuts, never fast-forwards
  • Chapters — on-screen lower-thirds and real MP4 chapter markers
  • Framing — gradient + accent glows, rounded corners, layered shadow, padding, intro/outro fades

Requirements

  • agent-browser on PATH with its browser binaries installed (agent-browser install, plus

--with-deps on Linux). The browser is driven and recorded through it.

  • ffmpeg + ffprobe on PATH
  • Node 24+ — the renderer itself only needs 18+, but current agent-browser declares

engines.node >= 24, so that is the real floor. It auto-installs @napi-rs/canvas (a prebuilt binary, no compilation) into the skill directory on first run, at the exact version package.json pins, so the renderer never moves onto an untested build behind your back. To upgrade it, change that version and delete node_modules/.

Rendering is entirely local. The capture is whatever agent-browser is configured to drive, so "no cloud" holds for a local browser; a remote/cloud provider would host the page itself elsewhere.

How to use it

Invoke the wrapper instead of agent-browser for the actions you want in the video. It is a thin passthrough: anything it does not handle itself runs unchanged. Three caveats worth knowing — start, stop, render, chapter, highlight and note are the wrapper's own commands (and highlight shadows the upstream one of that name); only the actions listed under Commands below produce timeline events; and --private is consumed before the call rather than forwarded.

Run it as node <SKILL_DIR>/bin/screencast.mjs … (or symlink that file onto your PATH as screencast).

# 1. Start: sets a fixed 1280x720 viewport, starts recording, drops a sync flash.
screencast start demo https://app.example.com/login

# 2. Explore normally (not logged as an action):
screencast snapshot -i

# 3. Drive the demo. Mark chapters as you go; run actions through the wrapper:
screencast chapter "Sign in"
screencast type @e3 "[email protected]"
screencast type @e4 --private "$DEMO_PASSWORD"   # kept out of the log and the keycast
screencast press Enter

screencast chapter "Create a project"
screencast click @e12
screencast type @e15 "My first project"
screencast click @e18

# 4. Stop → renders ./demo.mp4
screencast stop

Output: ./<name>.mp4 in the current directory (1920×1080, 30 fps, H.264, with chapters).

Commands

Command Purpose
screencast start <name> [url] Set viewport, start recording, inject the sync flash
screencast <agent-browser cmd…> Run any agent-browser command; actions are logged
screencast chapter "<title>" Mark a chapter at the current moment
screencast highlight @ref [dur] Spotlight (or ring) an element and zoom to it
screencast note "<text>" @ref [dur] Point a labelled callout at an element or point
screencast stop Stop recording and render <name>.mp4
screencast render [name] Re-render from an existing take (fast iteration on styling)

Logged actions: click, dblclick, type, fill, press, keyboard, hover, check, uncheck, select. Everything else (snapshot, get, wait, screenshot, open, …) passes straight through without adding an event.

Secrets

Typed text is written to the event log and drawn into the keystroke overlay, so anything you type lands in the shipped MP4. Two things guard against that:

  • Fields that declare themselves (<input type="password">) are redacted automatically.
  • --private forces redaction on type, fill and keyboard, for fields that do not — API keys,

tokens, one-time codes, a password rendered as a plain text input.

Redacted actions still show a chip (••••••••) so the demo reads correctly; only the value is dropped, and it is never written to .screencast/<name>/events.jsonl in the first place. The flag is consumed by the wrapper and never reaches agent-browser. If the type of a field cannot be read, the value is redacted anyway and a warning is logged — the wrapper does not guess when a leak is the cost of guessing wrong.

What it cannot do: the recording is a video of the page, so --private only keeps a value out of the event log and the keystroke overlay. It cannot un-draw what the browser itself renders. A password field shows dots and is therefore safe; a token typed into a normal text input is on screen in the video regardless of the flag. For those, either use a field the browser masks, or do not type the real value on camera.

To type a literal --private, put it after --: screencast type @e1 -- --private.

Redaction covers the recording, not your machine: the expanded value is still an argument to the wrapper and to agent-browser, so it is visible in the process list while the command runs. Passing it as "$DEMO_PASSWORD" keeps the secret itself out of shell history (the history holds the variable name), which is why the examples above do that — prefer an environment variable or a credential store over pasting the literal.

Highlights & annotations

Both hold the element on screen for their duration automatically (they issue a matching wait), so the recording actually contains the moment you are pointing at.

# Spotlight: dim everything except the element, glowing outline, zoom in (default 2.5s)
screencast highlight @e9
screencast highlight @e9 3 --mode ring   # just a glowing outline, no dimming
screencast highlight @e9 --no-zoom        # keep the current framing

# Note: a labelled callout with a connector pointing at the element (default 3s)
screencast note "Pick your plan here" @e12
screencast note "This updates live" --at 640,360 4   # anchor at a viewport point
screencast note "Read this first" @e5 --side top --zoom

--mode spotlight|ring, --no-zoom, --side auto|top|bottom|left|right, --zoom (needs an element target — a point anchored with --at has no box to frame), --at x,y (viewport CSS pixels). Highlights and notes are frame-local: anchor a fresh one after the page scrolls or navigates rather than expecting one to track across a reflow.

Tips for good-looking demos

  • Leave ~1 second between actions (e.g. screencast wait 900) so the cursor travel and zoom have

time to animate. Back-to-back actions look rushed.

  • Leave ~3 seconds after an action whose result matters — that is what buys the pull-back to 1x

where the viewer actually sees the row move, the count change, the panel appear. With the default timings the next action has to be at least 2.85 s later (zoomHold + zoomOutTime + zoomInTime + zoomRestMin) or the camera stays pushed in and the result happens off-frame.

  • One chapter per logical step. The title appears as a lower-third and as an MP4 chapter.
  • Long pauses are fine — idle trimming removes them. You don't have to rush.
  • Scroll the subject into view before recording if the page opens with a tall hero or upload

zone above it. The camera only frames what the viewport holds, so a demo of a table two viewport heights below the fold otherwise spends its whole runtime showing an empty dropzone.

  • Auth: record start opens a fresh context and may or may not carry the existing session. Check

it (screencast get url, or grep the page for a logged-in marker) right after start; if it landed logged out, sign in inside the recording with --private on the password field. Idle trimming will not remove the login: typing is activity, so it is kept. Plan for it to be in the video, or re-record with a session that is already signed in. Do not assume a login done before start survives.

Troubleshooting

  • Clicks report ✓ Done but nothing happens. agent-browser reports the dispatch, not the

effect, so a click on a stale ref still exits 0. Refs go stale whenever the page re-renders, which includes the wait that highlight and note issue. Re-snapshot and click again with the fresh ref. Verify state rather than trusting the exit code: screencast get text ..., or an eval that reads the property the click was supposed to change. If several clicks in a row no-op, the take is not recoverable — stop, discard it, and start over.

  • Overlays drift out of sync with the video. The render logged `no flash found (wall-clock

fallback): the sync flash was missed, usually because the recorder started on about:blank (navigation raced start`), so events are aligned on wall-clock instead. It renders, but timing is approximate. Discard the take and restart with the page already open.

  • Two renders of the same take at once. Not supported: they share the extracted-frame directory

and would delete frames from under each other. Render takes one at a time.

  • Framing is wrong but the actions were fine. Do not re-record. Framing, zoom, trimming and all

cosmetics are render-time: edit CONFIG in lib/util.mjs and re-run screencast render <name>. captureScale and viewport are the exceptions — both shape the capture itself, so changing either needs a fresh recording.

How it works (internals)

  1. Captureagent-browser record start streams the page to raw.webm (CDP screencast, ~10 fps, no

cursor). The viewport is set with a deviceScaleFactor (CONFIG.captureScale, default 2), so a 1280×720 layout is captured at 2560×1440; the compositor detects that ratio and supersamples, keeping text crisp after framing and zoom instead of upscaling a 720p source. Each wrapped action is timestamped; click/type targets are resolved to coordinates via agent-browser get box. A one-shot full-viewport colour flash is injected at start to align the event log with the video timeline.

  1. Timeline (lib/timeline.mjs) — events are converted to video-time (anchored on the detected flash

frame), then turned into zoom/pan keyframes, a cursor path, ripples, key chips, chapters, and an idle-trim remap between source-time and output-time. A focus point normally gets a matching rest keyframe back at 1x (zoomHold after the action, or the end of the effect, whichever is later), which is what produces the punch-in/pull-back rhythm — a keyframe holds until the next one, so focus points alone would park the camera at high zoom for the entire take. The rest is dropped when the next action leaves no room for it (zoomRestMin) or when it would land past the end of the recording, which is what keeps rapid sequences from strobing. Rests carry their own keep-intervals so the trimmer cannot cut the beat that shows the result.

  1. Composite (lib/render.mjs + lib/draw.mjs) — frames are extracted with ffmpeg; every output

frame is drawn in @napi-rs/canvas (background, zoomed/rounded/shadowed card, cursor, trail, ripple, keystroke chip, chapter) and streamed as raw RGBA to ffmpeg, which encodes H.264 and muxes the chapters. All text is drawn in canvas (the local ffmpeg has no drawtext).

Tuning

All cosmetics live in CONFIG at the top of lib/util.mjs: captureScale (retina capture factor — drop to 1 only if a headless browser renders black at 2×), output size and fps, gradient colours, zoom, transition timing, cursor size/trail, ripple, chip and chapter durations, and the idle-trim thresholds. Edit and re-run screencast render <name> to preview without re-recording (styling, trim and framing are all render-time; only captureScale and viewport need a fresh recording).

The zoom knobs are the ones worth knowing:

Key Default Effect
zoomTargetFrac 0.34 how much of the frame the focused element fills — lower = wider, more context
zoomMax 1.7 ceiling. Keep it modest; past ~1.8 a demo shows a control, not a product
zoomMin 1.0 1.0 lets an already-large element get no zoom at all
zoomHold 0.85 seconds held in close after the action before pulling back to 1x (a highlight/note holds until its effect ends instead, if that is later)
zoomRestMin 0.55 minimum calm at 1x for a pull-back to be worth it; under this the camera stays in, which is what stops rapid clicks from strobing

To make the camera calmer still, lower zoomTargetFrac and zoomMax together. To make it hold on each action longer before pulling back, raise zoomHold.

Environment overrides: SCREENCASTFPS=60 (silkier overlay motion, ~2× render time), SCREENCASTSESSION (dedicated agent-browser session), SCREENCASTKEEP=1 (keep extracted source frames for debugging), SCREENCASTAB_BIN (path to the agent-browser binary).