@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.
- 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 +66 -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 +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/export.d.ts +18 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +64 -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 +36 -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 +9 -0
- package/dist/narration.d.ts.map +1 -0
- package/dist/narration.js +27 -0
- package/dist/narration.js.map +1 -0
- package/dist/overlays/index.d.ts +8 -0
- package/dist/overlays/index.d.ts.map +1 -0
- package/dist/overlays/index.js +34 -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 +7 -0
- package/dist/overlays/templates.d.ts.map +1 -0
- package/dist/overlays/templates.js +98 -0
- package/dist/overlays/templates.js.map +1 -0
- package/dist/overlays/types.d.ts +42 -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 +15 -0
- package/dist/overlays/zones.d.ts.map +1 -0
- package/dist/overlays/zones.js +69 -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 +93 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/record.d.ts +14 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +100 -0
- package/dist/record.js.map +1 -0
- package/dist/tts/align.d.ts +17 -0
- package/dist/tts/align.d.ts.map +1 -0
- package/dist/tts/align.js +40 -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 +20 -0
- package/dist/tts/generate.d.ts.map +1 -0
- package/dist/tts/generate.js +58 -0
- package/dist/tts/generate.js.map +1 -0
- package/dist/tts/kokoro.d.ts +13 -0
- package/dist/tts/kokoro.d.ts.map +1 -0
- package/dist/tts/kokoro.js +46 -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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTS clip generation with manifest parsing and cache integration.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { ClipCache } from './cache.js';
|
|
6
|
+
export async function generateClips(options) {
|
|
7
|
+
const { manifestPath, demoName, engine, projectRoot, defaults } = options;
|
|
8
|
+
// 1. Check manifest exists
|
|
9
|
+
if (!fs.existsSync(manifestPath)) {
|
|
10
|
+
throw new Error(`Manifest file not found: ${manifestPath}`);
|
|
11
|
+
}
|
|
12
|
+
// 2. Read and parse JSON
|
|
13
|
+
let rawEntries;
|
|
14
|
+
try {
|
|
15
|
+
const content = fs.readFileSync(manifestPath, 'utf-8');
|
|
16
|
+
rawEntries = JSON.parse(content);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err instanceof SyntaxError) {
|
|
20
|
+
throw new Error(`Failed to parse manifest ${manifestPath}: ${err.message}`);
|
|
21
|
+
}
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(rawEntries)) {
|
|
25
|
+
throw new Error(`Manifest ${manifestPath} must contain a JSON array`);
|
|
26
|
+
}
|
|
27
|
+
// 3. Validate entries
|
|
28
|
+
for (const entry of rawEntries) {
|
|
29
|
+
const e = entry;
|
|
30
|
+
if (typeof e.scene !== 'string' || typeof e.text !== 'string') {
|
|
31
|
+
throw new Error('Manifest entry missing required field: scene and text are required');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const cache = new ClipCache(projectRoot);
|
|
35
|
+
const results = [];
|
|
36
|
+
for (const raw of rawEntries) {
|
|
37
|
+
const r = raw;
|
|
38
|
+
// 4. Build ManifestEntry with defaults
|
|
39
|
+
const manifestEntry = {
|
|
40
|
+
scene: r.scene,
|
|
41
|
+
text: r.text,
|
|
42
|
+
voice: r.voice ?? defaults?.voice,
|
|
43
|
+
speed: r.speed ?? defaults?.speed,
|
|
44
|
+
};
|
|
45
|
+
const clipPath = cache.getClipPath(demoName, manifestEntry);
|
|
46
|
+
// 5/6. Check cache or generate
|
|
47
|
+
if (!cache.isCached(demoName, manifestEntry)) {
|
|
48
|
+
const wavBuffer = await engine.generate(manifestEntry.text, {
|
|
49
|
+
voice: manifestEntry.voice,
|
|
50
|
+
speed: manifestEntry.speed,
|
|
51
|
+
});
|
|
52
|
+
cache.cacheClip(demoName, manifestEntry, wavBuffer);
|
|
53
|
+
}
|
|
54
|
+
results.push({ scene: manifestEntry.scene, clipPath });
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/tts/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAsB,MAAM,YAAY,CAAC;AAe3D,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAE1E,2BAA2B;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,yBAAyB;IACzB,IAAI,UAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACvD,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,YAAY,YAAY,4BAA4B,CAAC,CAAC;IACxE,CAAC;IAED,sBAAsB;IACtB,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,WAAW,CAAC,CAAC;IACzC,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,GAA8B,CAAC;QAEzC,uCAAuC;QACvC,MAAM,aAAa,GAAkB;YACnC,KAAK,EAAE,CAAC,CAAC,KAAe;YACxB,IAAI,EAAE,CAAC,CAAC,IAAc;YACtB,KAAK,EAAG,CAAC,CAAC,KAA4B,IAAI,QAAQ,EAAE,KAAK;YACzD,KAAK,EAAG,CAAC,CAAC,KAA4B,IAAI,QAAQ,EAAE,KAAK;SAC1D,CAAC;QAEF,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAE5D,+BAA+B;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC;YAC7C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE;gBAC1D,KAAK,EAAE,aAAa,CAAC,KAAK;gBAC1B,KAAK,EAAE,aAAa,CAAC,KAAK;aAC3B,CAAC,CAAC;YACH,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TTSEngine, TTSEngineOptions } from './engine.js';
|
|
2
|
+
export declare class KokoroEngine implements TTSEngine {
|
|
3
|
+
private tts;
|
|
4
|
+
private modelId;
|
|
5
|
+
private dtype;
|
|
6
|
+
constructor(options?: {
|
|
7
|
+
modelId?: string;
|
|
8
|
+
dtype?: string;
|
|
9
|
+
});
|
|
10
|
+
private getTTS;
|
|
11
|
+
generate(text: string, options: TTSEngineOptions): Promise<Buffer>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=kokoro.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kokoro.d.ts","sourceRoot":"","sources":["../../src/tts/kokoro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/D,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;YAK5C,MAAM;IAiBd,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;CAqBzE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class KokoroEngine {
|
|
2
|
+
tts = null;
|
|
3
|
+
modelId;
|
|
4
|
+
dtype;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.modelId = options?.modelId ?? 'onnx-community/Kokoro-82M-ONNX';
|
|
7
|
+
this.dtype = options?.dtype ?? 'fp32';
|
|
8
|
+
}
|
|
9
|
+
async getTTS() {
|
|
10
|
+
if (this.tts)
|
|
11
|
+
return this.tts;
|
|
12
|
+
try {
|
|
13
|
+
const { KokoroTTS } = await import('kokoro-js');
|
|
14
|
+
this.tts = await KokoroTTS.from_pretrained(this.modelId, {
|
|
15
|
+
dtype: this.dtype,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
throw new Error(`Failed to initialize Kokoro TTS (model: ${this.modelId}, dtype: ${this.dtype}). ` +
|
|
20
|
+
`This may require an internet connection for first-time model download. ` +
|
|
21
|
+
`Original error: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
return this.tts;
|
|
24
|
+
}
|
|
25
|
+
async generate(text, options) {
|
|
26
|
+
if (!text?.trim())
|
|
27
|
+
throw new Error('TTS text must not be empty');
|
|
28
|
+
const tts = await this.getTTS();
|
|
29
|
+
const audio = await tts.generate(text, {
|
|
30
|
+
voice: options.voice ?? 'af_heart',
|
|
31
|
+
speed: options.speed ?? 1.0,
|
|
32
|
+
});
|
|
33
|
+
const samples = audio.data ?? audio.audio;
|
|
34
|
+
if (!samples || !(samples instanceof Float32Array)) {
|
|
35
|
+
throw new Error('kokoro-js returned unexpected audio format: neither .data nor .audio contains Float32Array samples. ' +
|
|
36
|
+
'Check that your kokoro-js version is compatible.');
|
|
37
|
+
}
|
|
38
|
+
const sampleRate = audio.sampling_rate;
|
|
39
|
+
if (typeof sampleRate !== 'number' || sampleRate <= 0) {
|
|
40
|
+
throw new Error(`kokoro-js returned invalid sample rate: ${sampleRate}.`);
|
|
41
|
+
}
|
|
42
|
+
const { createWavBuffer } = await import('./engine.js');
|
|
43
|
+
return createWavBuffer(samples, sampleRate);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=kokoro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kokoro.js","sourceRoot":"","sources":["../../src/tts/kokoro.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IACf,GAAG,GAAQ,IAAI,CAAC;IAChB,OAAO,CAAS;IAChB,KAAK,CAAS;IAEtB,YAAY,OAA8C;QACxD,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,gCAAgC,CAAC;QACpE,IAAI,CAAC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC;IACxC,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,IAAI,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YAChD,IAAI,CAAC,GAAG,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE;gBACvD,KAAK,EAAE,IAAI,CAAC,KAAgD;aAC7D,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,2CAA2C,IAAI,CAAC,OAAO,YAAY,IAAI,CAAC,KAAK,KAAK;gBAClF,yEAAyE;gBACzE,mBAAoB,GAAa,CAAC,OAAO,EAAE,CAC5C,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAyB;QACpD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;YACrC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,UAAU;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG;SAC5B,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC;QAC1C,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,YAAY,YAAY,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,sGAAsG;gBACtG,kDAAkD,CACnD,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC;QACvC,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,KAAK,CAAC,2CAA2C,UAAU,GAAG,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACxD,OAAO,eAAe,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC9C,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argo-video/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Turn Playwright demo scripts into polished product demo videos with AI voiceover",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,6 +28,18 @@
|
|
|
28
28
|
"commander": "^12.0.0",
|
|
29
29
|
"kokoro-js": "^1.2.1"
|
|
30
30
|
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/shreyaskarnik/argo.git"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"bin"
|
|
42
|
+
],
|
|
31
43
|
"devDependencies": {
|
|
32
44
|
"@playwright/test": "^1.50.0",
|
|
33
45
|
"@types/node": "^25.5.0",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(git add:*)",
|
|
5
|
-
"Bash(git commit:*)",
|
|
6
|
-
"Bash(gh repo:*)",
|
|
7
|
-
"Bash(gh api:*)",
|
|
8
|
-
"Bash(/Users/shreyas/work/rnd/argo/bin/argo.js:*)",
|
|
9
|
-
"Bash(chmod +x /Users/shreyas/work/rnd/argo/bin/argo.js)",
|
|
10
|
-
"Bash(npm install:*)",
|
|
11
|
-
"Bash(npx tsc:*)",
|
|
12
|
-
"Bash(ls /Users/shreyas/work/rnd/argo/src/ /Users/shreyas/work/rnd/argo/tests/ 2>/dev/null; ls /Users/shreyas/work/rnd/argo/vitest.config.* 2>/dev/null)",
|
|
13
|
-
"Bash(npx vitest:*)",
|
|
14
|
-
"Bash(CI=true npx vitest run tests/tts/kokoro.test.ts 2>&1)",
|
|
15
|
-
"mcp__plugin_context7_context7__resolve-library-id",
|
|
16
|
-
"mcp__plugin_context7_context7__query-docs",
|
|
17
|
-
"Bash(node -e \"\nimport\\('@huggingface/transformers'\\).then\\(async \\(m\\) => {\n // Check what task maps to what\n console.log\\('SUPPORTED_TASKS keys that include audio or speech:'\\);\n const keys = Object.keys\\(m\\).filter\\(k => k.toLowerCase\\(\\).includes\\('task'\\) || k.toLowerCase\\(\\).includes\\('mapping'\\)\\);\n console.log\\(keys.slice\\(0, 20\\)\\);\n}\\).catch\\(e => console.error\\(e.message\\)\\);\n\" 2>&1)",
|
|
18
|
-
"Bash(node -e \"\nimport\\('@huggingface/transformers'\\).then\\(async \\(m\\) => {\n try {\n const p = await m.pipeline\\('text-to-audio', 'onnx-community/Kokoro-82M-v1.0-ONNX', { dtype: 'fp32' }\\);\n console.log\\('SUCCESS with text-to-audio'\\);\n } catch\\(e\\) {\n console.log\\('text-to-audio error:', e.message\\);\n }\n}\\).catch\\(e => console.error\\(e.message\\)\\);\n\" 2>&1)",
|
|
19
|
-
"Bash(npm ls:*)",
|
|
20
|
-
"Bash(npm run:*)",
|
|
21
|
-
"Bash(node bin/argo.js init 2>&1)",
|
|
22
|
-
"Bash(node bin/argo.js --help 2>&1)",
|
|
23
|
-
"Bash(rm -rf demos argo.config.ts && node bin/argo.js init 2>&1)",
|
|
24
|
-
"Bash(git rm:*)",
|
|
25
|
-
"Bash(rm -f argo.config.ts argo.config.js && rm -rf demos/ && npm run build 2>&1 && node bin/argo.js init 2>&1)",
|
|
26
|
-
"Bash(node -e \"import\\('./dist/config.js'\\).then\\(m => m.loadConfig\\(process.cwd\\(\\)\\)\\).then\\(c => console.log\\(JSON.stringify\\(c, null, 2\\)\\)\\)\" 2>&1)",
|
|
27
|
-
"Bash(rm -rf demos argo.config.* playwright.config.ts && npm run build 2>&1 && node bin/argo.js init 2>&1)",
|
|
28
|
-
"Bash(node bin/argo.js record example 2>&1)",
|
|
29
|
-
"Bash(npx playwright:*)",
|
|
30
|
-
"Bash(npm link:*)",
|
|
31
|
-
"Bash(node bin/argo.js pipeline example 2>&1)"
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
}
|
package/DESIGN.md
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
# Argo — Playwright Demo Recording with Voiceover
|
|
2
|
-
|
|
3
|
-
**Date:** 2026-03-12
|
|
4
|
-
**Status:** Draft
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
Argo is a standalone, open-source tool that turns Playwright scripts into polished product demo videos with AI-generated voiceover. Developers author demo scripts using Playwright fixtures; anyone can regenerate the final video with a single CLI command.
|
|
9
|
-
|
|
10
|
-
The pipeline: **TTS** (Kokoro generates voiceover clips via Transformers.js, cached) → **record** (Playwright captures video + scene timestamps) → **align** (clips placed at recorded timestamps) → **export** (ffmpeg merges video + audio into MP4).
|
|
11
|
-
|
|
12
|
-
## Target Users
|
|
13
|
-
|
|
14
|
-
- **Developers** author demo scripts using Playwright's familiar test API
|
|
15
|
-
- **Non-developers** (marketing, DevRel) regenerate videos via CLI without touching code
|
|
16
|
-
|
|
17
|
-
## Architecture
|
|
18
|
-
|
|
19
|
-
### Library + CLI
|
|
20
|
-
|
|
21
|
-
Argo ships as a single package with two interfaces:
|
|
22
|
-
|
|
23
|
-
**Library** — Playwright fixtures and helpers for authoring demos:
|
|
24
|
-
- `test`, `expect` — re-exported Playwright fixtures with `narration` auto-injected
|
|
25
|
-
- `NarrationTimeline` — low-level control for manual usage
|
|
26
|
-
- `showCaption`, `hideCaption`, `withCaption` — DOM caption helpers
|
|
27
|
-
- `defineConfig` — creates a full Argo config with sensible defaults
|
|
28
|
-
- `demosProject` — creates a Playwright project entry for integration into existing configs
|
|
29
|
-
|
|
30
|
-
**CLI** — pipeline commands anyone can run:
|
|
31
|
-
- `argo record <demo>` — run Playwright for a specific demo
|
|
32
|
-
- `argo tts generate <manifest>` — generate TTS clips from `.voiceover.json`
|
|
33
|
-
- `argo tts align <demo>` — align clips to recording timestamps
|
|
34
|
-
- `argo export <demo>` — merge video + audio via ffmpeg
|
|
35
|
-
- `argo pipeline <demo>` — all steps end-to-end
|
|
36
|
-
- `argo init` — scaffold demo files + config into a project
|
|
37
|
-
|
|
38
|
-
### Project Layout
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
argo/
|
|
42
|
-
├── src/
|
|
43
|
-
│ ├── index.ts # Library exports
|
|
44
|
-
│ ├── cli.ts # CLI entry point
|
|
45
|
-
│ ├── fixtures.ts # Playwright test fixture (injects narration)
|
|
46
|
-
│ ├── narration.ts # NarrationTimeline class
|
|
47
|
-
│ ├── captions.ts # DOM caption overlay helpers
|
|
48
|
-
│ ├── tts/
|
|
49
|
-
│ │ ├── engine.ts # TTSEngine interface
|
|
50
|
-
│ │ ├── kokoro.ts # Default Kokoro via @huggingface/transformers
|
|
51
|
-
│ │ └── align.ts # Timestamp alignment logic
|
|
52
|
-
│ ├── export.ts # ffmpeg video+audio merge
|
|
53
|
-
│ ├── pipeline.ts # Orchestrates record→tts→align→export
|
|
54
|
-
│ └── config.ts # defineConfig helper + defaults
|
|
55
|
-
├── bin/
|
|
56
|
-
│ └── argo.js # CLI bin entry
|
|
57
|
-
├── package.json
|
|
58
|
-
└── tsconfig.json
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Dependencies
|
|
62
|
-
|
|
63
|
-
- `playwright` — recording engine (peer dependency)
|
|
64
|
-
- `@huggingface/transformers@next` — Kokoro TTS via ONNX in Node.js
|
|
65
|
-
- `ffmpeg` — system dependency for video export (not bundled)
|
|
66
|
-
|
|
67
|
-
No Python required. The entire pipeline runs in Node.js.
|
|
68
|
-
|
|
69
|
-
**ffmpeg detection:** All CLI commands that need ffmpeg (`export`, `pipeline`) check for it on startup and exit with a clear error message + install instructions if missing.
|
|
70
|
-
|
|
71
|
-
## Authoring API
|
|
72
|
-
|
|
73
|
-
A demo consists of two files:
|
|
74
|
-
|
|
75
|
-
### Demo Script (`demos/onboarding.demo.ts`)
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
import { test, demoType } from 'argo';
|
|
79
|
-
|
|
80
|
-
test('onboarding', async ({ page, narration }) => {
|
|
81
|
-
// narration.start() is called automatically by the fixture before the test runs
|
|
82
|
-
await page.goto('/');
|
|
83
|
-
|
|
84
|
-
// Show caption + record scene timestamp
|
|
85
|
-
await narration.showCaption(page, 'welcome', 'Welcome to our app', 3000);
|
|
86
|
-
|
|
87
|
-
// Caption around an action
|
|
88
|
-
await narration.withCaption(page, 'signup', 'Sign up in seconds', async () => {
|
|
89
|
-
await page.fill('[name=email]', 'demo@example.com');
|
|
90
|
-
await demoType(page, '[name=password]', 'supersecure');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Timestamp-only scene (no visible caption, used for voiceover alignment)
|
|
94
|
-
narration.mark('dashboard-loaded');
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Constraints:** Each demo file must contain exactly one `test()` block (one video per demo). Use separate demo files for separate videos.
|
|
99
|
-
|
|
100
|
-
**Key API:**
|
|
101
|
-
- `narration.showCaption(page, scene, text, durationMs)` — show overlay + record timestamp
|
|
102
|
-
- `narration.withCaption(page, scene, text, action)` — wrap action with caption
|
|
103
|
-
- `narration.mark(scene)` — record timestamp without visual
|
|
104
|
-
- `demoType(page, selector, text)` — standalone helper, slow-types for demo effect (60ms/char)
|
|
105
|
-
- `narration.start()` — sets timestamp zero; called automatically by fixture before each test
|
|
106
|
-
- `narration.flush()` — writes `.timing.json`; called automatically by fixture after each test
|
|
107
|
-
|
|
108
|
-
### Voiceover Manifest (`demos/onboarding.voiceover.json`)
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
[
|
|
112
|
-
{
|
|
113
|
-
"scene": "welcome",
|
|
114
|
-
"text": "Welcome to Acme — get started in under a minute."
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"scene": "signup",
|
|
118
|
-
"text": "Just enter your email and choose a password.",
|
|
119
|
-
"speed": 0.9
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"scene": "dashboard-loaded",
|
|
123
|
-
"text": "And you're in.",
|
|
124
|
-
"voice": "af_heart"
|
|
125
|
-
}
|
|
126
|
-
]
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Scene names link the manifest to the demo script. `voice` and `speed` are optional (defaults from config).
|
|
130
|
-
|
|
131
|
-
### Config (`argo.config.ts`, optional)
|
|
132
|
-
|
|
133
|
-
The CLI looks for `argo.config.ts` (or `.js`, `.mjs`) in the current working directory. Override with `--config <path>` on any CLI command.
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
import { defineConfig } from 'argo';
|
|
137
|
-
|
|
138
|
-
export default defineConfig({
|
|
139
|
-
baseURL: 'http://localhost:3000',
|
|
140
|
-
demosDir: 'demos/',
|
|
141
|
-
outputDir: 'videos/',
|
|
142
|
-
tts: {
|
|
143
|
-
defaultVoice: 'af_heart',
|
|
144
|
-
defaultSpeed: 1.0,
|
|
145
|
-
// engine: myCustomTTSEngine
|
|
146
|
-
},
|
|
147
|
-
video: {
|
|
148
|
-
width: 2560,
|
|
149
|
-
height: 1440,
|
|
150
|
-
fps: 30,
|
|
151
|
-
},
|
|
152
|
-
export: {
|
|
153
|
-
preset: 'slow',
|
|
154
|
-
crf: 16,
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## TTS System
|
|
160
|
-
|
|
161
|
-
### Default Engine: Kokoro via Transformers.js
|
|
162
|
-
|
|
163
|
-
- Uses `@huggingface/transformers@next` with ONNX Kokoro model
|
|
164
|
-
- Runs in Node.js — no Python, no GPU
|
|
165
|
-
- Model downloaded on first run (~80MB ONNX), cached locally
|
|
166
|
-
- Generates one WAV clip per scene
|
|
167
|
-
|
|
168
|
-
### Alignment
|
|
169
|
-
|
|
170
|
-
1. Recording produces `.timing.json` (scene name → millisecond timestamp)
|
|
171
|
-
2. Aligner reads `.timing.json` + clip durations
|
|
172
|
-
3. Places each clip at its scene's recorded timestamp
|
|
173
|
-
4. Prevents overlap — clips pushed forward with 100ms minimum gap
|
|
174
|
-
5. Outputs single `narration-aligned.wav` matching video duration
|
|
175
|
-
|
|
176
|
-
### Plugin Interface
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
interface TTSEngine {
|
|
180
|
-
generate(text: string, options: {
|
|
181
|
-
voice?: string;
|
|
182
|
-
speed?: number;
|
|
183
|
-
lang?: string;
|
|
184
|
-
}): Promise<Buffer>;
|
|
185
|
-
// Must return a complete WAV file (with headers).
|
|
186
|
-
// Required format: mono, 24kHz, 32-bit float.
|
|
187
|
-
// Argo will resample if needed, but matching this format avoids overhead.
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Custom engines (ElevenLabs, OpenAI TTS, Piper, etc.) implement this single method. Argo validates the returned WAV headers and resamples to 24kHz mono if the format doesn't match.
|
|
192
|
-
|
|
193
|
-
### Clip Caching
|
|
194
|
-
|
|
195
|
-
Clips stored in `.argo/<demoName>/clips/`. Each clip is keyed by a hash of its manifest entry (`scene` + `text` + `voice` + `speed`). When a clip's entry hasn't changed, the cached WAV is reused. When an entry changes (different text, voice, or speed), only that clip is regenerated. This is per-entry, not per-file — reordering entries or changing one scene doesn't invalidate others.
|
|
196
|
-
|
|
197
|
-
## Recording
|
|
198
|
-
|
|
199
|
-
- `argo record` runs Playwright with the demos project config
|
|
200
|
-
- Uses Playwright's default VP8 encoder (no patching)
|
|
201
|
-
- Higher quality achieved during export via ffmpeg re-encoding
|
|
202
|
-
- Output: `video.webm` + `.timing.json` in `.argo/<demoName>/`
|
|
203
|
-
|
|
204
|
-
## Export
|
|
205
|
-
|
|
206
|
-
- ffmpeg merges video + aligned audio
|
|
207
|
-
- Re-encodes to `libx264` (compensates for VP8's lower quality)
|
|
208
|
-
- Default: preset slow, crf 16, AAC audio @ 192k
|
|
209
|
-
- Configurable via `argo.config.ts` or CLI flags (`--crf`, `--preset`, `--fps`, `--width`, `--height`)
|
|
210
|
-
- Uses `-shortest` so audio trims to video length
|
|
211
|
-
- Output: `<outputDir>/<demoName>.mp4`
|
|
212
|
-
|
|
213
|
-
## Pipeline
|
|
214
|
-
|
|
215
|
-
`argo pipeline <demo>` chains all steps:
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
1. Generate TTS clips (skip if cached & manifest unchanged)
|
|
219
|
-
2. Record demo via Playwright → .webm + .timing.json
|
|
220
|
-
3. Align clips to timestamps → narration-aligned.wav
|
|
221
|
-
4. Export → final .mp4
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Each step is independently runnable.
|
|
225
|
-
|
|
226
|
-
## `argo init`
|
|
227
|
-
|
|
228
|
-
Scaffolds into an existing project:
|
|
229
|
-
- Creates `demos/` directory
|
|
230
|
-
- Generates sample `example.demo.ts` and `example.voiceover.json`
|
|
231
|
-
- Creates starter `argo.config.ts`
|
|
232
|
-
- Adds `demos` project to `playwright.config.ts` (or creates one) using `defineConfig`
|
|
233
|
-
- Prints next steps
|
|
234
|
-
|
|
235
|
-
## Playwright Integration
|
|
236
|
-
|
|
237
|
-
Two modes:
|
|
238
|
-
|
|
239
|
-
**Standalone** — Argo provides its own Playwright config via `defineConfig`. Users just install and point at their app URL. `argo init` sets this up.
|
|
240
|
-
|
|
241
|
-
**Integrated** — Power users add a `demos` project to their existing `playwright.config.ts` using the exported config helper:
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { demosProject } from 'argo';
|
|
245
|
-
|
|
246
|
-
export default defineConfig({
|
|
247
|
-
projects: [
|
|
248
|
-
// ... existing projects
|
|
249
|
-
demosProject({ baseURL: 'http://localhost:3000' }),
|
|
250
|
-
],
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Out of Scope (v1)
|
|
255
|
-
|
|
256
|
-
- Post-render subtitle burn-in via ffmpeg (future enhancement)
|
|
257
|
-
- npm registry publishing
|
|
258
|
-
- Non-Playwright backends (Puppeteer, Cypress)
|
|
259
|
-
- Video editing (cuts, transitions, zooms)
|
|
260
|
-
- Background music / audio mixing
|
|
261
|
-
- GUI / visual editor
|