demo-reel 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.claude-plugin/commands/demo-script.md +200 -0
  2. package/.claude-plugin/marketplace.json +13 -0
  3. package/.claude-plugin/plugin.json +8 -0
  4. package/README.md +208 -85
  5. package/bin/demo-reel.mjs +102 -0
  6. package/dist/audio-processor.d.ts +13 -1
  7. package/dist/audio-processor.d.ts.map +1 -1
  8. package/dist/audio-processor.js +72 -31
  9. package/dist/audio-processor.js.map +1 -1
  10. package/dist/auth.d.ts +6 -6
  11. package/dist/auth.d.ts.map +1 -1
  12. package/dist/auth.js +36 -25
  13. package/dist/auth.js.map +1 -1
  14. package/dist/cli.d.ts +27 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +365 -50
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config-loader.d.ts +1 -1
  19. package/dist/config-loader.d.ts.map +1 -1
  20. package/dist/config-loader.js +25 -41
  21. package/dist/config-loader.js.map +1 -1
  22. package/dist/index.d.ts +12 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +232 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/narration-manifest.d.ts +31 -0
  27. package/dist/narration-manifest.d.ts.map +1 -0
  28. package/dist/narration-manifest.js +32 -0
  29. package/dist/narration-manifest.js.map +1 -0
  30. package/dist/presets.d.ts +11 -0
  31. package/dist/presets.d.ts.map +1 -0
  32. package/dist/presets.js +106 -0
  33. package/dist/presets.js.map +1 -0
  34. package/dist/random.d.ts +3 -0
  35. package/dist/random.d.ts.map +1 -0
  36. package/dist/random.js +26 -0
  37. package/dist/random.js.map +1 -0
  38. package/dist/runner.d.ts +15 -4
  39. package/dist/runner.d.ts.map +1 -1
  40. package/dist/runner.js +357 -109
  41. package/dist/runner.js.map +1 -1
  42. package/dist/schemas.d.ts +5155 -4522
  43. package/dist/schemas.d.ts.map +1 -1
  44. package/dist/schemas.js +316 -160
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/script/assembler.d.ts +26 -0
  47. package/dist/script/assembler.d.ts.map +1 -0
  48. package/dist/script/assembler.js +173 -0
  49. package/dist/script/assembler.js.map +1 -0
  50. package/dist/script/cli.d.ts +48 -0
  51. package/dist/script/cli.d.ts.map +1 -0
  52. package/dist/script/cli.js +168 -0
  53. package/dist/script/cli.js.map +1 -0
  54. package/dist/script/crawl-cli.d.ts +3 -0
  55. package/dist/script/crawl-cli.d.ts.map +1 -0
  56. package/dist/script/crawl-cli.js +30 -0
  57. package/dist/script/crawl-cli.js.map +1 -0
  58. package/dist/script/crawler.d.ts +24 -0
  59. package/dist/script/crawler.d.ts.map +1 -0
  60. package/dist/script/crawler.js +193 -0
  61. package/dist/script/crawler.js.map +1 -0
  62. package/dist/script/explore.d.ts +3 -0
  63. package/dist/script/explore.d.ts.map +1 -0
  64. package/dist/script/explore.js +221 -0
  65. package/dist/script/explore.js.map +1 -0
  66. package/dist/script/generator.d.ts +40 -0
  67. package/dist/script/generator.d.ts.map +1 -0
  68. package/dist/script/generator.js +209 -0
  69. package/dist/script/generator.js.map +1 -0
  70. package/dist/script/index.d.ts +8 -0
  71. package/dist/script/index.d.ts.map +1 -0
  72. package/dist/script/index.js +7 -0
  73. package/dist/script/index.js.map +1 -0
  74. package/dist/script/timing.d.ts +9 -0
  75. package/dist/script/timing.d.ts.map +1 -0
  76. package/dist/script/timing.js +140 -0
  77. package/dist/script/timing.js.map +1 -0
  78. package/dist/script/tts.d.ts +33 -0
  79. package/dist/script/tts.d.ts.map +1 -0
  80. package/dist/script/tts.js +458 -0
  81. package/dist/script/tts.js.map +1 -0
  82. package/dist/script/types.d.ts +1203 -0
  83. package/dist/script/types.d.ts.map +1 -0
  84. package/dist/script/types.js +84 -0
  85. package/dist/script/types.js.map +1 -0
  86. package/dist/script/voice-cli.d.ts +3 -0
  87. package/dist/script/voice-cli.d.ts.map +1 -0
  88. package/dist/script/voice-cli.js +145 -0
  89. package/dist/script/voice-cli.js.map +1 -0
  90. package/dist/types.d.ts +1 -1
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/video-handler.d.ts +13 -5
  93. package/dist/video-handler.d.ts.map +1 -1
  94. package/dist/video-handler.js +324 -57
  95. package/dist/video-handler.js.map +1 -1
  96. package/dist/voice-config.d.ts +95 -0
  97. package/dist/voice-config.d.ts.map +1 -0
  98. package/dist/voice-config.js +109 -0
  99. package/dist/voice-config.js.map +1 -0
  100. package/package.json +66 -35
  101. package/src/audio-processor.ts +259 -0
  102. package/src/auth.ts +392 -0
  103. package/src/cli.ts +490 -0
  104. package/src/config-loader.ts +138 -0
  105. package/src/index.ts +292 -0
  106. package/src/narration-manifest.ts +41 -0
  107. package/src/presets.ts +115 -0
  108. package/src/random.ts +31 -0
  109. package/src/runner.ts +1073 -0
  110. package/src/schemas.ts +518 -0
  111. package/src/script/assembler.ts +199 -0
  112. package/src/script/cli.ts +246 -0
  113. package/src/script/crawl-cli.ts +32 -0
  114. package/src/script/crawler.ts +237 -0
  115. package/src/script/explore.ts +279 -0
  116. package/src/script/generator.ts +256 -0
  117. package/src/script/index.ts +23 -0
  118. package/src/script/timing.ts +166 -0
  119. package/src/script/tts.ts +578 -0
  120. package/src/script/types.ts +109 -0
  121. package/src/script/voice-cli.ts +176 -0
  122. package/src/types.ts +25 -0
  123. package/src/video-handler.ts +648 -0
  124. package/src/voice-config.ts +138 -0
  125. package/templates/dashboard-demo.demo.ts +113 -43
  126. package/templates/demo-reel.config.ts +39 -43
  127. package/templates/scenario.demo.ts +26 -28
@@ -0,0 +1,200 @@
1
+ You are helping the user create a demo video script for their web application. This is a collaborative process — you figure out the flow together, then build the script scene by scene.
2
+
3
+ ## Modular Video Series Pattern
4
+
5
+ Demo videos are designed as **modular, standalone segments** that also work together as a guided journey. Each video:
6
+
7
+ 1. **Is fully independent** — has its own setup steps that create required state from scratch (login, create tenant, create template, etc.)
8
+ 2. **Opens with brief context** (~3-5 seconds narration) — "We hebben een template aangemaakt — laten we varianten toevoegen" so standalone viewers aren't lost
9
+ 3. **Covers one focused topic** — 30-90 seconds, one clear goal
10
+ 4. **Ends on the result** — the viewer sees the accomplished outcome
11
+
12
+ ### Example series structure:
13
+
14
+ ```
15
+ 01-create-template.demo.ts
16
+ setup: login → create tenant
17
+ recorded: navigate to templates → create template → open editor
18
+
19
+ 02-add-variants.demo.ts
20
+ setup: login → create tenant → create template (fast, off-screen)
21
+ recorded: open template → add variants → show variant list
22
+
23
+ 03-editor-walkthrough.demo.ts
24
+ setup: login → create tenant → create template (fast, off-screen)
25
+ recorded: open editor → drag blocks → edit text → preview
26
+ ```
27
+
28
+ ## Step 1: Understand the Goal
29
+
30
+ Ask clarifying questions to understand:
31
+
32
+ - **Who is the audience?** New users? Existing customers? Potential buyers?
33
+ - **What's the starting point?** Where in the app does this flow begin?
34
+ - **What's the end state?** What should the viewer see at the end?
35
+ - **Any specifics?** Test data (emails, names, project names), things to emphasize, things to skip
36
+ - **Tone and language?** Casual/formal, which language for narration and content
37
+ - **Part of a series?** Does this build on previous demos? What state already exists?
38
+
39
+ If the user provided $ARGUMENTS, treat it as the goal description and ask follow-up questions.
40
+
41
+ ## Step 2: Explore the App
42
+
43
+ Before writing any script, explore the app. Use the explore command:
44
+
45
+ ```bash
46
+ pnpm demo-reel explore <url>
47
+ ```
48
+
49
+ For authenticated pages, write a quick inline exploration script that logs in, clicks through to the target page, and extracts elements. Always **click through the UI** rather than navigating by URL — SPAs often break with direct URL navigation.
50
+
51
+ Summarize what you find for the user — page structure, available actions, selectors.
52
+
53
+ ## Step 3: Identify Setup vs. Recorded Steps
54
+
55
+ Some steps need to happen before recording starts. These are **setup** and **auth**: they run but aren't captured in the video (they run in a separate browser).
56
+
57
+ Ask the user: "Is there anything we need to do before the recording starts?"
58
+
59
+ Common setup steps:
60
+
61
+ - Login flow → use the `auth` block with `loginSteps`, `validate`, `storage`
62
+ - Create a fresh tenant/workspace → keeps demos reproducible and independent
63
+ - Create prerequisite data (templates, users, etc.) that this video builds upon
64
+ - Navigate past landing pages to the actual starting point
65
+
66
+ Also identify **cleanup** steps that should run after recording (e.g., delete tenant).
67
+
68
+ ## Step 4: Plan the Recorded Flow
69
+
70
+ Before crawling anything, sketch the high-level flow together. Present it and ask: "Does this flow make sense?"
71
+
72
+ ## Step 5: Build Scenes (Page by Page)
73
+
74
+ Work through the flow one page at a time.
75
+
76
+ **a) Crawl it** — explore the page, extract interactive elements. Always click links in the SPA.
77
+
78
+ **b) Draft scene(s):**
79
+
80
+ ```
81
+ Scene 2: "Laten we een nieuw template aanmaken."
82
+ → hover [href: "/tenants/demo/templates"]
83
+ → click [href: "/tenants/demo/templates"]
84
+ → wait 1500ms
85
+ ```
86
+
87
+ CRITICAL: Only use selectors from the crawl output. Never invent selectors. Always hover before clicking for natural cursor movement.
88
+
89
+ **c) Get feedback** — "How's this? Want to adjust the narration or steps?"
90
+
91
+ **d) Move to the next page** — "What happens after this?"
92
+
93
+ ## Step 6: Review the Full Script
94
+
95
+ Show the complete script. The user can reorder, cut, merge, tighten narration, adjust pacing.
96
+
97
+ ## Step 7: Save
98
+
99
+ Ask for a name. Write the `.demo.ts` config:
100
+
101
+ ```typescript
102
+ import { demo } from 'demo-reel';
103
+
104
+ export default demo({
105
+ video: { resolution: "FHD" },
106
+ cursor: "dot",
107
+ motion: "smooth",
108
+ typing: "humanlike",
109
+ timing: "normal",
110
+ outputFormat: "mp4",
111
+ name: "create-template",
112
+ outputDir: "./output",
113
+
114
+ voice: {
115
+ provider: "elevenlabs",
116
+ voice: "CwhRBWXzGAHq8TQ4Fs17",
117
+ pronunciation: { "template": "template" },
118
+ },
119
+
120
+ auth: { /* loginSteps, validate, storage */ },
121
+ setup: [ /* steps to run before recording */ ],
122
+ cleanup: [ /* steps to run after recording */ ],
123
+
124
+ scenes: [
125
+ { narration: "...", stepIndex: 0, isIntro: true },
126
+ { narration: "...", stepIndex: 3 },
127
+ ],
128
+
129
+ steps: [
130
+ // Scene 1: ...
131
+ { action: "hover", selector: { ... }, delayAfterMs: 800 },
132
+ { action: "click", selector: { ... }, delayAfterMs: 1500 },
133
+ // Scene 2: ...
134
+ { action: "type", selector: { ... }, text: "...", delayAfterMs: 300 },
135
+ ],
136
+ });
137
+ ```
138
+
139
+ Key points:
140
+
141
+ - Use `demo()` (or `defineConfig()`) from `'demo-reel'`
142
+ - `setup` = steps before recording (off-screen), `cleanup` = steps after recording
143
+ - `voice` config inline — voiceover is auto-generated during recording
144
+ - `voice` values are provider-specific:
145
+ - `piper`: `nl_NL-mls-medium`, `en_US-amy-medium`
146
+ - `openai`: `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`
147
+ - `elevenlabs`: `21m00Tcm4TlvDq8ikWAM`, `5zhopMftSdRGaPYVcwKK`
148
+ - for a custom Piper model, use `voicePath` instead of `voice`
149
+ - `scenes` map narration text to step indices for subtitles + metadata
150
+ - Always `hover` before `click` for natural cursor movement
151
+
152
+ ## Step 8: Record
153
+
154
+ One command does everything (compile, voice, record, subtitles, cleanup):
155
+
156
+ ```bash
157
+ pnpm demo-reel <name> --verbose
158
+ ```
159
+
160
+ This compiles the .demo.ts, generates voiceover (if voice config + narration present), runs the recording in Docker, and outputs video + subtitles + metadata.
161
+
162
+ For local debugging with a visible browser:
163
+
164
+ ```bash
165
+ pnpm demo-reel-local <name> --headed --verbose
166
+ ```
167
+
168
+ If it fails, check `output/debug/step-N-failure.png` for a screenshot of the page at the failed step.
169
+
170
+ ---
171
+
172
+ ## Writing Guidelines
173
+
174
+ **Narration:**
175
+
176
+ - Goal-oriented — explain what we're trying to accomplish, not just what we're clicking
177
+ - Concise — 1-3 sentences per scene, 30-90 seconds total
178
+ - Conversational — "Let's", "Notice how", "Here's where we" (or Dutch equivalents)
179
+ - No filler — cut "As you can see" and "Now we're going to"
180
+ - Value-focused — "This saves you from having to..." not just "Click here"
181
+ - For series: open with brief context so standalone viewers aren't lost
182
+
183
+ **Pacing:**
184
+
185
+ - `delayAfterMs: 500-1500` after visual changes
186
+ - `wait: 1500-2500` after page navigations
187
+ - `waitFor` after clicks that trigger loading
188
+ - Longer pauses at "aha" moments
189
+ - Quick pace through routine steps (form filling)
190
+ - Always `hover` before `click` — shows cursor movement
191
+
192
+ **Selectors:** `data-testid` > `id` > `href` > unique `class` > `custom`
193
+
194
+ **Navigation:** Always click elements in the UI. Never use `goto` for internal SPA pages — only for the initial page load after setup.
195
+
196
+ **Scene structure:**
197
+
198
+ - Each scene = one logical beat, not one page
199
+ - First scene: brief context intro for standalone viewers
200
+ - Last scene: linger on the result
@@ -0,0 +1,13 @@
1
+ {
2
+ "plugins": [
3
+ {
4
+ "name": "demo-reel",
5
+ "description": "Create demo videos from web apps — interactive script building with Claude Code",
6
+ "source": {
7
+ "source": "github",
8
+ "repo": "whit3st/demo-reel"
9
+ },
10
+ "version": "0.1.4"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "demo-reel",
3
+ "description": "Create demo videos from web apps — interactive script building with Claude Code",
4
+ "version": "0.1.4",
5
+ "author": {
6
+ "name": "whit3st"
7
+ }
8
+ }
package/README.md CHANGED
@@ -1,133 +1,254 @@
1
1
  # Demo Reel
2
2
 
3
- Create beautiful demo videos from web apps using Playwright. Perfect for showcasing features, creating onboarding tutorials, or documenting workflows.
3
+ ![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/whit3st/demo-reel/main/.github/badges/coverage.json)
4
4
 
5
- ## What is Demo Reel?
5
+ Create professional demo videos from web apps. Code your demos in TypeScript, record via Docker, with automatic voiceover and subtitles.
6
6
 
7
- Demo Reel is a developer-first tool for creating professional demo videos from web applications. Unlike manual screen recording, Demo Reel uses Playwright automation to create pixel-perfect, reproducible demos.
7
+ ## Quick Start
8
8
 
9
- **Key Benefits:**
10
- - **Code as Config**: Demo configurations are TypeScript files
11
- - **Version Controlled**: Track demo changes in git
12
- - **CI/CD Ready**: Generate videos in automated pipelines
13
- - **Human-Like**: Natural cursor movements and typing
14
- - **Audio Support**: Mix narration and background music
9
+ ```bash
10
+ pnpm add -D demo-reel
11
+ ```
15
12
 
16
- ## Installation
13
+ ```typescript
14
+ // demos/signup.demo.ts
15
+ import { generate } from "demo-reel";
16
+
17
+ await generate(
18
+ {
19
+ name: "signup",
20
+ outputDir: "./output",
21
+ video: { resolution: "FHD" },
22
+ cursor: "dot",
23
+ motion: "smooth",
24
+ typing: "humanlike",
25
+ timing: "normal",
26
+ outputFormat: "mp4",
27
+
28
+ voice: {
29
+ provider: "elevenlabs", // or "piper" (local/free) or "openai"
30
+ voice: "5zhopMftSdRGaPYVcwKK",
31
+ },
32
+
33
+ auth: {
34
+ loginSteps: [
35
+ { action: "goto", url: "https://myapp.com/login" },
36
+ { action: "type", selector: { strategy: "id", value: "email" }, text: "demo@example.com" },
37
+ { action: "type", selector: { strategy: "id", value: "password" }, text: "password" },
38
+ { action: "click", selector: { strategy: "class", value: "btn-primary" } },
39
+ ],
40
+ validate: {
41
+ protectedUrl: "https://myapp.com/dashboard",
42
+ successIndicator: { strategy: "custom", value: "h1:has-text('Dashboard')" },
43
+ },
44
+ storage: { name: "demo-session", types: ["cookies"] },
45
+ },
46
+
47
+ setup: [
48
+ // Runs before recording (off-screen) — create test data, navigate
49
+ { action: "goto", url: "https://myapp.com/dashboard" },
50
+ ],
51
+
52
+ cleanup: [
53
+ // Runs after recording — delete test data
54
+ ],
55
+
56
+ scenes: [
57
+ { narration: "Welcome to our app. Let's create a new project.", stepIndex: 0, isIntro: true },
58
+ { narration: "Fill in the details and click Create.", stepIndex: 2 },
59
+ ],
60
+
61
+ steps: [
62
+ // Scene 1
63
+ {
64
+ action: "hover",
65
+ selector: { strategy: "testId", value: "new-project" },
66
+ delayAfterMs: 800,
67
+ },
68
+ {
69
+ action: "click",
70
+ selector: { strategy: "testId", value: "new-project" },
71
+ delayAfterMs: 1500,
72
+ },
73
+
74
+ // Scene 2
75
+ {
76
+ action: "type",
77
+ selector: { strategy: "id", value: "name" },
78
+ text: "My Project",
79
+ delayAfterMs: 500,
80
+ },
81
+ {
82
+ action: "hover",
83
+ selector: { strategy: "custom", value: "button[type='submit']" },
84
+ delayAfterMs: 600,
85
+ },
86
+ {
87
+ action: "click",
88
+ selector: { strategy: "custom", value: "button[type='submit']" },
89
+ delayAfterMs: 2000,
90
+ },
91
+ ],
92
+ },
93
+ { verbose: true },
94
+ );
95
+ ```
17
96
 
18
97
  ```bash
19
- npm install -D demo-reel playwright
98
+ npx tsx demos/signup.demo.ts
20
99
  ```
21
100
 
22
- ## Quick Start
101
+ Output: `output/signup.mp4` + `.srt` + `.vtt` + `.meta.json`
23
102
 
24
- ### 1. Create a Config File
103
+ ## How It Works
25
104
 
26
- Create `demo-reel.config.ts` in your project root:
105
+ 1. **You write a `.demo.ts`** TypeScript config with steps, scenes, and narration
106
+ 2. **`generate()` handles everything** — compiles config, generates voiceover (via Docker), records the video (via Docker), outputs subtitles and metadata
107
+ 3. **Docker runs the heavy stuff** — Chromium, FFmpeg, Piper TTS are in the Docker image, not on your machine
27
108
 
28
- ```typescript
29
- import { defineConfig } from 'demo-reel';
30
-
31
- export default defineConfig({
32
- viewport: { width: 1920, height: 1080 },
33
- video: { enabled: true, size: { width: 1920, height: 1080 } },
34
- name: 'my-demo',
35
- steps: [
36
- { action: 'goto', url: 'https://example.com' },
37
- { action: 'wait', ms: 2000 },
38
- ],
39
- });
40
- ```
109
+ ### Requirements
41
110
 
42
- ### 2. Run the Demo
111
+ - **Docker** for recording and voice generation
112
+ - **Node.js 18+** — for running your demo scripts
113
+ - **API keys** (optional) — `ELEVENLABS_KEY` or `OPENAI_API_KEY` for cloud TTS
114
+
115
+ ## Claude Code Integration
116
+
117
+ Build demo scripts interactively with Claude Code:
43
118
 
44
119
  ```bash
45
- npx demo-reel
120
+ pnpm demo-reel setup # shows how to install the /demo-script plugin
46
121
  ```
47
122
 
48
- Output: `videos/my-demo.mp4`
123
+ Then use `/demo-script` in Claude Code:
49
124
 
50
- ## CLI Usage
51
-
52
- ```bash
53
- # Run default config
54
- npx demo-reel
125
+ ```
126
+ /demo-script https://myapp.com show the signup flow
127
+ ```
55
128
 
56
- # Run specific scenario
57
- npx demo-reel onboarding
129
+ Claude crawls your app, builds the script with you scene by scene, and generates the `.demo.ts`.
58
130
 
59
- # Run all scenarios
60
- npx demo-reel --all
131
+ ## Configuration
61
132
 
62
- # Validate config without recording
63
- npx demo-reel --dry-run
133
+ ### Voice / TTS
64
134
 
65
- # Verbose output
66
- npx demo-reel --verbose
135
+ ```typescript
136
+ voice: {
137
+ provider: "elevenlabs", // "piper" | "openai" | "elevenlabs"
138
+ voice: "5zhopMftSdRGaPYVcwKK", // provider-specific autocomplete
139
+ speed: 1.0,
140
+ pronunciation: { // word replacements before TTS
141
+ "template": "template", // prevent Dutch pronunciation of English words
142
+ },
143
+ },
67
144
  ```
68
145
 
69
- ## Configuration
146
+ Built-in voice values:
70
147
 
71
- ### Config File Discovery
148
+ - `piper`: `"nl_NL-mls-medium"`, `"en_US-amy-medium"`
149
+ - `openai`: `"alloy"`, `"echo"`, `"fable"`, `"onyx"`, `"nova"`, `"shimmer"`
150
+ - `elevenlabs`: `"21m00Tcm4TlvDq8ikWAM"`, `"5zhopMftSdRGaPYVcwKK"`, `CwhRBWXzGAHq8TQ4Fs17`
72
151
 
73
- The CLI looks for configs in this order:
74
- 1. `demo-reel.config.ts` (default)
75
- 2. `<scenario>.demo.ts` (when specifying scenario name)
76
- 3. All `*.demo.ts` files (with `--all` flag)
152
+ For a custom Piper `.onnx` model, use `voicePath` instead of `voice`:
77
153
 
78
- ### Available Steps
154
+ ```typescript
155
+ voice: {
156
+ provider: "piper",
157
+ voicePath: "/models/custom-voice.onnx",
158
+ speed: 1.0,
159
+ }
160
+ ```
79
161
 
80
- - `goto` - Navigate to URL
81
- - `click` - Click an element
82
- - `hover` - Hover over element
83
- - `type` - Type text into input
84
- - `press` - Press a key
85
- - `scroll` - Scroll element
86
- - `wait` - Wait for duration
87
- - `waitFor` - Wait for condition
162
+ Voiceover is auto-generated when `scenes` have `narration` text and `voice` is configured. Cached by content hash — only regenerates when narration changes.
88
163
 
89
- ### Selector Strategies
164
+ ### Setup & Cleanup
90
165
 
91
166
  ```typescript
92
- // By test ID (data-testid attribute)
93
- { strategy: 'testId', value: 'submit-button' }
167
+ setup: [
168
+ // Runs in a separate browser BEFORE recording (not visible in video)
169
+ { action: "goto", url: "https://myapp.com/" },
170
+ { action: "click", selector: { strategy: "id", value: "create-workspace" } },
171
+ ],
172
+
173
+ cleanup: [
174
+ // Runs AFTER recording (even on failure) — delete test data
175
+ { action: "goto", url: "https://myapp.com/admin" },
176
+ { action: "click", selector: { strategy: "custom", value: "button.delete" } },
177
+ ],
178
+ ```
94
179
 
95
- // By ID (without #)
96
- { strategy: 'id', value: 'username' }
180
+ Setup and cleanup run in tolerant mode — failed steps are skipped.
97
181
 
98
- // By class (without .)
99
- { strategy: 'class', value: 'btn-primary' }
182
+ ### Scenes & Subtitles
100
183
 
101
- // By href
102
- { strategy: 'href', value: '/dashboard' }
184
+ ```typescript
185
+ scenes: [
186
+ { narration: "Welcome to our app.", stepIndex: 0, isIntro: true },
187
+ { narration: "Let's create something.", stepIndex: 3 },
188
+ ],
103
189
  ```
104
190
 
105
- ## Features
191
+ - `narration` — voiceover text (also used for subtitles)
192
+ - `stepIndex` — which step starts this scene
193
+ - `isIntro` — marks the intro scene (used by presentation systems to skip context)
106
194
 
107
- ### Human-Like Cursor Movement
195
+ Generates `.srt`, `.vtt` (subtitles) and `.meta.json` (scene timestamps for interactive players).
108
196
 
109
- Smooth Bezier curve paths with configurable speed and easing.
197
+ ### Steps
110
198
 
111
- ### Natural Typing
199
+ | Action | Description |
200
+ | --------- | -------------------------------------------------------------------- |
201
+ | `goto` | Navigate to URL |
202
+ | `click` | Click an element |
203
+ | `hover` | Hover over element |
204
+ | `type` | Type text into input |
205
+ | `press` | Press a key |
206
+ | `scroll` | Scroll element |
207
+ | `select` | Select dropdown option(s) |
208
+ | `check` | Check/uncheck checkbox |
209
+ | `upload` | Upload files |
210
+ | `drag` | Drag and drop |
211
+ | `wait` | Wait for duration |
212
+ | `waitFor` | Wait for condition (selector, URL, load state, network, JS function) |
112
213
 
113
- Variable delays based on character type (spaces, punctuation, etc.)
214
+ ### Selectors
114
215
 
115
- ### Audio Support
216
+ ```typescript
217
+ { strategy: "testId", value: "submit-button" } // data-testid
218
+ { strategy: "id", value: "username" } // id (no #)
219
+ { strategy: "class", value: "btn-primary" } // class (no .)
220
+ { strategy: "href", value: "/dashboard" } // link href
221
+ { strategy: "custom", value: "button:has-text('Save')" } // any CSS selector
222
+ { strategy: "class", value: "card", index: 2 } // nth match
223
+ ```
116
224
 
117
- Mix narration and background music:
225
+ ### Presets
118
226
 
119
227
  ```typescript
120
- audio: {
121
- narration: './voiceover.mp3',
122
- narrationDelay: 2000, // Delay before narration starts
123
- background: './music.mp3',
124
- backgroundVolume: 0.3,
228
+ cursor: "dot" | "arrow" | "none";
229
+ motion: "smooth" | "snappy" | "instant";
230
+ typing: "humanlike" | "fast" | "instant";
231
+ timing: "normal" | "fast" | "instant";
232
+ video: {
233
+ resolution: "HD" | "FHD" | "2K" | "4K";
125
234
  }
126
235
  ```
127
236
 
128
- ## CI/CD Integration
237
+ ## Modular Video Series
238
+
239
+ Demo videos are designed as standalone segments that also work as a series:
240
+
241
+ ```
242
+ demos/
243
+ └── my-app/
244
+ ├── 01-signup/signup.demo.ts # setup: create workspace
245
+ ├── 02-create-project/project.demo.ts # setup: create workspace + template
246
+ └── 03-editor/editor.demo.ts # setup: create workspace + template + project
247
+ ```
248
+
249
+ Each video has its own `setup` that recreates the required state. Later videos have heavier setup. Every video is independently recordable.
129
250
 
130
- Example GitHub Actions workflow:
251
+ ## CI/CD
131
252
 
132
253
  ```yaml
133
254
  name: Generate Demo Videos
@@ -138,13 +259,15 @@ jobs:
138
259
  runs-on: ubuntu-latest
139
260
  steps:
140
261
  - uses: actions/checkout@v4
141
- - run: npm ci
142
- - run: npx playwright install chromium
143
- - run: npx demo-reel --all
262
+ - uses: pnpm/action-setup@v4
263
+ - run: pnpm install
264
+ - run: npx tsx demos/01-signup/signup.demo.ts
265
+ env:
266
+ ELEVENLABS_KEY: ${{ secrets.ELEVENLABS_KEY }}
144
267
  - uses: actions/upload-artifact@v4
145
268
  with:
146
269
  name: demo-videos
147
- path: ./videos/*.mp4
270
+ path: ./output/*.mp4
148
271
  ```
149
272
 
150
273
  ## License
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { execSync, spawn } from "child_process";
3
+ import { dirname, resolve } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const SKILL_URL =
7
+ "https://raw.githubusercontent.com/whit3st/demo-reel/main/.claude-plugin/commands/demo-script.md";
8
+ const DEFAULT_IMAGE = "ghcr.io/whit3st/demo-reel:latest";
9
+ const LOCAL_IMAGE = "demo-reel:latest";
10
+
11
+ const args = process.argv.slice(2);
12
+
13
+ if (args[0] === "setup") {
14
+ console.log(`demo-reel setup
15
+
16
+ Install the /demo-script Claude Code plugin to build demo scripts interactively.
17
+
18
+ Option 1 — Claude Code plugin (recommended):
19
+ /plugin marketplace add whit3st/demo-reel
20
+ /plugin install demo-reel@whit3st-demo-reel
21
+
22
+ Option 2 — Manual copy:
23
+ mkdir -p .claude/commands
24
+ curl -sL ${SKILL_URL} -o .claude/commands/demo-script.md
25
+
26
+ Then use /demo-script in Claude Code to create demo videos collaboratively.`);
27
+ } else if (args[0] === "explore") {
28
+ const image = getImage();
29
+ const dockerArgs = [
30
+ "run",
31
+ "--rm",
32
+ "-v",
33
+ `${process.cwd()}:/work:z`,
34
+ "-w",
35
+ "/work",
36
+ "--entrypoint",
37
+ "node",
38
+ image,
39
+ "/app/dist/script/crawl-cli.js",
40
+ ...args.slice(1),
41
+ ];
42
+ const proc = spawn("docker", dockerArgs, { stdio: "inherit" });
43
+ forwardSignals(proc);
44
+ proc.on("close", (code) => process.exit(code ?? 1));
45
+ } else if (args.length > 0) {
46
+ const cliPath = resolve(dirname(fileURLToPath(import.meta.url)), "../dist/cli.js");
47
+ const proc = spawn(process.execPath, ["--import", "tsx/esm", cliPath, ...args], {
48
+ stdio: "inherit",
49
+ });
50
+ forwardSignals(proc);
51
+ proc.on("close", (code) => process.exit(code ?? 1));
52
+ } else if (args.length > 0) {
53
+ const cliPath = resolve(dirname(fileURLToPath(import.meta.url)), "../dist/cli.js");
54
+ const proc = spawn(process.execPath, [cliPath, ...args], { stdio: "inherit" });
55
+ proc.on("close", (code) => process.exit(code ?? 1));
56
+ } else {
57
+ console.log(`demo-reel — Create demo videos from web apps
58
+
59
+ Usage in your code:
60
+ import { generate } from "demo-reel";
61
+ await generate({ steps: [...], scenes: [...] }, { verbose: true });
62
+
63
+ CLI commands:
64
+ demo-reel setup Install the Claude Code plugin
65
+ demo-reel explore <url> Crawl a page and show selectors`);
66
+ }
67
+
68
+ function getImage() {
69
+ try {
70
+ execSync(`docker image inspect ${LOCAL_IMAGE}`, { stdio: "ignore" });
71
+ return LOCAL_IMAGE;
72
+ } catch {
73
+ /* no local image */
74
+ }
75
+ try {
76
+ execSync(`docker image inspect ${DEFAULT_IMAGE}`, { stdio: "ignore" });
77
+ return DEFAULT_IMAGE;
78
+ } catch {
79
+ console.log(`Pulling ${DEFAULT_IMAGE}...`);
80
+ execSync(`docker pull ${DEFAULT_IMAGE}`, { stdio: "inherit" });
81
+ return DEFAULT_IMAGE;
82
+ }
83
+ }
84
+
85
+ function forwardSignals(child) {
86
+ const signals = ["SIGINT", "SIGTERM"];
87
+ const forward = (signal) => {
88
+ if (!child.killed) {
89
+ child.kill(signal);
90
+ }
91
+ };
92
+
93
+ for (const signal of signals) {
94
+ process.on(signal, () => forward(signal));
95
+ }
96
+
97
+ process.on("exit", () => {
98
+ if (!child.killed) {
99
+ child.kill("SIGTERM");
100
+ }
101
+ });
102
+ }