| name | demo-videos |
| description | Produce the two narrated demo videos for a scope of work — a five-minute highlights cut and a full-length walkthrough — filmed from a screenplay against the seeded product. Use when the user invokes /demo-videos, asks for a demo video, demo videos, a screencast, a product walkthrough video, a narrated demo, video narration, a recording of what shipped, or a video to show stakeholders what a project delivered. |
Demo videos
Every scope of work gets two videos, and they are made together from one source project:
- Highlights, about five minutes. The story: the struggling moment, the gesture that answers it, the outcome. One arc. Everything the arc does not need is cut.
- The full-length demo. A systematic tour of every functionality in scope, in the order someone learning it would walk it. It is as long as the scope is — twenty minutes, forty, whatever the work was.
They ship together, from one demo-videos/<slug>/ project. Neither is delivered alone: the highlights cut is what gets watched, and the full cut is what gets consulted when someone wants the part the story skipped.
The audience is internal stakeholders learning the scope and giving feedback. Not prospects, not the market, not a launch. Someone who was not in the room watches this to understand what we built and to tell us where it is wrong.
The machinery is built and documented — the rendering rig and the narrator are deterministic code owned by the workflow package, generated into this skill's gitignored scripts/ tree and regenerated by every sync, so a missing script means the install has not run in this checkout. Read scripts/rig/README.md for the screenplay format, the moves, how sync is measured and how retakes work, and scripts/README.md for the narrator, the deliveries and the transcribe check. This skill is the part that is not machinery: how a scope becomes a screenplay, how a screenplay sounds, and what has to be true before a video is handed to anyone.
Everything runs on Apple Silicon with ffmpeg on PATH. The narrator's model weights are never in the workflow package and never fetched by its install: a setup step of its own fetches them — about three gigabytes — into a local, gitignored cache, with the narration voice pinned by checksum so every machine speaks with the same voice. Once per machine:
.claude/skills/demo-videos/scripts/setup.sh
The doctor subcommand checks the toolchain and the weights, so a machine that never ran setup surfaces there, not mid-render.
1. Settle the scope
There are two ways in, and the first question is always which one you are in.
Pointed at a shaping document
A shaping document under shaping/<slug>/index.html names a project. The scope of the videos is what actually shipped during that project, which is not the same thing as what the document proposed. Read all three:
- The merged PRs.
gh pr list --state merged --search '<terms>' --limit 50, and the merge commits on main between the project's first and last. This is the ground truth for what exists.
- The project's documentation, where it keeps any — its docs coverage manifest and the articles the project added. A surface that shipped documented is a surface with a walkthrough already written for it.
- The document itself.
A demo of something that was shaped and then scope-hammered out is a lie, and it is the easiest lie to tell — the document is the most readable source and the least accurate one. When the PRs and the document disagree, the PRs win and the difference is worth narrating: "the document also called for X; we did not build it, and here is why" is exactly the kind of sentence this audience wants.
The document is the storytelling source. Its framing is where the highlights cut gets its opening: the struggling moment it names, in the words it names it in; the forces that made people put up with the old way; the fat marker sketch that shows the gesture. The full cut is organised by what shipped; the highlights cut is organised by what the document said the problem was.
Pointed at anything vaguer
"Make a demo of the inventory stuff" is not a scope. Interview until scope and narrative are settled — one question per message, and wait for the answer before the next one. Never bundle two questions, however related they are. Inside a /goal loop, ask through questionnaire questions rather than conversation, because that is the only form that stops the goal checker.
Keep going until you can answer all of these, and stop asking once you can:
- What exactly is in scope, and what is deliberately out?
- Who is watching, and what do they already know?
- What was hard before this shipped — the struggling moment, in their words?
- What is the one gesture that answers it?
- What changed as a result?
- What is rough, unfinished or provisional?
- What feedback would actually help?
- What language? (See the language rule below — ask only if it is genuinely unsettled.)
Write the answers down as you get them. They are the scope record, and quoting the user's own words back in the narration is most of what makes a demo sound like a colleague.
The scope record
demo-videos/<slug>/SCOPE.md holds what is in scope, where each part of it came from (documents, PR numbers, interview answers), the narrative decisions and their reasons, and what is deliberately out with the reason it is out. Someone re-rendering this video in a year reads this file to know why the highlights cut opens where it opens.
2. The language rule
The language explicitly asked for. Else the language the request was written in. Else English.
Settle it before writing a word of narration. It goes in the screenplay's language as an ISO 639-1 code and both cuts of a project speak it. The narrator speaks 23 languages and clones the same voice across all of them, so a Portuguese video sounds like the same colleague as an English one. Note the model exposes pt, not pt-BR.
3. Voice — a colleague demoing, never marketing
This is the part of the skill that has no machinery behind it, and the part a reviewer will notice first.
- Product names, not superlatives. Say what the thing is called and what it does. Never "seamless", "powerful", "effortless", "intuitive", "at a glance". Never sell a benefit the viewer can see for themselves on screen.
- The framing's own "why". The reason this exists is the one the shaping document or the interview gave, in their language, not a reason invented to make the feature sound necessary.
- Rough edges acknowledged, not hidden. Say what is not built yet, what is slow, what you would do differently, where a screen is provisional. A demo that admits nothing is a demo nobody trusts, and this audience is being asked for feedback — hiding the seams is hiding the question.
- Every video ends by naming what feedback would help. Specifically: not "let me know what you think" but the two or three rulings only a person can make. That sentence is the screenplay's
closingAsk, and the rig refuses to render a cut whose last scene does not actually say it.
Two more that follow from the same place: never narrate something the video does not show — the old world a contrast reaches back to is the one exception, and it stays a sentence on the narration track — and never narrate a screen the viewer is not looking at yet. For any term the product itself displays, use the product's own word — never a synonym the screen does not show.
4. Write the screenplays
A screenplay is a TypeScript module under demo-videos/<slug>/, named by its cut: highlights.ts and full.ts. The types are in scripts/rig/screenplay.ts and the format is documented in the rig README. Read both before writing one.
Screenplay first, always. Numbered scenes, each carrying its narration text, its browser choreography, and the demo state it needs. Nothing gets filmed that was not written down first, because the screenplay is what a retake re-runs and what a refresh a year from now re-runs.
Narration is written before choreography. Write the sentence a colleague would say, then decide what the cursor is doing while it is said, then attach each move to the fragment of the sentence it belongs to with cue. Doing it the other way round produces a click tour with a voice-over.
The narration alternates between the current world and the one it replaced. The contrast is why the audience is watching, and it does not survive being used once: open on it — "now, instead of doing X, we do Y" — then let the next stretch live entirely in the current world, and bring the old world back at each moment that matters, with a transition written for the return rather than the same comparison pasted in again. A cut that draws the contrast in every scene sounds like an advert; a cut where the contrast never returns after the opening is a feature tour with no stakes. The old way is always a sentence on the narration track, never a re-enactment on screen.
Choreography is written in narrative order, never QA order. The rig is not a test. It goes where the story goes, and it revisits a screen if the story returns to it. A move exists because a sentence needs something pointed at, not because a flow needs covering.
A scene is the retake unit. One scene, one idea, one screen where possible — short enough that re-speaking it costs a minute of compute rather than ten. Number the ids and keep them stable, because they name the scene's footage, narration and cut on disk: scene-01-the-problem, scene-02-….
Pick each scene's delivery. colleague at 160 words per minute is the default and most scenes want it. precise at 148 is for a passage dense with numbers, where the viewer is reading a figure off the screen while hearing it. engaged at 172 suits the highlights cut's opening and closing, where the story is moving rather than explaining. One cut can mix all three; a cut where every scene is engaged sounds like an advert.
Spell alphanumeric identifiers out phonetically in the narration — "revision vee seventeen", never V17. This is the narrator's weakest point in every language and the rig README has the measurements. The transcribe check will report the spelled form against the printed one as a difference; that is the expected shape, not a retake.
needs is a scene's contract with the demo state. Write it in prose, and write it for a person reading DEMO-STATE.md beside it. Nothing verifies it, so an empty list is a claim that the seed already provides everything.
The two cuts share the scope, the demo state, the production config and the narrator — and nothing else. Narration is written twice. A sentence lifted from the highlights cut into the full cut arrives with the wrong altitude, because the two are answering different questions.
Budget the compute before you start: roughly 90 seconds of speech generation per minute of narration. A five-minute highlights cut is about eight minutes of narration compute on top of the filming; a thirty-minute full cut is about forty-five. Retakes reuse any take whose words did not change, so the second run is always the cheap one.
5. Prepare the demo state
Film against a fresh worktree with a freshly seeded database — provision the lane with the workflow's provisioning, then build and serve the product from that same worktree over plain HTTP; the exact serve command and port discipline are project facts, carried in the project specifics below. The rig reads DATABASE_URL from the worktree's .env to mint the actor's session, so the rig and the server must be the same checkout pointed at the same database. Serve the real build: a dev server that compiles a route the first time it is asked for puts that wait in the middle of a take — list every route a scene walks into under its warm if you film one.
Every row a scene needs comes from the project's dev seed run on an empty database. Never hand-craft demo state: it cannot be reproduced, so the video cannot be re-rendered. If a scene wants state the seed does not have, the seed is what to change — and a seed change ships under the normal rule, with its entry in the committed seed manifest. The seed is single-shot and refuses a populated database; to reseed, drop the database and build it again.
Record the product commit. production.ts carries productCommit, and DEMO-STATE.md carries the same value with the commands that reproduce the state. That commit is what an exact reproduction checks out.
demo-videos/<slug>/DEMO-STATE.md names the product commit, the database recipe, the actor (userEmail and companyName, both of which must exist in that database), what each scene needs, and the server command. The rig README documents the shape to copy.
6. Film it
PORT=<port> .claude/skills/demo-videos/scripts/demo-video <slug> highlights
PORT=<port> .claude/skills/demo-videos/scripts/demo-video <slug> full
The rig speaks each scene, warms its routes, performs the choreography at human pace, finds the frame the clapper ends on, cuts the footage to the narration and assembles the cut. Everything it makes lands under .claude/skills/demo-videos/out/<slug>/<cut>/, and every assembly also copies the finished video beside its screenplay, as demo-videos/<slug>/<YYYY-MM-DD>-<slug>-<cut>.mp4 — demo-videos/order-tracking/2026-08-18-order-tracking-highlights.mp4. That copy is the one to hand over.
Retakes, all documented in the rig README (same launcher, abbreviated here):
demo-video <slug> <cut> --scene scene-04-reports
demo-video <slug> <cut> --assemble
demo-video <slug> <cut> --refresh-narration
demo-video <slug> <cut> --base-url http://…
Two things the run tells you that are yours to act on, not the rig's:
- Cue drift in
report.json. Anything past about half a second means the choreography cannot keep up with its own narration — three things named in one breath with a second and a half of travel between them. Rewrite the screenplay, not the rig: lengthen the sentence or drop a move.
- A clamped take. The narrator read so far off the delivery's target that the retimer could not pull it back, and next to an unclamped scene the pace change is audible. Shorten or lengthen the line, or pin a
seed on that scene.
7. The self-review — the one gate
Nothing else in this flow is gated. Nobody approves a scope before it is written down and nobody signs off a screenplay before it is filmed. This review is the gate, it is yours, and it runs in full on both cuts before either is handed to anyone.
1. Listen back to every scene.
.claude/skills/demo-videos/scripts/demo-video <slug> <cut> --check
Every scene is transcribed and compared to its line. Read every difference the run prints: an identifier written phonetically coming back spelled is correct; a repeated clause, a dropped clause or a mangled sentence is a retake, not a judgement call.
One failure wears a retake's costume and is not one: a transcript that runs past the script's end into garbled text the narrator never spoke. The transcriber hallucinates continuations over trailing silence. Before retaking a scene for extra words at its tail, check the arithmetic — a WAV whose duration fits the scripted word count at the scene's delivery pace did not speak them — and transcribe the finished cut's final seconds directly; silence coming back as silence settles it.
2. Read the frames as images. .claude/skills/demo-videos/out/<slug>/<cut>/frames/*.jpg, one every four seconds. Actually look at them: the cursor visible and on what the screenplay put it on, the product real and fully rendered, no half-loaded list, no stray overlay, nothing on screen that should not be in a video. A five-minute cut is about seventy frames and you read all of them; a forty-minute cut is six hundred, so read a sample spread evenly across it plus every frame of any scene the check or the drift already flagged.
3. Read report.json for the drift on every move, and for the scene lengths you need next.
4. Verify audio against video, directly, at four checkpoints spread across the cut. Each scene in report.json carries its seconds and a timings entry per move with that move's cue and its scheduledAt inside the scene. A cue's second on the finished timeline is the seconds of every scene before it plus its own scheduledAt. Then:
ffmpeg -ss <t> -t 3 -i <video>.mp4 -vn /tmp/slice.wav
.claude/skills/demo-videos/scripts/transcribe.sh --audio /tmp/slice.wav
ffmpeg -ss <t+1.1> -i <video>.mp4 -frames:v 1 /tmp/frame.png
The audio has to start with the cue's words, and the frame — read as an image — has to show the cursor on what the narrator is naming at that second. Four checkpoints is enough to prove the sync; two is not, because two can both be inside the same scene.
A cut that fails any of the four is retaken and reviewed again. There is no "close enough" here: the whole point of the rig measuring its own sync is that nobody has to watch eighty minutes of video to find a scene that drifted.
8. Source in git, and the two ways to regenerate
Committed, under demo-videos/<slug>/:
demo-videos/<slug>/
production.ts product commit, resolution, pacing, engine and model pin, seed
highlights.ts the five-minute cut
full.ts the full-length cut
SCOPE.md what is in scope, where it came from, the narrative decisions
DEMO-STATE.md the demo state, the actor, and how to reproduce it
Derived, and never committed: rendered videos, per-scene footage, narration WAVs and their transcripts, frames, report.json. All of it lives under .claude/skills/demo-videos/out/, inside the workflow's generated tree, which the workflow keeps effectively gitignored — so nothing derived ever reaches a commit or a lint run. The model weights live in the machine-local cache, and the narrator's virtualenv sits beside the generated scripts, ignored the same way.
The one derived file that lives outside out/ is the finished video, which every assembly also copies beside the screenplay it was filmed from, as demo-videos/<slug>/<YYYY-MM-DD>-<slug>-<cut>.mp4. It is gitignored by /demo-videos/*/*.mp4 — an entry the workflow's ignore manager keeps effective — and never committed; it sits there so the video is where its source is, and it carries the date and the whole project in its name because it gets sent to people who will only ever see the file. Re-rendering the same day replaces that day's video and re-rendering later leaves it alone, so the folder keeps every version anybody was sent. Hand a video over by that path, or upload it wherever the team watches videos. Never by committing it.
Because the source is committed and the output is not, a video can be made again two ways:
- Exact reproduction. Check out the
productCommit in a worktree, seed it, serve it, run the rig. The same words in the same voice over the same choreography — the take fingerprint covers text, language, delivery, voice, engine and seed, and the production pins the model, so a silent model swap fails the run rather than changing the voice halfway through.
- Refresh. Run the same screenplay against current
main. Where the product has moved, a move can no longer find what it points at and the run fails loudly, naming the scene and the move. That failure is the report you wanted: it is the list of places the video went stale. Fix the choreography, fix the narration that described what changed, re-render, review again.
This is why targets are written as role, text, label and placeholder rather than CSS selectors. A selector survives a redesign it should not survive.
9. Starting a project
The rig README carries a sample project — a test card, not a template. Copy its file layout; copy nothing about its narrative, because a real cut opens on the struggling moment its shaping document names, not on a statement about itself.
Film only a scope that has fully landed. Never film a project that is still moving: the choreography is written against screens that change under it, and every take goes stale before the review finishes.
Shipping
One PR per video project, built in an isolated worktree. Gates: pnpm run lint, pnpm run tsc, pnpm run test:unit, and the self-review above passing on both cuts. The rig's and narrator's own test suites live in the workflow package, not in the project's gates — the project ships screenplays, never machinery. The Definition of Done's product-surface criteria do not apply to a video project, which is repository content rather than a product surface. A seed change made for a video is a product change, and it ships under the full rule with its entry in the committed seed manifest.
The PR body is for someone who was not involved: what the two videos show, where the scope came from, what feedback is being asked for, and where each rendered file is — demo-videos/<slug>/<YYYY-MM-DD>-<slug>-<cut>.mp4, stated plainly as a path because the files are not in the repo.
Where lessons go
Project-empirical lessons about this skill land in workflow-content/demo-videos.md through a pull request on the project — never by editing this file, which is regenerated on every upgrade. A lesson that turns out to be true of every project travels as an issue on the workflow package instead.