demo-dev 0.0.1-alpha.0 → 0.0.2-alpha.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.
package/dist/index.js CHANGED
@@ -161,6 +161,29 @@ var ZOOM_INJECT_SCRIPT = `
161
161
  };
162
162
  })();
163
163
  `;
164
+ var SCENE_TRANSITION_SCRIPT = `
165
+ (() => {
166
+ if (document.getElementById('__scene-fade')) return;
167
+ const overlay = document.createElement('div');
168
+ overlay.id = '__scene-fade';
169
+ Object.assign(overlay.style, {
170
+ position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh',
171
+ zIndex: '999997', pointerEvents: 'none',
172
+ background: 'white', opacity: '0',
173
+ transition: 'opacity 0.4s ease-in-out',
174
+ });
175
+ document.body.appendChild(overlay);
176
+ window.__fadeOut = () => { overlay.style.opacity = '1'; };
177
+ window.__fadeIn = () => { overlay.style.opacity = '0'; };
178
+ })();
179
+ `;
180
+ var fadeTransition = async (page) => {
181
+ await page.evaluate(SCENE_TRANSITION_SCRIPT).catch(() => void 0);
182
+ await page.evaluate(() => window.__fadeOut?.()).catch(() => void 0);
183
+ await page.waitForTimeout(450);
184
+ await page.evaluate(() => window.__fadeIn?.()).catch(() => void 0);
185
+ await page.waitForTimeout(450);
186
+ };
164
187
  var zoomToPoint = async (page, x, y, scale = 1.5) => {
165
188
  await page.evaluate(ZOOM_INJECT_SCRIPT).catch(() => void 0);
166
189
  await page.evaluate(
@@ -195,13 +218,13 @@ var CURSOR_OVERLAY_SCRIPT = `
195
218
  cursor.id = '__ghost-cursor';
196
219
  Object.assign(cursor.style, {
197
220
  position: 'fixed', zIndex: '999999', pointerEvents: 'none',
198
- left: '0px', top: '0px', width: '22px', height: '32px',
199
- transition: 'left 0.04s cubic-bezier(.2,.8,.3,1), top 0.04s cubic-bezier(.2,.8,.3,1)',
200
- filter: 'drop-shadow(0 2px 3px rgba(0,0,0,0.4))',
221
+ left: '0px', top: '0px', width: '20px', height: '28px',
222
+ transition: 'left 0.03s linear, top 0.03s linear',
201
223
  });
202
- /* Standard macOS cursor: white fill, black outline, clean geometry */
203
- cursor.innerHTML = '<svg width="22" height="32" viewBox="0 0 22 32" xmlns="http://www.w3.org/2000/svg">' +
204
- '<path d="M1.5 0.5L1.5 24.5L7 18.5L11.5 28.5L14.5 27L10 17.5L18 17.5Z" fill="white" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>' +
224
+ /* Precise macOS default cursor \u2014 traced from Apple's actual cursor spec */
225
+ cursor.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="28" viewBox="0 0 20 28">' +
226
+ '<defs><filter id="cs" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0.5" dy="1.5" stdDeviation="0.8" flood-opacity="0.45"/></filter></defs>' +
227
+ '<path filter="url(#cs)" d="M2.2 0L2.2 21.4L6.8 16.4L10.6 24.8L13.2 23.6L9.4 15.6L16 15.6Z" fill="white" stroke="black" stroke-width="1.1" stroke-linejoin="round"/>' +
205
228
  '</svg>';
206
229
  document.body.appendChild(cursor);
207
230
 
@@ -263,6 +286,7 @@ var runActionWithCursor = async (page, action, baseUrl, cursor) => {
263
286
  await page.evaluate(CURSOR_OVERLAY_SCRIPT).catch(() => void 0);
264
287
  await page.evaluate(CURSOR_TRACKER_SCRIPT).catch(() => void 0);
265
288
  await page.evaluate(ZOOM_INJECT_SCRIPT).catch(() => void 0);
289
+ await page.evaluate(SCENE_TRANSITION_SCRIPT).catch(() => void 0);
266
290
  await page.waitForTimeout(humanDelay(400));
267
291
  break;
268
292
  }
@@ -497,7 +521,7 @@ var capturePlanContinuous = async (plan, options) => {
497
521
  endMs: sceneEnd,
498
522
  url: page.url()
499
523
  });
500
- await page.waitForTimeout(humanDelay(400));
524
+ await fadeTransition(page);
501
525
  }
502
526
  await drainCursorSamples(page, cursorLog, cursorTrackingOffset);
503
527
  const totalDurationMs = elapsed();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demo-dev",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.2-alpha.0",
4
4
  "description": "Generate polished product demo videos with one command. Just give a URL and a prompt.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,153 +1,109 @@
1
1
  ---
2
2
  name: demo-dev
3
- description: Generate PR demo videos for any web app repo using demo.dev. Use when a user wants an agent to turn a PR, diff, feature branch, or live app flow into a product demo video, especially for authenticated SaaS flows, launch-style walkthroughs, or PR review artifacts.
3
+ description: Generate product demo videos for any web app. Give a URL and a prompt, get a narrated Screen Studio-style video. Supports authenticated SaaS, prompt-driven or diff-driven planning, and AI narration.
4
4
  allowed-tools: Bash Read Edit Write
5
5
  ---
6
6
 
7
7
  # demo-dev
8
8
 
9
- Use this skill when the user wants an agent to create or update a demo video with the `demo.dev` pipeline.
9
+ Use this skill when the user wants to generate a demo video of a web app.
10
10
 
11
11
  ## What this skill does
12
12
 
13
- - Reads project config from `demo.dev.config.json`
14
- - Runs the pipeline from diff -> plan -> probe -> capture -> render
15
- - Supports authenticated products via storage state
16
- - Can also create manual plans for feature-specific demos
17
- - Produces artifacts like mp4, manifest, captures, and PR comments
13
+ - Opens a web app in a headless browser
14
+ - AI plans the demo from a natural language prompt (or git diff)
15
+ - Ghost-cursor navigates with human-like mouse movement
16
+ - Records continuously with Playwright screencast + CSS zoom
17
+ - Generates narration per scene with ElevenLabs / OpenAI / local TTS
18
+ - Composes a polished mp4 with FFmpeg (speed ramps, browser frame, audio)
18
19
 
19
- ## Default workflow
20
+ ## Quick start
20
21
 
21
- ### 1. Inspect config
22
+ ### 1. Check environment
22
23
 
23
24
  ```bash
24
- demo-dev config
25
- demo-dev config --field baseUrl
26
25
  demo-dev doctor
27
26
  ```
28
27
 
29
- If config is missing, run `demo-dev init` or create one from [`../../demo.dev.config.example.json`](../../demo.dev.config.example.json).
28
+ Requires: Node.js >= 20, FFmpeg, Chromium (`npx playwright install chromium`).
30
29
 
31
- See [references/configuration.md](references/configuration.md).
32
-
33
- ### 2. If auth is required, bootstrap login
30
+ ### 2. If auth is required
34
31
 
35
32
  ```bash
36
- demo-dev auth:bootstrap \
33
+ demo-dev auth \
34
+ --base-url https://app.example.com \
37
35
  --email you@example.com \
38
36
  --password 'your-password'
39
37
  ```
40
38
 
41
- This writes storage state using the configured path or `artifacts/storage-state.json`.
42
-
43
- ### 3. Run the full pipeline
39
+ ### 3. Generate a demo
44
40
 
45
41
  ```bash
46
- demo-dev pr-demo
42
+ demo-dev demo \
43
+ --base-url https://app.example.com \
44
+ --prompt "Show the dashboard, create a new project, invite a teammate" \
45
+ --frame
47
46
  ```
48
47
 
49
- Or explicitly:
48
+ This single command will: explore the site → AI plan scenes → record → narrate → compose → mp4.
50
49
 
51
- ```bash
52
- demo-dev pr-demo --base-url http://localhost:3000 --base-ref origin/main
53
- ```
54
-
55
- ### 4. Re-render only when capture and manifest already exist
50
+ ### Diff-driven mode (from a PR branch)
56
51
 
57
52
  ```bash
58
- demo-dev render --manifest artifacts/render-manifest.json --out artifacts/pr-demo.mp4
53
+ demo-dev demo --base-url http://localhost:3000
59
54
  ```
60
55
 
61
- ## When to use manual plans
62
-
63
- Use a manual plan when:
64
- - the feature is behind auth or deep navigation
65
- - the planner cannot infer the correct route from diff
66
- - the user wants a specific product moment, such as AI editing, onboarding, settings, inbox, or dashboards
67
- - the user wants a more launch-style product film rather than raw replay
68
-
69
- Place plans in an artifacts folder such as:
70
-
71
- ```bash
72
- artifacts/manual-plan.json
73
- ```
74
-
75
- Then run capture / manifest / render around that plan.
76
-
77
- See [references/recipes.md](references/recipes.md).
78
-
79
- ## Recommended agent behavior
56
+ No `--prompt` = auto-detect changes from git diff.
80
57
 
81
- 1. Prefer stable, product-facing flows over exhaustive QA coverage.
82
- 2. For the same route, keep one screen object and move the camera instead of cutting to a new screen.
83
- 3. Prefer stable states over loading transitions.
84
- 4. If auth is required, verify storage state early.
85
- 5. If planner output is weak, create a tighter manual plan instead of forcing bad automation.
86
- 6. Keep copy concise and product-first.
58
+ ## Key flags
87
59
 
88
- ## Core commands
60
+ | Flag | Description |
61
+ |------|-------------|
62
+ | `--prompt "..."` | Describe the demo in natural language |
63
+ | `--frame` | Add Screen Studio-style browser frame with gradient background |
64
+ | `--quality draft\|standard\|high` | Video quality preset |
65
+ | `--base-url` | URL of the app |
66
+ | `--output-dir` | Where to write output (default: artifacts) |
89
67
 
90
- Preferred first-class CLI:
68
+ ## All commands
91
69
 
92
70
  ```bash
93
- demo-dev config
94
- demo-dev providers
95
- demo-dev plan
96
- demo-dev probe
97
- demo-dev auth:bootstrap --email you@example.com --password 'your-password'
98
- demo-dev capture
99
- demo-dev voice
100
- demo-dev manifest
101
- demo-dev render --manifest artifacts/render-manifest.json --out artifacts/pr-demo.mp4
102
- demo-dev comment --output-dir artifacts --pr-number 123
103
- demo-dev pr-demo
71
+ demo-dev demo # Full pipeline
72
+ demo-dev auth # Login and save session
73
+ demo-dev capture # Record only
74
+ demo-dev voice # Generate narration only
75
+ demo-dev render # Record + narrate + compose
76
+ demo-dev plan # Generate plan from git diff
77
+ demo-dev probe # Plan + probe pages
78
+ demo-dev init # Create config file
79
+ demo-dev doctor # Check environment
80
+ demo-dev config # Show config
81
+ demo-dev providers # List AI/TTS providers
82
+ demo-dev comment # Post as PR comment
104
83
  ```
105
84
 
106
- Repo-local fallback:
107
-
108
- ```bash
109
- npm run config
110
- npm run providers
111
- npm run plan
112
- npm run probe
113
- npm run auth:bootstrap
114
- npm run capture
115
- npm run voice
116
- npm run manifest
117
- npm run render -- --manifest artifacts/render-manifest.json --out artifacts/pr-demo.mp4
118
- npm run comment -- --output-dir artifacts --pr-number 123
119
- npm run pr-demo
120
- ```
121
-
122
- ## Outputs to check
123
-
124
- - `artifacts/demo-context.json`
125
- - `artifacts/demo-plan.initial.json`
126
- - `artifacts/page-probes.json`
127
- - `artifacts/demo-plan.json`
128
- - `artifacts/captures.json`
129
- - `artifacts/render-manifest.json`
130
- - `artifacts/pr-demo.mp4`
131
-
132
- ## Troubleshooting
85
+ ## When to use prompt vs diff mode
133
86
 
134
- - If the app requires auth, run `auth:bootstrap` first.
135
- - If the video is wrong, inspect `captures.json` and `render-manifest.json` before changing renderer code.
136
- - If the planner misses the feature, create a manual plan.
137
- - If the route is unstable, add project hints in config.
87
+ | Mode | When to use |
88
+ |------|-------------|
89
+ | `--prompt "..."` | Feature demos, product tours, any app you can reach via URL |
90
+ | No prompt (diff) | PR walkthroughs, auto-detected from code changes |
138
91
 
139
- ## Share this skill
140
-
141
- Other users can install this repo as a pi package or point pi at this repo's `skills/` directory.
92
+ ## Recommended agent behavior
142
93
 
143
- Example:
94
+ 1. Prefer prompt-driven mode — it works with any web app, no git repo needed.
95
+ 2. Use `--frame` for polished output.
96
+ 3. If auth is required, run `demo-dev auth` first.
97
+ 4. If AI-generated selectors fail, the system auto-retries with fuzzy matching.
98
+ 5. Keep narration conversational and product-focused.
99
+ 6. For best voice quality, set `DEMO_TTS_PROVIDER=elevenlabs`.
144
100
 
145
- ```bash
146
- pi install /absolute/path/to/demo.dev
147
- ```
101
+ ## Outputs
148
102
 
149
- or
103
+ - `artifacts/pr-demo.mp4` — the final video
104
+ - `artifacts/demo-plan.json` — AI-generated scene plan
105
+ - `artifacts/visual-plan.json` — zoom keyframes + speed ramps
106
+ - `artifacts/voice-script.json` — narration text + audio paths
107
+ - `artifacts/continuous-capture.json` — recording metadata
150
108
 
151
- ```bash
152
- pi install git:github.com/your-org/demo.dev
153
- ```
109
+ See [references/configuration.md](references/configuration.md) and [references/recipes.md](references/recipes.md).
@@ -1,45 +1,19 @@
1
- # demo.dev skill configuration
1
+ # demo-dev configuration
2
2
 
3
- ## Quick bootstrap
3
+ ## Config file (optional)
4
4
 
5
- A fast way to bootstrap a repo is:
5
+ Config is **optional for prompt-driven mode**. Just pass `--base-url` and `--prompt` directly.
6
6
 
7
- ```bash
8
- demo-dev init
9
- ```
10
-
11
- That writes a starter config plus the workflow template.
12
-
13
- ## Minimal config
14
-
15
- Create `demo.dev.config.json` in the target repo:
7
+ For project-level defaults, create `demo.dev.config.json`:
16
8
 
17
9
  ```json
18
10
  {
19
11
  "projectName": "My App",
20
- "baseUrl": "http://localhost:3000",
21
- "readyUrl": "http://localhost:3000",
22
- "devCommand": "npm run dev",
23
- "baseRef": "origin/main",
24
- "outputDir": "artifacts",
25
- "preferredRoutes": ["/", "/dashboard"],
26
- "featureHints": ["home", "dashboard"]
27
- }
28
- ```
29
-
30
- ## Auth-enabled config
31
-
32
- ```json
33
- {
34
- "projectName": "My SaaS",
35
12
  "baseUrl": "https://app.example.com",
36
- "readyUrl": "https://app.example.com",
37
13
  "baseRef": "origin/main",
38
14
  "outputDir": "artifacts",
39
- "storageStatePath": "artifacts/storage-state.json",
40
- "saveStorageStatePath": "artifacts/storage-state.json",
41
- "preferredRoutes": ["/dashboard", "/settings"],
42
- "featureHints": ["dashboard", "settings"],
15
+ "preferredRoutes": ["/", "/dashboard", "/settings"],
16
+ "featureHints": ["dashboard", "onboarding", "settings"],
43
17
  "authRequiredRoutes": ["/dashboard", "/settings"],
44
18
  "auth": {
45
19
  "loginPath": "/login",
@@ -51,52 +25,70 @@ Create `demo.dev.config.json` in the target repo:
51
25
  }
52
26
  ```
53
27
 
54
- ## Useful overrides
28
+ Or bootstrap with `demo-dev init`.
55
29
 
56
- You can override config through:
30
+ ## Config fields
57
31
 
58
- - CLI flags like `--base-url`, `--output-dir`, `--base-ref`
59
- - env vars like `DEMO_STORAGE_STATE`, `DEMO_SAVE_STORAGE_STATE`, `DEMO_CONFIG`
60
- - GitHub Variables in workflow:
61
- - `DEMO_BASE_URL`
62
- - `DEMO_READY_URL`
63
- - `DEMO_DEV_COMMAND`
64
- - `DEMO_OUTPUT_DIR`
32
+ | Field | Description |
33
+ |-------|-------------|
34
+ | `projectName` | Display name for the project |
35
+ | `baseUrl` | Default URL of the web app |
36
+ | `baseRef` | Git base ref for diff-based planning (default: origin/main) |
37
+ | `outputDir` | Where to write artifacts (default: artifacts) |
38
+ | `preferredRoutes` | Routes the AI planner should explore and prioritize |
39
+ | `featureHints` | Feature names to help the AI planner |
40
+ | `authRequiredRoutes` | Routes that need login |
41
+ | `auth.*` | Login flow configuration |
65
42
 
66
- ## AI and TTS environment variables
43
+ ## Environment variables
67
44
 
68
- `demo.dev` uses explicit `DEMO_*` env vars for provider credentials.
69
- It does not fall back to generic provider keys such as `OPENAI_API_KEY` or `ELEVENLABS_API_KEY`.
45
+ ### AI providers
70
46
 
71
- Common values:
47
+ ```bash
48
+ DEMO_AI_PROVIDER=auto # auto, claude, cursor, codex, openai
49
+ DEMO_OPENAI_API_KEY=sk-... # Required for openai provider
50
+ DEMO_OPENAI_BASE_URL=... # Optional
51
+ DEMO_OPENAI_MODEL=... # Optional model override
52
+ ```
72
53
 
73
- - `DEMO_OPENAI_API_KEY`
74
- - `DEMO_OPENAI_BASE_URL`
75
- - `DEMO_OPENAI_MODEL`
76
- - `DEMO_AI_PROVIDER`
77
- - `DEMO_AI_MODEL`
78
- - `DEMO_TTS_PROVIDER`
79
- - `DEMO_TTS_MODEL`
80
- - `DEMO_TTS_VOICE`
81
- - `DEMO_ELEVENLABS_API_KEY`
82
- - `DEMO_ELEVENLABS_VOICE_ID`
54
+ ### TTS providers
55
+
56
+ ```bash
57
+ DEMO_TTS_PROVIDER=auto # auto, elevenlabs, openai, local
83
58
 
84
- Example:
59
+ # ElevenLabs (best quality)
60
+ DEMO_ELEVENLABS_API_KEY=sk_...
61
+ DEMO_ELEVENLABS_VOICE_ID=...
62
+
63
+ # OpenAI TTS
64
+ DEMO_OPENAI_API_KEY=sk-...
65
+
66
+ # Local (macOS only, free)
67
+ DEMO_LOCAL_TTS_VOICE=Samantha
68
+ ```
69
+
70
+ ### Auth
71
+
72
+ ```bash
73
+ DEMO_STORAGE_STATE=path/to/storage-state.json
74
+ DEMO_LOGIN_EMAIL=you@example.com
75
+ DEMO_LOGIN_PASSWORD=your-password
76
+ ```
77
+
78
+ ### Background music
85
79
 
86
80
  ```bash
87
- DEMO_OPENAI_API_KEY=your_openai_key
88
- DEMO_AI_PROVIDER=openai
89
- DEMO_TTS_PROVIDER=openai
81
+ DEMO_BGM_PATH=./assets/music/bed.mp3
82
+ DEMO_BGM_VOLUME=0.16
90
83
  ```
91
84
 
92
- ## Suggested setup for other repos
85
+ ## Precedence
86
+
87
+ CLI flags > Environment variables > Config file > Defaults
93
88
 
94
- 1. Copy `demo.dev.config.example.json` into the target repo.
95
- 2. Adjust `baseUrl`, `devCommand`, and `auth`.
96
- 3. Validate the repo:
89
+ ## Verify setup
97
90
 
98
91
  ```bash
99
92
  demo-dev doctor
100
93
  demo-dev config
101
- demo-dev pr-demo
102
94
  ```
@@ -1,83 +1,100 @@
1
- # demo.dev skill recipes
1
+ # demo-dev recipes
2
2
 
3
- ## Recipe: run on a simple local app
3
+ ## Recipe: prompt-driven demo (recommended)
4
4
 
5
5
  ```bash
6
- npm install
7
- npx playwright install chromium
8
- demo-dev doctor
9
- demo-dev pr-demo
6
+ demo-dev demo \
7
+ --base-url https://app.example.com \
8
+ --prompt "Show the onboarding flow, create a workspace, invite a teammate" \
9
+ --frame
10
+ ```
11
+
12
+ ## Recipe: prompt-driven with premium voice
13
+
14
+ ```bash
15
+ DEMO_ELEVENLABS_API_KEY=sk_... \
16
+ DEMO_ELEVENLABS_VOICE_ID=... \
17
+ DEMO_TTS_PROVIDER=elevenlabs \
18
+ demo-dev demo \
19
+ --base-url https://app.example.com \
20
+ --prompt "Show the dashboard and settings page" \
21
+ --frame
10
22
  ```
11
23
 
12
24
  ## Recipe: authenticated SaaS
13
25
 
14
26
  ```bash
15
- demo-dev auth:bootstrap \
27
+ demo-dev auth \
28
+ --base-url https://app.example.com \
16
29
  --email you@example.com \
17
30
  --password 'your-password'
18
31
 
19
- demo-dev pr-demo
32
+ demo-dev demo \
33
+ --base-url https://app.example.com \
34
+ --prompt "Show the inbox and open a conversation" \
35
+ --frame
20
36
  ```
21
37
 
22
- ## Recipe: OpenAI for planning and TTS
38
+ ## Recipe: diff-driven PR demo
23
39
 
24
40
  ```bash
25
- DEMO_OPENAI_API_KEY=your_openai_key \
26
- DEMO_AI_PROVIDER=openai \
27
- DEMO_TTS_PROVIDER=openai \
28
- demo-dev pr-demo
41
+ demo-dev demo --base-url http://localhost:3000
29
42
  ```
30
43
 
31
- This repo only reads `DEMO_*` provider keys for AI and TTS credentials.
44
+ No `--prompt` means it reads the git diff and auto-plans scenes.
32
45
 
33
- ## Recipe: inspect plan quality first
46
+ ## Recipe: high quality render
34
47
 
35
48
  ```bash
36
- demo-dev plan
37
- demo-dev probe
49
+ demo-dev demo \
50
+ --base-url https://app.example.com \
51
+ --prompt "..." \
52
+ --frame \
53
+ --quality high
38
54
  ```
39
55
 
40
- Use this when the feature is not obvious from the diff.
41
-
42
- ## Recipe: re-render without recapturing
56
+ ## Recipe: fast draft for iteration
43
57
 
44
58
  ```bash
45
- demo-dev render --manifest artifacts/render-manifest.json --out artifacts/pr-demo.mp4
59
+ demo-dev demo \
60
+ --base-url https://app.example.com \
61
+ --prompt "..." \
62
+ --quality draft
46
63
  ```
47
64
 
48
- ## Recipe: add background music
65
+ ## Recipe: with background music
49
66
 
50
67
  ```bash
51
68
  DEMO_BGM_PATH=./assets/music/bed.mp3 \
52
69
  DEMO_BGM_VOLUME=0.14 \
53
- DEMO_BGM_DUCKING=0.28 \
54
- demo-dev pr-demo
70
+ demo-dev demo \
71
+ --base-url https://app.example.com \
72
+ --prompt "..." \
73
+ --frame
55
74
  ```
56
75
 
57
- This will loop the music bed across the full video, fade it in and out, and lower it automatically while narration is playing.
58
-
59
- ## Recipe: manual feature film
60
-
61
- Use a manual plan when you need a specific flow like:
62
- - AI editing
63
- - inbox triage
64
- - onboarding
65
- - dashboard walkthrough
76
+ ## Recipe: OpenAI for planning and TTS
66
77
 
67
- Suggested flow:
78
+ ```bash
79
+ DEMO_OPENAI_API_KEY=sk-... \
80
+ DEMO_AI_PROVIDER=openai \
81
+ DEMO_TTS_PROVIDER=openai \
82
+ demo-dev demo \
83
+ --base-url https://app.example.com \
84
+ --prompt "..." \
85
+ --frame
86
+ ```
68
87
 
69
- 1. Create `artifacts/manual-plan.json`
70
- 2. Run capture against that plan
71
- 3. Build manifest
72
- 4. Render mp4
88
+ ## Recipe: inspect plan before recording
73
89
 
74
- ## Recipe: PR automation
90
+ ```bash
91
+ demo-dev plan
92
+ cat artifacts/demo-plan.json
93
+ ```
75
94
 
76
- In CI, run:
95
+ ## Recipe: PR automation in CI
77
96
 
78
97
  ```bash
79
- demo-dev pr-demo
98
+ demo-dev demo --base-url http://localhost:3000
80
99
  demo-dev comment --output-dir artifacts
81
100
  ```
82
-
83
- If the app needs a server, set `devCommand` and `readyUrl` in config or GitHub Variables.
@@ -257,6 +257,32 @@ const ZOOM_INJECT_SCRIPT = `
257
257
  })();
258
258
  `;
259
259
 
260
+ const SCENE_TRANSITION_SCRIPT = `
261
+ (() => {
262
+ if (document.getElementById('__scene-fade')) return;
263
+ const overlay = document.createElement('div');
264
+ overlay.id = '__scene-fade';
265
+ Object.assign(overlay.style, {
266
+ position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh',
267
+ zIndex: '999997', pointerEvents: 'none',
268
+ background: 'white', opacity: '0',
269
+ transition: 'opacity 0.4s ease-in-out',
270
+ });
271
+ document.body.appendChild(overlay);
272
+ window.__fadeOut = () => { overlay.style.opacity = '1'; };
273
+ window.__fadeIn = () => { overlay.style.opacity = '0'; };
274
+ })();
275
+ `;
276
+
277
+ /** Fade to white and back — used between scenes. */
278
+ const fadeTransition = async (page: Page) => {
279
+ await page.evaluate(SCENE_TRANSITION_SCRIPT).catch(() => undefined);
280
+ await page.evaluate(() => (window as any).__fadeOut?.()).catch(() => undefined);
281
+ await page.waitForTimeout(450);
282
+ await page.evaluate(() => (window as any).__fadeIn?.()).catch(() => undefined);
283
+ await page.waitForTimeout(450);
284
+ };
285
+
260
286
  /** Smoothly zoom into a point on the page (captured by screencast at 60fps). */
261
287
  const zoomToPoint = async (page: Page, x: number, y: number, scale = 1.5) => {
262
288
  await page.evaluate(ZOOM_INJECT_SCRIPT).catch(() => undefined);
@@ -307,13 +333,13 @@ const CURSOR_OVERLAY_SCRIPT = `
307
333
  cursor.id = '__ghost-cursor';
308
334
  Object.assign(cursor.style, {
309
335
  position: 'fixed', zIndex: '999999', pointerEvents: 'none',
310
- left: '0px', top: '0px', width: '22px', height: '32px',
311
- transition: 'left 0.04s cubic-bezier(.2,.8,.3,1), top 0.04s cubic-bezier(.2,.8,.3,1)',
312
- filter: 'drop-shadow(0 2px 3px rgba(0,0,0,0.4))',
336
+ left: '0px', top: '0px', width: '20px', height: '28px',
337
+ transition: 'left 0.03s linear, top 0.03s linear',
313
338
  });
314
- /* Standard macOS cursor: white fill, black outline, clean geometry */
315
- cursor.innerHTML = '<svg width="22" height="32" viewBox="0 0 22 32" xmlns="http://www.w3.org/2000/svg">' +
316
- '<path d="M1.5 0.5L1.5 24.5L7 18.5L11.5 28.5L14.5 27L10 17.5L18 17.5Z" fill="white" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>' +
339
+ /* Precise macOS default cursor traced from Apple's actual cursor spec */
340
+ cursor.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="28" viewBox="0 0 20 28">' +
341
+ '<defs><filter id="cs" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0.5" dy="1.5" stdDeviation="0.8" flood-opacity="0.45"/></filter></defs>' +
342
+ '<path filter="url(#cs)" d="M2.2 0L2.2 21.4L6.8 16.4L10.6 24.8L13.2 23.6L9.4 15.6L16 15.6Z" fill="white" stroke="black" stroke-width="1.1" stroke-linejoin="round"/>' +
317
343
  '</svg>';
318
344
  document.body.appendChild(cursor);
319
345
 
@@ -391,6 +417,7 @@ const runActionWithCursor = async (
391
417
  await page.evaluate(CURSOR_OVERLAY_SCRIPT).catch(() => undefined);
392
418
  await page.evaluate(CURSOR_TRACKER_SCRIPT).catch(() => undefined);
393
419
  await page.evaluate(ZOOM_INJECT_SCRIPT).catch(() => undefined);
420
+ await page.evaluate(SCENE_TRANSITION_SCRIPT).catch(() => undefined);
394
421
  await page.waitForTimeout(humanDelay(400));
395
422
  break;
396
423
  }
@@ -711,8 +738,8 @@ export const capturePlanContinuous = async (
711
738
  url: page.url(),
712
739
  });
713
740
 
714
- // Brief pause between scenes (feels like a human taking a breath)
715
- await page.waitForTimeout(humanDelay(400));
741
+ // Smooth fade transition between scenes
742
+ await fadeTransition(page);
716
743
  }
717
744
 
718
745
  // Final drain of cursor samples
@@ -6,13 +6,13 @@ import { demoPlanSchema } from "./schema.js";
6
6
 
7
7
  const buildPlannerPrompt = (context: DiffContext, projectConfig?: ProjectConfig) => {
8
8
  return [
9
- "You are a product demo director, not a QA engineer.",
9
+ "You are a product demo director creating a polished product walkthrough video.",
10
10
  "Goal: turn a git diff into a concise, recordable, voice-friendly demo plan.",
11
11
  "Requirements:",
12
12
  "1. Pick only 2-4 scenes that best represent visible user-facing changes.",
13
13
  "2. Keep actions stable and executable. Prefer navigate / click / fill / waitForText / waitForUrl / scroll.",
14
14
  "3. If the diff does not reveal specific elements, do not invent complex actions. Fall back to page-level presentation.",
15
- "4. narration should sound like product storytelling, not a test report.",
15
+ "4. narration should sound conversational and human — like giving a product tour to a friend, not reading a test report.",
16
16
  "5. Output strict JSON only, with no markdown explanation.",
17
17
  "6. All URLs must be in-app relative paths such as /dashboard.",
18
18
  "7. Keep captions short enough for on-screen usage.",