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.
Files changed (41) hide show
  1. package/README.md +174 -0
  2. package/bin/demo-cli.js +26 -0
  3. package/bin/demo-dev.js +26 -0
  4. package/demo.dev.config.example.json +20 -0
  5. package/dist/index.d.ts +392 -0
  6. package/dist/index.js +2116 -0
  7. package/package.json +76 -0
  8. package/skills/demo-dev/SKILL.md +153 -0
  9. package/skills/demo-dev/references/configuration.md +102 -0
  10. package/skills/demo-dev/references/recipes.md +83 -0
  11. package/src/ai/provider.ts +254 -0
  12. package/src/auth/bootstrap.ts +72 -0
  13. package/src/browser/session.ts +43 -0
  14. package/src/capture/continuous-capture.ts +739 -0
  15. package/src/cli.ts +337 -0
  16. package/src/config/project.ts +183 -0
  17. package/src/github/comment.ts +134 -0
  18. package/src/index.ts +10 -0
  19. package/src/lib/data-uri.ts +21 -0
  20. package/src/lib/fs.ts +7 -0
  21. package/src/lib/git.ts +59 -0
  22. package/src/lib/media.ts +23 -0
  23. package/src/orchestrate.ts +166 -0
  24. package/src/planner/heuristic.ts +180 -0
  25. package/src/planner/index.ts +26 -0
  26. package/src/planner/llm.ts +85 -0
  27. package/src/planner/openai.ts +77 -0
  28. package/src/planner/prompt.ts +331 -0
  29. package/src/planner/refine.ts +155 -0
  30. package/src/planner/schema.ts +62 -0
  31. package/src/presentation/polish.ts +84 -0
  32. package/src/probe/page-probe.ts +225 -0
  33. package/src/render/browser-frame.ts +176 -0
  34. package/src/render/ffmpeg-compose.ts +779 -0
  35. package/src/render/visual-plan.ts +422 -0
  36. package/src/setup/doctor.ts +158 -0
  37. package/src/setup/init.ts +90 -0
  38. package/src/types.ts +105 -0
  39. package/src/voice/script.ts +42 -0
  40. package/src/voice/tts.ts +286 -0
  41. 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
@@ -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,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
+ }
@@ -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 };