@argo-video/cli 0.1.0 → 0.1.1

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/asset-server.d.ts +7 -0
  4. package/dist/asset-server.d.ts.map +1 -0
  5. package/dist/asset-server.js +66 -0
  6. package/dist/asset-server.js.map +1 -0
  7. package/dist/captions.d.ts +17 -0
  8. package/dist/captions.d.ts.map +1 -0
  9. package/dist/captions.js +23 -0
  10. package/dist/captions.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +87 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +44 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +74 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/export.d.ts +18 -0
  20. package/dist/export.d.ts.map +1 -0
  21. package/dist/export.js +64 -0
  22. package/dist/export.js.map +1 -0
  23. package/dist/fixtures.d.ts +13 -0
  24. package/dist/fixtures.d.ts.map +1 -0
  25. package/dist/fixtures.js +36 -0
  26. package/dist/fixtures.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +14 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/init.d.ts +2 -0
  32. package/dist/init.d.ts.map +1 -0
  33. package/{src/init.ts → dist/init.js} +39 -54
  34. package/dist/init.js.map +1 -0
  35. package/dist/narration.d.ts +9 -0
  36. package/dist/narration.d.ts.map +1 -0
  37. package/dist/narration.js +27 -0
  38. package/dist/narration.js.map +1 -0
  39. package/dist/overlays/index.d.ts +8 -0
  40. package/dist/overlays/index.d.ts.map +1 -0
  41. package/dist/overlays/index.js +34 -0
  42. package/dist/overlays/index.js.map +1 -0
  43. package/dist/overlays/manifest.d.ts +5 -0
  44. package/dist/overlays/manifest.d.ts.map +1 -0
  45. package/dist/overlays/manifest.js +52 -0
  46. package/dist/overlays/manifest.js.map +1 -0
  47. package/dist/overlays/motion.d.ts +4 -0
  48. package/dist/overlays/motion.d.ts.map +1 -0
  49. package/dist/overlays/motion.js +25 -0
  50. package/dist/overlays/motion.js.map +1 -0
  51. package/dist/overlays/templates.d.ts +7 -0
  52. package/dist/overlays/templates.d.ts.map +1 -0
  53. package/dist/overlays/templates.js +98 -0
  54. package/dist/overlays/templates.js.map +1 -0
  55. package/dist/overlays/types.d.ts +42 -0
  56. package/dist/overlays/types.d.ts.map +1 -0
  57. package/dist/overlays/types.js +25 -0
  58. package/dist/overlays/types.js.map +1 -0
  59. package/dist/overlays/zones.d.ts +15 -0
  60. package/dist/overlays/zones.d.ts.map +1 -0
  61. package/dist/overlays/zones.js +69 -0
  62. package/dist/overlays/zones.js.map +1 -0
  63. package/dist/pipeline.d.ts +3 -0
  64. package/dist/pipeline.d.ts.map +1 -0
  65. package/dist/pipeline.js +93 -0
  66. package/dist/pipeline.js.map +1 -0
  67. package/dist/record.d.ts +14 -0
  68. package/dist/record.d.ts.map +1 -0
  69. package/dist/record.js +100 -0
  70. package/dist/record.js.map +1 -0
  71. package/dist/tts/align.d.ts +17 -0
  72. package/dist/tts/align.d.ts.map +1 -0
  73. package/dist/tts/align.js +40 -0
  74. package/dist/tts/align.js.map +1 -0
  75. package/dist/tts/cache.d.ts +31 -0
  76. package/dist/tts/cache.d.ts.map +1 -0
  77. package/dist/tts/cache.js +51 -0
  78. package/dist/tts/cache.js.map +1 -0
  79. package/dist/tts/engine.d.ts +41 -0
  80. package/dist/tts/engine.d.ts.map +1 -0
  81. package/dist/tts/engine.js +108 -0
  82. package/dist/tts/engine.js.map +1 -0
  83. package/dist/tts/generate.d.ts +20 -0
  84. package/dist/tts/generate.d.ts.map +1 -0
  85. package/dist/tts/generate.js +58 -0
  86. package/dist/tts/generate.js.map +1 -0
  87. package/dist/tts/kokoro.d.ts +13 -0
  88. package/dist/tts/kokoro.d.ts.map +1 -0
  89. package/dist/tts/kokoro.js +46 -0
  90. package/dist/tts/kokoro.js.map +1 -0
  91. package/package.json +13 -1
  92. package/.claude/settings.local.json +0 -34
  93. package/DESIGN.md +0 -261
  94. package/docs/enhancement-proposal.md +0 -262
  95. package/docs/superpowers/plans/2026-03-12-argo.md +0 -208
  96. package/docs/superpowers/plans/2026-03-12-editorial-overlay-system.md +0 -1560
  97. package/docs/superpowers/plans/2026-03-13-npm-rename-skill-showcase.md +0 -499
  98. package/docs/superpowers/specs/2026-03-13-npm-rename-skill-showcase-design.md +0 -109
  99. package/skills/argo-demo-creator.md +0 -355
  100. package/src/asset-server.ts +0 -81
  101. package/src/captions.ts +0 -36
  102. package/src/cli.ts +0 -97
  103. package/src/config.ts +0 -125
  104. package/src/export.ts +0 -93
  105. package/src/fixtures.ts +0 -50
  106. package/src/index.ts +0 -41
  107. package/src/narration.ts +0 -31
  108. package/src/overlays/index.ts +0 -54
  109. package/src/overlays/manifest.ts +0 -68
  110. package/src/overlays/motion.ts +0 -27
  111. package/src/overlays/templates.ts +0 -121
  112. package/src/overlays/types.ts +0 -73
  113. package/src/overlays/zones.ts +0 -82
  114. package/src/pipeline.ts +0 -120
  115. package/src/record.ts +0 -123
  116. package/src/tts/align.ts +0 -75
  117. package/src/tts/cache.ts +0 -65
  118. package/src/tts/engine.ts +0 -147
  119. package/src/tts/generate.ts +0 -83
  120. package/src/tts/kokoro.ts +0 -51
  121. package/tests/asset-server.test.ts +0 -67
  122. package/tests/captions.test.ts +0 -76
  123. package/tests/cli.test.ts +0 -131
  124. package/tests/config.test.ts +0 -150
  125. package/tests/e2e/fake-server.ts +0 -45
  126. package/tests/e2e/record.e2e.test.ts +0 -131
  127. package/tests/export.test.ts +0 -155
  128. package/tests/fixtures.test.ts +0 -74
  129. package/tests/init.test.ts +0 -77
  130. package/tests/narration.test.ts +0 -120
  131. package/tests/overlays/index.test.ts +0 -73
  132. package/tests/overlays/manifest.test.ts +0 -120
  133. package/tests/overlays/motion.test.ts +0 -34
  134. package/tests/overlays/templates.test.ts +0 -69
  135. package/tests/overlays/types.test.ts +0 -36
  136. package/tests/overlays/zones.test.ts +0 -49
  137. package/tests/pipeline.test.ts +0 -177
  138. package/tests/record.test.ts +0 -87
  139. package/tests/tts/align.test.ts +0 -118
  140. package/tests/tts/cache.test.ts +0 -110
  141. package/tests/tts/engine.test.ts +0 -204
  142. package/tests/tts/generate.test.ts +0 -177
  143. package/tests/tts/kokoro.test.ts +0 -25
  144. 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. |
@@ -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
- }