demo-dev 0.0.1-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/README.md +174 -0
- package/bin/demo-cli.js +26 -0
- package/bin/demo-dev.js +26 -0
- package/demo.dev.config.example.json +20 -0
- package/dist/index.d.ts +392 -0
- package/dist/index.js +2116 -0
- package/package.json +76 -0
- package/skills/demo-dev/SKILL.md +153 -0
- package/skills/demo-dev/references/configuration.md +102 -0
- package/skills/demo-dev/references/recipes.md +83 -0
- package/src/ai/provider.ts +254 -0
- package/src/auth/bootstrap.ts +72 -0
- package/src/browser/session.ts +43 -0
- package/src/capture/continuous-capture.ts +739 -0
- package/src/cli.ts +337 -0
- package/src/config/project.ts +183 -0
- package/src/github/comment.ts +134 -0
- package/src/index.ts +10 -0
- package/src/lib/data-uri.ts +21 -0
- package/src/lib/fs.ts +7 -0
- package/src/lib/git.ts +59 -0
- package/src/lib/media.ts +23 -0
- package/src/orchestrate.ts +166 -0
- package/src/planner/heuristic.ts +180 -0
- package/src/planner/index.ts +26 -0
- package/src/planner/llm.ts +85 -0
- package/src/planner/openai.ts +77 -0
- package/src/planner/prompt.ts +331 -0
- package/src/planner/refine.ts +155 -0
- package/src/planner/schema.ts +62 -0
- package/src/presentation/polish.ts +84 -0
- package/src/probe/page-probe.ts +225 -0
- package/src/render/browser-frame.ts +176 -0
- package/src/render/ffmpeg-compose.ts +779 -0
- package/src/render/visual-plan.ts +422 -0
- package/src/setup/doctor.ts +158 -0
- package/src/setup/init.ts +90 -0
- package/src/types.ts +105 -0
- package/src/voice/script.ts +42 -0
- package/src/voice/tts.ts +286 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# demo-dev
|
|
4
|
+
|
|
5
|
+
### Generate polished product demo videos with one command.
|
|
6
|
+
|
|
7
|
+
Give a URL and a prompt. Get a narrated, Screen Studio-style video.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx demo-dev demo --base-url https://your-app.com --prompt "Show the dashboard and create a new project" --frame
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
`demo-dev` opens your web app in a headless browser, navigates it like a human, records everything, adds AI narration, and renders a polished mp4.
|
|
20
|
+
|
|
21
|
+
- AI plans the demo from a natural language prompt
|
|
22
|
+
- Ghost-cursor moves the mouse with human-like Bezier curves
|
|
23
|
+
- Playwright screencast records continuously at 60fps
|
|
24
|
+
- CSS zoom animates smoothly into click targets (Screen Studio style)
|
|
25
|
+
- ElevenLabs / OpenAI / local TTS generates narration per scene
|
|
26
|
+
- FFmpeg composes the final video with speed ramps, browser frame, and audio
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g demo-dev
|
|
34
|
+
npx playwright install chromium
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Generate a demo:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
demo-dev demo \
|
|
41
|
+
--base-url https://your-app.com \
|
|
42
|
+
--prompt "Show the onboarding flow and invite a teammate" \
|
|
43
|
+
--frame
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Authenticated apps
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
demo-dev auth --base-url https://your-app.com --email you@example.com --password 'your-password'
|
|
50
|
+
demo-dev demo --base-url https://your-app.com --prompt "..." --frame
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
demo-dev demo # Full pipeline: prompt -> capture -> voice -> render
|
|
59
|
+
demo-dev auth # Login and save browser session
|
|
60
|
+
demo-dev capture # Record only (no voice/render)
|
|
61
|
+
demo-dev voice # Generate TTS narration only
|
|
62
|
+
demo-dev render # Capture + voice + compose video
|
|
63
|
+
demo-dev plan # Generate demo plan from git diff
|
|
64
|
+
demo-dev probe # Plan + probe pages for element discovery
|
|
65
|
+
demo-dev init # Create config file in your project
|
|
66
|
+
demo-dev doctor # Check environment (ffmpeg, playwright, etc.)
|
|
67
|
+
demo-dev config # Show resolved config
|
|
68
|
+
demo-dev providers # List available AI/TTS providers
|
|
69
|
+
demo-dev comment # Post demo as a PR comment
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Run `demo-dev <command> --help` for detailed options.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Key flags
|
|
77
|
+
|
|
78
|
+
| Flag | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `--prompt "..."` | Natural language description of the demo to create |
|
|
81
|
+
| `--frame` | Wrap video in a browser window with gradient background |
|
|
82
|
+
| `--quality draft\|standard\|high` | Video quality preset |
|
|
83
|
+
| `--base-url` | URL of the app to demo |
|
|
84
|
+
| `--base-ref` | Git base ref for diff-based planning (default: origin/main) |
|
|
85
|
+
| `--output-dir` | Where to write artifacts (default: artifacts) |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## AI & voice providers
|
|
90
|
+
|
|
91
|
+
Set via environment variables:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# AI planning (pick one)
|
|
95
|
+
DEMO_AI_PROVIDER=claude # Uses local Claude CLI
|
|
96
|
+
DEMO_AI_PROVIDER=openai # Uses OpenAI API
|
|
97
|
+
DEMO_OPENAI_API_KEY=sk-...
|
|
98
|
+
|
|
99
|
+
# Voice narration (pick one)
|
|
100
|
+
DEMO_TTS_PROVIDER=elevenlabs # Best quality
|
|
101
|
+
DEMO_ELEVENLABS_API_KEY=sk_...
|
|
102
|
+
DEMO_ELEVENLABS_VOICE_ID=...
|
|
103
|
+
|
|
104
|
+
DEMO_TTS_PROVIDER=openai # Good quality
|
|
105
|
+
DEMO_OPENAI_API_KEY=sk-...
|
|
106
|
+
|
|
107
|
+
DEMO_TTS_PROVIDER=local # Free, uses macOS `say` command
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Config file
|
|
113
|
+
|
|
114
|
+
Create a `demo.dev.config.json` in your project:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"projectName": "My App",
|
|
119
|
+
"baseUrl": "http://localhost:3000",
|
|
120
|
+
"baseRef": "origin/main",
|
|
121
|
+
"outputDir": "artifacts",
|
|
122
|
+
"preferredRoutes": ["/", "/dashboard"],
|
|
123
|
+
"featureHints": ["dashboard", "settings"],
|
|
124
|
+
"auth": {
|
|
125
|
+
"loginPath": "/login",
|
|
126
|
+
"emailTarget": { "strategy": "css", "value": "#email" },
|
|
127
|
+
"passwordTarget": { "strategy": "css", "value": "#password" },
|
|
128
|
+
"submitTarget": { "strategy": "role", "role": "button", "name": "Login" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## How it works
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
prompt + URL
|
|
139
|
+
|
|
|
140
|
+
v
|
|
141
|
+
Playwright explores the site (screenshots + interactive elements)
|
|
142
|
+
|
|
|
143
|
+
v
|
|
144
|
+
AI generates a demo plan (scenes, actions, narration text)
|
|
145
|
+
|
|
|
146
|
+
v
|
|
147
|
+
ghost-cursor executes actions with human-like mouse movement
|
|
148
|
+
|
|
|
149
|
+
v
|
|
150
|
+
page.screencast records continuous video + CSS zoom on interactions
|
|
151
|
+
|
|
|
152
|
+
v
|
|
153
|
+
ElevenLabs/OpenAI generates narration audio per scene
|
|
154
|
+
|
|
|
155
|
+
v
|
|
156
|
+
FFmpeg composes: speed ramps + browser frame + audio sync
|
|
157
|
+
|
|
|
158
|
+
v
|
|
159
|
+
polished mp4
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Requirements
|
|
165
|
+
|
|
166
|
+
- Node.js >= 20
|
|
167
|
+
- FFmpeg (install via `brew install ffmpeg` or equivalent)
|
|
168
|
+
- Chromium (installed via `npx playwright install chromium`)
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
package/bin/demo-cli.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = resolve(here, "..");
|
|
10
|
+
const cliPath = resolve(root, "src", "cli.ts");
|
|
11
|
+
const tsxPackagePath = require.resolve("tsx/package.json");
|
|
12
|
+
const tsxCliPath = resolve(dirname(tsxPackagePath), "dist", "cli.mjs");
|
|
13
|
+
|
|
14
|
+
const child = spawn(process.execPath, [tsxCliPath, cliPath, ...process.argv.slice(2)], {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
child.on("exit", (code, signal) => {
|
|
21
|
+
if (signal) {
|
|
22
|
+
process.kill(process.pid, signal);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
process.exit(code ?? 0);
|
|
26
|
+
});
|
package/bin/demo-dev.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = resolve(here, "..");
|
|
10
|
+
const cliPath = resolve(root, "src", "cli.ts");
|
|
11
|
+
const tsxPackagePath = require.resolve("tsx/package.json");
|
|
12
|
+
const tsxCliPath = resolve(dirname(tsxPackagePath), "dist", "cli.mjs");
|
|
13
|
+
|
|
14
|
+
const child = spawn(process.execPath, [tsxCliPath, cliPath, ...process.argv.slice(2)], {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
child.on("exit", (code, signal) => {
|
|
21
|
+
if (signal) {
|
|
22
|
+
process.kill(process.pid, signal);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
process.exit(code ?? 0);
|
|
26
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projectName": "My App",
|
|
3
|
+
"baseUrl": "http://localhost:3000",
|
|
4
|
+
"readyUrl": "http://localhost:3000",
|
|
5
|
+
"devCommand": "npm run dev",
|
|
6
|
+
"baseRef": "origin/main",
|
|
7
|
+
"outputDir": "artifacts",
|
|
8
|
+
"storageStatePath": "artifacts/storage-state.json",
|
|
9
|
+
"saveStorageStatePath": "artifacts/storage-state.json",
|
|
10
|
+
"preferredRoutes": ["/", "/dashboard"],
|
|
11
|
+
"featureHints": ["home", "dashboard"],
|
|
12
|
+
"authRequiredRoutes": ["/dashboard"],
|
|
13
|
+
"auth": {
|
|
14
|
+
"loginPath": "/login",
|
|
15
|
+
"emailTarget": { "strategy": "css", "value": "#email" },
|
|
16
|
+
"passwordTarget": { "strategy": "css", "value": "#password" },
|
|
17
|
+
"submitTarget": { "strategy": "role", "role": "button", "name": "Login" },
|
|
18
|
+
"postSubmitWaitMs": 1500
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
type ActionTarget = {
|
|
2
|
+
strategy: "label";
|
|
3
|
+
value: string;
|
|
4
|
+
exact?: boolean;
|
|
5
|
+
} | {
|
|
6
|
+
strategy: "text";
|
|
7
|
+
value: string;
|
|
8
|
+
exact?: boolean;
|
|
9
|
+
} | {
|
|
10
|
+
strategy: "placeholder";
|
|
11
|
+
value: string;
|
|
12
|
+
exact?: boolean;
|
|
13
|
+
} | {
|
|
14
|
+
strategy: "testId";
|
|
15
|
+
value: string;
|
|
16
|
+
} | {
|
|
17
|
+
strategy: "css";
|
|
18
|
+
value: string;
|
|
19
|
+
} | {
|
|
20
|
+
strategy: "role";
|
|
21
|
+
role: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
exact?: boolean;
|
|
24
|
+
};
|
|
25
|
+
type SceneAction = {
|
|
26
|
+
type: "navigate";
|
|
27
|
+
url: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "wait";
|
|
30
|
+
ms: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: "scroll";
|
|
33
|
+
y: number;
|
|
34
|
+
} | {
|
|
35
|
+
type: "scrollIntoView";
|
|
36
|
+
target: ActionTarget;
|
|
37
|
+
} | {
|
|
38
|
+
type: "click";
|
|
39
|
+
target: ActionTarget;
|
|
40
|
+
} | {
|
|
41
|
+
type: "hover";
|
|
42
|
+
target: ActionTarget;
|
|
43
|
+
} | {
|
|
44
|
+
type: "fill";
|
|
45
|
+
target: ActionTarget;
|
|
46
|
+
value: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: "press";
|
|
49
|
+
key: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: "select";
|
|
52
|
+
target: ActionTarget;
|
|
53
|
+
value: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: "dragSelect";
|
|
56
|
+
target: ActionTarget;
|
|
57
|
+
startX?: number;
|
|
58
|
+
startY?: number;
|
|
59
|
+
endX?: number;
|
|
60
|
+
endY?: number;
|
|
61
|
+
} | {
|
|
62
|
+
type: "waitForText";
|
|
63
|
+
value: string;
|
|
64
|
+
exact?: boolean;
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
} | {
|
|
67
|
+
type: "waitForUrl";
|
|
68
|
+
value: string;
|
|
69
|
+
timeoutMs?: number;
|
|
70
|
+
};
|
|
71
|
+
interface DiffContext {
|
|
72
|
+
currentBranch: string;
|
|
73
|
+
baseRef: string;
|
|
74
|
+
changedFiles: string[];
|
|
75
|
+
diffPreview: string;
|
|
76
|
+
}
|
|
77
|
+
interface DemoScene {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string;
|
|
80
|
+
goal: string;
|
|
81
|
+
url: string;
|
|
82
|
+
viewport: {
|
|
83
|
+
width: number;
|
|
84
|
+
height: number;
|
|
85
|
+
};
|
|
86
|
+
actions: SceneAction[];
|
|
87
|
+
narration: string;
|
|
88
|
+
caption: string;
|
|
89
|
+
durationMs: number;
|
|
90
|
+
evidenceHints: string[];
|
|
91
|
+
}
|
|
92
|
+
interface DemoPlan {
|
|
93
|
+
title: string;
|
|
94
|
+
summary: string;
|
|
95
|
+
branch: string;
|
|
96
|
+
generatedAt: string;
|
|
97
|
+
scenes: DemoScene[];
|
|
98
|
+
}
|
|
99
|
+
interface ProbeElement {
|
|
100
|
+
tag: string;
|
|
101
|
+
role: string;
|
|
102
|
+
name: string;
|
|
103
|
+
text?: string;
|
|
104
|
+
label?: string;
|
|
105
|
+
placeholder?: string;
|
|
106
|
+
type?: string;
|
|
107
|
+
href?: string;
|
|
108
|
+
testId?: string;
|
|
109
|
+
}
|
|
110
|
+
interface ProbeSnapshot {
|
|
111
|
+
resolvedUrl?: string;
|
|
112
|
+
pageTitle?: string;
|
|
113
|
+
headings: string[];
|
|
114
|
+
textPreview: string;
|
|
115
|
+
interactiveElements: ProbeElement[];
|
|
116
|
+
error?: string;
|
|
117
|
+
}
|
|
118
|
+
interface PageProbe {
|
|
119
|
+
sceneId: string;
|
|
120
|
+
sceneTitle: string;
|
|
121
|
+
requestedUrl: string;
|
|
122
|
+
initial: ProbeSnapshot;
|
|
123
|
+
followUpAction?: SceneAction;
|
|
124
|
+
followUp?: ProbeSnapshot;
|
|
125
|
+
}
|
|
126
|
+
interface VoiceToken {
|
|
127
|
+
text: string;
|
|
128
|
+
startMs: number;
|
|
129
|
+
endMs: number;
|
|
130
|
+
}
|
|
131
|
+
interface VoiceLine {
|
|
132
|
+
sceneId: string;
|
|
133
|
+
text: string;
|
|
134
|
+
estimatedMs: number;
|
|
135
|
+
audioDurationMs?: number;
|
|
136
|
+
tokens: VoiceToken[];
|
|
137
|
+
audioPath?: string;
|
|
138
|
+
audioSrc?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Continuous capture module.
|
|
143
|
+
*
|
|
144
|
+
* Instead of recording each scene as a separate browser context (which produces
|
|
145
|
+
* choppy, slideshow-like output), this module runs ALL scenes in a single
|
|
146
|
+
* continuous Playwright session while:
|
|
147
|
+
*
|
|
148
|
+
* 1. Using the Playwright 1.59 `page.screencast` API for a single unbroken
|
|
149
|
+
* WebM recording.
|
|
150
|
+
* 2. Using `ghost-cursor-playwright` for human-like Bézier-curve mouse
|
|
151
|
+
* movement (Fitts's Law, overshoot, random landing points inside elements).
|
|
152
|
+
* 3. Logging high-frequency cursor positions + interaction events as metadata
|
|
153
|
+
* so post-processing can generate intelligent zoom keyframes à la Screen
|
|
154
|
+
* Studio.
|
|
155
|
+
*
|
|
156
|
+
* The entire module is headless-capable and CI-friendly.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/** A single logged cursor position sample. */
|
|
160
|
+
interface CursorSample {
|
|
161
|
+
/** Milliseconds since capture start */
|
|
162
|
+
atMs: number;
|
|
163
|
+
x: number;
|
|
164
|
+
y: number;
|
|
165
|
+
}
|
|
166
|
+
/** A logged interaction event with screen coordinates + timing. */
|
|
167
|
+
interface CaptureInteraction {
|
|
168
|
+
type: SceneAction["type"] | "scene-start" | "scene-end" | "stable";
|
|
169
|
+
sceneId: string;
|
|
170
|
+
atMs: number;
|
|
171
|
+
x?: number;
|
|
172
|
+
y?: number;
|
|
173
|
+
width?: number;
|
|
174
|
+
height?: number;
|
|
175
|
+
/** For fill actions – the value typed. */
|
|
176
|
+
fillValue?: string;
|
|
177
|
+
}
|
|
178
|
+
/** Scene timing marker within the continuous recording. */
|
|
179
|
+
interface SceneMarker {
|
|
180
|
+
sceneId: string;
|
|
181
|
+
sceneTitle: string;
|
|
182
|
+
startMs: number;
|
|
183
|
+
endMs: number;
|
|
184
|
+
url: string;
|
|
185
|
+
}
|
|
186
|
+
/** Full output of a continuous capture session. */
|
|
187
|
+
interface ContinuousCaptureResult {
|
|
188
|
+
/** Path to the single continuous WebM recording. */
|
|
189
|
+
videoPath: string;
|
|
190
|
+
/** High-frequency cursor position log. */
|
|
191
|
+
cursorLog: CursorSample[];
|
|
192
|
+
/** Interaction events (clicks, fills, navigations, etc.). */
|
|
193
|
+
interactions: CaptureInteraction[];
|
|
194
|
+
/** Per-scene timing markers. */
|
|
195
|
+
sceneMarkers: SceneMarker[];
|
|
196
|
+
/** Total duration in milliseconds. */
|
|
197
|
+
totalDurationMs: number;
|
|
198
|
+
/** Viewport size used for the recording. */
|
|
199
|
+
viewport: {
|
|
200
|
+
width: number;
|
|
201
|
+
height: number;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
interface ContinuousCaptureOptions {
|
|
205
|
+
baseUrl: string;
|
|
206
|
+
outputDir: string;
|
|
207
|
+
viewport?: {
|
|
208
|
+
width: number;
|
|
209
|
+
height: number;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
declare const capturePlanContinuous: (plan: DemoPlan, options: ContinuousCaptureOptions) => Promise<ContinuousCaptureResult>;
|
|
213
|
+
|
|
214
|
+
interface ProjectAuthConfig {
|
|
215
|
+
loginPath?: string;
|
|
216
|
+
emailTarget?: ActionTarget;
|
|
217
|
+
passwordTarget?: ActionTarget;
|
|
218
|
+
submitTarget?: ActionTarget;
|
|
219
|
+
successUrlPattern?: string;
|
|
220
|
+
postSubmitWaitMs?: number;
|
|
221
|
+
}
|
|
222
|
+
interface ProjectConfig {
|
|
223
|
+
projectName?: string;
|
|
224
|
+
baseUrl?: string;
|
|
225
|
+
readyUrl?: string;
|
|
226
|
+
devCommand?: string;
|
|
227
|
+
baseRef?: string;
|
|
228
|
+
outputDir?: string;
|
|
229
|
+
storageStatePath?: string;
|
|
230
|
+
saveStorageStatePath?: string;
|
|
231
|
+
preferredRoutes?: string[];
|
|
232
|
+
featureHints?: string[];
|
|
233
|
+
authRequiredRoutes?: string[];
|
|
234
|
+
auth?: ProjectAuthConfig;
|
|
235
|
+
}
|
|
236
|
+
declare const loadProjectConfig: (configPath?: string) => Promise<{
|
|
237
|
+
path?: string;
|
|
238
|
+
config: ProjectConfig;
|
|
239
|
+
}>;
|
|
240
|
+
declare const applyProjectEnvironment: (config: ProjectConfig) => void;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Prompt-driven demo planner.
|
|
244
|
+
*
|
|
245
|
+
* Instead of starting from a git diff, this planner takes a natural language
|
|
246
|
+
* prompt ("show the Magic Inbox, filter by positive replies, open a thread")
|
|
247
|
+
* and generates a DemoPlan by:
|
|
248
|
+
*
|
|
249
|
+
* 1. Launching Playwright to explore the target site
|
|
250
|
+
* 2. Taking screenshots + collecting interactive elements from key pages
|
|
251
|
+
* 3. Sending the screenshots + element inventory + user prompt to an LLM
|
|
252
|
+
* 4. Parsing the LLM response into a validated DemoPlan
|
|
253
|
+
*
|
|
254
|
+
* This gives users a zero-config, one-shot way to create demos.
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
declare const buildPromptPlan: (options: {
|
|
258
|
+
prompt: string;
|
|
259
|
+
baseUrl: string;
|
|
260
|
+
outputDir: string;
|
|
261
|
+
projectConfig?: ProjectConfig;
|
|
262
|
+
}) => Promise<DemoPlan>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Post-processing intelligence layer.
|
|
266
|
+
*
|
|
267
|
+
* Analyzes the metadata from a continuous capture session (cursor positions,
|
|
268
|
+
* interaction events, scene markers) and generates a "visual plan" — a timeline
|
|
269
|
+
* of zoom keyframes, speed ramps, and cursor smoothing parameters that can be
|
|
270
|
+
* consumed by the FFmpeg composition pipeline.
|
|
271
|
+
*
|
|
272
|
+
* The goal is to replicate Screen Studio's approach: raw recording + metadata
|
|
273
|
+
* → intelligent post-processing that makes the video feel cinematic.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
interface ZoomKeyframe {
|
|
277
|
+
/** Time offset in the raw recording (ms). */
|
|
278
|
+
atMs: number;
|
|
279
|
+
/** Zoom center X as fraction of viewport width (0–1). */
|
|
280
|
+
centerX: number;
|
|
281
|
+
/** Zoom center Y as fraction of viewport height (0–1). */
|
|
282
|
+
centerY: number;
|
|
283
|
+
/** Zoom scale (1.0 = no zoom, 2.0 = 2x zoom). */
|
|
284
|
+
scale: number;
|
|
285
|
+
/** Duration to hold this zoom (ms) before transitioning. */
|
|
286
|
+
holdMs: number;
|
|
287
|
+
/** Easing for transitioning INTO this keyframe. */
|
|
288
|
+
easing: "ease-in-out" | "ease-out" | "spring";
|
|
289
|
+
/** Transition duration from previous keyframe (ms). */
|
|
290
|
+
transitionMs: number;
|
|
291
|
+
}
|
|
292
|
+
interface SpeedSegment {
|
|
293
|
+
/** Start time in raw recording (ms). */
|
|
294
|
+
startMs: number;
|
|
295
|
+
/** End time in raw recording (ms). */
|
|
296
|
+
endMs: number;
|
|
297
|
+
/** Playback speed (1.0 = normal, 2.0 = 2x, 0.5 = slow). */
|
|
298
|
+
speed: number;
|
|
299
|
+
/** Reason for this speed change. */
|
|
300
|
+
reason: "normal" | "loading" | "idle" | "transition";
|
|
301
|
+
}
|
|
302
|
+
interface SmoothedCursorPoint {
|
|
303
|
+
/** Time in output video timeline (ms). */
|
|
304
|
+
atMs: number;
|
|
305
|
+
x: number;
|
|
306
|
+
y: number;
|
|
307
|
+
}
|
|
308
|
+
interface VisualPlanResult {
|
|
309
|
+
zoomKeyframes: ZoomKeyframe[];
|
|
310
|
+
speedSegments: SpeedSegment[];
|
|
311
|
+
smoothedCursor: SmoothedCursorPoint[];
|
|
312
|
+
/** Total duration after speed adjustments (ms). */
|
|
313
|
+
adjustedDurationMs: number;
|
|
314
|
+
}
|
|
315
|
+
declare const buildVisualPlan: (capture: ContinuousCaptureResult) => VisualPlanResult;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Screen Studio–style browser frame compositing.
|
|
319
|
+
*
|
|
320
|
+
* Wraps raw screen recordings in a macOS browser window shell with:
|
|
321
|
+
* - Gradient background (configurable colors)
|
|
322
|
+
* - macOS-style browser chrome (traffic lights, URL bar)
|
|
323
|
+
* - Rounded corners + drop shadow
|
|
324
|
+
* - Padding/margin around the window
|
|
325
|
+
*
|
|
326
|
+
* Implemented as an FFmpeg filter chain that composites a pre-rendered
|
|
327
|
+
* browser chrome PNG on top of the recording, positioned within a
|
|
328
|
+
* gradient background canvas.
|
|
329
|
+
*/
|
|
330
|
+
interface BrowserFrameOptions {
|
|
331
|
+
/** Gradient start color (top-left). Default: "#f97316" (orange) */
|
|
332
|
+
gradientFrom?: string;
|
|
333
|
+
/** Gradient end color (bottom-right). Default: "#a855f7" (purple) */
|
|
334
|
+
gradientTo?: string;
|
|
335
|
+
/** Padding around the browser window in pixels. Default: 48 */
|
|
336
|
+
padding?: number;
|
|
337
|
+
/** URL to display in the address bar. Default: inferred from capture */
|
|
338
|
+
displayUrl?: string;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* FFmpeg-based video composition pipeline.
|
|
343
|
+
*
|
|
344
|
+
* Takes the raw continuous recording + visual plan and produces the final
|
|
345
|
+
* polished mp4 with:
|
|
346
|
+
* - Smooth zoom/pan following interaction targets
|
|
347
|
+
* - Variable playback speed (compress loading, normal for interactions)
|
|
348
|
+
* - Narration audio overlay with proper sync
|
|
349
|
+
* - Background music with ducking
|
|
350
|
+
* - Intro/outro title cards
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
type VideoQuality = "draft" | "standard" | "high";
|
|
354
|
+
interface ComposeOptions {
|
|
355
|
+
/** Path to the continuous WebM recording. */
|
|
356
|
+
videoPath: string;
|
|
357
|
+
/** Output mp4 path. */
|
|
358
|
+
outputPath: string;
|
|
359
|
+
/** Visual plan with zoom keyframes and speed segments. */
|
|
360
|
+
visualPlan: VisualPlanResult;
|
|
361
|
+
/** Capture result for viewport info and scene markers. */
|
|
362
|
+
capture: ContinuousCaptureResult;
|
|
363
|
+
/** Voice lines (narration audio files). */
|
|
364
|
+
voiceLines?: VoiceLine[];
|
|
365
|
+
/** Background music. */
|
|
366
|
+
bgm?: {
|
|
367
|
+
path: string;
|
|
368
|
+
volume?: number;
|
|
369
|
+
ducking?: number;
|
|
370
|
+
};
|
|
371
|
+
/** Video title for intro card. */
|
|
372
|
+
title?: string;
|
|
373
|
+
/** Output resolution. */
|
|
374
|
+
width?: number;
|
|
375
|
+
height?: number;
|
|
376
|
+
fps?: number;
|
|
377
|
+
/** Video quality: "draft" (fast, low), "standard" (default), "high" (slow, best). */
|
|
378
|
+
quality?: VideoQuality;
|
|
379
|
+
/** Wrap video in a Screen Studio–style browser frame with gradient background. */
|
|
380
|
+
frame?: BrowserFrameOptions | boolean;
|
|
381
|
+
}
|
|
382
|
+
declare const composeVideo: (options: ComposeOptions) => Promise<string>;
|
|
383
|
+
|
|
384
|
+
declare const writeJson: (path: string, value: unknown) => Promise<void>;
|
|
385
|
+
|
|
386
|
+
declare const buildVoiceScript: (plan: DemoPlan) => VoiceLine[];
|
|
387
|
+
|
|
388
|
+
declare const synthesizeVoice: (lines: VoiceLine[], options?: {
|
|
389
|
+
outputDir?: string;
|
|
390
|
+
}) => Promise<VoiceLine[]>;
|
|
391
|
+
|
|
392
|
+
export { type ActionTarget, type DemoPlan, type DemoScene, type DiffContext, type PageProbe, type ProbeElement, type ProbeSnapshot, type SceneAction, type VoiceLine, type VoiceToken, applyProjectEnvironment, buildPromptPlan, buildVisualPlan, buildVoiceScript, capturePlanContinuous, composeVideo, loadProjectConfig, synthesizeVoice, writeJson };
|