@vosjs/cli 0.1.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 +63 -0
- package/dist/chunk-A5AN2K5L.js +282 -0
- package/dist/chunk-A5AN2K5L.js.map +1 -0
- package/dist/cli.js +301 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vosso
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @vosjs/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vosjs/cli)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
|
|
6
|
+
Command line for [vos](https://vos.so/engine), the open visual operating system — render deterministic videos and stills from vos configs, headlessly, from your terminal, CI, or an AI agent.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add -D @vosjs/cli
|
|
10
|
+
npx vos render animation.json out.webm
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
vos render <config.json|url> [out] # config → video (WebM/MP4)
|
|
17
|
+
vos still <config.json|url> [out] # config → single frame (WebP)
|
|
18
|
+
vos info <config.json|url> # inspect a config
|
|
19
|
+
vos preview <config.json|url> # serve a local playback page
|
|
20
|
+
vos versions # installed @vosjs/* versions
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`vos render` accepts `--width` / `--height` / `--fps` / `--duration` / `--format webm|mp4`; `vos still` accepts `--time` / `--width` / `--height`. Configs can be local files or URLs, and API `{ "config": … }` envelopes are unwrapped automatically. Old config versions are migrated before rendering.
|
|
24
|
+
|
|
25
|
+
Rendering runs the same deterministic pipeline everywhere: the config is compiled with `@vosjs/core`, wrapped in the engine's capture template, and encoded frame-by-frame (WebCodecs) in headless Chromium. Same input, same video — locally, in CI, or on a server.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
A Chromium-family browser. The CLI uses your installed Google Chrome automatically; otherwise run `npx playwright install chromium` once, or point `VOS_BROWSER_PATH` at a Chrome/Chromium executable. Module dependencies (three, gsap) load from the CDN inside the render page, so rendering needs network access.
|
|
30
|
+
|
|
31
|
+
## For scripts and agents
|
|
32
|
+
|
|
33
|
+
- Results on stdout, logs on stderr.
|
|
34
|
+
- `--json` switches stdout to NDJSON events, ending with `{"event":"done",…}`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
vos render animation.json --json
|
|
38
|
+
{"event":"phase","phase":"compile"}
|
|
39
|
+
{"event":"phase","phase":"render"}
|
|
40
|
+
{"event":"done","out":"animation.webm","bytes":812345,"width":1920,"height":1080,"fps":30,"duration":5,"format":"webm"}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Exit codes: `0` ok · `1` error · `2` usage · `3` no browser available.
|
|
44
|
+
- No auth, no account — rendering is local.
|
|
45
|
+
|
|
46
|
+
The same core is available programmatically:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { launchBrowser, loadVosConfig, renderVideo } from '@vosjs/cli'
|
|
50
|
+
|
|
51
|
+
const browser = await launchBrowser()
|
|
52
|
+
const { config } = await loadVosConfig('animation.json')
|
|
53
|
+
const { bytes } = await renderVideo(browser, {
|
|
54
|
+
config,
|
|
55
|
+
width: 1920,
|
|
56
|
+
height: 1080,
|
|
57
|
+
fps: 30,
|
|
58
|
+
duration: 5,
|
|
59
|
+
format: 'webm',
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Part of [vos](https://github.com/vosjs/vos), the open visual operating system. MIT.
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/args.ts
|
|
4
|
+
var UsageError = class extends Error {
|
|
5
|
+
};
|
|
6
|
+
function parseArgs(argv, booleanFlags) {
|
|
7
|
+
const positionals = [];
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < argv.length; i++) {
|
|
10
|
+
const arg = argv[i];
|
|
11
|
+
if (arg === "--") {
|
|
12
|
+
positionals.push(...argv.slice(i + 1));
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
if (arg.startsWith("--")) {
|
|
16
|
+
const eq = arg.indexOf("=");
|
|
17
|
+
if (eq !== -1) {
|
|
18
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const name = arg.slice(2);
|
|
22
|
+
if (booleanFlags.has(name)) {
|
|
23
|
+
flags[name] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const next = argv[i + 1];
|
|
27
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
28
|
+
throw new UsageError(`--${name} expects a value`);
|
|
29
|
+
}
|
|
30
|
+
flags[name] = next;
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
positionals.push(arg);
|
|
35
|
+
}
|
|
36
|
+
return { positionals, flags };
|
|
37
|
+
}
|
|
38
|
+
function numFlag(flags, name, fallback) {
|
|
39
|
+
const v = flags[name];
|
|
40
|
+
if (v === void 0) return fallback;
|
|
41
|
+
const n = Number(v);
|
|
42
|
+
if (!Number.isFinite(n)) throw new UsageError(`--${name} expects a number, got "${String(v)}"`);
|
|
43
|
+
return n;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/loadConfig.ts
|
|
47
|
+
import { readFile } from "fs/promises";
|
|
48
|
+
import { CURRENT_CONFIG_VERSION, migrateConfig, vosConfigJsonSchema } from "@vosjs/core";
|
|
49
|
+
async function loadVosConfig(source) {
|
|
50
|
+
const warnings = [];
|
|
51
|
+
let raw;
|
|
52
|
+
if (/^https?:\/\//.test(source)) {
|
|
53
|
+
const res = await fetch(source);
|
|
54
|
+
if (!res.ok) throw new Error(`fetch ${source} \u2192 ${res.status}`);
|
|
55
|
+
raw = await res.text();
|
|
56
|
+
} else {
|
|
57
|
+
raw = await readFile(source, "utf8");
|
|
58
|
+
}
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(raw);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
throw new UsageError(`${source} is not valid JSON: ${e.message}`);
|
|
64
|
+
}
|
|
65
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
66
|
+
throw new UsageError(`${source} does not contain a JSON object`);
|
|
67
|
+
}
|
|
68
|
+
let obj = parsed;
|
|
69
|
+
if (typeof obj.config === "object" && obj.config !== null && !("createTimeline" in obj)) {
|
|
70
|
+
obj = obj.config;
|
|
71
|
+
warnings.push("unwrapped { config } envelope");
|
|
72
|
+
}
|
|
73
|
+
const version = obj.version;
|
|
74
|
+
const migrated = migrateConfig(obj);
|
|
75
|
+
if (version !== CURRENT_CONFIG_VERSION) {
|
|
76
|
+
warnings.push(`migrated config v${String(version ?? 1)} \u2192 v${CURRENT_CONFIG_VERSION}`);
|
|
77
|
+
}
|
|
78
|
+
const check = vosConfigJsonSchema.safeParse(migrated);
|
|
79
|
+
if (!check.success) {
|
|
80
|
+
const issues = check.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
81
|
+
throw new UsageError(`invalid vos config:
|
|
82
|
+
${issues}`);
|
|
83
|
+
}
|
|
84
|
+
return { config: migrated, warnings };
|
|
85
|
+
}
|
|
86
|
+
function configDuration(config) {
|
|
87
|
+
const d = config.duration;
|
|
88
|
+
return typeof d === "number" && Number.isFinite(d) && d > 0 ? d : void 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/browser.ts
|
|
92
|
+
import { chromium } from "playwright";
|
|
93
|
+
var BrowserUnavailableError = class extends Error {
|
|
94
|
+
constructor(cause) {
|
|
95
|
+
super(
|
|
96
|
+
`Could not launch a Chromium-family browser (${cause}).
|
|
97
|
+
Fixes, in order of preference:
|
|
98
|
+
1. Install Google Chrome (used automatically), or
|
|
99
|
+
2. npx playwright install chromium, or
|
|
100
|
+
3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
async function launchBrowser() {
|
|
105
|
+
const explicit = process.env.VOS_BROWSER_PATH;
|
|
106
|
+
if (explicit) {
|
|
107
|
+
return chromium.launch({ executablePath: explicit }).catch((e) => {
|
|
108
|
+
throw new BrowserUnavailableError(`VOS_BROWSER_PATH failed: ${e.message}`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
return await chromium.launch({ channel: "chrome" });
|
|
113
|
+
} catch {
|
|
114
|
+
try {
|
|
115
|
+
return await chromium.launch();
|
|
116
|
+
} catch (e) {
|
|
117
|
+
throw new BrowserUnavailableError(e.message.split("\n")[0] ?? "unknown");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/render.ts
|
|
123
|
+
import { compileVosConfig } from "@vosjs/core";
|
|
124
|
+
import { generateRenderTemplate } from "@vosjs/core/runtime";
|
|
125
|
+
import { elementsBundleCode } from "@vosjs/elements/bundle";
|
|
126
|
+
import { tweenRuntimeCode } from "@vosjs/tween/bundle";
|
|
127
|
+
var RENDER_ORIGIN = "https://vos-cli.render";
|
|
128
|
+
function compile(config) {
|
|
129
|
+
return compileVosConfig(config, { tweenEngine: "vos" });
|
|
130
|
+
}
|
|
131
|
+
async function runCapturePage(browser, html, opts) {
|
|
132
|
+
const context = await browser.newContext({
|
|
133
|
+
viewport: { width: opts.width, height: opts.height }
|
|
134
|
+
});
|
|
135
|
+
const page = await context.newPage();
|
|
136
|
+
const errors = [];
|
|
137
|
+
page.on("console", (m) => {
|
|
138
|
+
if (m.type() === "error") errors.push(m.text());
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
await page.route(
|
|
142
|
+
`${RENDER_ORIGIN}/**`,
|
|
143
|
+
(route) => route.fulfill({ status: 200, contentType: "text/html", body: html })
|
|
144
|
+
);
|
|
145
|
+
await page.goto(`${RENDER_ORIGIN}/render`, { waitUntil: "domcontentloaded" });
|
|
146
|
+
const start = Date.now();
|
|
147
|
+
for (; ; ) {
|
|
148
|
+
const done = await page.evaluate("window.__renderComplete ?? null");
|
|
149
|
+
if (done) return done;
|
|
150
|
+
if (Date.now() - start > opts.timeoutMs) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: `render timed out after ${Math.round(opts.timeoutMs / 1e3)}s${errors.length ? ` (page errors: ${errors.slice(0, 3).join(" | ")})` : ""}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
await context.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function decodeDataUrl(dataUrl) {
|
|
163
|
+
const m = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl);
|
|
164
|
+
if (!m) throw new Error("capture page returned an unexpected payload");
|
|
165
|
+
return { bytes: Uint8Array.from(Buffer.from(m[2], "base64")), mimeType: m[1] };
|
|
166
|
+
}
|
|
167
|
+
async function renderVideo(browser, opts) {
|
|
168
|
+
opts.onPhase?.("compile");
|
|
169
|
+
const code = compile(opts.config);
|
|
170
|
+
const html = generateRenderTemplate(code, {
|
|
171
|
+
mode: "capture-video",
|
|
172
|
+
capture: {
|
|
173
|
+
width: opts.width,
|
|
174
|
+
height: opts.height,
|
|
175
|
+
duration: opts.duration,
|
|
176
|
+
fps: opts.fps,
|
|
177
|
+
format: opts.format
|
|
178
|
+
},
|
|
179
|
+
elementsBundleCode,
|
|
180
|
+
tweenEngine: "vos",
|
|
181
|
+
tweenBundleCode: tweenRuntimeCode
|
|
182
|
+
});
|
|
183
|
+
opts.onPhase?.("render");
|
|
184
|
+
const timeoutMs = 9e4 + opts.duration * opts.fps * 400;
|
|
185
|
+
const done = await runCapturePage(browser, html, {
|
|
186
|
+
width: opts.width,
|
|
187
|
+
height: opts.height,
|
|
188
|
+
timeoutMs
|
|
189
|
+
});
|
|
190
|
+
if (!done.success || !done.data) throw new Error(done.error ?? "render failed");
|
|
191
|
+
return decodeDataUrl(done.data);
|
|
192
|
+
}
|
|
193
|
+
async function renderStill(browser, opts) {
|
|
194
|
+
opts.onPhase?.("compile");
|
|
195
|
+
const code = compile(opts.config);
|
|
196
|
+
const html = generateRenderTemplate(code, {
|
|
197
|
+
mode: "capture-thumbnail",
|
|
198
|
+
capture: {
|
|
199
|
+
width: opts.width,
|
|
200
|
+
height: opts.height,
|
|
201
|
+
duration: Math.max(1, opts.time + 1),
|
|
202
|
+
fps: 30,
|
|
203
|
+
thumbnailTime: opts.time
|
|
204
|
+
},
|
|
205
|
+
elementsBundleCode,
|
|
206
|
+
tweenEngine: "vos",
|
|
207
|
+
tweenBundleCode: tweenRuntimeCode
|
|
208
|
+
});
|
|
209
|
+
opts.onPhase?.("render");
|
|
210
|
+
const done = await runCapturePage(browser, html, {
|
|
211
|
+
width: opts.width,
|
|
212
|
+
height: opts.height,
|
|
213
|
+
timeoutMs: 12e4
|
|
214
|
+
});
|
|
215
|
+
if (!done.success || !done.data) throw new Error(done.error ?? "still render failed");
|
|
216
|
+
return decodeDataUrl(done.data);
|
|
217
|
+
}
|
|
218
|
+
function previewPages(config) {
|
|
219
|
+
const code = compile(config);
|
|
220
|
+
const playerHtml = generateRenderTemplate(code, {
|
|
221
|
+
mode: "playback",
|
|
222
|
+
elementsBundleCode,
|
|
223
|
+
tweenEngine: "vos",
|
|
224
|
+
tweenBundleCode: tweenRuntimeCode
|
|
225
|
+
});
|
|
226
|
+
const codeJson = JSON.stringify(code).replace(/<\//g, "<\\/");
|
|
227
|
+
const hostHtml = `<!doctype html><html><head><meta charset="utf-8"><title>vos preview</title>
|
|
228
|
+
<style>
|
|
229
|
+
html,body{margin:0;height:100%;background:#000;overflow:hidden}
|
|
230
|
+
iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
|
|
231
|
+
#cover{position:fixed;inset:0;cursor:pointer}
|
|
232
|
+
#hud{position:fixed;left:12px;bottom:10px;color:#fff;opacity:.75;font:12px/1.4 ui-monospace,monospace;
|
|
233
|
+
background:rgba(0,0,0,.45);padding:4px 8px;border-radius:6px;pointer-events:none}
|
|
234
|
+
</style></head><body>
|
|
235
|
+
<iframe id="p" src="/player" allow="autoplay"></iframe>
|
|
236
|
+
<div id="cover"></div>
|
|
237
|
+
<div id="hud">loading\u2026</div>
|
|
238
|
+
<script>
|
|
239
|
+
const code = ${codeJson}
|
|
240
|
+
const frame = document.getElementById('p')
|
|
241
|
+
const hud = document.getElementById('hud')
|
|
242
|
+
let ready = false
|
|
243
|
+
let playing = true
|
|
244
|
+
const post = (msg) => frame.contentWindow && frame.contentWindow.postMessage(msg, '*')
|
|
245
|
+
const load = () => post({ type: 'LOAD', code, autoplay: true })
|
|
246
|
+
window.addEventListener('message', (e) => {
|
|
247
|
+
const msg = e.data || {}
|
|
248
|
+
if (msg.type === 'BRIDGE_READY') load()
|
|
249
|
+
if (msg.type === 'READY') { ready = true; hud.textContent = '0.0 / ' + msg.duration.toFixed(1) + 's \u2014 click to pause' }
|
|
250
|
+
if (msg.type === 'UPDATE') hud.textContent = msg.time.toFixed(1) + ' / ' + msg.duration.toFixed(1) + 's \u2014 click to ' + (playing ? 'pause' : 'play')
|
|
251
|
+
if (msg.type === 'ERROR') hud.textContent = 'error: ' + msg.error
|
|
252
|
+
})
|
|
253
|
+
// Belt and braces: if BRIDGE_READY raced the host, retry LOAD until READY.
|
|
254
|
+
let tries = 0
|
|
255
|
+
const iv = setInterval(() => {
|
|
256
|
+
if (ready || tries++ > 20) return clearInterval(iv)
|
|
257
|
+
load()
|
|
258
|
+
}, 400)
|
|
259
|
+
// The iframe swallows clicks \u2014 capture them on an overlay above it.
|
|
260
|
+
document.getElementById('cover').addEventListener('click', () => {
|
|
261
|
+
if (!ready) return
|
|
262
|
+
playing = !playing
|
|
263
|
+
post({ type: playing ? 'PLAY' : 'PAUSE' })
|
|
264
|
+
hud.textContent = hud.textContent.replace(/click to (pause|play)/, 'click to ' + (playing ? 'pause' : 'play'))
|
|
265
|
+
})
|
|
266
|
+
</script></body></html>`;
|
|
267
|
+
return { hostHtml, playerHtml };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export {
|
|
271
|
+
UsageError,
|
|
272
|
+
parseArgs,
|
|
273
|
+
numFlag,
|
|
274
|
+
loadVosConfig,
|
|
275
|
+
configDuration,
|
|
276
|
+
BrowserUnavailableError,
|
|
277
|
+
launchBrowser,
|
|
278
|
+
renderVideo,
|
|
279
|
+
renderStill,
|
|
280
|
+
previewPages
|
|
281
|
+
};
|
|
282
|
+
//# sourceMappingURL=chunk-A5AN2K5L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/args.ts","../src/loadConfig.ts","../src/browser.ts","../src/render.ts"],"sourcesContent":["/**\n * Minimal argv parser — positionals + `--flag value` / `--flag=value` /\n * boolean flags. Deliberately tiny: the CLI has a small, stable surface and\n * agents benefit from predictable, dependency-free parsing.\n */\nexport interface ParsedArgs {\n positionals: string[]\n flags: Record<string, string | true>\n}\n\nexport class UsageError extends Error {}\n\nexport function parseArgs(argv: string[], booleanFlags: ReadonlySet<string>): ParsedArgs {\n const positionals: string[] = []\n const flags: Record<string, string | true> = {}\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') {\n positionals.push(...argv.slice(i + 1))\n break\n }\n if (arg.startsWith('--')) {\n const eq = arg.indexOf('=')\n if (eq !== -1) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n continue\n }\n const name = arg.slice(2)\n if (booleanFlags.has(name)) {\n flags[name] = true\n continue\n }\n const next = argv[i + 1]\n if (next === undefined || next.startsWith('--')) {\n throw new UsageError(`--${name} expects a value`)\n }\n flags[name] = next\n i++\n continue\n }\n positionals.push(arg)\n }\n return { positionals, flags }\n}\n\nexport function numFlag(\n flags: ParsedArgs['flags'],\n name: string,\n fallback: number,\n): number {\n const v = flags[name]\n if (v === undefined) return fallback\n const n = Number(v)\n if (!Number.isFinite(n)) throw new UsageError(`--${name} expects a number, got \"${String(v)}\"`)\n return n\n}\n","import { readFile } from 'node:fs/promises'\nimport { CURRENT_CONFIG_VERSION, migrateConfig, vosConfigJsonSchema } from '@vosjs/core'\nimport { UsageError } from './args'\n\nexport interface LoadedConfig {\n config: Record<string, unknown>\n warnings: string[]\n}\n\n/**\n * Load a VosConfigJson from a file path or http(s) URL, unwrap API `{ config }`\n * envelopes, migrate old versions, and validate against the schema.\n */\nexport async function loadVosConfig(source: string): Promise<LoadedConfig> {\n const warnings: string[] = []\n let raw: string\n if (/^https?:\\/\\//.test(source)) {\n const res = await fetch(source)\n if (!res.ok) throw new Error(`fetch ${source} → ${res.status}`)\n raw = await res.text()\n } else {\n raw = await readFile(source, 'utf8')\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (e) {\n throw new UsageError(`${source} is not valid JSON: ${(e as Error).message}`)\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new UsageError(`${source} does not contain a JSON object`)\n }\n\n // API endpoints wrap the config: { config: {...} }.\n let obj = parsed as Record<string, unknown>\n if (\n typeof obj.config === 'object' &&\n obj.config !== null &&\n !('createTimeline' in obj)\n ) {\n obj = obj.config as Record<string, unknown>\n warnings.push('unwrapped { config } envelope')\n }\n\n const version = obj.version\n const migrated = migrateConfig(obj)\n if (version !== CURRENT_CONFIG_VERSION) {\n warnings.push(`migrated config v${String(version ?? 1)} → v${CURRENT_CONFIG_VERSION}`)\n }\n\n const check = vosConfigJsonSchema.safeParse(migrated)\n if (!check.success) {\n const issues = check.error.issues\n .slice(0, 5)\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new UsageError(`invalid vos config:\\n${issues}`)\n }\n\n return { config: migrated, warnings }\n}\n\n/** Best-effort duration from the config (seconds). */\nexport function configDuration(config: Record<string, unknown>): number | undefined {\n const d = config.duration\n return typeof d === 'number' && Number.isFinite(d) && d > 0 ? d : undefined\n}\n","import { chromium } from 'playwright'\nimport type { Browser } from 'playwright'\n\nexport class BrowserUnavailableError extends Error {\n constructor(cause: string) {\n super(\n `Could not launch a Chromium-family browser (${cause}).\\n` +\n `Fixes, in order of preference:\\n` +\n ` 1. Install Google Chrome (used automatically), or\\n` +\n ` 2. npx playwright install chromium, or\\n` +\n ` 3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`,\n )\n }\n}\n\n/**\n * Launch headless Chromium: explicit VOS_BROWSER_PATH → system Chrome\n * (no download needed) → Playwright's bundled Chromium.\n */\nexport async function launchBrowser(): Promise<Browser> {\n const explicit = process.env.VOS_BROWSER_PATH\n if (explicit) {\n return chromium.launch({ executablePath: explicit }).catch((e) => {\n throw new BrowserUnavailableError(`VOS_BROWSER_PATH failed: ${(e as Error).message}`)\n })\n }\n try {\n return await chromium.launch({ channel: 'chrome' })\n } catch {\n try {\n return await chromium.launch()\n } catch (e) {\n throw new BrowserUnavailableError((e as Error).message.split('\\n')[0] ?? 'unknown')\n }\n }\n}\n","import { compileVosConfig } from '@vosjs/core'\nimport { generateRenderTemplate } from '@vosjs/core/runtime'\nimport { elementsBundleCode } from '@vosjs/elements/bundle'\nimport { tweenRuntimeCode } from '@vosjs/tween/bundle'\nimport type { Browser, Page } from 'playwright'\n\n/** Fake secure origin the render page is served from (WebCodecs needs one). */\nconst RENDER_ORIGIN = 'https://vos-cli.render'\n\nexport interface RenderCommonOptions {\n config: Record<string, unknown>\n width: number\n height: number\n /** Phase callback for progress reporting. */\n onPhase?: (phase: string) => void\n}\n\nexport interface RenderVideoOptions extends RenderCommonOptions {\n fps: number\n /** Output duration in seconds. */\n duration: number\n format: 'webm' | 'mp4'\n}\n\nexport interface RenderStillOptions extends RenderCommonOptions {\n /** Seek time for the captured frame, seconds. */\n time: number\n}\n\nexport interface RenderResult {\n bytes: Uint8Array\n mimeType: string\n}\n\ninterface RenderComplete {\n success: boolean\n data?: string\n error?: string\n}\n\nfunction compile(config: Record<string, unknown>): string {\n return compileVosConfig(config as never, { tweenEngine: 'vos' })\n}\n\nasync function runCapturePage(\n browser: Browser,\n html: string,\n opts: { width: number; height: number; timeoutMs: number },\n): Promise<RenderComplete> {\n const context = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n })\n const page: Page = await context.newPage()\n const errors: string[] = []\n page.on('console', (m) => {\n if (m.type() === 'error') errors.push(m.text())\n })\n try {\n await page.route(`${RENDER_ORIGIN}/**`, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html', body: html }),\n )\n await page.goto(`${RENDER_ORIGIN}/render`, { waitUntil: 'domcontentloaded' })\n const start = Date.now()\n for (;;) {\n const done = (await page.evaluate('window.__renderComplete ?? null')) as RenderComplete | null\n if (done) return done\n if (Date.now() - start > opts.timeoutMs) {\n return {\n success: false,\n error: `render timed out after ${Math.round(opts.timeoutMs / 1000)}s${\n errors.length ? ` (page errors: ${errors.slice(0, 3).join(' | ')})` : ''\n }`,\n }\n }\n await new Promise((r) => setTimeout(r, 400))\n }\n } finally {\n await context.close()\n }\n}\n\nfunction decodeDataUrl(dataUrl: string): RenderResult {\n const m = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl)\n if (!m) throw new Error('capture page returned an unexpected payload')\n return { bytes: Uint8Array.from(Buffer.from(m[2], 'base64')), mimeType: m[1] }\n}\n\n/** Render a vos config to a video (WebM/MP4) in a headless browser. */\nexport async function renderVideo(\n browser: Browser,\n opts: RenderVideoOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-video',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: opts.duration,\n fps: opts.fps,\n format: opts.format,\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n // Generous ceiling: startup + CDN module fetch + ~4× realtime per frame batch.\n const timeoutMs = 90_000 + opts.duration * opts.fps * 400\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs,\n })\n if (!done.success || !done.data) throw new Error(done.error ?? 'render failed')\n return decodeDataUrl(done.data)\n}\n\n/** Render a single frame of a vos config to an image in a headless browser. */\nexport async function renderStill(\n browser: Browser,\n opts: RenderStillOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-thumbnail',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: Math.max(1, opts.time + 1),\n fps: 30,\n thumbnailTime: opts.time,\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs: 120_000,\n })\n if (!done.success || !done.data) throw new Error(done.error ?? 'still render failed')\n return decodeDataUrl(done.data)\n}\n\nexport interface PreviewPages {\n /** Host page: iframes the player and drives it over the bridge. */\n hostHtml: string\n /** The engine's playback-mode player (waits for a LOAD postMessage). */\n playerHtml: string\n}\n\n/**\n * Pages for `vos preview`. The playback template is the iframe HALF of the\n * player bridge — it renders nothing until a host posts `LOAD`, so the CLI\n * serves a minimal host page that sends `LOAD { code, autoplay }` and shows\n * a transport line (time / duration, click to play-pause).\n */\nexport function previewPages(config: Record<string, unknown>): PreviewPages {\n const code = compile(config)\n const playerHtml = generateRenderTemplate(code, {\n mode: 'playback',\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n const codeJson = JSON.stringify(code).replace(/<\\//g, '<\\\\/')\n const hostHtml = `<!doctype html><html><head><meta charset=\"utf-8\"><title>vos preview</title>\n<style>\n html,body{margin:0;height:100%;background:#000;overflow:hidden}\n iframe{position:absolute;inset:0;width:100%;height:100%;border:0}\n #cover{position:fixed;inset:0;cursor:pointer}\n #hud{position:fixed;left:12px;bottom:10px;color:#fff;opacity:.75;font:12px/1.4 ui-monospace,monospace;\n background:rgba(0,0,0,.45);padding:4px 8px;border-radius:6px;pointer-events:none}\n</style></head><body>\n<iframe id=\"p\" src=\"/player\" allow=\"autoplay\"></iframe>\n<div id=\"cover\"></div>\n<div id=\"hud\">loading…</div>\n<script>\n const code = ${codeJson}\n const frame = document.getElementById('p')\n const hud = document.getElementById('hud')\n let ready = false\n let playing = true\n const post = (msg) => frame.contentWindow && frame.contentWindow.postMessage(msg, '*')\n const load = () => post({ type: 'LOAD', code, autoplay: true })\n window.addEventListener('message', (e) => {\n const msg = e.data || {}\n if (msg.type === 'BRIDGE_READY') load()\n if (msg.type === 'READY') { ready = true; hud.textContent = '0.0 / ' + msg.duration.toFixed(1) + 's — click to pause' }\n if (msg.type === 'UPDATE') hud.textContent = msg.time.toFixed(1) + ' / ' + msg.duration.toFixed(1) + 's — click to ' + (playing ? 'pause' : 'play')\n if (msg.type === 'ERROR') hud.textContent = 'error: ' + msg.error\n })\n // Belt and braces: if BRIDGE_READY raced the host, retry LOAD until READY.\n let tries = 0\n const iv = setInterval(() => {\n if (ready || tries++ > 20) return clearInterval(iv)\n load()\n }, 400)\n // The iframe swallows clicks — capture them on an overlay above it.\n document.getElementById('cover').addEventListener('click', () => {\n if (!ready) return\n playing = !playing\n post({ type: playing ? 'PLAY' : 'PAUSE' })\n hud.textContent = hud.textContent.replace(/click to (pause|play)/, 'click to ' + (playing ? 'pause' : 'play'))\n })\n</script></body></html>`\n return { hostHtml, playerHtml }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BrowserUnavailableError,
|
|
4
|
+
UsageError,
|
|
5
|
+
configDuration,
|
|
6
|
+
launchBrowser,
|
|
7
|
+
loadVosConfig,
|
|
8
|
+
numFlag,
|
|
9
|
+
parseArgs,
|
|
10
|
+
previewPages,
|
|
11
|
+
renderStill,
|
|
12
|
+
renderVideo
|
|
13
|
+
} from "./chunk-A5AN2K5L.js";
|
|
14
|
+
|
|
15
|
+
// src/cli.ts
|
|
16
|
+
import { writeFile } from "fs/promises";
|
|
17
|
+
import { readFileSync } from "fs";
|
|
18
|
+
import { basename, dirname, join } from "path";
|
|
19
|
+
import { createServer } from "http";
|
|
20
|
+
import { createRequire } from "module";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
|
|
23
|
+
// src/output.ts
|
|
24
|
+
var EXIT_OK = 0;
|
|
25
|
+
var EXIT_ERROR = 1;
|
|
26
|
+
var EXIT_USAGE = 2;
|
|
27
|
+
var EXIT_NO_BROWSER = 3;
|
|
28
|
+
function createReporter(json) {
|
|
29
|
+
return {
|
|
30
|
+
json,
|
|
31
|
+
log: (msg) => {
|
|
32
|
+
if (!json) process.stderr.write(`${msg}
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
event: (obj) => {
|
|
36
|
+
if (json) process.stdout.write(`${JSON.stringify(obj)}
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
done: (obj, humanLine) => {
|
|
40
|
+
if (json) process.stdout.write(`${JSON.stringify({ event: "done", ...obj })}
|
|
41
|
+
`);
|
|
42
|
+
else process.stdout.write(`${humanLine}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/cli.ts
|
|
49
|
+
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "version"]);
|
|
50
|
+
var HELP = `vos \u2014 command line for the vos programmatic video engine (https://vos.so)
|
|
51
|
+
|
|
52
|
+
Usage
|
|
53
|
+
vos render <config.json|url> [out] [--width 1920] [--height 1080] [--fps 30]
|
|
54
|
+
[--duration <s>] [--format webm|mp4] [--json]
|
|
55
|
+
vos still <config.json|url> [out.webp] [--time 0] [--width] [--height] [--json]
|
|
56
|
+
vos info <config.json|url> [--json]
|
|
57
|
+
vos preview <config.json|url> [--port 0]
|
|
58
|
+
vos versions [--json]
|
|
59
|
+
vos voila <subcommand> \u2026 (product-video pipeline \u2014 separate install)
|
|
60
|
+
|
|
61
|
+
Conventions
|
|
62
|
+
Results go to stdout; logs go to stderr. --json switches stdout to NDJSON
|
|
63
|
+
events ending with {"event":"done",\u2026}. Exit codes: 0 ok, 1 error, 2 usage,
|
|
64
|
+
3 no browser available.
|
|
65
|
+
`;
|
|
66
|
+
function outName(source, ext) {
|
|
67
|
+
const base = basename(source).replace(/\.[a-z0-9]+$/i, "") || "vos";
|
|
68
|
+
return `${base}.${ext}`;
|
|
69
|
+
}
|
|
70
|
+
async function cmdRender(argv) {
|
|
71
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS);
|
|
72
|
+
const source = positionals[0];
|
|
73
|
+
if (!source) throw new UsageError("vos render <config.json|url> [out]");
|
|
74
|
+
const r = createReporter(flags.json === true);
|
|
75
|
+
const format = flags.format ?? "webm";
|
|
76
|
+
if (format !== "webm" && format !== "mp4") throw new UsageError("--format must be webm or mp4");
|
|
77
|
+
const { config, warnings } = await loadVosConfig(source);
|
|
78
|
+
for (const w of warnings) r.log(`note: ${w}`);
|
|
79
|
+
const duration = numFlag(flags, "duration", configDuration(config) ?? 5);
|
|
80
|
+
const width = numFlag(flags, "width", 1920);
|
|
81
|
+
const height = numFlag(flags, "height", 1080);
|
|
82
|
+
const fps = numFlag(flags, "fps", 30);
|
|
83
|
+
const out = positionals[1] ?? outName(source, format);
|
|
84
|
+
const browser = await launchBrowser();
|
|
85
|
+
try {
|
|
86
|
+
const result = await renderVideo(browser, {
|
|
87
|
+
config,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
fps,
|
|
91
|
+
duration,
|
|
92
|
+
format,
|
|
93
|
+
onPhase: (phase) => {
|
|
94
|
+
r.log(`${phase}\u2026`);
|
|
95
|
+
r.event({ event: "phase", phase });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
await writeFile(out, result.bytes);
|
|
99
|
+
r.done(
|
|
100
|
+
{ out, bytes: result.bytes.length, width, height, fps, duration, format },
|
|
101
|
+
`Wrote ${out} (${(result.bytes.length / 1024).toFixed(0)} KB, ${width}x${height}@${fps}, ${duration}s)`
|
|
102
|
+
);
|
|
103
|
+
return EXIT_OK;
|
|
104
|
+
} finally {
|
|
105
|
+
await browser.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function cmdStill(argv) {
|
|
109
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS);
|
|
110
|
+
const source = positionals[0];
|
|
111
|
+
if (!source) throw new UsageError("vos still <config.json|url> [out.webp]");
|
|
112
|
+
const r = createReporter(flags.json === true);
|
|
113
|
+
const { config, warnings } = await loadVosConfig(source);
|
|
114
|
+
for (const w of warnings) r.log(`note: ${w}`);
|
|
115
|
+
const time = numFlag(flags, "time", 0);
|
|
116
|
+
const width = numFlag(flags, "width", 1280);
|
|
117
|
+
const height = numFlag(flags, "height", 720);
|
|
118
|
+
const out = positionals[1] ?? outName(source, "webp");
|
|
119
|
+
const browser = await launchBrowser();
|
|
120
|
+
try {
|
|
121
|
+
const result = await renderStill(browser, {
|
|
122
|
+
config,
|
|
123
|
+
width,
|
|
124
|
+
height,
|
|
125
|
+
time,
|
|
126
|
+
onPhase: (phase) => {
|
|
127
|
+
r.log(`${phase}\u2026`);
|
|
128
|
+
r.event({ event: "phase", phase });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
await writeFile(out, result.bytes);
|
|
132
|
+
r.done(
|
|
133
|
+
{ out, bytes: result.bytes.length, width, height, time, mimeType: result.mimeType },
|
|
134
|
+
`Wrote ${out} (${(result.bytes.length / 1024).toFixed(0)} KB, ${width}x${height} @ t=${time}s)`
|
|
135
|
+
);
|
|
136
|
+
return EXIT_OK;
|
|
137
|
+
} finally {
|
|
138
|
+
await browser.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function cmdInfo(argv) {
|
|
142
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS);
|
|
143
|
+
const source = positionals[0];
|
|
144
|
+
if (!source) throw new UsageError("vos info <config.json|url>");
|
|
145
|
+
const r = createReporter(flags.json === true);
|
|
146
|
+
const { config, warnings } = await loadVosConfig(source);
|
|
147
|
+
const elements = Array.isArray(config.elements) ? config.elements.length : 0;
|
|
148
|
+
const data = typeof config.data === "object" && config.data !== null ? Object.keys(config.data) : [];
|
|
149
|
+
const fns = ["setup", "createContent", "createTimeline", "onFrame"].filter(
|
|
150
|
+
(k) => typeof config[k] === "string"
|
|
151
|
+
);
|
|
152
|
+
const info = {
|
|
153
|
+
version: config.version,
|
|
154
|
+
duration: configDuration(config) ?? null,
|
|
155
|
+
camera: config.camera?.preset ?? null,
|
|
156
|
+
elements,
|
|
157
|
+
dataKeys: data,
|
|
158
|
+
functions: fns,
|
|
159
|
+
warnings
|
|
160
|
+
};
|
|
161
|
+
if (r.json) r.done(info, "");
|
|
162
|
+
else {
|
|
163
|
+
for (const w of warnings) r.log(`note: ${w}`);
|
|
164
|
+
process.stdout.write(
|
|
165
|
+
`version: v${String(info.version)}
|
|
166
|
+
duration: ${info.duration === null ? "(none)" : `${info.duration}s`}
|
|
167
|
+
camera: ${String(info.camera ?? "(default)")}
|
|
168
|
+
elements: ${elements}
|
|
169
|
+
data keys: ${data.length ? data.join(", ") : "(none)"}
|
|
170
|
+
functions: ${fns.join(", ")}
|
|
171
|
+
`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return EXIT_OK;
|
|
175
|
+
}
|
|
176
|
+
function packageVersion(name) {
|
|
177
|
+
const require2 = createRequire(import.meta.url);
|
|
178
|
+
try {
|
|
179
|
+
return require2(`${name}/package.json`).version;
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
let entry = null;
|
|
183
|
+
try {
|
|
184
|
+
entry = fileURLToPath(import.meta.resolve(name));
|
|
185
|
+
} catch {
|
|
186
|
+
try {
|
|
187
|
+
entry = require2.resolve(name);
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
let dir = dirname(entry);
|
|
193
|
+
for (let i = 0; i < 6; i++) {
|
|
194
|
+
try {
|
|
195
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
196
|
+
if (pkg.name === name && pkg.version) return pkg.version;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
const parent = dirname(dir);
|
|
200
|
+
if (parent === dir) return null;
|
|
201
|
+
dir = parent;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
async function cmdVersions(argv) {
|
|
206
|
+
const { flags } = parseArgs(argv, BOOLEAN_FLAGS);
|
|
207
|
+
const r = createReporter(flags.json === true);
|
|
208
|
+
const versions = {};
|
|
209
|
+
try {
|
|
210
|
+
const own = JSON.parse(
|
|
211
|
+
readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8")
|
|
212
|
+
);
|
|
213
|
+
versions["@vosjs/cli"] = own.version;
|
|
214
|
+
} catch {
|
|
215
|
+
versions["@vosjs/cli"] = "(unknown)";
|
|
216
|
+
}
|
|
217
|
+
for (const name of ["@vosjs/core", "@vosjs/elements", "@vosjs/tween", "playwright"]) {
|
|
218
|
+
versions[name] = packageVersion(name) ?? "(not found)";
|
|
219
|
+
}
|
|
220
|
+
if (r.json) r.done({ versions }, "");
|
|
221
|
+
else for (const [k, v] of Object.entries(versions)) process.stdout.write(`${k} ${v}
|
|
222
|
+
`);
|
|
223
|
+
return EXIT_OK;
|
|
224
|
+
}
|
|
225
|
+
async function cmdPreview(argv) {
|
|
226
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS);
|
|
227
|
+
const source = positionals[0];
|
|
228
|
+
if (!source) throw new UsageError("vos preview <config.json|url> [--port 0]");
|
|
229
|
+
const r = createReporter(false);
|
|
230
|
+
const { config, warnings } = await loadVosConfig(source);
|
|
231
|
+
for (const w of warnings) r.log(`note: ${w}`);
|
|
232
|
+
const { hostHtml, playerHtml } = previewPages(config);
|
|
233
|
+
const server = createServer((req, res) => {
|
|
234
|
+
const path = new URL(req.url ?? "/", "http://x").pathname;
|
|
235
|
+
res.writeHead(200, { "content-type": "text/html" });
|
|
236
|
+
res.end(path === "/player" ? playerHtml : hostHtml);
|
|
237
|
+
});
|
|
238
|
+
const port = numFlag(flags, "port", 0);
|
|
239
|
+
await new Promise((resolve) => server.listen(port, resolve));
|
|
240
|
+
const addr = server.address();
|
|
241
|
+
const url = `http://localhost:${typeof addr === "object" && addr ? addr.port : port}/`;
|
|
242
|
+
process.stdout.write(`${url}
|
|
243
|
+
`);
|
|
244
|
+
r.log("Serving playback preview \u2014 Ctrl-C to stop.");
|
|
245
|
+
await new Promise(() => {
|
|
246
|
+
});
|
|
247
|
+
return EXIT_OK;
|
|
248
|
+
}
|
|
249
|
+
async function cmdVoila(argv) {
|
|
250
|
+
try {
|
|
251
|
+
const mod = await import("@vosso/voila-cli");
|
|
252
|
+
if (typeof mod.run === "function") return await mod.run(argv);
|
|
253
|
+
process.stderr.write("@vosso/voila-cli is installed but exposes no run() \u2014 update it.\n");
|
|
254
|
+
return EXIT_ERROR;
|
|
255
|
+
} catch {
|
|
256
|
+
process.stderr.write(
|
|
257
|
+
"The Voila product-video pipeline ships separately.\n npm i -D @vosso/voila-cli\nthen re-run: vos voila \u2026\n"
|
|
258
|
+
);
|
|
259
|
+
return EXIT_ERROR;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function main() {
|
|
263
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
264
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
265
|
+
process.stdout.write(HELP);
|
|
266
|
+
return cmd ? EXIT_OK : EXIT_USAGE;
|
|
267
|
+
}
|
|
268
|
+
if (cmd === "--version") return cmdVersions(["--json"]);
|
|
269
|
+
switch (cmd) {
|
|
270
|
+
case "render":
|
|
271
|
+
return cmdRender(rest);
|
|
272
|
+
case "still":
|
|
273
|
+
return cmdStill(rest);
|
|
274
|
+
case "info":
|
|
275
|
+
return cmdInfo(rest);
|
|
276
|
+
case "versions":
|
|
277
|
+
return cmdVersions(rest);
|
|
278
|
+
case "preview":
|
|
279
|
+
return cmdPreview(rest);
|
|
280
|
+
case "voila":
|
|
281
|
+
return cmdVoila(rest);
|
|
282
|
+
default:
|
|
283
|
+
throw new UsageError(`unknown command "${cmd}" \u2014 run vos help`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
main().then((code) => process.exit(code)).catch((e) => {
|
|
287
|
+
if (e instanceof UsageError) {
|
|
288
|
+
process.stderr.write(`usage error: ${e.message}
|
|
289
|
+
`);
|
|
290
|
+
process.exit(EXIT_USAGE);
|
|
291
|
+
}
|
|
292
|
+
if (e instanceof BrowserUnavailableError) {
|
|
293
|
+
process.stderr.write(`${e.message}
|
|
294
|
+
`);
|
|
295
|
+
process.exit(EXIT_NO_BROWSER);
|
|
296
|
+
}
|
|
297
|
+
process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
|
|
298
|
+
`);
|
|
299
|
+
process.exit(EXIT_ERROR);
|
|
300
|
+
});
|
|
301
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/output.ts"],"sourcesContent":["import { writeFile } from 'node:fs/promises'\nimport { readFileSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport { createServer } from 'node:http'\nimport { createRequire } from 'node:module'\nimport { fileURLToPath } from 'node:url'\nimport { parseArgs, numFlag, UsageError } from './args'\nimport {\n createReporter,\n EXIT_ERROR,\n EXIT_NO_BROWSER,\n EXIT_OK,\n EXIT_USAGE,\n} from './output'\nimport { loadVosConfig, configDuration } from './loadConfig'\nimport { launchBrowser, BrowserUnavailableError } from './browser'\nimport { renderVideo, renderStill, previewPages } from './render'\n\nconst BOOLEAN_FLAGS = new Set(['json', 'help', 'version'])\n\nconst HELP = `vos — command line for the vos programmatic video engine (https://vos.so)\n\nUsage\n vos render <config.json|url> [out] [--width 1920] [--height 1080] [--fps 30]\n [--duration <s>] [--format webm|mp4] [--json]\n vos still <config.json|url> [out.webp] [--time 0] [--width] [--height] [--json]\n vos info <config.json|url> [--json]\n vos preview <config.json|url> [--port 0]\n vos versions [--json]\n vos voila <subcommand> … (product-video pipeline — separate install)\n\nConventions\n Results go to stdout; logs go to stderr. --json switches stdout to NDJSON\n events ending with {\"event\":\"done\",…}. Exit codes: 0 ok, 1 error, 2 usage,\n 3 no browser available.\n`\n\nfunction outName(source: string, ext: string): string {\n const base = basename(source).replace(/\\.[a-z0-9]+$/i, '') || 'vos'\n return `${base}.${ext}`\n}\n\nasync function cmdRender(argv: string[]): Promise<number> {\n const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS)\n const source = positionals[0]\n if (!source) throw new UsageError('vos render <config.json|url> [out]')\n const r = createReporter(flags.json === true)\n const format = (flags.format as string) ?? 'webm'\n if (format !== 'webm' && format !== 'mp4') throw new UsageError('--format must be webm or mp4')\n\n const { config, warnings } = await loadVosConfig(source)\n for (const w of warnings) r.log(`note: ${w}`)\n const duration = numFlag(flags, 'duration', configDuration(config) ?? 5)\n const width = numFlag(flags, 'width', 1920)\n const height = numFlag(flags, 'height', 1080)\n const fps = numFlag(flags, 'fps', 30)\n const out = positionals[1] ?? outName(source, format)\n\n const browser = await launchBrowser()\n try {\n const result = await renderVideo(browser, {\n config,\n width,\n height,\n fps,\n duration,\n format,\n onPhase: (phase) => {\n r.log(`${phase}…`)\n r.event({ event: 'phase', phase })\n },\n })\n await writeFile(out, result.bytes)\n r.done(\n { out, bytes: result.bytes.length, width, height, fps, duration, format },\n `Wrote ${out} (${(result.bytes.length / 1024).toFixed(0)} KB, ${width}x${height}@${fps}, ${duration}s)`,\n )\n return EXIT_OK\n } finally {\n await browser.close()\n }\n}\n\nasync function cmdStill(argv: string[]): Promise<number> {\n const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS)\n const source = positionals[0]\n if (!source) throw new UsageError('vos still <config.json|url> [out.webp]')\n const r = createReporter(flags.json === true)\n\n const { config, warnings } = await loadVosConfig(source)\n for (const w of warnings) r.log(`note: ${w}`)\n const time = numFlag(flags, 'time', 0)\n const width = numFlag(flags, 'width', 1280)\n const height = numFlag(flags, 'height', 720)\n const out = positionals[1] ?? outName(source, 'webp')\n\n const browser = await launchBrowser()\n try {\n const result = await renderStill(browser, {\n config,\n width,\n height,\n time,\n onPhase: (phase) => {\n r.log(`${phase}…`)\n r.event({ event: 'phase', phase })\n },\n })\n await writeFile(out, result.bytes)\n r.done(\n { out, bytes: result.bytes.length, width, height, time, mimeType: result.mimeType },\n `Wrote ${out} (${(result.bytes.length / 1024).toFixed(0)} KB, ${width}x${height} @ t=${time}s)`,\n )\n return EXIT_OK\n } finally {\n await browser.close()\n }\n}\n\nasync function cmdInfo(argv: string[]): Promise<number> {\n const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS)\n const source = positionals[0]\n if (!source) throw new UsageError('vos info <config.json|url>')\n const r = createReporter(flags.json === true)\n\n const { config, warnings } = await loadVosConfig(source)\n const elements = Array.isArray(config.elements) ? config.elements.length : 0\n const data = typeof config.data === 'object' && config.data !== null ? Object.keys(config.data) : []\n const fns = ['setup', 'createContent', 'createTimeline', 'onFrame'].filter(\n (k) => typeof config[k] === 'string',\n )\n const info = {\n version: config.version,\n duration: configDuration(config) ?? null,\n camera: (config.camera as Record<string, unknown> | undefined)?.preset ?? null,\n elements,\n dataKeys: data,\n functions: fns,\n warnings,\n }\n if (r.json) r.done(info, '')\n else {\n for (const w of warnings) r.log(`note: ${w}`)\n process.stdout.write(\n `version: v${String(info.version)}\\n` +\n `duration: ${info.duration === null ? '(none)' : `${info.duration}s`}\\n` +\n `camera: ${String(info.camera ?? '(default)')}\\n` +\n `elements: ${elements}\\n` +\n `data keys: ${data.length ? data.join(', ') : '(none)'}\\n` +\n `functions: ${fns.join(', ')}\\n`,\n )\n }\n return EXIT_OK\n}\n\nfunction packageVersion(name: string): string | null {\n const require = createRequire(import.meta.url)\n // Fast path — packages that export ./package.json (e.g. playwright).\n try {\n return (require(`${name}/package.json`) as { version: string }).version\n } catch {\n // Strict `exports` maps (the @vosjs packages) hide package.json — resolve\n // the entry module instead and walk up to the owning package.json.\n }\n let entry: string | null = null\n try {\n entry = fileURLToPath(import.meta.resolve(name))\n } catch {\n try {\n entry = require.resolve(name)\n } catch {\n return null\n }\n }\n let dir = dirname(entry)\n for (let i = 0; i < 6; i++) {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {\n name?: string\n version?: string\n }\n if (pkg.name === name && pkg.version) return pkg.version\n } catch {\n // keep walking\n }\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n return null\n}\n\nasync function cmdVersions(argv: string[]): Promise<number> {\n const { flags } = parseArgs(argv, BOOLEAN_FLAGS)\n const r = createReporter(flags.json === true)\n const versions: Record<string, string> = {}\n // Own version: read relative to dist (self-require is blocked by `exports`).\n try {\n const own = JSON.parse(\n readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8'),\n ) as { version: string }\n versions['@vosjs/cli'] = own.version\n } catch {\n versions['@vosjs/cli'] = '(unknown)'\n }\n for (const name of ['@vosjs/core', '@vosjs/elements', '@vosjs/tween', 'playwright']) {\n versions[name] = packageVersion(name) ?? '(not found)'\n }\n if (r.json) r.done({ versions }, '')\n else for (const [k, v] of Object.entries(versions)) process.stdout.write(`${k} ${v}\\n`)\n return EXIT_OK\n}\n\nasync function cmdPreview(argv: string[]): Promise<number> {\n const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS)\n const source = positionals[0]\n if (!source) throw new UsageError('vos preview <config.json|url> [--port 0]')\n const r = createReporter(false)\n const { config, warnings } = await loadVosConfig(source)\n for (const w of warnings) r.log(`note: ${w}`)\n const { hostHtml, playerHtml } = previewPages(config)\n const server = createServer((req, res) => {\n const path = new URL(req.url ?? '/', 'http://x').pathname\n res.writeHead(200, { 'content-type': 'text/html' })\n res.end(path === '/player' ? playerHtml : hostHtml)\n })\n const port = numFlag(flags, 'port', 0)\n await new Promise<void>((resolve) => server.listen(port, resolve))\n const addr = server.address()\n const url = `http://localhost:${typeof addr === 'object' && addr ? addr.port : port}/`\n process.stdout.write(`${url}\\n`)\n r.log('Serving playback preview — Ctrl-C to stop.')\n await new Promise(() => {}) // keep alive until interrupted\n return EXIT_OK\n}\n\nasync function cmdVoila(argv: string[]): Promise<number> {\n try {\n const mod = (await import('@vosso/voila-cli' as string)) as {\n run?: (argv: string[]) => Promise<number>\n }\n if (typeof mod.run === 'function') return await mod.run(argv)\n process.stderr.write('@vosso/voila-cli is installed but exposes no run() — update it.\\n')\n return EXIT_ERROR\n } catch {\n process.stderr.write(\n 'The Voila product-video pipeline ships separately.\\n' +\n ' npm i -D @vosso/voila-cli\\n' +\n 'then re-run: vos voila …\\n',\n )\n return EXIT_ERROR\n }\n}\n\nasync function main(): Promise<number> {\n const [cmd, ...rest] = process.argv.slice(2)\n if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {\n process.stdout.write(HELP)\n return cmd ? EXIT_OK : EXIT_USAGE\n }\n if (cmd === '--version') return cmdVersions(['--json'])\n switch (cmd) {\n case 'render':\n return cmdRender(rest)\n case 'still':\n return cmdStill(rest)\n case 'info':\n return cmdInfo(rest)\n case 'versions':\n return cmdVersions(rest)\n case 'preview':\n return cmdPreview(rest)\n case 'voila':\n return cmdVoila(rest)\n default:\n throw new UsageError(`unknown command \"${cmd}\" — run vos help`)\n }\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((e) => {\n if (e instanceof UsageError) {\n process.stderr.write(`usage error: ${e.message}\\n`)\n process.exit(EXIT_USAGE)\n }\n if (e instanceof BrowserUnavailableError) {\n process.stderr.write(`${e.message}\\n`)\n process.exit(EXIT_NO_BROWSER)\n }\n process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}\\n`)\n process.exit(EXIT_ERROR)\n })\n","/**\n * Output conventions (agent-safe by construction):\n * - logs/progress → stderr (human readable)\n * - results → stdout; with --json, NDJSON events (`{\"event\":\"phase\"...}` then\n * `{\"event\":\"done\"...}`) so scripts and agents never parse prose\n * - exit codes: 0 ok · 1 failure · 2 usage · 3 browser unavailable\n */\nexport const EXIT_OK = 0\nexport const EXIT_ERROR = 1\nexport const EXIT_USAGE = 2\nexport const EXIT_NO_BROWSER = 3\n\nexport interface Reporter {\n json: boolean\n /** Human log line (stderr; suppressed in --json mode). */\n log: (msg: string) => void\n /** Machine event (stdout NDJSON in --json mode; no-op otherwise). */\n event: (obj: Record<string, unknown>) => void\n /** Final result: NDJSON `done` event in --json mode, plain line otherwise. */\n done: (obj: Record<string, unknown>, humanLine: string) => void\n}\n\nexport function createReporter(json: boolean): Reporter {\n return {\n json,\n log: (msg) => {\n if (!json) process.stderr.write(`${msg}\\n`)\n },\n event: (obj) => {\n if (json) process.stdout.write(`${JSON.stringify(obj)}\\n`)\n },\n done: (obj, humanLine) => {\n if (json) process.stdout.write(`${JSON.stringify({ event: 'done', ...obj })}\\n`)\n else process.stdout.write(`${humanLine}\\n`)\n },\n }\n}\n"],"mappings":"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","names":["require"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Browser } from 'playwright';
|
|
2
|
+
|
|
3
|
+
interface RenderCommonOptions {
|
|
4
|
+
config: Record<string, unknown>;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
/** Phase callback for progress reporting. */
|
|
8
|
+
onPhase?: (phase: string) => void;
|
|
9
|
+
}
|
|
10
|
+
interface RenderVideoOptions extends RenderCommonOptions {
|
|
11
|
+
fps: number;
|
|
12
|
+
/** Output duration in seconds. */
|
|
13
|
+
duration: number;
|
|
14
|
+
format: 'webm' | 'mp4';
|
|
15
|
+
}
|
|
16
|
+
interface RenderStillOptions extends RenderCommonOptions {
|
|
17
|
+
/** Seek time for the captured frame, seconds. */
|
|
18
|
+
time: number;
|
|
19
|
+
}
|
|
20
|
+
interface RenderResult {
|
|
21
|
+
bytes: Uint8Array;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
}
|
|
24
|
+
/** Render a vos config to a video (WebM/MP4) in a headless browser. */
|
|
25
|
+
declare function renderVideo(browser: Browser, opts: RenderVideoOptions): Promise<RenderResult>;
|
|
26
|
+
/** Render a single frame of a vos config to an image in a headless browser. */
|
|
27
|
+
declare function renderStill(browser: Browser, opts: RenderStillOptions): Promise<RenderResult>;
|
|
28
|
+
interface PreviewPages {
|
|
29
|
+
/** Host page: iframes the player and drives it over the bridge. */
|
|
30
|
+
hostHtml: string;
|
|
31
|
+
/** The engine's playback-mode player (waits for a LOAD postMessage). */
|
|
32
|
+
playerHtml: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pages for `vos preview`. The playback template is the iframe HALF of the
|
|
36
|
+
* player bridge — it renders nothing until a host posts `LOAD`, so the CLI
|
|
37
|
+
* serves a minimal host page that sends `LOAD { code, autoplay }` and shows
|
|
38
|
+
* a transport line (time / duration, click to play-pause).
|
|
39
|
+
*/
|
|
40
|
+
declare function previewPages(config: Record<string, unknown>): PreviewPages;
|
|
41
|
+
|
|
42
|
+
interface LoadedConfig {
|
|
43
|
+
config: Record<string, unknown>;
|
|
44
|
+
warnings: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load a VosConfigJson from a file path or http(s) URL, unwrap API `{ config }`
|
|
48
|
+
* envelopes, migrate old versions, and validate against the schema.
|
|
49
|
+
*/
|
|
50
|
+
declare function loadVosConfig(source: string): Promise<LoadedConfig>;
|
|
51
|
+
/** Best-effort duration from the config (seconds). */
|
|
52
|
+
declare function configDuration(config: Record<string, unknown>): number | undefined;
|
|
53
|
+
|
|
54
|
+
declare class BrowserUnavailableError extends Error {
|
|
55
|
+
constructor(cause: string);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Launch headless Chromium: explicit VOS_BROWSER_PATH → system Chrome
|
|
59
|
+
* (no download needed) → Playwright's bundled Chromium.
|
|
60
|
+
*/
|
|
61
|
+
declare function launchBrowser(): Promise<Browser>;
|
|
62
|
+
|
|
63
|
+
export { BrowserUnavailableError, type LoadedConfig, type RenderResult, type RenderStillOptions, type RenderVideoOptions, configDuration, launchBrowser, loadVosConfig, previewPages, renderStill, renderVideo };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BrowserUnavailableError,
|
|
4
|
+
configDuration,
|
|
5
|
+
launchBrowser,
|
|
6
|
+
loadVosConfig,
|
|
7
|
+
previewPages,
|
|
8
|
+
renderStill,
|
|
9
|
+
renderVideo
|
|
10
|
+
} from "./chunk-A5AN2K5L.js";
|
|
11
|
+
export {
|
|
12
|
+
BrowserUnavailableError,
|
|
13
|
+
configDuration,
|
|
14
|
+
launchBrowser,
|
|
15
|
+
loadVosConfig,
|
|
16
|
+
previewPages,
|
|
17
|
+
renderStill,
|
|
18
|
+
renderVideo
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vosjs/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command line for the vos programmatic video engine — render deterministic videos and stills from vos configs, headlessly, from your terminal or an AI agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "vosso",
|
|
8
|
+
"homepage": "https://vos.so/engine",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/vosjs/vos.git",
|
|
12
|
+
"directory": "packages/cli"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/vosjs/vos/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"vos",
|
|
19
|
+
"cli",
|
|
20
|
+
"video",
|
|
21
|
+
"render",
|
|
22
|
+
"deterministic",
|
|
23
|
+
"headless",
|
|
24
|
+
"agent",
|
|
25
|
+
"animation",
|
|
26
|
+
"three"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"vos": "./dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"sideEffects": false,
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"playwright": "^1.49.0",
|
|
45
|
+
"@vosjs/core": "^0.7.0",
|
|
46
|
+
"@vosjs/tween": "^0.7.2",
|
|
47
|
+
"@vosjs/elements": "^0.3.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22",
|
|
51
|
+
"tsup": "^8.5.0",
|
|
52
|
+
"typescript": "^5",
|
|
53
|
+
"vitest": "^3.0.5"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"lint": "eslint .",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"clean": "rm -rf dist node_modules"
|
|
64
|
+
}
|
|
65
|
+
}
|