@argo-video/cli 0.1.0 → 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.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/asset-server.d.ts +7 -0
- package/dist/asset-server.d.ts.map +1 -0
- package/dist/asset-server.js +69 -0
- package/dist/asset-server.js.map +1 -0
- package/dist/captions.d.ts +17 -0
- package/dist/captions.d.ts.map +1 -0
- package/dist/captions.js +23 -0
- package/dist/captions.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +87 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +76 -0
- package/dist/config.js.map +1 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +66 -0
- package/dist/export.js.map +1 -0
- package/dist/fixtures.d.ts +13 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +49 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/{src/init.ts → dist/init.js} +39 -54
- package/dist/init.js.map +1 -0
- package/dist/narration.d.ts +32 -0
- package/dist/narration.d.ts.map +1 -0
- package/dist/narration.js +86 -0
- package/dist/narration.js.map +1 -0
- package/dist/overlays/index.d.ts +13 -0
- package/dist/overlays/index.d.ts.map +1 -0
- package/dist/overlays/index.js +45 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/overlays/manifest.d.ts +5 -0
- package/dist/overlays/manifest.d.ts.map +1 -0
- package/dist/overlays/manifest.js +52 -0
- package/dist/overlays/manifest.js.map +1 -0
- package/dist/overlays/motion.d.ts +4 -0
- package/dist/overlays/motion.d.ts.map +1 -0
- package/dist/overlays/motion.js +25 -0
- package/dist/overlays/motion.js.map +1 -0
- package/dist/overlays/templates.d.ts +8 -0
- package/dist/overlays/templates.d.ts.map +1 -0
- package/dist/overlays/templates.js +102 -0
- package/dist/overlays/templates.js.map +1 -0
- package/dist/overlays/types.d.ts +46 -0
- package/dist/overlays/types.d.ts.map +1 -0
- package/dist/overlays/types.js +25 -0
- package/dist/overlays/types.js.map +1 -0
- package/dist/overlays/zones.d.ts +23 -0
- package/dist/overlays/zones.d.ts.map +1 -0
- package/dist/overlays/zones.js +117 -0
- package/dist/overlays/zones.js.map +1 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +109 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/record.d.ts +15 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +110 -0
- package/dist/record.js.map +1 -0
- package/dist/tts/align.d.ts +26 -0
- package/dist/tts/align.d.ts.map +1 -0
- package/dist/tts/align.js +53 -0
- package/dist/tts/align.js.map +1 -0
- package/dist/tts/cache.d.ts +31 -0
- package/dist/tts/cache.d.ts.map +1 -0
- package/dist/tts/cache.js +51 -0
- package/dist/tts/cache.js.map +1 -0
- package/dist/tts/engine.d.ts +41 -0
- package/dist/tts/engine.d.ts.map +1 -0
- package/dist/tts/engine.js +108 -0
- package/dist/tts/engine.js.map +1 -0
- package/dist/tts/generate.d.ts +21 -0
- package/dist/tts/generate.d.ts.map +1 -0
- package/dist/tts/generate.js +61 -0
- package/dist/tts/generate.js.map +1 -0
- package/dist/tts/kokoro.d.ts +30 -0
- package/dist/tts/kokoro.d.ts.map +1 -0
- package/dist/tts/kokoro.js +66 -0
- package/dist/tts/kokoro.js.map +1 -0
- package/package.json +13 -1
- package/.claude/settings.local.json +0 -34
- package/DESIGN.md +0 -261
- package/docs/enhancement-proposal.md +0 -262
- package/docs/superpowers/plans/2026-03-12-argo.md +0 -208
- package/docs/superpowers/plans/2026-03-12-editorial-overlay-system.md +0 -1560
- package/docs/superpowers/plans/2026-03-13-npm-rename-skill-showcase.md +0 -499
- package/docs/superpowers/specs/2026-03-13-npm-rename-skill-showcase-design.md +0 -109
- package/skills/argo-demo-creator.md +0 -355
- package/src/asset-server.ts +0 -81
- package/src/captions.ts +0 -36
- package/src/cli.ts +0 -97
- package/src/config.ts +0 -125
- package/src/export.ts +0 -93
- package/src/fixtures.ts +0 -50
- package/src/index.ts +0 -41
- package/src/narration.ts +0 -31
- package/src/overlays/index.ts +0 -54
- package/src/overlays/manifest.ts +0 -68
- package/src/overlays/motion.ts +0 -27
- package/src/overlays/templates.ts +0 -121
- package/src/overlays/types.ts +0 -73
- package/src/overlays/zones.ts +0 -82
- package/src/pipeline.ts +0 -120
- package/src/record.ts +0 -123
- package/src/tts/align.ts +0 -75
- package/src/tts/cache.ts +0 -65
- package/src/tts/engine.ts +0 -147
- package/src/tts/generate.ts +0 -83
- package/src/tts/kokoro.ts +0 -51
- package/tests/asset-server.test.ts +0 -67
- package/tests/captions.test.ts +0 -76
- package/tests/cli.test.ts +0 -131
- package/tests/config.test.ts +0 -150
- package/tests/e2e/fake-server.ts +0 -45
- package/tests/e2e/record.e2e.test.ts +0 -131
- package/tests/export.test.ts +0 -155
- package/tests/fixtures.test.ts +0 -74
- package/tests/init.test.ts +0 -77
- package/tests/narration.test.ts +0 -120
- package/tests/overlays/index.test.ts +0 -73
- package/tests/overlays/manifest.test.ts +0 -120
- package/tests/overlays/motion.test.ts +0 -34
- package/tests/overlays/templates.test.ts +0 -69
- package/tests/overlays/types.test.ts +0 -36
- package/tests/overlays/zones.test.ts +0 -49
- package/tests/pipeline.test.ts +0 -177
- package/tests/record.test.ts +0 -87
- package/tests/tts/align.test.ts +0 -118
- package/tests/tts/cache.test.ts +0 -110
- package/tests/tts/engine.test.ts +0 -204
- package/tests/tts/generate.test.ts +0 -177
- package/tests/tts/kokoro.test.ts +0 -25
- package/tsconfig.json +0 -19
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: argo-demo-creator
|
|
3
|
-
description: Create polished product demo videos using Argo. Handles the full workflow from installation through finished video with AI voiceover and overlays.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
Argo is a CLI tool that turns Playwright demo scripts into polished product demo videos with AI voiceover and animated overlays. You write a Playwright script that records your app, mark scene boundaries with `narration.mark()`, provide voiceover text in a JSON manifest, and Argo handles TTS generation, timing alignment, overlay injection, and video export via ffmpeg.
|
|
9
|
-
|
|
10
|
-
Two operating modes:
|
|
11
|
-
|
|
12
|
-
- **Autonomous mode**: The agent drives the full pipeline — installs Argo, initializes the project, explores the target app, writes the demo script and manifests, and runs the pipeline to produce a finished video.
|
|
13
|
-
- **Assistive mode**: The agent helps a user who already has Argo scripts, config, or a partial setup — answering questions, fixing errors, editing manifests, or re-running pipeline steps.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Prerequisites
|
|
18
|
-
|
|
19
|
-
Before writing or running any demo, verify the following:
|
|
20
|
-
|
|
21
|
-
1. **`@argo-video/cli` is installed** — check `devDependencies` in `package.json`. If missing:
|
|
22
|
-
```bash
|
|
23
|
-
npm i -D @argo-video/cli
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
2. **`argo.config.js` exists in the project root** — if missing, scaffold it:
|
|
27
|
-
```bash
|
|
28
|
-
npx argo init
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
3. **Node.js** — required to run Argo and Playwright.
|
|
32
|
-
|
|
33
|
-
4. **Playwright** — required for browser recording. Installed as a dependency of `@argo-video/cli`.
|
|
34
|
-
|
|
35
|
-
5. **ffmpeg** — required for video export and audio alignment.
|
|
36
|
-
- macOS: `brew install ffmpeg`
|
|
37
|
-
- Linux: `apt install ffmpeg`
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Quick Start (Autonomous Workflow)
|
|
42
|
-
|
|
43
|
-
Follow these steps in order when creating a demo from scratch:
|
|
44
|
-
|
|
45
|
-
1. **Check installation** — look for `@argo-video/cli` in `package.json` devDependencies. Install with `npm i -D @argo-video/cli` if absent.
|
|
46
|
-
|
|
47
|
-
2. **Check config** — look for `argo.config.js` in the project root. Run `npx argo init` if absent.
|
|
48
|
-
|
|
49
|
-
3. **Ask for the app's base URL** — Argo needs a running app to record. Ask the user: "What is the URL of the running app?" (e.g., `http://localhost:3000`). Set this as `baseURL` in `argo.config.js`.
|
|
50
|
-
|
|
51
|
-
4. **Explore the app** — navigate to the `baseURL` and explore routes and features so you can write a meaningful demo script.
|
|
52
|
-
|
|
53
|
-
5. **Write the demo script** — create `demos/<name>.demo.ts` with Playwright actions and `narration.mark()` calls to define scene boundaries.
|
|
54
|
-
|
|
55
|
-
6. **Write the voiceover manifest** — create `demos/<name>.voiceover.json` with narration text for each scene. Scene names must exactly match `narration.mark()` arguments.
|
|
56
|
-
|
|
57
|
-
7. **Optionally write the overlay manifest** — create `demos/<name>.overlays.json` with overlay cues keyed to scenes.
|
|
58
|
-
|
|
59
|
-
8. **Run the pipeline**:
|
|
60
|
-
```bash
|
|
61
|
-
npx argo pipeline <name>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
9. **Report the output** — the finished video will be in the `outputDir` (default: `videos/`). Report the file path to the user.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Script Authoring
|
|
69
|
-
|
|
70
|
-
Demo scripts live in the `demos/` directory and use the `.demo.ts` extension.
|
|
71
|
-
|
|
72
|
-
**Critical**: always import `test` from `@argo-video/cli`, not from `@playwright/test`. The Argo test fixture provides the `narration` object alongside `page`. Using the wrong import means `narration` will be undefined and the pipeline will have no timing data.
|
|
73
|
-
|
|
74
|
-
The `test` fixture provides:
|
|
75
|
-
- `page` — a Playwright `Page` instance
|
|
76
|
-
- `narration` — a `NarrationTimeline` instance with a `mark(sceneName: string)` method
|
|
77
|
-
|
|
78
|
-
Use `narration.mark('scene-name')` to define scene boundaries. These timestamps are written to a timing file that the pipeline uses to align voiceover clips and overlays with the correct moments in the video.
|
|
79
|
-
|
|
80
|
-
Use `page.waitForTimeout(ms)` to add deliberate pauses for pacing — giving the viewer time to absorb what is happening on screen.
|
|
81
|
-
|
|
82
|
-
Example:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { test } from '@argo-video/cli';
|
|
86
|
-
import { showOverlay } from '@argo-video/cli';
|
|
87
|
-
|
|
88
|
-
test('my-demo', async ({ page, narration }) => {
|
|
89
|
-
await page.goto('/');
|
|
90
|
-
await page.waitForTimeout(1000);
|
|
91
|
-
|
|
92
|
-
narration.mark('intro');
|
|
93
|
-
await showOverlay(page, 'intro', {
|
|
94
|
-
type: 'lower-third',
|
|
95
|
-
text: 'Welcome to our app',
|
|
96
|
-
motion: 'fade-in',
|
|
97
|
-
}, 3000);
|
|
98
|
-
|
|
99
|
-
narration.mark('feature');
|
|
100
|
-
await page.click('#start-button');
|
|
101
|
-
await page.waitForTimeout(2000);
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Overlay API
|
|
108
|
-
|
|
109
|
-
Three functions are available for injecting overlays directly from the demo script.
|
|
110
|
-
|
|
111
|
-
### `showOverlay(page, scene, cue, durationMs)`
|
|
112
|
-
|
|
113
|
-
Show an overlay for a fixed duration, then auto-remove it.
|
|
114
|
-
|
|
115
|
-
- `page` — Playwright Page
|
|
116
|
-
- `scene` — string scene name (used for logging/alignment)
|
|
117
|
-
- `cue` — overlay template object (see template types below)
|
|
118
|
-
- `durationMs` — how long to display the overlay in milliseconds
|
|
119
|
-
|
|
120
|
-
### `withOverlay(page, scene, cue, action)`
|
|
121
|
-
|
|
122
|
-
Show an overlay for the duration of an async action, then hide it automatically (even if the action throws).
|
|
123
|
-
|
|
124
|
-
- `page` — Playwright Page
|
|
125
|
-
- `scene` — string scene name
|
|
126
|
-
- `cue` — overlay template object
|
|
127
|
-
- `action` — async function to run while overlay is visible
|
|
128
|
-
|
|
129
|
-
### `hideOverlay(page, zone?)`
|
|
130
|
-
|
|
131
|
-
Manually hide an overlay in a specific zone, or all zones if `zone` is omitted.
|
|
132
|
-
|
|
133
|
-
- `page` — Playwright Page
|
|
134
|
-
- `zone` — optional zone string (see Zones below)
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
### Overlay Template Types
|
|
139
|
-
|
|
140
|
-
Every cue object must include a `type` field. All other fields are optional unless noted.
|
|
141
|
-
|
|
142
|
-
**`lower-third`** — text banner, typically at the bottom of the screen.
|
|
143
|
-
```typescript
|
|
144
|
-
{
|
|
145
|
-
type: 'lower-third',
|
|
146
|
-
text: string, // required
|
|
147
|
-
placement?: Zone, // default: 'bottom-center'
|
|
148
|
-
motion?: MotionPreset, // default: 'none'
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**`headline-card`** — large card with a title and optional supporting text.
|
|
153
|
-
```typescript
|
|
154
|
-
{
|
|
155
|
-
type: 'headline-card',
|
|
156
|
-
title: string, // required
|
|
157
|
-
kicker?: string, // small label above the title
|
|
158
|
-
body?: string, // supporting paragraph below the title
|
|
159
|
-
placement?: Zone, // default: 'bottom-center'
|
|
160
|
-
motion?: MotionPreset, // default: 'none'
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
**`callout`** — compact annotation for pointing out a UI element or fact.
|
|
165
|
-
```typescript
|
|
166
|
-
{
|
|
167
|
-
type: 'callout',
|
|
168
|
-
text: string, // required
|
|
169
|
-
placement?: Zone, // default: 'bottom-center'
|
|
170
|
-
motion?: MotionPreset, // default: 'none'
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**`image-card`** — image with optional caption text. `src` is a path relative to `demos/assets/`.
|
|
175
|
-
```typescript
|
|
176
|
-
{
|
|
177
|
-
type: 'image-card',
|
|
178
|
-
src: string, // required, relative to demos/assets/
|
|
179
|
-
title?: string,
|
|
180
|
-
body?: string,
|
|
181
|
-
placement?: Zone, // default: 'bottom-center'
|
|
182
|
-
motion?: MotionPreset, // default: 'none'
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
### Zones
|
|
189
|
-
|
|
190
|
-
Controls where the overlay appears on screen. Only one overlay can occupy a zone at a time. Different zones can display overlays simultaneously.
|
|
191
|
-
|
|
192
|
-
| Zone | Description |
|
|
193
|
-
|---|---|
|
|
194
|
-
| `bottom-center` | Default. Horizontally centered at the bottom. |
|
|
195
|
-
| `top-left` | Top-left corner. |
|
|
196
|
-
| `top-right` | Top-right corner. |
|
|
197
|
-
| `bottom-left` | Bottom-left corner. |
|
|
198
|
-
| `bottom-right` | Bottom-right corner. |
|
|
199
|
-
| `center` | Center of the screen. |
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
### Motion Presets
|
|
204
|
-
|
|
205
|
-
Controls how the overlay animates in.
|
|
206
|
-
|
|
207
|
-
| Preset | Description |
|
|
208
|
-
|---|---|
|
|
209
|
-
| `none` | Default. Overlay appears instantly with no animation. |
|
|
210
|
-
| `fade-in` | 300ms opacity transition. |
|
|
211
|
-
| `slide-in` | 400ms combined translateX and opacity transition. |
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Overlay Manifest (optional)
|
|
216
|
-
|
|
217
|
-
File: `demos/<name>.overlays.json`
|
|
218
|
-
|
|
219
|
-
An optional JSON array of overlay entries. Used by the pipeline for automated overlay injection keyed to scene timestamps. The `scene` field must match a `narration.mark()` call in the demo script.
|
|
220
|
-
|
|
221
|
-
```json
|
|
222
|
-
[
|
|
223
|
-
{
|
|
224
|
-
"scene": "intro",
|
|
225
|
-
"type": "lower-third",
|
|
226
|
-
"text": "Welcome to our app",
|
|
227
|
-
"motion": "fade-in"
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
"scene": "feature",
|
|
231
|
-
"type": "headline-card",
|
|
232
|
-
"title": "Key Feature",
|
|
233
|
-
"body": "Description of the feature",
|
|
234
|
-
"placement": "top-right",
|
|
235
|
-
"motion": "slide-in"
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Supported fields in each entry: `scene`, `type`, `text`, `title`, `kicker`, `body`, `src`, `placement`, `motion`. Which fields are valid depends on the `type` — see Overlay Template Types above.
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Voiceover Manifest
|
|
245
|
-
|
|
246
|
-
File: `demos/<name>.voiceover.json`
|
|
247
|
-
|
|
248
|
-
A JSON array of voiceover entries. Each `scene` must exactly match a `narration.mark()` argument in the corresponding demo script. The TTS step generates an audio clip per scene and the align step places each clip at the correct timestamp in the video.
|
|
249
|
-
|
|
250
|
-
```json
|
|
251
|
-
[
|
|
252
|
-
{
|
|
253
|
-
"scene": "intro",
|
|
254
|
-
"text": "Welcome to our application. Let me show you around."
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
"scene": "feature",
|
|
258
|
-
"text": "This feature makes everything easier.",
|
|
259
|
-
"voice": "af_heart",
|
|
260
|
-
"speed": 1.0
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Fields:
|
|
266
|
-
- `scene` — **required**. Must exactly match a `narration.mark()` call.
|
|
267
|
-
- `text` — **required**. The spoken narration for this scene.
|
|
268
|
-
- `voice` — optional. Default: `af_heart`.
|
|
269
|
-
- `speed` — optional. Default: `1.0`.
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## Configuration
|
|
274
|
-
|
|
275
|
-
File: `argo.config.js` in the project root. Uses ES module `export default { ... }` syntax.
|
|
276
|
-
|
|
277
|
-
| Field | Default | Description |
|
|
278
|
-
|---|---|---|
|
|
279
|
-
| `baseURL` | *(required, no default)* | URL of the running app to record. |
|
|
280
|
-
| `demosDir` | `'demos'` | Directory containing demo scripts and manifests. |
|
|
281
|
-
| `outputDir` | `'videos'` | Directory where finished videos are written. |
|
|
282
|
-
| `tts.defaultVoice` | `'af_heart'` | Default TTS voice used when `voice` is omitted from a voiceover entry. |
|
|
283
|
-
| `tts.defaultSpeed` | `1.0` | Default TTS speed used when `speed` is omitted from a voiceover entry. |
|
|
284
|
-
| `video.width` | `1920` | Recording and output video width in pixels. |
|
|
285
|
-
| `video.height` | `1080` | Recording and output video height in pixels. |
|
|
286
|
-
| `video.fps` | `30` | Frames per second. |
|
|
287
|
-
| `export.preset` | `'slow'` | ffmpeg encoding preset. Slower presets produce smaller files. |
|
|
288
|
-
| `export.crf` | `16` | ffmpeg constant rate factor. Lower = higher quality, larger file. |
|
|
289
|
-
|
|
290
|
-
Example:
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
export default {
|
|
294
|
-
baseURL: 'http://localhost:3000',
|
|
295
|
-
demosDir: 'demos/',
|
|
296
|
-
outputDir: 'videos/',
|
|
297
|
-
tts: { defaultVoice: 'af_heart', defaultSpeed: 1.0 },
|
|
298
|
-
video: { width: 1920, height: 1080, fps: 30 },
|
|
299
|
-
export: { preset: 'slow', crf: 16 },
|
|
300
|
-
};
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
## Pipeline
|
|
306
|
-
|
|
307
|
-
The pipeline runs four steps in order: **TTS → Record → Align → Export**
|
|
308
|
-
|
|
309
|
-
### All-in-one (recommended)
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
npx argo pipeline <name>
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Takes a bare demo name (e.g., `showcase` for `demos/showcase.demo.ts`). Handles all four steps internally in the correct order.
|
|
316
|
-
|
|
317
|
-
### Standalone commands
|
|
318
|
-
|
|
319
|
-
Run individual steps when debugging or re-running a single stage.
|
|
320
|
-
|
|
321
|
-
```bash
|
|
322
|
-
# Step 1: Generate TTS audio clips from the voiceover manifest
|
|
323
|
-
# IMPORTANT: takes a FILE PATH, not a bare name
|
|
324
|
-
npx argo tts generate demos/<name>.voiceover.json
|
|
325
|
-
|
|
326
|
-
# Step 2: Record the Playwright demo
|
|
327
|
-
# Takes a bare demo name
|
|
328
|
-
npx argo record <name>
|
|
329
|
-
|
|
330
|
-
# Step 3 & 4: Export (align + encode)
|
|
331
|
-
# Takes a bare demo name
|
|
332
|
-
npx argo export <name>
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**Common mistake with `tts generate`**: passing `showcase` instead of `demos/showcase.voiceover.json` will fail silently. Always pass the full file path.
|
|
336
|
-
|
|
337
|
-
### Other commands
|
|
338
|
-
|
|
339
|
-
```bash
|
|
340
|
-
# Scaffold a new project with example demo, config, and manifests
|
|
341
|
-
npx argo init
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
## Troubleshooting
|
|
347
|
-
|
|
348
|
-
| Error | Cause | Fix |
|
|
349
|
-
|---|---|---|
|
|
350
|
-
| `"No video recording found"` | Playwright did not record the browser session. | Ensure `playwright.config.ts` has `use: { video: 'on' }` or `video: { mode: 'on' }`. |
|
|
351
|
-
| `"No timing file found"` | The timing file was not written — either wrong import or no `narration.mark()` calls. | Verify the demo script imports `test` from `@argo-video/cli` (not `@playwright/test`) and calls `narration.mark()` at least once. |
|
|
352
|
-
| `"ffmpeg/ffprobe not found"` | ffmpeg is not installed or not on PATH. | Install ffmpeg: `brew install ffmpeg` (macOS) or `apt install ffmpeg` (Linux). |
|
|
353
|
-
| `"Playwright recording failed"` | Playwright cannot reach the app. | Verify the `baseURL` in `argo.config.js` points to a running, accessible app. |
|
|
354
|
-
| `"No TTS clips generated"` | Scene names in the voiceover manifest do not match `narration.mark()` arguments. | Check that every `scene` value in `<name>.voiceover.json` exactly matches a `narration.mark('...')` call in the demo script. |
|
|
355
|
-
| `"Failed to parse overlay manifest"` | `<name>.overlays.json` is malformed or uses unsupported values. | Validate the JSON (no trailing commas, balanced brackets). Confirm all `type`, `placement`, and `motion` values are from the supported lists above. |
|
package/src/asset-server.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
import { createReadStream, existsSync } from 'node:fs';
|
|
3
|
-
import { resolve, relative, extname, basename } from 'node:path';
|
|
4
|
-
import type { AddressInfo } from 'node:net';
|
|
5
|
-
|
|
6
|
-
export interface AssetServer {
|
|
7
|
-
url: string;
|
|
8
|
-
port: number;
|
|
9
|
-
close: () => Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const MIME_TYPES: Record<string, string> = {
|
|
13
|
-
'.png': 'image/png',
|
|
14
|
-
'.jpg': 'image/jpeg',
|
|
15
|
-
'.jpeg': 'image/jpeg',
|
|
16
|
-
'.gif': 'image/gif',
|
|
17
|
-
'.svg': 'image/svg+xml',
|
|
18
|
-
'.webp': 'image/webp',
|
|
19
|
-
'.txt': 'text/plain',
|
|
20
|
-
'.json': 'application/json',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export function startAssetServer(assetDir: string): Promise<AssetServer> {
|
|
24
|
-
const resolvedDir = resolve(assetDir);
|
|
25
|
-
|
|
26
|
-
return new Promise((resolvePromise) => {
|
|
27
|
-
const server = http.createServer((req, res) => {
|
|
28
|
-
const rawUrl = req.url ?? '/';
|
|
29
|
-
const urlPath = decodeURIComponent(rawUrl);
|
|
30
|
-
|
|
31
|
-
// Reject any URL containing path traversal sequences before normalization
|
|
32
|
-
if (urlPath.includes('..')) {
|
|
33
|
-
res.writeHead(403);
|
|
34
|
-
res.end('Forbidden');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const filePath = resolve(resolvedDir, '.' + urlPath);
|
|
39
|
-
|
|
40
|
-
// Path traversal check after resolution
|
|
41
|
-
const rel = relative(resolvedDir, filePath);
|
|
42
|
-
if (rel.startsWith('..') || resolve(filePath) !== filePath) {
|
|
43
|
-
res.writeHead(403);
|
|
44
|
-
res.end('Forbidden');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!existsSync(filePath)) {
|
|
49
|
-
// Defense against normalized path traversal: HTTP clients (e.g. fetch/undici)
|
|
50
|
-
// normalize paths like "/../secret.txt" → "/secret.txt" before sending,
|
|
51
|
-
// so the raw ".." never reaches the server. As a secondary guard, if the
|
|
52
|
-
// requested filename exists outside assetDir (e.g. in its parent), return
|
|
53
|
-
// 403 to prevent leaking information about files reachable via traversal.
|
|
54
|
-
const filename = basename(filePath);
|
|
55
|
-
const parentDir = resolve(resolvedDir, '..');
|
|
56
|
-
if (parentDir !== resolvedDir && existsSync(resolve(parentDir, filename))) {
|
|
57
|
-
res.writeHead(403);
|
|
58
|
-
res.end('Forbidden');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
res.writeHead(404);
|
|
62
|
-
res.end('Not found');
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const ext = extname(filePath).toLowerCase();
|
|
67
|
-
const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';
|
|
68
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
69
|
-
createReadStream(filePath).pipe(res);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
server.listen(0, '127.0.0.1', () => {
|
|
73
|
-
const { port } = server.address() as AddressInfo;
|
|
74
|
-
resolvePromise({
|
|
75
|
-
url: `http://127.0.0.1:${port}`,
|
|
76
|
-
port,
|
|
77
|
-
close: () => new Promise((res) => server.close(() => res())),
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
package/src/captions.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Page } from '@playwright/test';
|
|
2
|
-
import { showOverlay, hideOverlay, withOverlay } from './overlays/index.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Show a lower-third caption for `durationMs`, then remove it.
|
|
6
|
-
* @deprecated Use showOverlay() for new code.
|
|
7
|
-
*/
|
|
8
|
-
export async function showCaption(
|
|
9
|
-
page: Page,
|
|
10
|
-
scene: string,
|
|
11
|
-
text: string,
|
|
12
|
-
durationMs: number,
|
|
13
|
-
): Promise<void> {
|
|
14
|
-
await showOverlay(page, scene, { type: 'lower-third', text }, durationMs);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Remove the caption overlay.
|
|
19
|
-
* @deprecated Use hideOverlay() for new code.
|
|
20
|
-
*/
|
|
21
|
-
export async function hideCaption(page: Page): Promise<void> {
|
|
22
|
-
await hideOverlay(page, 'bottom-center');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Show a caption while running `action`, then hide it (even on error).
|
|
27
|
-
* @deprecated Use withOverlay() for new code.
|
|
28
|
-
*/
|
|
29
|
-
export async function withCaption(
|
|
30
|
-
page: Page,
|
|
31
|
-
scene: string,
|
|
32
|
-
text: string,
|
|
33
|
-
action: () => Promise<void>,
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
await withOverlay(page, scene, { type: 'lower-third', text }, action);
|
|
36
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { loadConfig, type ArgoConfig } from './config.js';
|
|
3
|
-
import { record } from './record.js';
|
|
4
|
-
import { generateClips } from './tts/generate.js';
|
|
5
|
-
import { exportVideo } from './export.js';
|
|
6
|
-
import { runPipeline } from './pipeline.js';
|
|
7
|
-
import { init } from './init.js';
|
|
8
|
-
|
|
9
|
-
async function ensureTTSEngine(config: ArgoConfig): Promise<ArgoConfig> {
|
|
10
|
-
if (!config.tts.engine) {
|
|
11
|
-
const { KokoroEngine } = await import('./tts/kokoro.js');
|
|
12
|
-
config.tts.engine = new KokoroEngine();
|
|
13
|
-
}
|
|
14
|
-
return config;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function createProgram(): Command {
|
|
18
|
-
const program = new Command();
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.name('argo')
|
|
22
|
-
.description('Turn Playwright demo scripts into polished product demo videos with AI voiceover')
|
|
23
|
-
.option('-c, --config <path>', 'path to config file');
|
|
24
|
-
|
|
25
|
-
program
|
|
26
|
-
.command('record <demo>')
|
|
27
|
-
.description('Record a demo using Playwright')
|
|
28
|
-
.action(async (demo: string) => {
|
|
29
|
-
const configPath = program.opts().config;
|
|
30
|
-
const config = await loadConfig(process.cwd(), configPath);
|
|
31
|
-
if (!config.baseURL) {
|
|
32
|
-
throw new Error('baseURL is required but not set. Set it in argo.config.js or pass --config.');
|
|
33
|
-
}
|
|
34
|
-
await record(demo, {
|
|
35
|
-
demosDir: config.demosDir,
|
|
36
|
-
baseURL: config.baseURL,
|
|
37
|
-
video: { width: config.video.width, height: config.video.height },
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const tts = program
|
|
42
|
-
.command('tts')
|
|
43
|
-
.description('TTS commands');
|
|
44
|
-
|
|
45
|
-
tts
|
|
46
|
-
.command('generate <manifest>')
|
|
47
|
-
.description('Generate TTS clips from a manifest file')
|
|
48
|
-
.action(async (manifest: string) => {
|
|
49
|
-
const configPath = program.opts().config;
|
|
50
|
-
const config = await ensureTTSEngine(await loadConfig(process.cwd(), configPath));
|
|
51
|
-
await generateClips({
|
|
52
|
-
manifestPath: manifest,
|
|
53
|
-
demoName: manifest.replace(/^.*\//, '').replace(/\.voiceover\.json$/, '').replace(/\.json$/, ''),
|
|
54
|
-
engine: config.tts.engine!,
|
|
55
|
-
projectRoot: '.',
|
|
56
|
-
defaults: { voice: config.tts.defaultVoice, speed: config.tts.defaultSpeed },
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
program
|
|
61
|
-
.command('export <demo>')
|
|
62
|
-
.description('Export demo to MP4')
|
|
63
|
-
.action(async (demo: string) => {
|
|
64
|
-
const configPath = program.opts().config;
|
|
65
|
-
const config = await loadConfig(process.cwd(), configPath);
|
|
66
|
-
await exportVideo({
|
|
67
|
-
demoName: demo,
|
|
68
|
-
argoDir: '.argo',
|
|
69
|
-
outputDir: config.outputDir,
|
|
70
|
-
preset: config.export.preset,
|
|
71
|
-
crf: config.export.crf,
|
|
72
|
-
fps: config.video.fps,
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
program
|
|
77
|
-
.command('pipeline <demo>')
|
|
78
|
-
.description('Run the full pipeline: TTS → record → export')
|
|
79
|
-
.action(async (demo: string) => {
|
|
80
|
-
const configPath = program.opts().config;
|
|
81
|
-
const config = await ensureTTSEngine(await loadConfig(process.cwd(), configPath));
|
|
82
|
-
await runPipeline(demo, config);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
program
|
|
86
|
-
.command('init')
|
|
87
|
-
.description('Initialize a new Argo project')
|
|
88
|
-
.action(async () => {
|
|
89
|
-
await init();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return program;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (process.env.VITEST === undefined) {
|
|
96
|
-
createProgram().parseAsync(process.argv).catch((err) => { console.error(err.message); process.exit(1); });
|
|
97
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { access } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import type { TTSEngine } from './tts/engine.js';
|
|
5
|
-
export type { TTSEngine };
|
|
6
|
-
|
|
7
|
-
// ---- Types ----
|
|
8
|
-
|
|
9
|
-
export interface TTSConfig {
|
|
10
|
-
defaultVoice: string;
|
|
11
|
-
defaultSpeed: number;
|
|
12
|
-
engine?: TTSEngine;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface VideoConfig {
|
|
16
|
-
width: number;
|
|
17
|
-
height: number;
|
|
18
|
-
fps: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ExportConfig {
|
|
22
|
-
preset: string;
|
|
23
|
-
crf: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ArgoConfig {
|
|
27
|
-
baseURL?: string;
|
|
28
|
-
demosDir: string;
|
|
29
|
-
outputDir: string;
|
|
30
|
-
tts: TTSConfig;
|
|
31
|
-
video: VideoConfig;
|
|
32
|
-
export: ExportConfig;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type UserConfig = Partial<
|
|
36
|
-
Omit<ArgoConfig, 'tts' | 'video' | 'export'> & {
|
|
37
|
-
tts: Partial<TTSConfig>;
|
|
38
|
-
video: Partial<VideoConfig>;
|
|
39
|
-
export: Partial<ExportConfig>;
|
|
40
|
-
}
|
|
41
|
-
>;
|
|
42
|
-
|
|
43
|
-
// ---- Defaults ----
|
|
44
|
-
|
|
45
|
-
const DEFAULTS: ArgoConfig = {
|
|
46
|
-
demosDir: 'demos',
|
|
47
|
-
outputDir: 'videos',
|
|
48
|
-
tts: { defaultVoice: 'af_heart', defaultSpeed: 1.0 },
|
|
49
|
-
video: { width: 1920, height: 1080, fps: 30 },
|
|
50
|
-
export: { preset: 'slow', crf: 16 },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// ---- Functions ----
|
|
54
|
-
|
|
55
|
-
export function defineConfig(userConfig: UserConfig): ArgoConfig {
|
|
56
|
-
return {
|
|
57
|
-
...DEFAULTS,
|
|
58
|
-
...userConfig,
|
|
59
|
-
tts: { ...DEFAULTS.tts, ...userConfig.tts },
|
|
60
|
-
video: { ...DEFAULTS.video, ...userConfig.video },
|
|
61
|
-
export: { ...DEFAULTS.export, ...userConfig.export },
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function demosProject(options: {
|
|
66
|
-
baseURL: string;
|
|
67
|
-
demosDir?: string;
|
|
68
|
-
}) {
|
|
69
|
-
return {
|
|
70
|
-
name: 'demos',
|
|
71
|
-
testDir: options.demosDir ?? 'demos',
|
|
72
|
-
testMatch: '**/*.demo.ts',
|
|
73
|
-
use: {
|
|
74
|
-
baseURL: options.baseURL,
|
|
75
|
-
video: 'on' as const,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const CONFIG_FILENAMES = [
|
|
81
|
-
'argo.config.ts',
|
|
82
|
-
'argo.config.js',
|
|
83
|
-
'argo.config.mjs',
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
87
|
-
try {
|
|
88
|
-
await access(filePath);
|
|
89
|
-
return true;
|
|
90
|
-
} catch (err: any) {
|
|
91
|
-
if (err?.code === 'ENOENT') return false;
|
|
92
|
-
throw new Error(`Cannot access ${filePath}: ${err.message}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export async function loadConfig(
|
|
97
|
-
cwd: string,
|
|
98
|
-
explicitPath?: string,
|
|
99
|
-
): Promise<ArgoConfig> {
|
|
100
|
-
let configPath: string | undefined = explicitPath;
|
|
101
|
-
|
|
102
|
-
if (!configPath) {
|
|
103
|
-
for (const filename of CONFIG_FILENAMES) {
|
|
104
|
-
const candidate = join(cwd, filename);
|
|
105
|
-
if (await fileExists(candidate)) {
|
|
106
|
-
configPath = candidate;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!configPath) {
|
|
113
|
-
return defineConfig({});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let mod: any;
|
|
117
|
-
try {
|
|
118
|
-
const fileUrl = pathToFileURL(configPath).href;
|
|
119
|
-
mod = await import(fileUrl);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
throw new Error(`Failed to load config from ${configPath}: ${(err as Error).message}`);
|
|
122
|
-
}
|
|
123
|
-
const userConfig: UserConfig = mod.default ?? mod;
|
|
124
|
-
return defineConfig(userConfig);
|
|
125
|
-
}
|