agentleFS
mrieckmrieck/demoday-claude-plugin/skills/demo-script/SKILL.md

demo-script

Skill12 starsChanged 58 days agoMITView on GitHub

Writing the script and shot list for a demo video, and the demo.json manifest schema. Use when planning what a product video should say and show, writing voiceover narration, structuring scenes, or editing demo.json.

  • Reads credentials

2 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions2 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 2

    1. ChristopherAlphonse/calphonse-skills★ 1same name
    2. burnoutscript/sales-skills★ 0same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository5 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    All paths are relative to the project directory.

    line 128
  2. Distinctive:

    Keep people generic. Named people, logos and brands trip content filters, and the model ladder will burn through several attempts before …

    line 236
  3. Very specific:

    Write all narration into demo.json first.

    line 246

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Writing the script

Narration that works

You are writing for a voice, not a page.

  • Short sentences. One idea each. The TTS will honour a comma; it will not rescue a subordinate clause.
  • Say what the viewer is watching, half a beat before they see it. "You start by creating a project" lands as the cursor moves toward the button.
  • Benefit, then mechanism. "Ship in one click" beats "click the deploy button".
  • No feature lists. Three things well beats nine things fast.
  • Write the hook last. It is easier once you know what the demo actually shows.
  • Spell out anything you want pronounced. "S3" not "S₃"; "dot dev" if it must be said aloud.

Roughly 2.5 words per second. A 60-second video is ~150 words total. That is much less than it sounds — cut accordingly.

Shape of a 60-second demo

BeatTimeJob
Hook0–8sThe problem, felt. Not the product name.
Core action8–24sThe single best thing it does
Context beat24–30sB-roll, breathing room, credibility
Payoff30–50sThe result, and why it matters
Close50–60sOne clear next step

Presenter lines vs voiceover lines

Both use the same voice, but they read differently. A presenter line is addressed to camera ("Let me show you what changed"). A voiceover line describes what is on screen ("Projects appear the moment they're created"). Do not write voiceover for a presenter shot; it sounds like someone reading a manual at you.

The manifest

demo/<slug>/demo.json is the single source of truth — each video keeps its own project folder under demo/ (e.g. demo/project-overview/), and every script reads and writes the manifest inside it. Anything that exists only in your context is lost on the next run.

{
  "version": 1,
  "slug": "acme-dashboard",
  "product": { "name": "Acme", "tagline": "...", "audience": "...", "valueProps": ["..."] },
  "brand":   { "colors": { "primary": "#5B8DEF", "bg": "#0B0D12", "text": "#FFFFFF" },
               "logo": "assets/logo.png", "font": "Inter" },
  "format":  { "width": 1920, "height": 1080, "fps": 30 },
  // Default: a fal.ai stock voice. `provider` may be omitted (means "fal").
  "voice":   { "provider": "fal", "model": "fal-ai/elevenlabs/tts/eleven-v3",
               "voice": "Rachel", "stability": 0.4 },
  // OR a designed character voice (needs ELEVENLABS_API_KEY; written for you by
  // gen/voice-design.mjs — do not invent a voiceId by hand):
  // "voice": { "provider": "elevenlabs", "model": "eleven_v3",
  //            "voiceId": "abc123...", "voiceName": "Captain",
  //            "designPrompt": "weathered lobster-boat captain...", "stability": 0.4 },
  "presenter": { "mode": "hybrid", "description": "a woman in her 30s, smart casual, warm and direct",
                 "characterImage": "assets/presenter.png", "engine": "fal-ai/infinitalk" },
  // In mode "always" (the tutorial style) every plain demo scene gets a
  // lip-synced corner bubble; "pip" configures its look. All fields optional:
  // shape "circle" (default) | "square", position any corner (default
  // bottom-left), sizePct 10-40 (% of frame width, default 22).
  // "presenter": { "mode": "always", "pip": { "shape": "circle", "position": "bottom-left", "sizePct": 22 }, ... },
  // presenter.continuousPip: true (seeded by --style tutorial) makes
  // gen/presenter.mjs --all produce ONE continuous bubble take instead of
  // per-scene clips; it writes presenter.pipTake — segments laid on the
  // absolute timeline ([{ video, audio, fromSec, durationSec, overlapSec,
  // scenes }], one chunk per scene, named after its first scene) that the
  // renderer plays straight through underneath the cuts, cross-dissolving
  // each seam over the previous chunk's overlapSec silent tail. Re-voicing a
  // scene regenerates only its chunk.
  // "zoomToClick": false at the top level turns the click push-in off for every
  // demo scene (tutorial default); a scene's own zoomToClick wins.
  // Dynamic style (--style dynamic): the same continuous take, RE-FRAMED along
  // the beats' `layout` (see beats below). presenter.takeAspect "16:9" shoots
  // the take wide (assets/presenter-16x9.png) so face beats fill the frame;
  // presenter.frame.morphMs (default 300) is how long a move between layouts
  // takes — 0 for hard cuts; pace "lively" (a move every 4-6s) | "calm" (one
  // layout per step); faceFit "auto" letterboxes a square/9:16 take for
  // full-frame layouts | "cover" crops | "contain"; panelZoom 1-1.6 (default 1,
  // the side panel is a plain cover-fit). No pip block; transitions must be empty.
  // "presenter": { "mode": "always", "continuousPip": true, "takeAspect": "16:9",
  //                "frame": { "morphMs": 300, "pace": "lively", "faceFit": "auto" }, ... },
  "music": { "enabled": false, "bed": "assets/bed.mp3", "duckDb": -14 },
  // Optional sound-effect pack (needs ELEVENLABS_API_KEY): named cues fired on
  // events — insert, stamp, face, bullet, coverExit, transition, card,
  // sceneStart. Any beat/transition/scene/bullet overrides with
  // "sfx": "pop" | false | { "cue", "gain", "offsetSec" }. See demo-assembly § Audio.
  // "sfx": { "enabled": true, "gain": 0.7, "duckDb": -6,
  //          "cues": { "whoosh": { "prompt": "short fast air whoosh, dry", "durationSec": 0.6, "file": null } },
  //          "auto": { "insert": "whoosh", "stamp": "pop", "face": "hit", "transition": "whoosh" } },
  // style: "clean" (pill near the bottom edge), "shorts" (big centered
  // karaoke for vertical cuts — see the demo-shorts skill), "boxed" (1-3 word
  // phrase cards, listicle), or "buildup" (dynamic style: bottom phrase box
  // whose words appear as spoken, one auto-picked word in an accent box —
  // "autoEmphasis": false turns the pick off; captionEmphasis overrides it)
  "captions": { "enabled": true, "style": "clean" },
  // Optional persistent channel-handle watermark (text and/or a small PNG).
  // position: top-left (default) | top-center | top-right | bottom-left | bottom-right
  // — the insets already avoid the zones platform UI covers on vertical video.
  // Opt a scene out with "watermark": false on the scene (e.g. the CTA card).
  // "watermark": { "text": "@handle", "position": "top-left", "opacity": 0.55 },
  // Vertical (and the 16:9 dynamic style, kind "overlay" only — frame 0 is the
  // poster of a README embed): the hook card that IS frame 0 — the thumbnail every platform
  // shows — held ~0.5s then wiped away over the first scene. See demo-shorts §2b.
  // layout: stack | band | corner | stripe   exit: wipe-up | wipe-down | dissolve | slide-left
  // (a landscape overlay defaults to the lower third and wipe-down, clear of the face)
  // "cover": { "hook": "You use AI for all your coding.", "kicker": "AI tech stack",
  //            "layout": "band", "accent": "#8B5CF6", "portrait": "assets/cover-mark.png" },
  // — or a frozen frame of the video itself instead of a card (no hook text):
  // "cover": { "kind": "frame", "scene": "s-r2", "atSec": 2.6, "holdSec": 0.45, "captions": false },
  "timeline": [ /* scenes */ ],
  "transitions": [ { "after": "hook", "type": "fade", "ms": 400 } ]
}

All paths are relative to the project directory. Never store absolute paths.

A vertical Short is a sibling manifest (shorts.json) in the same project dir — same schema, format 1080×1920, its own timeline with s--prefixed scene ids. Created with plan.mjs --init --manifest shorts.json --from demo.json --style <listicle|cohost|flashcard|glide>; the whole workflow is in the demo-shorts skill.

Demo scenes in beat styles carry a beats array — the voice flows while the picture hard-cuts between windows:

"captionsStyle": "boxed",                      // per-scene caption override
"captions": false,                             // no captions on this scene at all
"captionEmphasis": [ { "match": "average", "tone": "neg" },   // red
                     { "match": "one click", "tone": "pos" } ], // brand color
"beats": [
  { "atSec": 0,   "shot": "face" },                    // full-bleed presenterVideo
  { "atSec": 1.4, "shot": "screen" },                  // the scene's capture
  { "atSec": 3.0, "shot": "split" },                   // scene.bottom applies
  { "atSec": 4.6, "shot": "insert", "title": "DemoDay", "stamp": "#1" },  // or "image"
  { "atSec": 6.0, "shot": "screen", "videoStartSec": 12 }  // jump the capture
]

In the 16:9 dynamic style the beats frame the presenter instead (shot is optional and defaults to screen; face/split/card shots are rejected there — see the demo-video skill's dynamic recipe):

"beats": [
  { "atSec": 0,   "layout": "face" },                                  // full-frame hook, ≤3s
  { "atSec": 2.4, "layout": "panel-left",                              // presenter left, screen right…
    "focus": { "x": 0.05, "y": 0.55, "w": 0.45, "h": 0.3 } },          // …zoomed into the chat box
  { "atSec": 6.0, "focus": { "x": 0.5, "y": 0.1, "w": 0.4, "h": 0.4 } }, // same layout (inherited), focus glides
  { "atSec": 9.5, "layout": "card-tr", "videoStartSec": 14 }           // move to a corner card + zoom-dissolve to 14s
]
// A card scene carries a scene-level layout: "face-band" for the closer (the
// presenter across the top, the card's logo/title/cta as a band below) or
// "none" (take runs on underneath, presenter hidden). build-props flattens
// every layout onto the absolute timeline (presenter.layoutTrack) for the
// renderer's single presenter layer.

Scene kinds

// A screen-capture segment. target: "web" | "mac" | "cli" | "browser".
// A browser scene records a real browser window, toolbar and side panel
// included — use it for a browser extension.
// A cli scene records a real Terminal window running the real program; add
// "cwd" (the project the demo shell starts in) and expect a *.raw.mp4 kept
// beside the clip (the unretimed take).
{ "id": "feature-1", "kind": "demo", "target": "web",
  "narration": "Creating a project takes one click.",
  "audio": "audio/feature-1.mp3",        // written by gen/tts.mjs
  "words": "audio/feature-1.words.json", // word timings, for captions and pins
  "durationSec": 14.0,                   // measured from the audio
  "actions": "actions/feature-1.json",   // written by demo_save_actions
  "video": "clips/feature-1.mp4",        // written by the perform runner
  "events": "clips/feature-1.events.json",
  "overlays": [ { "type": "callout", "atSec": 3.2, "text": "One click", "anchor": "lastClick" } ],
  // Only when the composition aspect differs from the capture (a Short cut from
  // 16:9 footage): "pan" (default — window follows the clicks), "card" (whole UI
  // letterboxed in a card, with an optional "headline" below), or "cover" (crop).
  "framing": "pan",
  // Chop one continuous take: play this scene's `video` from 22s in (tutorial
  // style — every step shares the take). Ignored when the scene has beats
  // (each beat carries its own videoStartSec).
  "videoStartSec": 22,
  // Corner bubble (presenter.mode "always" only): false suppresses it on this
  // scene; an object overrides presenter.pip for this scene.
  "pip": { "position": "bottom-right" } }

// On-camera presenter
{ "id": "hook", "kind": "presenter",
  "narration": "Shipping used to take us three days.",
  "lowerThird": { "title": "Alex Rivera", "subtitle": "Product Lead" } }

// Generated cutscene
{ "id": "broll-1", "kind": "broll", "durationSec": 5,
  "prompt": "a developer at a sunlit desk, closing a laptop, relieved" }

// Pure motion graphics — also what a presenter scene degrades to in mode "none"
{ "id": "outro", "kind": "card", "durationSec": 4,
  "title": "Start free today", "subtitle": "No card required", "cta": "acme.dev/start" }

transitions[].type is fade, wipe or slide. They overlap the scenes they join, so the finished video is shorter than the sum of its scenes — the renderer accounts for this, but keep it in mind when hitting a target length.

Composite scenes (memes, layered clips)

A demo scene may carry a composite block instead of a capture: a plate image, positioned video/image layers (slow-mo, crop, alpha cutouts, freeze on the last frame, a layer's own audio), a timed push-in and a music cue. scripts/edit/composite.mjs --scene <id> bakes it to clips/<id>.mp4 and patches the scene. Full field list in that script's header; recipe in the demo-shorts skill (§2c). Give any scene without narration a note — it is what SCRIPT.md (regenerated on every render) says about it.

Writing b-roll prompts

Describe a shot, not a concept. "A developer at a sunlit desk closing a laptop" works; "productivity" does not.

  • Say the framing and light: close-up, wide, golden hour, overhead.
  • Keep people generic. Named people, logos and brands trip content filters, and the model ladder will burn through several attempts before failing.
  • 4–6 seconds. Generated video gets strange beyond that, and it is priced per second.
  • For a hard composite shot, build the still first with gen/still.mjs (generate → look → --edit → look; see the demo-video skill) and set the scene's still field — broll.mjs animates a supplied still instead of generating its own.

Order that saves money

  1. Write all narration into demo.json first.
  2. node scripts/plan.mjs --project demo/<slug> — check the estimate.
  3. gen/tts.mjs --all — now every scene has a real duration.
  4. Capture and generate against those durations.

Artifacts are cached on the hash of their inputs, so editing one line of narration regenerates one audio file and one presenter shot — not the whole video.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.