@vosjs/cli 0.13.0 → 0.13.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/README.md +171 -90
- package/dist/{chunk-5BFGODBI.js → chunk-EFI3WUQB.js} +5 -1
- package/dist/chunk-EFI3WUQB.js.map +1 -0
- package/dist/{chunk-X2BKHZ56.js → chunk-GCWE75OA.js} +1 -105
- package/dist/chunk-GCWE75OA.js.map +1 -0
- package/dist/chunk-NHKHTIDR.js +216 -0
- package/dist/chunk-NHKHTIDR.js.map +1 -0
- package/dist/{chunk-6TFKMNSG.js → chunk-YICRU4ER.js} +74 -28
- package/dist/chunk-YICRU4ER.js.map +1 -0
- package/dist/cli.js +32 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -7
- package/dist/manifest-UCS6OVWZ.js +8 -0
- package/dist/run-PCWIQ4GI.js +9 -0
- package/package.json +9 -9
- package/schema/doc.schema.json +1 -1
- package/dist/chunk-5BFGODBI.js.map +0 -1
- package/dist/chunk-6TFKMNSG.js.map +0 -1
- package/dist/chunk-A2VSDK3C.js +0 -42
- package/dist/chunk-A2VSDK3C.js.map +0 -1
- package/dist/chunk-X2BKHZ56.js.map +0 -1
- package/dist/manifest-3LIL5M3F.js +0 -8
- package/dist/run-VU6B3CXC.js +0 -9
- /package/dist/{manifest-3LIL5M3F.js.map → manifest-UCS6OVWZ.js.map} +0 -0
- /package/dist/{run-VU6B3CXC.js.map → run-PCWIQ4GI.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,101 +1,123 @@
|
|
|
1
1
|
# @vosjs/cli
|
|
2
2
|
|
|
3
|
+
> The `vos` binary. Render vos configs to video and stills, record the real product from a scripted browser flow, plan zooms and pacing from the cursor track, cut as data, deliver a release's media per channel spec, and sync with vos.so. One package, every verb, MIT.
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@vosjs/cli)
|
|
4
|
-
[](https://github.com/vosjs/vos/blob/main/LICENSE)
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
Part of [vos](https://github.com/vosjs/vos), the open programmatic video engine behind [vos.so](https://vos.so). Designed to be driven by coding agents (Claude Code, Codex, Cursor) as well as by hand: logs on stderr, results on stdout, `--json` everywhere, and every editing decision in a JSON file.
|
|
7
9
|
|
|
8
10
|
```bash
|
|
9
|
-
npm i -D @vosjs/cli
|
|
10
|
-
|
|
11
|
+
npm i -g @vosjs/cli # or: npm i -D @vosjs/cli && npx vos …
|
|
12
|
+
vos render animation.json out.webm
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Deterministic: the preview is the render, and every edit is a data patch to `doc.json`, never a re-record.
|
|
15
|
+
Deterministic: the preview is the render, and every edit is a data patch to `doc.json`, never a re-record. Rendering is local and free at every resolution up to 4K, no watermark. The workflow skills for agents install with `npx skills add vosjs/skills` (`product-video` records and cuts one video, `vos-cut` cuts an existing recording, `launch-kit` ships the media with a release).
|
|
16
|
+
|
|
17
|
+
Until 0.9 the take pipeline and the vos.so verbs shipped separately as `@vosso/vos-plugin`, and before that as `@vosso/cli` and `@vosso/voila-cli`; those names are deprecated on npm and forward here.
|
|
18
|
+
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [Requirements](#requirements)
|
|
22
|
+
- [Engine verbs](#engine-verbs)
|
|
23
|
+
- [The take pipeline](#the-take-pipeline)
|
|
24
|
+
- [The take directory](#the-take-directory)
|
|
25
|
+
- [actions.json](#actionsjson)
|
|
26
|
+
- [doc.json](#docjson)
|
|
27
|
+
- [Overrides on render and frames](#overrides-on-render-and-frames)
|
|
28
|
+
- [Delivering a release](#delivering-a-release)
|
|
29
|
+
- [The vos.so loop](#the-vosso-loop)
|
|
30
|
+
- [CI](#ci)
|
|
31
|
+
- [For scripts and agents](#for-scripts-and-agents)
|
|
32
|
+
- [Programmatic use](#programmatic-use)
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
14
35
|
|
|
15
|
-
|
|
36
|
+
Node 18 or newer and a Chromium-family browser. A system Chrome is used when present; otherwise `npx playwright install chromium` once, or set `VOS_BROWSER_PATH`. mp4 output needs Chrome (Chromium ships no AVC encoder). Render pages load `three`, `gsap` and mediabunny from a CDN, so rendering needs network access.
|
|
16
37
|
|
|
17
38
|
## Engine verbs
|
|
18
39
|
|
|
19
40
|
```bash
|
|
20
|
-
vos render
|
|
21
|
-
vos still
|
|
22
|
-
vos info
|
|
23
|
-
vos check
|
|
24
|
-
vos preview <config.json|url>
|
|
25
|
-
vos versions
|
|
41
|
+
vos render <config.json|url|take> [out] # config → video (a take directory renders through the take pipeline)
|
|
42
|
+
vos still <config.json|url> [out.webp] # config → one frame, WebP
|
|
43
|
+
vos info <config.json|url> # inspect a config
|
|
44
|
+
vos check <config.json|url> # migrate → schema → syntax → compile → determinism and dialect lints, all local
|
|
45
|
+
vos preview <config.json|url> [--port N] # serve a local playback page
|
|
46
|
+
vos versions # installed @vosjs/* versions
|
|
26
47
|
```
|
|
27
48
|
|
|
28
|
-
|
|
49
|
+
| Verb | Flags and defaults |
|
|
50
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `render` | `--width 1920` `--height 1080` `--fps 30` `--duration <config.duration>` `--format webm\|mp4` (webm); `out` defaults to `<name>.<format>` |
|
|
52
|
+
| `still` | `--time 0` `--width 1280` `--height 720`; the output is always WebP (a `.png` name is refused) |
|
|
53
|
+
| `check` | Exits 1 on any error. Runs the same compiler a hosted push runs, so a clean check is a config that compiles anywhere |
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+
Configs can be local files or URLs; a platform `{ "config": … }` envelope is unwrapped. Rendering compiles the config with `@vosjs/core`, wraps it in the engine's capture template, and encodes frame by frame (WebCodecs) in headless Chromium. Same input, same video: locally, in CI, or on a server. Every engine verb takes a directory too: a take (its `doc.json` is a recording document) renders through the take pipeline, and a program directory (`config.json`, composed with the program document beside it when there is one) renders as what the studio plays. A deterministic sniff of the document, never a flag.
|
|
31
56
|
|
|
32
57
|
## The take pipeline
|
|
33
58
|
|
|
59
|
+
A **take** is a directory: the recording of a scripted browser flow, its exact cursor track, and `doc.json`, the editable cut.
|
|
60
|
+
|
|
34
61
|
```bash
|
|
35
|
-
vos create --actions actions.json out.webm --strict # one
|
|
36
|
-
vos record --actions actions.json --out take --strict # drive the page,
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
vos
|
|
40
|
-
vos
|
|
41
|
-
|
|
42
|
-
vos frames take --
|
|
43
|
-
vos
|
|
44
|
-
vos render take out.webm # deterministic polished render
|
|
45
|
-
vos frames take --frame 2.5 --size 1280x800 # exact-size still — posters, OG cards, store screenshots
|
|
46
|
-
vos deliver take --to cws,producthunt,og --release "v2.1" # the release's assets per channel spec + verified kit.json
|
|
47
|
-
vos deliver take --to cws,og,linkedin --poster poster.json # + the CARD half: covers composed by your poster program, this release's shot baked in
|
|
48
|
-
vos validate take/kit/kit.json # re-measure every kit asset from its bytes against the channel specs
|
|
49
|
-
vos brand https://your.app --out BRAND.md # the brand kit, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)
|
|
50
|
-
vos actions from-agent-browser steps.jsonl # the walk an agent made in agent-browser → actions.json (no second script; what cannot follow is named)
|
|
51
|
-
vos open take # hand the take to the studio — a human can drag every zoom span
|
|
52
|
-
vos push take --yes --note "first pass" # host it: private vos + version history (recording uploads once)
|
|
53
|
-
vos pull take # take the human's studio edits back: typed changelog + fresh doc.json
|
|
54
|
-
vos fetch <vosId> --media # a take you did not record: doc.json + the recording, ready for digest/frames/render
|
|
62
|
+
vos create --actions actions.json out.webm --strict # one shot: record, auto-plan, render
|
|
63
|
+
vos record --actions actions.json --out take --strict # drive the page, record it with an exact cursor track, plan the cut
|
|
64
|
+
vos digest take # SEE the recording before cutting: moments, frames, crops (an agent's eyes)
|
|
65
|
+
# … edit take/doc.json (zoom spans, trims, speed, overlays) by hand or by agent …
|
|
66
|
+
vos frames take --at-zooms --at-moments # PNG stills: contact sheet, every zoom apex, every moment
|
|
67
|
+
vos render take check.webm --range 4..8 --draft # spot-check an edit in seconds (half res, low bitrate)
|
|
68
|
+
vos render take out.webm # the polished render
|
|
69
|
+
vos frames take --frame 2.5 --size 1280x800 # an exact-size still: posters, OG cards, store screenshots
|
|
70
|
+
vos open take # hand the take to the studio; a human drags every span
|
|
55
71
|
```
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Platform verbs — the vos.so loop
|
|
60
|
-
|
|
61
|
-
All vos.so traffic lives behind one client: one origin (`VOS_ORIGIN`, default `https://vos.so`), one credential ladder (`--key` → `VOS_API_KEY` → `~/.config/vos/credentials`, written by `vos login`; a `vos_rg_` remix grant is just a key), one tracking file (`vos.json` beside the artifact; the legacy `push.json`/`meta.json` still read). Credentials are never printed. Keys can never publish — pushes are **private**; humans publish on vos.so.
|
|
73
|
+
When the product ships again, re-record the same script and keep the cut:
|
|
62
74
|
|
|
63
75
|
```bash
|
|
64
|
-
vos
|
|
65
|
-
|
|
66
|
-
vos fetch bright-loop # a program: writes config.json + vos.json (public needs no auth)
|
|
67
|
-
vos check bright-loop/config.json # full local validation
|
|
68
|
-
vos push bright-loop/config.json # create a PRIVATE vos (lineage from vos.json / --remix-of)
|
|
69
|
-
vos push bright-loop/config.json --vos <id> # add a version against your tracked base
|
|
70
|
-
vos push bright-loop/config.json --claimable # NO credential: a 72h claim link instead (programs only)
|
|
71
|
-
vos pull bright-loop # what changed on vos.so; syncs config.json (backup kept)
|
|
72
|
-
vos duplicate <vosId> # a private sibling of your OWN vos (someone else's is remixed: fetch + push --remix-of)
|
|
76
|
+
vos record --actions actions.json --out take --strict # footage replaced; the previous cut survives as doc.prev.json
|
|
77
|
+
vos plan take --reuse # re-time that cut onto the new recording; what could not follow is named
|
|
73
78
|
```
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
| Verb | Flags |
|
|
81
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--max-duration <s>` `--background <slug\|url\|none>` |
|
|
83
|
+
| `create` | The `record` flags plus the render flags (`--width` `--height` `--fps` `--format` `--parallel` `--draft` `--frame` `--set`), no `--range`. With `--strict` an incomplete recording exits 2 before anything is rendered |
|
|
84
|
+
| `plan` | `--fresh` (discard the current plan) `--reuse` `--from <doc.json>` (defaults to `<take>/doc.prev.json`) `--style <doc.json\|take\|vosId>` `--background` |
|
|
85
|
+
| `digest` | `--out <take>/digest` `--full 960` `--crop 640` (image long edges, the token budget) `--no-frames` `--transcript <file>` (Whisper-shaped segments merged as `said`) `--style <ref>` (report a reference document's style fields) |
|
|
86
|
+
| `frames` | `--times 0,25%,50%,75%,100%` (the default selector, output seconds or percent) `--frame <t>` `--at-zooms` `--at-moments` `--size WxH` `--out <take>/stills` `--background` `--set …`; writes `stills.json` |
|
|
87
|
+
| `render` | `--width` `--height` `--fps` `--format webm\|mp4` `--parallel N` (1..16 sessions) `--range a..b` (output seconds; keeps its audio) `--draft` `--frame <kind>` `--background` `--set …`; `out` defaults to `<take>/out.<format>` |
|
|
88
|
+
| `open` | `--studio http://localhost:6060` `--print` (print the URL, do not launch a browser) |
|
|
89
|
+
|
|
90
|
+
**Digest first.** `vos digest <take>` is how an agent sees a recording without reading the video. It writes `digest/digest.json`: one moment per thing the cursor track says mattered (click clusters, typing sessions, scroll runs, dwells, idle gaps, head, tail, and frame-diff scene changes), each with source and output extents, a normalized `focus` and `rect` you can copy into a zoom span, per-second `activity`, and the planners' `proposed` span ids; plus one footage frame and a crop around the target per moment, and `sheet.png`, the contact sheet. Read the JSON, then the sheet, then a crop only where you must decide. `vos validate` then warns when a zoom does not contain what was clicked under it, and `vos frames --at-moments` renders the composed output at every moment so a still and its footage crop share an id.
|
|
76
91
|
|
|
77
|
-
|
|
92
|
+
**A series shares its look by data.** `vos plan <take> --style <seed doc.json | vosId>` copies the seed's `zoomStyle`, `zoomParams`, `speedParams`, `tiltStyle`, `frame`, `cursor`, `cam` and `export` onto a new take (never its spans, overlays or audio) and re-plans the automatic spans under them.
|
|
93
|
+
|
|
94
|
+
**A fresh take opens on a backdrop.** `create`, `record` and `plan` put the first ready loop from `GET /api/backdrops` behind the card (its ground colour as `frame.background`); `--background <slug|url|none>` overrides it, and offline the frame stays bare with a note.
|
|
95
|
+
|
|
96
|
+
**The human handoff.** `vos open <take>` serves the take directory (CORS-open, ephemeral port) and opens the studio at `?take=<server>`. `doc.json` hydrates directly, so an agent's edits arrive intact and every span is draggable; it keeps serving until Ctrl-C. Inside the studio, `window.__vos` (`openTake`, `getDoc`, `edit`, `undo`, `redo`, `setSelection`) is the sanctioned scripting surface; edits go through the patch store, undoable like any user edit.
|
|
97
|
+
|
|
98
|
+
**Render time** (measured on an M-series laptop, 1080p): about 1.5x real time single-flight, of which several seconds are fixed browser launch and CDN module cost, so short takes are overhead-dominated and `--parallel` pays off on takes past roughly 30 s. 2K roughly doubles per-frame cost. Recording is real time plus a few seconds of encode.
|
|
78
99
|
|
|
79
100
|
## The take directory
|
|
80
101
|
|
|
81
102
|
```
|
|
82
103
|
take/
|
|
83
|
-
recording.webm encoded footage (CFR WebM)
|
|
84
|
-
frames/ raw screencast JPEGs (kept for re-encode)
|
|
85
|
-
cursor.json synthesized CursorTrack
|
|
86
|
-
meta.json RecordingMeta (producer: "cli")
|
|
87
|
-
actions.json the script that produced it
|
|
88
|
-
doc.json ProjectDoc
|
|
89
|
-
|
|
104
|
+
recording.webm encoded footage (30 fps CFR WebM)
|
|
105
|
+
frames/ raw screencast JPEGs, indexed by frames.json (kept for re-encode)
|
|
106
|
+
cursor.json the synthesized CursorTrack: exact coordinates, element rects
|
|
107
|
+
meta.json RecordingMeta (producer: "cli", per-step source extents)
|
|
108
|
+
actions.json the script that produced it, with the resolved url
|
|
109
|
+
doc.json ProjectDoc, the editable cut
|
|
110
|
+
doc.prev.json the previous cut, kept by a re-record for `vos plan --reuse`
|
|
111
|
+
vos.json hosted tracking (vos id + base version), written by push, fetch and pull
|
|
112
|
+
stills/ `vos frames` output + stills.json
|
|
113
|
+
digest/ `vos digest` output: digest.json, <id>.full.png, <id>.crop.png, sheet.png
|
|
114
|
+
kit/ `vos deliver` output: kit.json + the assets
|
|
115
|
+
mic.webm, cam.webm sidecar tracks, present on takes pulled from vos.so with --media
|
|
90
116
|
```
|
|
91
117
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Contracts that bite: time is **SOURCE seconds** everywhere in `zoom`/`segments`/`speed`; zoom `cx`/`cy` are **normalized [0..1] video-frame fractions** (0.5, 0.5 = center), never pixels; `level` is 1..5. The full shape ships as a JSON Schema at [`schema/doc.schema.json`](./schema/doc.schema.json) (a `oneOf`: the recording document and the program document, sharing the layer definitions), and `vos validate <dir>` lints the semantics of either (span overlap, footage bounds, coord ranges, export honesty) before you spend a render.
|
|
95
|
-
|
|
96
|
-
The package also ships [`schema/channel-specs.json`](./schema/channel-specs.json): per-channel launch-asset specs (store screenshots, social cuts, README loops — dimensions, byte and duration ceilings). `vos deliver <take> --to <channels>` loops them in one pass — stills at each spec's exact pixels, video cuts, every artifact verified (misses land in `skipped[]` with the reason) — and writes `kit.json`, the manifest the `launch-kit` agent skill at [github.com/vosjs/skills](https://github.com/vosjs/skills) builds the rest of the release around.
|
|
118
|
+
A re-record replaces the footage, the cursor track, the frames and everything derived from them, moves `doc.json` to `doc.prev.json`, and keeps `actions.json` and `vos.json`.
|
|
97
119
|
|
|
98
|
-
##
|
|
120
|
+
## actions.json
|
|
99
121
|
|
|
100
122
|
```json
|
|
101
123
|
{
|
|
@@ -104,63 +126,120 @@ The package also ships [`schema/channel-specs.json`](./schema/channel-specs.json
|
|
|
104
126
|
"steps": [
|
|
105
127
|
{ "do": "wait", "ms": 700 },
|
|
106
128
|
{ "do": "hover", "selector": "a[href='/pricing']", "ms": 700 },
|
|
107
|
-
{ "do": "click", "selector": "#signup" },
|
|
129
|
+
{ "id": "signup", "do": "click", "selector": "#signup" },
|
|
108
130
|
{
|
|
109
131
|
"do": "type",
|
|
110
132
|
"selector": "input[name=email]",
|
|
111
133
|
"text": "demo@example.com"
|
|
112
134
|
},
|
|
113
135
|
{ "do": "scroll", "dy": 400 },
|
|
114
|
-
{ "do": "move", "x": 640, "y": 320 }
|
|
136
|
+
{ "do": "move", "x": 640, "y": 320 },
|
|
137
|
+
{ "do": "drag", "selector": ".knob", "tx": 300, "ty": 0, "ms": 700 }
|
|
115
138
|
]
|
|
116
139
|
}
|
|
117
140
|
```
|
|
118
141
|
|
|
119
|
-
|
|
142
|
+
Seven verbs: `wait`, `hover` (`ms` 700), `click`, `type` (`delayMs` 40 per key; `focus: false` skips the focusing click, for a submitting Enter), `scroll`, `move`, `drag` (press, move, release: a range input, a canvas element, a timeline clip). Every step takes an optional unique `id`; give steps ids so a span anchored to a step survives script edits and `vos plan --reuse` can follow it. Because the CLI issues every input itself, the cursor track is synthesized with exact coordinates, exact timing and fresh element rects, which is what powers element-aware auto-zoom and click effects downstream. The schema is [`schema/actions.schema.json`](./schema/actions.schema.json); `vos validate actions.json` checks a script without running anything.
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
Verified the flow in agent-browser already? `vos actions from-agent-browser steps.jsonl [--out actions.json] [--url] [--viewport WxH]` writes the script from that walk (each command kept beside its `--json` result, the batch record shape; refs resolve through the last `snapshot -i`), and names every step the recorder cannot follow rather than dropping it.
|
|
122
145
|
|
|
123
|
-
|
|
146
|
+
## doc.json
|
|
124
147
|
|
|
125
|
-
|
|
148
|
+
`doc.json` is a `ProjectDoc` from [`@vosjs/studio-core`](../studio-core), all plain JSON. Zoom is `zoom: [{ id, in, out, level, cx, cy, source }]`, trims are `segments`, pacing is `speed`. Edit and re-render; nothing re-runs the browser. The full shape ships as a JSON Schema at [`schema/doc.schema.json`](./schema/doc.schema.json) (a `oneOf`: the recording document and the program document, sharing the layer definitions), and `vos validate <dir>` lints the semantics of either (span overlap, footage bounds, coordinate ranges, export honesty) before you spend a render.
|
|
149
|
+
|
|
150
|
+
**Contracts that bite.** Time is source seconds in `zoom`, `segments`, `speed` and `tilt`, and output seconds in `overlays` and `audio`. Zoom `cx`/`cy` and overlay `transform.x`/`y` are normalized fractions of the frame in `[0, 1]` (`0.5, 0.5` is the centre), never pixels. `level` is 1..5. The planners write `source: "auto"`; spans you add or edit carry `source: "manual"`, which a re-plan never touches, and a deleted automatic span is recorded in `doc.rejected` so a re-plan does not bring it back.
|
|
151
|
+
|
|
152
|
+
**Camera styles.** `doc.zoomStyle` is one of `glide` (default), `focus`, `cinema`, `snappy`, `cut`, `keynote` (gliding zooms plus a medium lean toward each focus, the launch-film register), `drift` (slow ambient zooms with a subtle lean) or `none`. In the studio, picking a style also stamps `tiltStyle` and plans the tilt spans; in `doc.json` set `tiltStyle` and `tilt` yourself.
|
|
153
|
+
|
|
154
|
+
**Tilt.** `doc.tilt`: source-anchored, non-overlapping spans where the card leans to a pose and returns to rest: `[{ "id": "u0", "in": 4, "out": 8, "rx": 6, "ry": -9, "source": "manual" }]`. `rx`/`ry` are degrees (±45 hard limit; ±5..18 reads well); `+rx` brings the top edge toward the camera, `+ry` the left edge, so a lean toward a right-side focus is a negative `ry`. Rest is flat. `doc.tiltStyle` (`off`, `subtle`, `medium`, `strong`) is the intensity the planner derives automatic spans from.
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
**Text overlays.** `doc.overlays`: screen-space clips above the card, outside the zoom, output-anchored. `{ "id": "t0", "kind": "text", "start": 1, "duration": 3, "text": "Ship it", "preset": "title", "transform": { "x": 0.5, "y": 0.82, "scale": 1, "rotation": 0 }, "enter": "rise", "exit": "fade" }`. Presets `title`, `caption`, `label`, overridable with `size` (12..200 design px) and `color`; `\n` breaks lines; a lower third sits at `y` ≈ 0.82. Enter and exit: `rise`, `fade`, `none`. Fonts load at render start, fail-open to system stacks.
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
**Image and video overlays.** Media kinds on the same lane: `{ "id": "m0", "kind": "image" | "video", "start": 2, "duration": 4, "key": "/logo.png", "width": 0.35, "radius": 12, "opacity": 1, "loop": false, "transform": { … } }`. `key` is a file inside the take directory (`"/logo.png"`) or a URL; `width` is a fraction of the frame width, height follows the media's aspect; corners in design px. Video time is clip-local and muted by design; soundtracks belong to `doc.audio`.
|
|
130
159
|
|
|
131
|
-
`
|
|
160
|
+
**3D props.** `doc.objects`: world-space props between the card and the overlays. `{ "id": "p0", "asset": { "kind": "primitive", "shape": "knot", "color": "#ffb03a" }, "span": { "start": 1, "duration": 3 }, "transform3d": { "x": 0.8, "y": 0.28, "z": 0.5, "rx": 0, "ry": 40, "rz": 0, "scale": 0.18 }, "animation": "spin" }`. Shapes `cube`, `sphere`, `torus`, `knot`; `kind: "gltf"` with `key` loads a GLB (bounding-box normalized so `scale` means the same for every model; a bad model fails open). `x`/`y` are frame fractions, `z` world units toward the camera from the card plane, `scale` a fraction of the frame height; `animation` is `spin` or `float`, deterministic.
|
|
132
161
|
|
|
133
|
-
`
|
|
162
|
+
**Audio.** `doc.audio` clips (music, effects, `key` a take-directory file or a URL) are mixed with gain, fade and loop envelopes and muxed into every full render: Opus for webm, AAC (Opus fallback) for mp4. Audio forces single-flight (`--parallel` is ignored with a note); a `--range` render keeps its audio.
|
|
134
163
|
|
|
135
|
-
**
|
|
164
|
+
**Background.** `doc.frame.backgroundMedia` puts a looping video or a still behind the card: `{ "kind": "video", "key": "<url or /file>", "duration": 10, "dim": 0.2 }`. Time is output-anchored modulo `duration`, so trims and speed never retime the ambience; `dim` is a black scrim for legibility; `frame.parallax` (0..1, 0.6 reads well) counter-pans it with the zoom and `backgroundMedia.blur` softens it in design px. The hand-picked set is `GET https://vos.so/api/backdrops` (no auth): copy `key` from a row's `urls["1080p"]`, `duration` from the row, and `frame.background` from its `ground`. Drawn under the card, outside the zoom, fail-open to the CSS `frame.background`.
|
|
136
165
|
|
|
137
|
-
|
|
166
|
+
## Overrides on render and frames
|
|
138
167
|
|
|
139
|
-
|
|
168
|
+
Check any presentation from the command line without hand-editing `doc.json`. The file on disk is untouched; the patched document is lint-gated, so a bad override fails exactly like a bad `doc.json`.
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
- `--set <path>=<value>` (repeatable) patches any field. The value is JSON when it parses (numbers, booleans, `null`, objects, arrays), else a string; array indices work: `--set zoom[0].level=3`, `--set frame.padding=120`, `--set 'frame.backgroundMedia={"kind":"video","key":"…","duration":10}'`.
|
|
171
|
+
- `--frame <kind>` on `render` sets the browser chrome: `macos` (`mac`, `mac-light`), `mac-dark`, `windows` (`windows-light`), `windows-dark`, `minimal`, `none` (`hidden`). On `frames`, `--frame <t>` is the still's time; set the chrome there with `--set frame.browserBar.kind=…`.
|
|
172
|
+
- `--background <slug|url|none>`: a slug is resolved against `GET /api/backdrops` (its 1080p loop and duration); a URL or take-directory path is used as is, an image extension meaning a still and anything else a video looping every 10 s; `none` clears it.
|
|
142
173
|
|
|
143
|
-
|
|
174
|
+
So `vos frames take --frame 2.0 --set frame.browserBar.kind=mac-light --set tilt[0].rx=8` previews a macOS-framed, tilted card as a still, and `vos render take out.webm --frame macos --background soft-beams` renders it on a loop from the set. Verifying a document field and offering it as a feature are the same act here: reach for a flag, not a bespoke harness.
|
|
144
175
|
|
|
145
|
-
|
|
176
|
+
## Delivering a release
|
|
146
177
|
|
|
147
|
-
|
|
178
|
+
```bash
|
|
179
|
+
vos deliver take --to cws,producthunt,og --release v2.1 # stills at each spec's pixels, video cuts, everything verified into kit.json
|
|
180
|
+
vos deliver take --to cws,og,linkedin --poster poster.json # the card half: covers composed by your poster program with this release's shot baked in
|
|
181
|
+
vos validate take/kit/kit.json # re-measure every asset from its bytes against the channel specs
|
|
182
|
+
vos brand https://your.app --out BRAND.md # the brand kit, witnessed: /design.md, /llms.txt, then the page
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
[`schema/channel-specs.json`](./schema/channel-specs.json) holds per-channel launch-asset specs: dimensions, byte and duration ceilings, and a genre per image destination (`screenshot` is the real page from the take, full bleed; `card` is a composed cover). Channels: `cws`, `producthunt` (`ph`), `x`, `linkedin` (`li`), `og`, `github` (`gh`), `youtube` (`yt`), `shorts-linkedin` (`shorts`), or `all`. `vos deliver <take> --to <channels>` loops them in one pass and writes `kit.json`, the manifest the `launch-kit` skill builds the rest of the release around; an asset that misses its spec lands in `skipped[]` with the reason, and the verb exits 1 when nothing was produced. Flags: `--release <tag>`, `--out <take>/kit`, `--times`, `--range`, `--parallel`, `--poster <config.json|vosId>`, `--shot-time <t>` (the take moment baked into the poster), `--poster-time <t>` (the poster's own clock), `--composed` (keep the cut's camera and chrome on screenshot stills instead of the full-bleed page), `--set`, `--background`. `vos validate` reads a kit back from its bytes (a `.png` that is WebP, a lying size or duration, a set under its count, a byte ceiling).
|
|
186
|
+
|
|
187
|
+
`vos brand <url>` reads the site's `/design.md` first (the convention beside `/llms.txt`), then `/llms.txt`, then witnesses one page, and writes `BRAND.md`: the palette, faces, marks and the avoid list, with the provenance of every value, so a brand is resolved before any asset is authored.
|
|
188
|
+
|
|
189
|
+
## The vos.so loop
|
|
190
|
+
|
|
191
|
+
Everything that runs on your machine is open source; [vos.so](https://vos.so) is the hosted platform: a studio for the human half of an edit, version history with a typed changelog, a shelf of projects and recipes. All vos.so traffic lives behind one client: one origin (`VOS_ORIGIN`, default `https://vos.so`; `--origin` per call), one credential ladder (`--key`, then `VOS_API_KEY`, then `~/.config/vos/credentials`, written by `vos login`; a `vos_rg_` remix grant is just a key), one tracking file (`vos.json` beside the artifact). Credentials are never printed. Keys can never publish: pushes are private, and humans publish on vos.so.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
vos login [--key <k>] [--label <name>] [--no-browser] # browser sign-in: a code and a vos.so/cli/auth URL, a human approves, the key stores itself
|
|
195
|
+
vos fetch <vosId|url> [--out <slug>] [--media] # a program writes config.json + vos.json; a take writes doc.json (+ the recording with --media)
|
|
196
|
+
vos check bright-loop/config.json # full local validation
|
|
197
|
+
vos push bright-loop/config.json # create a PRIVATE vos (lineage from vos.json or --remix-of)
|
|
198
|
+
vos push bright-loop/config.json --vos <id> # add a version against your tracked base
|
|
199
|
+
vos push bright-loop/config.json --claimable # no credential: a 72 h claim link instead (programs only)
|
|
200
|
+
vos push take --yes --label "first pass" --note "…" # host a take: private vos + version history (the recording uploads once)
|
|
201
|
+
vos pull bright-loop [--since <versionId>] [--check] # what changed on vos.so since your base; syncs config.json (backup kept), or doc.json for a take
|
|
202
|
+
# --check reports without writing; --since walks from a base you name
|
|
203
|
+
vos duplicate <vosId> # a private sibling of your OWN vos (someone else's is remixed: fetch, then push --remix-of)
|
|
204
|
+
vos folder list | create <name> [--parent] [--desc] | move <ids…> --to <folder|none> | pull <ref> [--media]
|
|
205
|
+
vos asset push <file…> [--folder <slug>] | rename <id> <name.ext>
|
|
206
|
+
vos recipe push <FILE.md> --folder <slug> | --asset <id> # the one recipe write: create, or replace in place
|
|
207
|
+
```
|
|
148
208
|
|
|
149
|
-
|
|
209
|
+
`push` is polymorphic by a deterministic sniff, never a flag: a take directory (a `doc.json` carrying `source`) pushes recording and document through the take pipeline; a `config.json` (or a directory holding one) pushes the program, and a `doc.json` beside it that carries `program` (a program document: overlays, objects, audio, speed, tween edits, its own length; `program.config` omitted on disk) rides along, lint-gated. `fetch` and `pull` write a program document back the same way. `pull` takes `--since <versionId>` (walk the changelog from that base instead of the tracked one) and `--check` (print what changed and stop: nothing on disk moves) on both paths. Program pushes take `--vos`, `--title`, `--slug`, `--desc`, `--tags`, `--folder`, `--remix-of`, `--base`, `--label`, `--note`, `--override <id>` (repeatable) and `--claimable`; take pushes take `--title`, `--label`, `--note`, `--folder`, `--override` and `--yes`.
|
|
150
210
|
|
|
151
|
-
- `--
|
|
152
|
-
- `--frame <kind>` (**render** only — `frames --frame <t>` is the still time selector; set the frame kind on `frames` via `--set frame.browserBar.kind=…`): `macos | mac-dark | windows | windows-dark | minimal | none`.
|
|
153
|
-
- `--background <url>` — a background media layer; kind inferred from the URL (`.webm`/`.mp4` → video at a 10s loop, image otherwise); `none` clears it.
|
|
211
|
+
Both paths share the same base tracking and the same two 409 shapes. `stale_base` replays the platform's typed changelog: run `vos pull`, re-apply, push again. `protected_conflict` lists nodes a human edited in the studio: keep their values, or re-push with `--override <id>` only when the user asked for that exact change. The first push of a take asks before uploading (`--yes` for headless); agents never upload unprompted. The take's duration rides the upload, and the platform refuses a take over the hosted recording cap. Every push should carry `--label` (what changed, one line) and `--note` (why: the user's ask); the version history reads as a conversation, and an unlabelled push is a turn the human cannot read.
|
|
154
212
|
|
|
155
|
-
|
|
213
|
+
`--claimable` is the credential-free rung, programs only: no key is resolved, no `vos.json` is written, and the response is a claim URL (72 h; unclaimed work is deleted, which is deliberate cleanup). Hand the link to the user and nowhere else: it is the only reference and the only credential. Claiming moves the vos into the user's library, and iteration after claim rides their key (`vos push --vos <id>`). Limits are the platform's: config ≤ 200 KB, 5 pushes per day per network.
|
|
156
214
|
|
|
157
|
-
|
|
215
|
+
`vos folder pull <ref>` writes a folder's context package to disk (its recipes, the inherited ones, the exemplar programs and assets), which is what an agent reads to create in the owner's style. Recipes are `.md` files named in capitals (`CUT.md`, `BRAND.md`): the server uppercases an agent-filed name.
|
|
158
216
|
|
|
159
|
-
|
|
217
|
+
## CI
|
|
218
|
+
|
|
219
|
+
Everything above is headless, so a tag push can produce the release's media. Keep the source in the repo (`media/actions.json`, the signed-off `media/doc.json`, `media/vos.json`, `LAUNCH.md`), then on each tag:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
vos record --actions media/actions.json --out /tmp/take --strict --json
|
|
223
|
+
vos plan /tmp/take --reuse --from media/doc.json --json # the committed cut re-times onto the new footage; the flagged list is the release's to-do
|
|
224
|
+
vos deliver /tmp/take --to <channels> --release "$TAG" --json
|
|
225
|
+
vos push /tmp/take --label "$TAG launch" --yes --json # VOS_API_KEY as a repository secret
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
[vosjs/action](https://github.com/vosjs/action) runs that loop as one step and keeps a comment on the pull request with the watch page and the kit; the hand-written workflow is on the docs page [vos.so/docs/guides/every-release](https://vos.so/docs/guides/every-release). GitHub's `ubuntu-latest` ships Google Chrome, which mp4 renders need. A stale-base push fails with the shelf's changelog rather than resolving a human race silently. Cloud render jobs (`POST /api/render/jobs`) take a `callbackUrl` that is POSTed the job JSON once at completed or failed: a doorbell, not the truth (poll the job for that).
|
|
229
|
+
|
|
230
|
+
## For scripts and agents
|
|
231
|
+
|
|
232
|
+
- **Output.** Logs on stderr, results on stdout. `--json` turns every verb into NDJSON events ending with `{"event":"done",…}`.
|
|
233
|
+
- **Exit codes.** 0 ok, 1 error, 2 usage (including `--strict` failures), 3 no browser found.
|
|
234
|
+
- **`--strict`** on `record` and `create` (agents: always): a skipped selector, a page that never reaches network idle, or a take that hit `--max-duration` exits 2 and lists `skipped[]` (and `capped`) in the done event. The default is lenient (exit 0, `skipped[]` still reported) for exploratory runs.
|
|
235
|
+
- **`--max-duration <s>`** on `record` and `create` defaults to the hosted recording cap, read live from `GET /api/limits` (2 s, fail-open to 30 min when the origin is unreachable): the capture stops there and the done event says so. Cut the flow rather than raising the cap; the platform refuses a longer take.
|
|
236
|
+
- **`create`** is the one-shot verb: record, auto-plan and render in one command and one browser session. The take directory still lands on disk, so the full loop (frames, edit `doc.json`, re-render) stays open afterwards.
|
|
237
|
+
- **`vos validate <thing>`** takes an `actions.json`, a take directory, a program directory (`config.json`, plus its program document when present), or a `kit.json`; exit 1 on any problem.
|
|
238
|
+
- **Environment.** `VOS_ORIGIN`, `VOS_API_KEY`, `VOS_BROWSER_PATH`, `VOS_CLIENT` (the client string a push self-reports). `vos voila <verb>` is still accepted as an alias of `vos <verb>` and says so.
|
|
160
239
|
|
|
161
240
|
## Programmatic use
|
|
162
241
|
|
|
163
|
-
The same
|
|
242
|
+
The same functions are available as a library:
|
|
164
243
|
|
|
165
244
|
```ts
|
|
166
245
|
import { launchBrowser, loadVosConfig, renderVideo } from '@vosjs/cli'
|
|
@@ -177,6 +256,8 @@ const { bytes } = await renderVideo(browser, {
|
|
|
177
256
|
})
|
|
178
257
|
```
|
|
179
258
|
|
|
180
|
-
`recordTake`, `planTake`, `digestTake`, `renderTake`, `validateActions`, `convertAgentBrowser`
|
|
259
|
+
Also exported: `renderStill`, `previewPages`, `configDuration`, `BrowserUnavailableError`; the take pipeline (`recordTake`, `encodeRecording`, `planTake`, `digestTake`, `parseTranscript`, `renderTake`, `renderAnimation`, `pullMedia`, `loadTake`, `takePaths`, `startTakeServer`, `validateActions`, `convertAgentBrowser`, `parseAgentBrowserLog`); the vos.so client helpers (`resolveCredential`, `platformOrigin`, `parseVosId`, `readSyncState`, `writeSyncState`, `apiJson`, `apiError`); `manifest`, the verb list `vos help` prints; and `run(argv)`, which dispatches any take or platform verb.
|
|
260
|
+
|
|
261
|
+
## License
|
|
181
262
|
|
|
182
|
-
|
|
263
|
+
[MIT](https://github.com/vosjs/vos/blob/main/LICENSE) © vosso
|
|
@@ -38,6 +38,10 @@ var manifest = {
|
|
|
38
38
|
name: "validate",
|
|
39
39
|
summary: "lint actions.json, a take dir (doc.json semantics), or re-measure a kit.json against the channel specs"
|
|
40
40
|
},
|
|
41
|
+
{
|
|
42
|
+
name: "actions",
|
|
43
|
+
summary: "from-agent-browser: turn an agent-browser walk (steps.jsonl) into actions.json; what cannot follow is named"
|
|
44
|
+
},
|
|
41
45
|
{
|
|
42
46
|
name: "brand",
|
|
43
47
|
summary: "write a product's BRAND.md, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)"
|
|
@@ -80,4 +84,4 @@ var manifest = {
|
|
|
80
84
|
export {
|
|
81
85
|
manifest
|
|
82
86
|
};
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
87
|
+
//# sourceMappingURL=chunk-EFI3WUQB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin/manifest.ts"],"sourcesContent":["/**\n * The verb manifest of the take pipeline and the vos.so verbs — what\n * `vos help` lists under the engine verbs. It kept the shape a separately\n * installed plugin once handed the host (name + host range), so a script\n * reading it keeps working.\n */\nexport interface PluginVerb {\n name: string\n summary: string\n}\n\nexport interface PluginManifest {\n name: string\n /** Host versions this plugin speaks the run(argv) contract with. */\n hostRange: string\n verbs: PluginVerb[]\n}\n\nexport const manifest: PluginManifest = {\n name: '@vosjs/cli',\n hostRange: '>=0.9.0',\n verbs: [\n {\n name: 'create',\n summary: 'record + auto-plan + render, one command (--strict)',\n },\n {\n name: 'record',\n summary: 'drive actions.json into a take (screencast + cursor track)',\n },\n {\n name: 'plan',\n summary:\n 'plan zoom/cursor effects into doc.json (wand contract); --reuse re-times a previous cut onto a re-recording',\n },\n {\n name: 'render',\n summary: 'render a take directory (engine configs render in the host)',\n },\n {\n name: 'frames',\n summary:\n 'PNG stills at output times / zoom apexes / moments / exact sizes',\n },\n {\n name: 'deliver',\n summary:\n 'render a take to release destinations (CWS, Product Hunt, X, LinkedIn, OG…) + verified kit.json',\n },\n {\n name: 'digest',\n summary:\n 'see a recording before cutting it: moments (clicks, typing, scrolls, idle, scenes) + footage frames + crops, in doc units',\n },\n { name: 'open', summary: 'serve the take into the vos.so studio' },\n {\n name: 'validate',\n summary:\n 'lint actions.json, a take dir (doc.json semantics), or re-measure a kit.json against the channel specs',\n },\n {\n name: 'actions',\n summary:\n 'from-agent-browser: turn an agent-browser walk (steps.jsonl) into actions.json; what cannot follow is named',\n },\n {\n name: 'brand',\n summary:\n \"write a product's BRAND.md, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)\",\n },\n {\n name: 'fetch',\n summary: 'download a hosted program: config.json + vos.json tracking',\n },\n {\n name: 'push',\n summary: 'push a config.json or take to vos.so (private; versioned)',\n },\n {\n name: 'duplicate',\n summary:\n 'a private sibling of your OWN vos (someone else\\u2019s is remixed: fetch + push --remix-of)',\n },\n {\n name: 'pull',\n summary:\n 'sync what changed on vos.so since your base (--media brings a take’s footage home)',\n },\n {\n name: 'folder',\n summary:\n 'list/create/pull folders, move voses and assets into them (pull = the context package on disk)',\n },\n {\n name: 'asset',\n summary: 'rename one of your assets in place (recipes included)',\n },\n {\n name: 'recipe',\n summary:\n 'push a recipe .md into a folder, or replace one in place (prior body kept)',\n },\n {\n name: 'login',\n summary: 'sign in via the browser (or --key); stores a content key',\n },\n ],\n}\n"],"mappings":";;;AAkBO,IAAM,WAA2B;AAAA,EACtC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA,EAAE,MAAM,QAAQ,SAAS,wCAAwC;AAAA,IACjE;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,104 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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))
|
|
43
|
-
throw new UsageError(`--${name} expects a number, got "${String(v)}"`);
|
|
44
|
-
return n;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/loadConfig.ts
|
|
48
|
-
import { readFile } from "fs/promises";
|
|
49
|
-
import {
|
|
50
|
-
CURRENT_CONFIG_VERSION,
|
|
51
|
-
migrateConfig,
|
|
52
|
-
vosConfigJsonSchema
|
|
53
|
-
} from "@vosjs/core";
|
|
54
|
-
async function loadVosConfig(source) {
|
|
55
|
-
const warnings = [];
|
|
56
|
-
let raw;
|
|
57
|
-
if (/^https?:\/\//.test(source)) {
|
|
58
|
-
const res = await fetch(source);
|
|
59
|
-
if (!res.ok) throw new Error(`fetch ${source} \u2192 ${res.status}`);
|
|
60
|
-
raw = await res.text();
|
|
61
|
-
} else {
|
|
62
|
-
raw = await readFile(source, "utf8");
|
|
63
|
-
}
|
|
64
|
-
let parsed;
|
|
65
|
-
try {
|
|
66
|
-
parsed = JSON.parse(raw);
|
|
67
|
-
} catch (e) {
|
|
68
|
-
throw new UsageError(`${source} is not valid JSON: ${e.message}`);
|
|
69
|
-
}
|
|
70
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
71
|
-
throw new UsageError(`${source} does not contain a JSON object`);
|
|
72
|
-
}
|
|
73
|
-
let obj = parsed;
|
|
74
|
-
if (typeof obj.config === "object" && obj.config !== null && !("createTimeline" in obj)) {
|
|
75
|
-
obj = obj.config;
|
|
76
|
-
warnings.push("unwrapped { config } envelope");
|
|
77
|
-
}
|
|
78
|
-
const version = obj.version;
|
|
79
|
-
const migrated = migrateConfig(obj);
|
|
80
|
-
if (version === void 0) {
|
|
81
|
-
warnings.push(
|
|
82
|
-
`config declares no "version". Add "version": ${CURRENT_CONFIG_VERSION} before pushing it.`
|
|
83
|
-
);
|
|
84
|
-
} else if (version !== CURRENT_CONFIG_VERSION) {
|
|
85
|
-
warnings.push(
|
|
86
|
-
`migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
const check = vosConfigJsonSchema.safeParse(migrated);
|
|
90
|
-
if (!check.success) {
|
|
91
|
-
const issues = check.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
92
|
-
throw new UsageError(`invalid vos config:
|
|
93
|
-
${issues}`);
|
|
94
|
-
}
|
|
95
|
-
return { config: migrated, warnings };
|
|
96
|
-
}
|
|
97
|
-
function configDuration(config) {
|
|
98
|
-
const d = config.duration;
|
|
99
|
-
return typeof d === "number" && Number.isFinite(d) && d > 0 ? d : void 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
3
|
// src/render.ts
|
|
103
4
|
import { compileVosConfig } from "@vosjs/core";
|
|
104
5
|
import { generateRenderTemplate } from "@vosjs/core/runtime";
|
|
@@ -254,13 +155,8 @@ function previewPages(config) {
|
|
|
254
155
|
}
|
|
255
156
|
|
|
256
157
|
export {
|
|
257
|
-
UsageError,
|
|
258
|
-
parseArgs,
|
|
259
|
-
numFlag,
|
|
260
|
-
loadVosConfig,
|
|
261
|
-
configDuration,
|
|
262
158
|
renderVideo,
|
|
263
159
|
renderStill,
|
|
264
160
|
previewPages
|
|
265
161
|
};
|
|
266
|
-
//# sourceMappingURL=chunk-
|
|
162
|
+
//# sourceMappingURL=chunk-GCWE75OA.js.map
|