@vosjs/cli 0.61.0 → 0.64.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/README.md +6 -4
- package/dist/{assetManifest-NS64PLY5.js → assetManifest-RQYJYQ7Q.js} +3 -3
- package/dist/{chunk-EZW6ZCEH.js → chunk-7CIAU4QP.js} +35 -11
- package/dist/chunk-7CIAU4QP.js.map +1 -0
- package/dist/{chunk-WZGCEAXB.js → chunk-AUFY3M22.js} +2 -2
- package/dist/{chunk-WZGCEAXB.js.map → chunk-AUFY3M22.js.map} +1 -1
- package/dist/{chunk-C4P6MZJG.js → chunk-CODEEH2Z.js} +627 -139
- package/dist/chunk-CODEEH2Z.js.map +1 -0
- package/dist/{chunk-LWHVZ5WI.js → chunk-DU3ANWU4.js} +2 -2
- package/dist/{chunk-WCF6KIOK.js → chunk-WVQAZTYU.js} +29 -2
- package/dist/chunk-WVQAZTYU.js.map +1 -0
- package/dist/cli.js +7 -7
- package/dist/index.js +5 -5
- package/dist/manifest-ZBK6IERM.js +8 -0
- package/dist/{run-IZ72SDZC.js → run-GYCIE77C.js} +5 -5
- package/package.json +5 -5
- package/schema/doc.schema.json +1 -1
- package/skills/VERSION +1 -1
- package/skills/vos-authoring/references/schema-reference.md +2 -0
- package/skills/vos-remix/references/3d-recipe.md +16 -10
- package/dist/chunk-C4P6MZJG.js.map +0 -1
- package/dist/chunk-EZW6ZCEH.js.map +0 -1
- package/dist/chunk-WCF6KIOK.js.map +0 -1
- package/dist/manifest-Q4QMVIYR.js +0 -8
- /package/dist/{assetManifest-NS64PLY5.js.map → assetManifest-RQYJYQ7Q.js.map} +0 -0
- /package/dist/{chunk-LWHVZ5WI.js.map → chunk-DU3ANWU4.js.map} +0 -0
- /package/dist/{manifest-Q4QMVIYR.js.map → manifest-ZBK6IERM.js.map} +0 -0
- /package/dist/{run-IZ72SDZC.js.map → run-GYCIE77C.js.map} +0 -0
package/README.md
CHANGED
|
@@ -271,7 +271,7 @@ Verified the flow in agent-browser already? `vos actions from-agent-browser step
|
|
|
271
271
|
|
|
272
272
|
**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`.
|
|
273
273
|
|
|
274
|
-
**HTML layers.** A UI component authored as DOM on the same lane, sharp at any export size because it is rasterized from source, not magnified from the footage: `{ "id": "h0", "kind": "html", "start": 2, "duration": 4, "html": "<div class=\"card\">Open Code</div>", "css": ".card{font-family:'Inter';background:#0b0b0d;border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45)}", "box": { "width": 452, "height": 132 }, "transform": { … } }`. The markup is well-formed XML (`<br />`, `&`, quoted attributes; `vos validate` refuses the rest in words, because a malformed layer decodes to nothing and says nothing). `box` is the design box in 1080p px; `width` defaults to the design size. The CSS is the chrome (shadow, radius, border; `bleed` is derived from the shadows, or stated), a `font-family` it names resolves against the hosted catalog at every `font-weight` it names (a face outside the catalog rides `fonts: [{ family, url }]`), and an image inside is inlined for you when its URL is on `https://assets.vos.so/` (`vos asset push` puts it there; any other host paints blank, so inline it as a `data:` URI). A still layer's `animation` runs on the wall clock rather than the timeline, so animate the layer with `anim` and `motion`; a LIVE layer (`"live": true`) is re-rasterized per frame at clip-local time: its `@keyframes` are scrubbed to `t` (paused, delayed by `-t`; fold any `animation-delay` into the keyframes) and `{{t}}` / `{{data.<name>}}` placeholders in the markup and CSS fill from the clock and the clip's `data`, so a counter, a progress bar or a typed line come out on the timeline's clock in the preview and in every export chunk alike. It is never re-hosted and never an upload.
|
|
274
|
+
**HTML layers.** A UI component authored as DOM on the same lane, sharp at any export size because it is rasterized from source, not magnified from the footage: `{ "id": "h0", "kind": "html", "start": 2, "duration": 4, "html": "<div class=\"card\">Open Code</div>", "css": ".card{font-family:'Inter';background:#0b0b0d;border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45)}", "box": { "width": 452, "height": 132 }, "transform": { … } }`. The markup is well-formed XML (`<br />`, `&`, quoted attributes; `vos validate` refuses the rest in words, because a malformed layer decodes to nothing and says nothing). `box` is the design box in 1080p px; `width` defaults to the design size. The CSS is the chrome (shadow, radius, border; `bleed` is derived from the shadows, or stated), a `font-family` it names resolves against the hosted catalog at every `font-weight` it names (a face outside the catalog rides `fonts: [{ family, url }]`) (a face's `url` is an https URL, or a `.woff2` beside the document, which `vos push` uploads like any layer file and a local render serves), and an image inside is inlined for you when its URL is on `https://assets.vos.so/` (`vos asset push` puts it there; any other host paints blank, so inline it as a `data:` URI). A still layer's `animation` runs on the wall clock rather than the timeline, so animate the layer with `anim` and `motion`; a LIVE layer (`"live": true`) is re-rasterized per frame at clip-local time: its `@keyframes` are scrubbed to `t` (paused, delayed by `-t`; fold any `animation-delay` into the keyframes) and `{{t}}` / `{{data.<name>}}` placeholders in the markup and CSS fill from the clock and the clip's `data`, so a counter, a progress bar or a typed line come out on the timeline's clock in the preview and in every export chunk alike. It is never re-hosted and never an upload.
|
|
275
275
|
|
|
276
276
|
**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.
|
|
277
277
|
|
|
@@ -328,19 +328,21 @@ vos pull bright-loop [--since <versionId>] [--check] # what changed on vos.so
|
|
|
328
328
|
# --check reports without writing; --since walks from a base you name
|
|
329
329
|
vos duplicate <vosId> # a private sibling of your OWN vos (someone else's is remixed: fetch, then push --remix-of)
|
|
330
330
|
vos folder list | create <name> [--parent] [--desc] | move <ids…> --to <folder|none> | pull <ref> [--media]
|
|
331
|
-
vos asset push <file…> [--folder <slug>] | rename <id> <name.ext> # any kind the platform takes (GET /api/limits)
|
|
331
|
+
vos asset push <file…> [--folder <slug>] | rename <id> <name.ext> # any kind the platform takes (GET /api/limits); prints asset:<id>, the url, and the manifest line
|
|
332
|
+
vos asset import <url> [--license <text>] [--attribution <text>] # fetch a file from an address and add it, with where it came from kept beside it
|
|
333
|
+
vos assets ls [--kind <kind>] [--q <text>] [--sort size] | usage | why <id> # what is in the library, what holds the storage, what uses a file
|
|
332
334
|
vos recipe push <FILE.md> --folder <slug> | --asset <id> # the one recipe write: create, or replace in place
|
|
333
335
|
```
|
|
334
336
|
|
|
335
337
|
`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. A program push LANDS where `vos.json` says: a directory that tracks a vos iterates it, so the fetch, edit, check, push, pull loop needs no flags, and making something separate from the same config is the explicit `--remix-of` door. 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`. Both take `--wait`. A new vos pushed without `--folder` lands unfiled, at the root of the shelf, and the done line says so.
|
|
336
338
|
|
|
337
|
-
**A program's files are declared, never typed into a function.** `config.json` names each file it uses in `assets` (`"assets": { "logo": { "ref": "./logo.png", "kind": "image" }, "shots": { "ref": ["./shots/1.png", "./shots/2.png"] } }`) and reads `ctx.assets.logo`, or `"$assets.logo"` in an element, object or font. On disk a `ref` is a path in the config's own directory or below, so `vos render`, `vos still` and `vos preview` serve the file to the page from disk. A path that climbs out of that directory (`../`, an absolute path, a link that points away) is not served and not uploaded, and a config loaded from a URL reads no local file: a program's code runs in a page, and what the page is served the program can send anywhere. `vos push` uploads each local file (content-addressed, so a re-push sends nothing
|
|
339
|
+
**A program's files are declared, never typed into a function.** `config.json` names each file it uses in `assets` (`"assets": { "logo": { "ref": "./logo.png", "kind": "image" }, "shots": { "ref": ["./shots/1.png", "./shots/2.png"] } }`) and reads `ctx.assets.logo`, or `"$assets.logo"` in an element, object or font. On disk a `ref` is a path in the config's own directory or below, so `vos render`, `vos still` and `vos preview` serve the file to the page from disk. A path that climbs out of that directory (`../`, an absolute path, a link that points away) is not served and not uploaded, and a config loaded from a URL reads no local file: a program's code runs in a page, and what the page is served the program can send anywhere. `vos push` uploads each local file (content-addressed, so a re-push sends nothing: pictures, SVG, video, sound, `.glb` models, fonts and HDR environments) and what it stores names the hosted file as `asset:<id>`, the one spelling the platform resolves for every render; the config on disk keeps its paths. A file an element names as `src` or a font names as `url` is lifted into the manifest on the way (the element then names `"$assets.<name>"`), and a path in `data` that is a file beside the config is uploaded and rewritten to the hosted file's path, so every place a program can name a file crosses. A file typed inside a function cannot be rewritten: `vos push` says so, with the manifest line to write. `vos fetch --media` brings a program's hosted files home under `assets/` and re-points the manifest at them (`assets/.hosted.json` remembers which hosted file each one is, so a later fetch replaces one that changed); without `--media` the manifest keeps `asset:<id>` and a local render reads each file with your key. `vos check` says when a `"$assets.<name>"` names nothing, when a declared path is not a file, and when a function names a hosted file in code instead of the manifest: a file named only in code does not load on a render of a private program and does not follow a remix. A `--claimable` push carries them too, through the claim's own upload session (below).
|
|
338
340
|
|
|
339
341
|
**Sound is a track, never an element.** A document's added sound lives in `doc.json` (`audio: [{ id, key, name, start, in, out, duration, gain, fadeIn, fadeOut }]`) for a program as for a take: `vos push` uploads each local file its keys name and keys the hosted asset; `vos render` of a program mixes it locally; the studio shows it as a track with gain and fades. An `audio` element embedded in the config is not a track (renders do not mix it, and a `data:` source can push a config past what a claimable push carries); `vos check` says so. `vos asset push score.ogg` uploads a sound on its own and prints the url a key takes; an unfiled sound that no document uses is removed after 7 days, so pass `--folder` or reference it.
|
|
340
342
|
|
|
341
343
|
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.
|
|
342
344
|
|
|
343
|
-
`--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.
|
|
345
|
+
`--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. Every local file the program names (the manifest, elements, fonts, `data`, and the document's overlays, props and sound) crosses through the claim's own upload session, which the push opens with no credential and the claim spends: at most 12 files and 50 MB in all, sound up to 5 minutes, video up to 60 seconds. Until the user claims it, the claim link is the only way to read them; at the claim they become the user's. Anything a push could not carry is a `warning` event under `--json`, never only a line on stderr. `--share` makes the claim page lead with "Claim and share", which claims it and gives it a link anyone can watch in the user's one press; it is refused on a keyed push, because a key never publishes. `--prompt "<text>"` shows the prompt that made it on the watch page, on any program push: pass only what the user wrote, and only when they said to show it.
|
|
344
346
|
|
|
345
347
|
`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.
|
|
346
348
|
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
localManifestRefs,
|
|
5
5
|
pullManifest,
|
|
6
6
|
uploadManifest
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-7CIAU4QP.js";
|
|
8
|
+
import "./chunk-WVQAZTYU.js";
|
|
9
9
|
export {
|
|
10
10
|
hostedLiteralWarnings,
|
|
11
11
|
localManifestRefs,
|
|
12
12
|
pullManifest,
|
|
13
13
|
uploadManifest
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=assetManifest-
|
|
15
|
+
//# sourceMappingURL=assetManifest-RQYJYQ7Q.js.map
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
manifestRefs,
|
|
10
10
|
nameForType,
|
|
11
11
|
resolveMediaType
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WVQAZTYU.js";
|
|
13
13
|
|
|
14
14
|
// src/plugin/assetManifest.ts
|
|
15
15
|
import {
|
|
@@ -166,7 +166,9 @@ function asAsset(body, reused) {
|
|
|
166
166
|
kind: typeof asset.kind === "string" ? asset.kind : "",
|
|
167
167
|
filename: typeof asset.filename === "string" ? asset.filename : "",
|
|
168
168
|
metadata: asset.metadata && typeof asset.metadata === "object" ? asset.metadata : null,
|
|
169
|
-
notes: Array.isArray(body.notes) ? body.notes.filter((n) => typeof n === "string") : []
|
|
169
|
+
notes: Array.isArray(body.notes) ? body.notes.filter((n) => typeof n === "string") : [],
|
|
170
|
+
ref: typeof asset.ref === "string" ? asset.ref : `asset:${String(asset.id)}`,
|
|
171
|
+
duration: typeof asset.duration === "number" ? asset.duration : null
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
174
|
function transient(status) {
|
|
@@ -174,7 +176,8 @@ function transient(status) {
|
|
|
174
176
|
}
|
|
175
177
|
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
176
178
|
async function uploadAsset(target, bytes, opts) {
|
|
177
|
-
const
|
|
179
|
+
const door = target.door ?? "/api/assets/uploads";
|
|
180
|
+
const begun = await apiJson(target.origin, door, {
|
|
178
181
|
method: "POST",
|
|
179
182
|
key: target.key,
|
|
180
183
|
body: {
|
|
@@ -206,7 +209,7 @@ async function uploadAsset(target, bytes, opts) {
|
|
|
206
209
|
try {
|
|
207
210
|
sent = await apiJson(
|
|
208
211
|
target.origin,
|
|
209
|
-
|
|
212
|
+
`${door}/${uploadId}/parts/${part.partNumber}`,
|
|
210
213
|
{
|
|
211
214
|
method: "PUT",
|
|
212
215
|
key: target.key,
|
|
@@ -239,15 +242,22 @@ async function uploadAsset(target, bytes, opts) {
|
|
|
239
242
|
}
|
|
240
243
|
const sealed = await apiJson(
|
|
241
244
|
target.origin,
|
|
242
|
-
|
|
243
|
-
{
|
|
245
|
+
`${door}/${uploadId}/complete`,
|
|
246
|
+
{
|
|
247
|
+
method: "POST",
|
|
248
|
+
key: target.key,
|
|
249
|
+
body: {
|
|
250
|
+
parts: etags,
|
|
251
|
+
...opts.provenance ? { provenance: opts.provenance } : {}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
244
254
|
);
|
|
245
255
|
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
246
256
|
throw failed("upload failed", sealed);
|
|
247
257
|
}
|
|
248
|
-
return asAsset(sealed.body,
|
|
258
|
+
return asAsset(sealed.body, sealed.body.reused === true);
|
|
249
259
|
} catch (err) {
|
|
250
|
-
await apiJson(target.origin,
|
|
260
|
+
await apiJson(target.origin, `${door}/${uploadId}`, {
|
|
251
261
|
method: "DELETE",
|
|
252
262
|
key: target.key
|
|
253
263
|
}).catch(() => void 0);
|
|
@@ -301,6 +311,19 @@ function docObjectRefs(doc, keep = isTakeRelativeKey) {
|
|
|
301
311
|
function docOverlayRefs(doc, keep = isTakeRelativeKey) {
|
|
302
312
|
const out = [];
|
|
303
313
|
for (const clip of doc.overlays ?? []) {
|
|
314
|
+
if (clip.kind === "html") {
|
|
315
|
+
for (const [i, font] of (clip.fonts ?? []).entries()) {
|
|
316
|
+
if (!keep(font.url)) continue;
|
|
317
|
+
out.push({
|
|
318
|
+
where: `overlay ${clip.id} font ${i}`,
|
|
319
|
+
key: font.url,
|
|
320
|
+
set: (next) => {
|
|
321
|
+
font.url = next;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
304
327
|
if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
|
|
305
328
|
out.push({
|
|
306
329
|
where: `overlay ${clip.id}`,
|
|
@@ -390,7 +413,9 @@ async function uploadDocRefs(refs, dir, target, log) {
|
|
|
390
413
|
for (const ref of refs) {
|
|
391
414
|
const file = join2(dir, takeRelativeFile(ref.key));
|
|
392
415
|
if (!existsSync2(file)) {
|
|
393
|
-
log(
|
|
416
|
+
log(
|
|
417
|
+
`warning ${ref.where}: ${ref.key} is not beside the document, so it was not uploaded`
|
|
418
|
+
);
|
|
394
419
|
continue;
|
|
395
420
|
}
|
|
396
421
|
const media = await readFile2(file);
|
|
@@ -667,7 +692,6 @@ export {
|
|
|
667
692
|
sourceSidecarRefs,
|
|
668
693
|
docLayerRefs,
|
|
669
694
|
docMediaRefs,
|
|
670
|
-
docAudioRefs,
|
|
671
695
|
uploadDocRefs,
|
|
672
696
|
readMediaHead,
|
|
673
697
|
pullMedia,
|
|
@@ -676,4 +700,4 @@ export {
|
|
|
676
700
|
pullManifest,
|
|
677
701
|
hostedLiteralWarnings
|
|
678
702
|
};
|
|
679
|
-
//# sourceMappingURL=chunk-
|
|
703
|
+
//# sourceMappingURL=chunk-7CIAU4QP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin/assetManifest.ts","../src/plugin/media.ts","../src/plugin/take.ts","../src/plugin/uploadAsset.ts"],"sourcesContent":["/**\n * A program's declared files (`config.assets`) crossing to and from vos.so.\n *\n * On disk a manifest names files beside the config (`./logo.png`), so a\n * program renders offline and reads like the repository it lives in. On the\n * platform a manifest names hosted assets (`asset:<id>`), the one spelling\n * the platform resolves for every surface: a private file reaches a render\n * page, a remix brings its files along, and a file a program still plays is\n * never collected. A push is the crossing one way, `vos fetch --media` the\n * other; neither rewrites the other side's spelling in place.\n */\nimport {\n existsSync,\n readFileSync,\n realpathSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { hostedAssetId, localFile, manifestRefs } from '../programAssets'\nimport { downloadMedia, uploadDocRefs } from './media'\nimport type { DocMediaRef } from './media'\nimport type { UploadTarget } from './uploadAsset'\n\ntype Manifest = Record<string, { ref: string | string[] }>\n\nconst isUrl = (ref: string) => /^(https?:|data:|blob:)/.test(ref)\n\n/** The origins whose `/api/assets/<id>/file` is a hosted file of ours. */\nconst homeOrigins = (origin: string) => [\n origin,\n 'https://vos.so',\n 'https://www.vos.so',\n]\n\n/** `assets/.hosted.json`: which hosted file each file brought home is. */\nconst HOME_INDEX = '.hosted.json'\n\n/** A deep copy of the manifest, so a caller's rewrite never reaches disk. */\nfunction cloneManifest(config: Record<string, unknown>): Manifest {\n return JSON.parse(JSON.stringify(config.assets ?? {})) as Manifest\n}\n\nfunction setRef(\n manifest: Manifest,\n name: string,\n index: number | null,\n next: string,\n): void {\n const decl = manifest[name]\n if (index === null) decl.ref = next\n else (decl.ref as string[])[index] = next\n}\n\nconst where = (name: string, index: number | null) =>\n index === null ? `assets.${name}` : `assets.${name}[${index}]`\n\n/**\n * Upload the manifest's local files and point every entry at its hosted\n * asset as `asset:<id>`; a hosted file spelled as a URL takes the same\n * spelling. `config.assets` is REPLACED on the object (the file on disk is\n * untouched), so everything composed from it afterwards carries the hosted\n * refs. Uploads are content-addressed: a re-push sends nothing.\n */\nexport async function uploadManifest(\n config: Record<string, unknown>,\n dir: string,\n target: UploadTarget,\n log: (line: string) => void,\n): Promise<{ uploaded: number }> {\n const refs = manifestRefs(config)\n if (refs.length === 0) return { uploaded: 0 }\n const manifest = cloneManifest(config)\n const local: DocMediaRef[] = []\n for (const { name, index, ref } of refs) {\n const hosted = hostedAssetId(ref, homeOrigins(target.origin))\n if (hosted) {\n setRef(manifest, name, index, `asset:${hosted}`)\n continue\n }\n if (isUrl(ref)) continue\n // A push uploads what the manifest names, so it holds the same line a\n // render does: only a file inside the program's own directory.\n const found = localFile(ref, dir)\n if ('refused' in found) {\n throw new Error(`${where(name, index)} names ${ref}, ${found.refused}`)\n }\n local.push({\n where: where(name, index),\n // The upload helper reads keys against the directory.\n key: relative(realpathSync(dir), found.file),\n set: (url) => {\n const id = hostedAssetId(url, homeOrigins(target.origin))\n setRef(manifest, name, index, id ? `asset:${id}` : url)\n },\n })\n }\n await uploadDocRefs(local, dir, target, log)\n config.assets = manifest\n return { uploaded: local.length }\n}\n\n/** The manifest entries that are still files on this machine. */\nexport function localManifestRefs(config: Record<string, unknown>): string[] {\n return manifestRefs(config)\n .filter(({ ref }) => !isUrl(ref) && !hostedAssetId(ref, homeOrigins('')))\n .map(({ name, index }) => where(name, index))\n}\n\n/**\n * Bring a fetched program's hosted files home: each `asset:<id>` lands as\n * `assets/<name>.<ext>` beside the config (the bytes choose the extension)\n * and its entry is re-pointed at that file.\n *\n * A file already there is kept only when it IS that hosted file\n * (`assets/.hosted.json` remembers which id each one came from). Kept by\n * name alone, a second fetch after someone changed `logo` left the old\n * picture under the new manifest, and the next push stored the old one\n * again without a word.\n *\n * Returns the rewritten manifest, or null when nothing in it is hosted.\n */\nexport async function pullManifest(\n ctx: { origin: string; key: string | null },\n dir: string,\n config: Record<string, unknown>,\n log: (line: string) => void,\n): Promise<{ assets: Manifest; files: string[] } | null> {\n const home = homeOrigins(ctx.origin)\n const hosted = manifestRefs(config).filter(({ ref }) =>\n hostedAssetId(ref, home),\n )\n if (hosted.length === 0) return null\n const manifest = cloneManifest(config)\n const indexPath = join(dir, 'assets', HOME_INDEX)\n let index: Record<string, string> = {}\n try {\n const parsed: unknown = JSON.parse(readFileSync(indexPath, 'utf8'))\n if (parsed && typeof parsed === 'object') {\n index = parsed as Record<string, string>\n }\n } catch {\n // no index yet: nothing here is known to be a hosted file\n }\n const files: string[] = []\n for (const { name, index: at, ref } of hosted) {\n const id = hostedAssetId(ref, home)!\n const stem = at === null ? name : `${name}-${at}`\n const known = Object.keys(index).find(\n (f) => f === stem || f.startsWith(`${stem}.`),\n )\n let file: string\n if (\n known &&\n index[known] === id &&\n existsSync(join(dir, 'assets', known))\n ) {\n file = `assets/${known}`\n } else {\n // A different hosted file under this name: the old copy goes first,\n // or a changed extension would leave both lying side by side.\n if (known) {\n rmSync(join(dir, 'assets', known), { force: true })\n delete index[known]\n }\n const got = await downloadMedia(ctx, `/api/assets/${id}/file`, {\n dir,\n subdir: 'assets',\n stem,\n fallbackExt: '',\n describe: `${where(name, at)} (asset ${id})`,\n notFoundHint: 'a private file needs a content key of its owner',\n })\n file = got.file\n index[file.slice('assets/'.length)] = id\n log(\n ` ${file} ← ${where(name, at)}, asset ${id} (${Math.round(got.bytes / 1024)} kB)`,\n )\n }\n files.push(file)\n setRef(manifest, name, at, `./${file}`)\n }\n writeFileSync(indexPath, JSON.stringify(index, null, 2) + '\\n')\n return { assets: manifest, files }\n}\n\nconst HOSTED_FILE_LITERAL =\n /(?:asset:|\\/api\\/assets\\/)([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})/g\nconst FUNCTION_FIELDS = ['setup', 'createContent', 'createTimeline', 'onFrame']\n\n/**\n * A hosted file named inside a function instead of in the manifest. The\n * platform cannot give a string in code a URL per surface, so the file does\n * not reach a render of a private program and does not follow a remix.\n * One line per function and file, in words that say the fix.\n */\nexport function hostedLiteralWarnings(\n config: Record<string, unknown>,\n): string[] {\n const out: string[] = []\n const scan = (label: string, src: unknown) => {\n if (typeof src !== 'string') return\n const seen = new Set<string>()\n for (const m of src.matchAll(HOSTED_FILE_LITERAL)) {\n if (seen.has(m[1])) continue\n seen.add(m[1])\n out.push(\n `${label} names hosted file ${m[1]} in code. Declare it instead: \"assets\": { \"name\": { \"ref\": \"asset:${m[1]}\" } }, and read ctx.assets.name, so it loads on every render and follows a remix`,\n )\n }\n }\n for (const field of FUNCTION_FIELDS) scan(field, config[field])\n const stack = Array.isArray(config.stack) ? config.stack : []\n for (const entry of stack as Record<string, unknown>[]) {\n for (const field of ['setup', 'createContent', 'onFrame']) {\n scan(`stack.${String(entry.id)}.${field}`, entry[field])\n }\n }\n return out\n}\n","import { isKeyedOverlay } from '@vosjs/studio-core'\n/**\n * A hosted take comes home: download the recording (and the\n * mic/cam sidecars when the doc carries them) beside a pulled doc.json, and\n * re-anchor the doc's keys to the local files, so `vos digest`, `frames` and\n * `render` run on a pulled take exactly as on a local one. Also writes the\n * take-dir markers (meta.json, cursor.json) from the doc when absent, so the\n * directory IS a take directory to every other verb.\n *\n * Same consent shape as the upload direction: nothing moves without the verb\n * the user ran (`--media`). A file already on disk is kept, never re-fetched.\n */\nimport { createHash } from 'node:crypto'\nimport { createWriteStream, existsSync } from 'node:fs'\nimport {\n mkdir,\n open,\n readFile,\n readdir,\n rename,\n rm,\n stat,\n writeFile,\n} from 'node:fs/promises'\nimport { basename, dirname, join } from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { takeMediaFiles, writeJson } from './take'\nimport {\n MEDIA_HEAD_BYTES,\n extensionFor,\n nameForType,\n resolveMediaType,\n} from './container'\nimport { uploadAsset } from './uploadAsset'\nimport type { UploadTarget, UploadedAsset } from './uploadAsset'\nimport type {\n AudioClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n} from '@vosjs/studio-core'\n\n/** The names the recorders write their sidecars under. */\nexport const MIC_NAME = 'mic.webm'\nexport const CAM_NAME = 'cam.webm'\n\n/**\n * A take's own media: the stem each one lives under, and the container to\n * assume when the bytes that come back say nothing. The EXTENSION is not part\n * of the identity — a pulled recording wears whatever its hosted asset holds.\n */\nconst SOURCE_MEDIA = [\n { key: 'videoKey', stem: 'recording', fallbackExt: '.webm' },\n { key: 'micKey', stem: 'mic', fallbackExt: '.webm' },\n { key: 'camKey', stem: 'cam', fallbackExt: '.webm' },\n] as const\n\n/** A take-relative media key: neither a URL, a blob, nor a hosted asset path. */\nexport function isTakeRelativeKey(key: string | undefined): key is string {\n // `media:<id>` names a document media (a layer that shows one); the media\n // itself rides the upload door, so the reference is never a file.\n return (\n !!key &&\n !/^(https?:|blob:|data:|media:|\\/\\/)/.test(key) &&\n !key.startsWith('/api/')\n )\n}\n\n/** The file a take-relative key names, under the take directory. */\nexport const takeRelativeFile = (key: string): string => key.replace(/^\\/+/, '')\n\nexport interface DocMediaRef {\n /** Where the key lives, in words (for a log line). */\n where: string\n key: string\n /** Replace the key in the document. */\n set: (next: string) => void\n}\n\n/**\n * A take's own sidecars, when they are local files: the voice (`micKey`)\n * and the webcam (`camKey`). A push used to DROP them (\"local-only\"), while\n * `vos fetch --media` writes them beside the take as `mic.webm` and\n * `cam.webm`: fetch, then push, and the voice and the webcam were gone from\n * the hosted take, in one log line nobody reads.\n */\nexport function sourceSidecarRefs(\n doc: ProjectDoc,\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const [field, where] of [\n ['micKey', 'mic track'],\n ['camKey', 'cam track'],\n ] as const) {\n const key = doc.source[field]\n if (!key || !keep(key)) continue\n out.push({\n where,\n key,\n set: (next) => {\n doc.source[field] = next\n },\n })\n }\n return out\n}\n\n/** The 3D props a document places: each names a model file (a GLB). */\nexport function docObjectRefs(\n doc: { objects?: ObjectClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.objects ?? []) {\n const asset = clip.asset as { kind?: string; key?: string }\n if (asset.kind !== 'gltf' || !keep(asset.key)) continue\n out.push({\n where: `object ${clip.id}`,\n key: asset.key as string,\n set: (next) => {\n asset.key = next\n },\n })\n }\n return out\n}\n\n/** The picture and video layers a document places, by key. */\nexport function docOverlayRefs(\n doc: { overlays?: OverlayClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.overlays ?? []) {\n // An HTML layer has no key: its content IS its source. Its only files are\n // the faces it brings (`fonts`), each a url the page fetches, so a face\n // still beside the document crosses like any other layer file.\n if (clip.kind === 'html') {\n for (const [i, font] of (clip.fonts ?? []).entries()) {\n if (!keep(font.url)) continue\n out.push({\n where: `overlay ${clip.id} font ${i}`,\n key: font.url,\n set: (next) => {\n font.url = next\n },\n })\n }\n continue\n }\n if (!isKeyedOverlay(clip) || !keep(clip.key)) continue\n out.push({\n where: `overlay ${clip.id}`,\n key: clip.key,\n set: (next) => {\n clip.key = next\n },\n })\n }\n return out\n}\n\n/**\n * The shared layers either kind of document carries, as files: picture and\n * video overlays, 3D props, and added sound. A program document has no\n * recording, frame or end card, so this is all it can name; a take reads\n * the same three through `docMediaRefs`.\n */\nexport function docLayerRefs(\n doc: {\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n audio?: AudioClip[]\n },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n return [\n ...docOverlayRefs(doc, keep),\n ...docObjectRefs(doc, keep),\n ...docAudioRefs(doc, keep),\n ]\n}\n\n/**\n * Every media key a document carries beside its recording, mic and cam:\n * image and video overlay clips (a poster's mark), 3D props, the end card's\n * mark, a background image or loop, added sound. The recording's own keys\n * are `KEYS` and handled by their own path. A push rehosts these; a pull\n * brings them home.\n */\nexport function docMediaRefs(\n doc: ProjectDoc,\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n // The take's OTHER media (concat): each recording, and its sidecars,\n // rides the upload door like the primary's; a pull brings them home.\n for (const m of doc.media ?? []) {\n if (keep(m.videoKey))\n out.push({\n where: `media ${m.id}`,\n key: m.videoKey,\n set: (next) => {\n m.videoKey = next\n },\n })\n const mic = m.micKey\n if (mic && keep(mic))\n out.push({\n where: `media ${m.id} mic`,\n key: mic,\n set: (next) => {\n m.micKey = next\n },\n })\n const cam = m.camKey\n if (cam && keep(cam))\n out.push({\n where: `media ${m.id} cam`,\n key: cam,\n set: (next) => {\n m.camKey = next\n },\n })\n }\n out.push(...docOverlayRefs(doc, keep))\n // A prop's model was never walked: a take with a GLB pushed a document\n // whose object keyed a file only the pusher's disk held.\n out.push(...docObjectRefs(doc, keep))\n const mark = doc.endCard?.mark\n if (mark && keep(mark.key))\n out.push({\n where: 'end card mark',\n key: mark.key,\n set: (next) => {\n mark.key = next\n },\n })\n const bg = doc.frame.backgroundMedia\n if (bg && keep(bg.key))\n out.push({\n where: 'background',\n key: bg.key,\n set: (next) => {\n bg.key = next\n },\n })\n out.push(...docAudioRefs(doc, keep))\n return out\n}\n\n/**\n * The sound a document adds (`doc.audio`: a score, a voice-over, an effect),\n * for either kind of document. A take reads it through `docMediaRefs`; a\n * program document, which has no recording or frame, reads it here. A\n * catalog track is an absolute URL and is never walked.\n */\nexport function docAudioRefs(\n doc: { audio?: AudioClip[] },\n keep: (key: string | undefined) => boolean = isTakeRelativeKey,\n): DocMediaRef[] {\n const out: DocMediaRef[] = []\n for (const clip of doc.audio ?? []) {\n if (!keep(clip.key)) continue\n out.push({\n where: `audio ${clip.id}`,\n key: clip.key,\n set: (next) => {\n clip.key = next\n },\n })\n }\n return out\n}\n\n/**\n * Upload the files a document names and point its keys at the hosted\n * assets. Each file rides the content-addressed upload door, so a re-push\n * reuses it; the bytes name the type, and the uploaded filename is corrected\n * to match so the asset is never self-contradictory. A key whose file is\n * missing is said and left as it is (the hosted render will 404 on it,\n * honestly). Take pushes and program pushes share it.\n */\nexport async function uploadDocRefs(\n refs: DocMediaRef[],\n dir: string,\n target: UploadTarget,\n log: (line: string) => void,\n): Promise<void> {\n for (const ref of refs) {\n const file = join(dir, takeRelativeFile(ref.key))\n if (!existsSync(file)) {\n log(\n `warning ${ref.where}: ${ref.key} is not beside the document, so it was not uploaded`,\n )\n continue\n }\n const media = await readFile(file)\n const mediaHash = createHash('sha256').update(media).digest('hex')\n const { type } = resolveMediaType({\n head: media.subarray(0, MEDIA_HEAD_BYTES),\n filename: file,\n })\n const name = nameForType(basename(file), type)\n if (name !== basename(file)) {\n log(` ${ref.where}: ${basename(file)} holds ${type} — sent as ${name}`)\n }\n let put: UploadedAsset\n try {\n put = await uploadAsset(target, new Uint8Array(media), {\n filename: name,\n contentType: type,\n contentHash: mediaHash,\n })\n } catch (e) {\n throw new Error(\n `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n ref.set(put.url)\n log(\n ` ${ref.where}: ${ref.key} → asset ${put.id}${put.reused ? ' (reused)' : ''}`,\n )\n for (const note of put.notes) log(` ${ref.where}: ${note}`)\n }\n}\n\n// The extension↔type table and the byte sniffer live in `container.ts`; these\n// stay the door every media caller already knows.\nexport { extensionFor, mediaContentType } from './container'\n\n/** An asset file URL (relative `/api/assets/{id}/file` or absolute). */\nexport function assetIdOf(url: string | undefined): string | null {\n if (!url) return null\n const m = /\\/api\\/assets\\/([A-Za-z0-9_-]+)\\/file/.exec(url)\n return m ? m[1] : null\n}\n\nexport interface MediaPullResult {\n downloaded: { file: string; assetId: string; bytes: number }[]\n kept: string[]\n}\n\n/** The first bytes of a file, enough for every container signature. */\nexport async function readMediaHead(file: string): Promise<Uint8Array> {\n const handle = await open(file, 'r')\n try {\n const buf = Buffer.alloc(MEDIA_HEAD_BYTES)\n const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0)\n return new Uint8Array(buf.subarray(0, bytesRead))\n } finally {\n await handle.close()\n }\n}\n\nexport interface DownloadTarget {\n dir: string\n /** Directory under the take dir, when the file is filed away. */\n subdir?: string\n /** The name without its extension — the extension is the BYTES' to choose. */\n stem: string\n /** The container to assume when the bytes say nothing. */\n fallbackExt: string\n /** What is being fetched, in words, for an error or a log line. */\n describe: string\n /** Added to a 404, where the reason is usually the credential. */\n notFoundHint?: string\n}\n\n/**\n * One media file, brought home under the name its BYTES earn.\n *\n * Streamed to a `.part` sibling first, then sniffed and renamed, so a take\n * never holds a file whose extension lies — whatever the server declared, and\n * whatever the hosted asset happened to be called. A push of a pulled take\n * re-uploads that file, so a wrong name here becomes a wrong Content-Type on\n * the asset, and a render that dies inside the video element.\n */\nexport async function downloadMedia(\n ctx: { origin: string; key: string | null },\n url: string,\n target: DownloadTarget,\n): Promise<{ file: string; bytes: number }> {\n const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`\n const res = await fetch(abs, {\n headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {},\n })\n if (!res.ok || !res.body) {\n throw new Error(\n `download of ${target.describe} failed (${res.status})${\n res.status === 404 && target.notFoundHint\n ? ` — ${target.notFoundHint}`\n : ''\n }`,\n )\n }\n const into = target.subdir ? join(target.dir, target.subdir) : target.dir\n const partial = join(into, `${target.stem}.part`)\n await mkdir(dirname(partial), { recursive: true })\n try {\n await pipeline(\n Readable.fromWeb(res.body as never),\n createWriteStream(partial),\n )\n const { type } = resolveMediaType({\n head: await readMediaHead(partial),\n declared: res.headers.get('content-type'),\n filename: `${target.stem}${target.fallbackExt}`,\n })\n const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`\n await rename(partial, join(into, name))\n const file = target.subdir ? `${target.subdir}/${name}` : name\n return { file, bytes: (await stat(join(target.dir, file))).size }\n } finally {\n // A rename leaves nothing behind; a failed transfer must not.\n await rm(partial, { force: true })\n }\n}\n\nexport async function pullMedia(\n ctx: { origin: string; key: string | null },\n dir: string,\n doc: ProjectDoc,\n log: (line: string) => void,\n): Promise<MediaPullResult> {\n const result: MediaPullResult = { downloaded: [], kept: [] }\n for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {\n const url = doc.source[key]\n const assetId = assetIdOf(url)\n if (!assetId) continue\n // A file already here is kept whatever container it wears, so a second\n // pull never re-fetches and never leaves two of the same footage.\n const present = takeMediaFiles(dir, stem)[0]\n if (present) {\n result.kept.push(basename(present))\n doc.source[key] = basename(present)\n continue\n }\n const got = await downloadMedia(ctx, url!, {\n dir,\n stem,\n fallbackExt,\n describe: stem,\n notFoundHint: 'a private recording needs a content key of its owner',\n })\n result.downloaded.push({ file: got.file, assetId, bytes: got.bytes })\n log(` ${got.file} ← asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`)\n doc.source[key] = got.file\n }\n // The document's other media (a poster's mark, a pasted ground): a hosted\n // asset comes home under media/<assetId>.<ext> and the key re-anchors to\n // it, so the pulled poster renders and re-pushes as a whole.\n for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {\n const assetId = assetIdOf(ref.key)!\n const existing = existsSync(join(dir, 'media'))\n ? (await readdir(join(dir, 'media'))).find(\n (f) => f === assetId || f.startsWith(`${assetId}.`),\n )\n : undefined\n if (existing) {\n const file = `media/${existing}`\n result.kept.push(file)\n ref.set(file)\n continue\n }\n const got = await downloadMedia(ctx, ref.key, {\n dir,\n subdir: 'media',\n stem: assetId,\n // A type with no extension here (an svg served as such) keeps the bare\n // asset id, exactly as before.\n fallbackExt: '',\n describe: `${ref.where} (${ref.key})`,\n })\n result.downloaded.push({ file: got.file, assetId, bytes: got.bytes })\n log(\n ` ${got.file} ← ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`,\n )\n ref.set(got.file)\n }\n // The take-dir markers: meta.json and cursor.json live on the doc for a\n // hosted take; write them out so loadTake() recognizes the directory.\n const metaPath = join(dir, 'meta.json')\n if (!existsSync(metaPath)) await writeJson(metaPath, doc.source.meta, true)\n const cursorPath = join(dir, 'cursor.json')\n if (!existsSync(cursorPath)) await writeJson(cursorPath, doc.source.cursor)\n await writeFile(join(dir, 'doc.json'), JSON.stringify(doc, null, 2))\n return result\n}\n","/**\n * The take directory — the CLI's unit of work, human-inspectable on purpose:\n * take/\n * recording.webm encoded footage (CFR; .mp4 when a pull brought it home\n * in that container, so readers RESOLVE the name)\n * frames/ raw screencast JPEGs + frames.json (kept for re-encode)\n * cursor.json synthesized CursorTrack\n * meta.json RecordingMeta (producer: 'cli')\n * actions.json the script that produced it (replay recipe)\n * doc.json ProjectDoc — the agent-editable surface\n */\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { existsSync, readdirSync, statSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport { TAKE_MEDIA_EXTENSIONS } from './container'\nimport type { CursorTrack, ProjectDoc, RecordingMeta } from '@vosjs/studio-core'\nimport type { ActionsFile } from './actions'\n\n/** The name `record` writes its footage under — its encoder emits WebM. */\nexport const RECORDING_NAME = 'recording.webm'\n\n/**\n * A take's own media under `dir`, whatever container it came home in.\n *\n * `record` writes WebM, but a PULLED take wears the container its hosted asset\n * actually holds, so nothing may spell `recording.webm` at a read site: a take\n * whose footage is mp4 must still render, digest and push. Ordered by\n * `TAKE_MEDIA_EXTENSIONS`, so the answer is deterministic if a stale sibling\n * survives.\n */\nexport function takeMediaFiles(dir: string, stem: string): string[] {\n return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(\n (file) => existsSync(file),\n )\n}\n\n/**\n * The one file a take's media stem names, falling back to what `record` would\n * write — so a caller can `existsSync` the answer and get an honest\n * \"no footage here\" rather than a path that never existed.\n */\nexport function takeMediaFile(\n dir: string,\n stem: string,\n fallback: string,\n): string {\n return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback)\n}\n\n/** The previous cut, preserved by a re-record. */\nexport const PREV_DOC_NAME = 'doc.prev.json'\n\nexport interface TakePaths {\n dir: string\n recording: string\n /** The recording's bare filename — what a doc key or a served URL spells. */\n recordingName: string\n framesDir: string\n framesIndex: string\n cursor: string\n meta: string\n actions: string\n doc: string\n}\n\nexport function takePaths(dir: string): TakePaths {\n const recording = takeMediaFile(dir, 'recording', RECORDING_NAME)\n return {\n dir,\n recording,\n recordingName: basename(recording),\n framesDir: join(dir, 'frames'),\n framesIndex: join(dir, 'frames.json'),\n cursor: join(dir, 'cursor.json'),\n meta: join(dir, 'meta.json'),\n actions: join(dir, 'actions.json'),\n doc: join(dir, 'doc.json'),\n }\n}\n\nexport async function ensureTakeDir(dir: string): Promise<TakePaths> {\n const p = takePaths(dir)\n await mkdir(p.framesDir, { recursive: true })\n return p\n}\n\n/**\n * Drop the screencast JPEGs once the recording they were encoded into is on\n * disk. They are the bulk of a take (one hero take: 437 MB of a 452 MB\n * directory) and nothing needs them afterwards: the digest reads frames\n * from them WHEN PRESENT and from the video otherwise, which is the path\n * every pulled take already runs on. Refuses unless the recording exists and\n * has bytes, so a failed encode never costs the only copy of the footage.\n * Returns the bytes freed, 0 when it left everything alone.\n */\nexport async function dropScreencastFrames(p: TakePaths): Promise<number> {\n if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0\n if (!existsSync(p.framesDir)) return 0\n let freed = 0\n for (const name of readdirSync(p.framesDir)) {\n try {\n freed += statSync(join(p.framesDir, name)).size\n } catch {\n // a file gone between the listing and the stat frees nothing\n }\n }\n await rm(p.framesDir, { recursive: true, force: true })\n await rm(p.framesIndex, { force: true })\n return freed\n}\n\n/**\n * Re-record into an existing take WITHOUT destroying the cut (the\n * re-render loop's first rung; `rm -rf` here is what made \"re-renders on the\n * next version\" a false claim). What the old footage produced is cleared\n * (recording, cursor, meta, raw frames, stills/, digest/ — all derived and\n * regenerable); what the human's work lives in survives: `doc.json` moves to\n * `doc.prev.json` — the explicit reuse base `vos plan --reuse` re-anchors\n * onto the new recording — and `actions.json` + `vos.json` stay put. The doc\n * is MOVED, never left in place: a doc.json embeds its footage's cursor and\n * meta (`doc.source`), so leaving it beside new footage would be a document\n * lying about its source, and the auto-plan after recording would replan\n * from the OLD cursor. Returns what was kept, for the terminal to say.\n */\nexport async function prepareReRecord(\n dir: string,\n): Promise<{ prevDoc: boolean; kept: string[] }> {\n const p = takePaths(dir)\n const kept: string[] = []\n let prevDoc = false\n if (existsSync(p.doc)) {\n await rename(p.doc, join(dir, PREV_DOC_NAME))\n prevDoc = true\n kept.push(PREV_DOC_NAME)\n }\n if (existsSync(p.actions)) kept.push('actions.json')\n if (existsSync(join(dir, 'vos.json'))) kept.push('vos.json')\n for (const stale of [\n // Every container, not just the one `record` writes: a pulled take's\n // footage may be mp4, and a survivor would outrank the new recording.\n ...takeMediaFiles(dir, 'recording'),\n ...takeMediaFiles(dir, 'mic'),\n ...takeMediaFiles(dir, 'cam'),\n p.cursor,\n p.meta,\n p.framesIndex,\n p.framesDir,\n join(dir, 'stills'),\n join(dir, 'digest'),\n ]) {\n await rm(stale, { recursive: true, force: true })\n }\n return { prevDoc, kept }\n}\n\nexport async function readJson<T>(file: string): Promise<T> {\n return JSON.parse(await readFile(file, 'utf8')) as T\n}\n\nexport async function writeJson(\n file: string,\n value: unknown,\n pretty = false,\n): Promise<void> {\n await writeFile(\n file,\n pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value),\n )\n}\n\nexport interface TakeData {\n paths: TakePaths\n cursor: CursorTrack\n meta: RecordingMeta\n actions: ActionsFile | null\n doc: ProjectDoc | null\n}\n\n/** Load a take directory; cursor+meta are required, doc/actions optional. */\nexport async function loadTake(dir: string): Promise<TakeData> {\n const paths = takePaths(dir)\n if (!existsSync(paths.meta)) {\n throw new Error(\n `${dir} is not a take directory (missing meta.json) — run record first`,\n )\n }\n const meta = await readJson<RecordingMeta>(paths.meta)\n const cursor = existsSync(paths.cursor)\n ? await readJson<CursorTrack>(paths.cursor)\n : []\n const actions = existsSync(paths.actions)\n ? await readJson<ActionsFile>(paths.actions)\n : null\n const doc = existsSync(paths.doc)\n ? await readJson<ProjectDoc>(paths.doc)\n : null\n return { paths, cursor, meta, actions, doc }\n}\n","/**\n * Uploading a file to vos.so: one door, for every kind and every size.\n *\n * The file is declared first, sent as parts, and sealed. Three reasons it\n * is never one request:\n *\n * - A request body has a ceiling the platform does not set and cannot\n * raise from inside a handler: the edge refuses anything past it before\n * the request arrives, with its own error page rather than a worded\n * refusal. A screen recording of any real length clears that ceiling.\n * - Everything the platform may refuse a file for (its kind, its size, a\n * daily rate, the account's storage) is weighed at the declaring call,\n * before a byte moves, so a refusal costs nothing and arrives in words.\n * - The declaring call is content-addressed: the same bytes pushed twice\n * are recognised and never re-sent, which is what keeps an iterating\n * push from re-uploading a take on every round.\n *\n * The part size is NOT decided here. The platform returns it from the\n * declaring call, so the split can change server-side without a CLI\n * release.\n *\n * What the file IS comes back from the sealing call: the platform reads\n * the stored bytes and answers its kind, the name it is stored under and\n * anything worth saying about it. Nothing here decides what is accepted;\n * `GET /api/limits` lists the kinds and their caps.\n */\nimport { apiJson } from './platform'\nimport type { ApiResult } from './platform'\n\n/** Attempts per part. A part commits nothing until the upload is sealed,\n * so it is always safe to send again. */\nconst PART_ATTEMPTS = 3\n\nexport interface PartPlan {\n /** Part numbers are 1-based. */\n partNumber: number\n start: number\n /** One past the last byte. */\n end: number\n}\n\n/**\n * The slices to send, in order. Every part but the last is exactly\n * `partBytes`; the last is the remainder. The platform holds each part to\n * exactly this length, so the split has to agree with the size that was\n * declared — which is why `partBytes` comes from the platform rather than\n * from a constant here.\n */\nexport function planParts(totalBytes: number, partBytes: number): PartPlan[] {\n if (totalBytes <= 0 || partBytes <= 0) return []\n const parts: PartPlan[] = []\n for (let start = 0; start < totalBytes; start += partBytes) {\n parts.push({\n partNumber: parts.length + 1,\n start,\n end: Math.min(start + partBytes, totalBytes),\n })\n }\n return parts\n}\n\nexport interface UploadedAsset {\n id: string\n /** The path a document names the file by. */\n url: string\n size: number\n /** The platform already had these exact bytes; nothing was sent. */\n reused: boolean\n /** What the bytes are, as the platform read them (video, audio, image,\n * vector, model, font, hdr, recipe, captions). */\n kind: string\n /** The name it is stored under: the extension follows the bytes. */\n filename: string\n /** The frontmatter summary of a recipe, a picture's size, a clip's codec. */\n metadata: Record<string, unknown> | null\n /** What the platform did to the file or wants said about it. */\n notes: string[]\n /** The reference a program's manifest takes. */\n ref: string\n /** Seconds, for what plays. */\n duration: number | null\n}\n\nexport interface UploadTarget {\n origin: string\n /** The bearer: a content key, or a claimable push's session token. */\n key: string\n /**\n * The door's path. The account's by default; a claimable push sends its\n * files through `/api/claim/uploads/files`, which speaks the same\n * protocol under its session token.\n */\n door?: string\n}\n\nexport interface UploadOptions {\n filename: string\n /** Only a hint: the platform types a file by its bytes. It settles the\n * one thing bytes cannot, a WebM that holds sound alone. */\n contentType?: string\n /** sha256 hex — the platform dedupes on it, per owner. */\n contentHash: string\n /** The take's length, so a refusal for one past the hosted cap arrives\n * before anything is sent. The platform measures the file either way. */\n durationSeconds?: number\n /** Parts landed, for a caller that reports a long upload's progress. */\n onPart?: (done: number, total: number) => void\n /** File the asset into one of the caller's folders. */\n folderId?: string\n /**\n * `library`: a file added to keep (`vos asset push`, a recipe).\n * `attached` (the default): a file a document is about to name, which\n * the platform collects if nothing ever does.\n */\n intent?: 'library' | 'attached'\n /**\n * Where the file came from, as the uploader states it: kept beside the\n * file and shown with it. The platform does not check it.\n */\n provenance?: { sourceUrl?: string; license?: string; attribution?: string }\n}\n\nfunction failed(what: string, r: ApiResult): Error {\n const detail = typeof r.body.error === 'string' ? r.body.error : ''\n const hint = typeof r.body.hint === 'string' ? ` ${r.body.hint}` : ''\n return new Error(`${what} (${r.status})${detail ? `: ${detail}${hint}` : ''}`)\n}\n\nfunction asAsset(\n body: Record<string, unknown>,\n reused: boolean,\n): UploadedAsset {\n const asset = (body.asset ?? {}) as Record<string, unknown>\n return {\n id: String(asset.id),\n url: String(asset.fileUrl),\n size: typeof asset.size === 'number' ? asset.size : 0,\n reused,\n kind: typeof asset.kind === 'string' ? asset.kind : '',\n filename: typeof asset.filename === 'string' ? asset.filename : '',\n metadata:\n asset.metadata && typeof asset.metadata === 'object'\n ? (asset.metadata as Record<string, unknown>)\n : null,\n notes: Array.isArray(body.notes)\n ? body.notes.filter((n): n is string => typeof n === 'string')\n : [],\n ref:\n typeof asset.ref === 'string' ? asset.ref : `asset:${String(asset.id)}`,\n duration: typeof asset.duration === 'number' ? asset.duration : null,\n }\n}\n\n/** Worth sending again: the platform was busy or briefly unreachable. A\n * refusal is a decision and will read the same on a second try. */\nfunction transient(status: number): boolean {\n return status >= 500 || status === 429\n}\n\nconst wait = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\n/** Upload one file as an owned asset: declare it, send its parts, seal it. */\nexport async function uploadAsset(\n target: UploadTarget,\n bytes: Uint8Array,\n opts: UploadOptions,\n): Promise<UploadedAsset> {\n // Declare it first. Every quota is weighed here, before a byte moves, so\n // a refusal costs nothing and arrives in words — and the dedupe answers\n // here too, which is what makes re-pushing a hosted take free.\n const door = target.door ?? '/api/assets/uploads'\n const begun = await apiJson(target.origin, door, {\n method: 'POST',\n key: target.key,\n body: {\n filename: opts.filename,\n ...(opts.contentType ? { contentType: opts.contentType } : {}),\n size: bytes.length,\n sha256: opts.contentHash,\n ...(opts.durationSeconds && opts.durationSeconds > 0\n ? { durationSeconds: opts.durationSeconds }\n : {}),\n ...(opts.folderId ? { folderId: opts.folderId } : {}),\n ...(opts.intent ? { intent: opts.intent } : {}),\n },\n })\n if (begun.status !== 201 && begun.status !== 200) {\n throw failed('upload failed', begun)\n }\n // The platform already holds these bytes: nothing to send.\n if (begun.body.asset) return asAsset(begun.body, true)\n\n const uploadId = String(begun.body.uploadId)\n const partBytes = Number(begun.body.partBytes)\n if (!Number.isFinite(partBytes) || partBytes <= 0) {\n throw new Error('upload failed: the platform returned no part size')\n }\n const parts = planParts(bytes.length, partBytes)\n const etags: { partNumber: number; etag: string }[] = []\n\n try {\n for (const part of parts) {\n const chunk = bytes.subarray(part.start, part.end)\n let sent: ApiResult | null = null\n for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {\n try {\n sent = await apiJson(\n target.origin,\n `${door}/${uploadId}/parts/${part.partNumber}`,\n {\n method: 'PUT',\n key: target.key,\n headers: {\n 'Content-Type': 'application/octet-stream',\n 'Content-Length': String(chunk.length),\n },\n raw: chunk,\n },\n )\n } catch (networkError) {\n // Dropped connection, a sleeping machine: worth another try.\n if (attempt === PART_ATTEMPTS) throw networkError\n await wait(500 * 2 ** (attempt - 1))\n continue\n }\n if (sent.status === 200) break\n if (!transient(sent.status) || attempt === PART_ATTEMPTS) {\n throw failed(`part ${part.partNumber} failed`, sent)\n }\n await wait(500 * 2 ** (attempt - 1))\n }\n if (!sent || sent.status !== 200) {\n throw new Error(`part ${part.partNumber} failed`)\n }\n etags.push({\n partNumber: Number(sent.body.partNumber),\n etag: String(sent.body.etag),\n })\n opts.onPart?.(etags.length, parts.length)\n }\n\n // Sealing is where the platform reads the file. A refusal here means\n // the bytes are not what the name said, and it has already taken them\n // back out.\n const sealed = await apiJson(\n target.origin,\n `${door}/${uploadId}/complete`,\n {\n method: 'POST',\n key: target.key,\n body: {\n parts: etags,\n ...(opts.provenance ? { provenance: opts.provenance } : {}),\n },\n },\n )\n if (sealed.status !== 201 && sealed.status !== 200) {\n throw failed('upload failed', sealed)\n }\n // The platform hashes what it stored: bytes this account already held\n // come back as the file it had, whatever was declared.\n return asAsset(sealed.body, sealed.body.reused === true)\n } catch (err) {\n // Hand the parts back rather than leaving them held server-side.\n await apiJson(target.origin, `${door}/${uploadId}`, {\n method: 'DELETE',\n key: target.key,\n }).catch(() => undefined)\n throw err\n }\n}\n"],"mappings":"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","names":["existsSync","join","existsSync","mkdir","readFile","rename","rm","writeFile","basename","join","where","join","existsSync","readFile","basename","mkdir","rename","rm","writeFile","join","existsSync"]}
|
|
@@ -84,7 +84,7 @@ var manifest = {
|
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
name: "asset",
|
|
87
|
-
summary: "
|
|
87
|
+
summary: "your files: push or import one, list them (assets ls), see storage (assets usage) and what uses a file (assets why)"
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
name: "recipe",
|
|
@@ -113,4 +113,4 @@ var manifest = {
|
|
|
113
113
|
export {
|
|
114
114
|
manifest
|
|
115
115
|
};
|
|
116
|
-
//# sourceMappingURL=chunk-
|
|
116
|
+
//# sourceMappingURL=chunk-AUFY3M22.js.map
|
|
@@ -1 +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: 'ingest',\n summary:\n 'a take from a recording someone else made, with its cursor track from a trace beside it (Playwright trace.zip, stamped records, CSV); without one nothing is planned',\n },\n {\n name: 'port',\n summary:\n 'bring a page, a HyperFrames or a Remotion piece: inventory (its words, colours, faces, media, scenes) then scaffold (a data-first program.mjs to translate the motion into)',\n },\n {\n name: 'build',\n summary:\n 'program.mjs (real functions) → config.json, refusing what cannot run on its own (module-scope reads, syntax the page cannot parse, non-JSON data)',\n },\n {\n name: 'compare',\n summary:\n \"a program against the source's own render: per-frame SSIM and source | vos | difference sheets; exits 1 when any frame is under the threshold\",\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:
|
|
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: 'ingest',\n summary:\n 'a take from a recording someone else made, with its cursor track from a trace beside it (Playwright trace.zip, stamped records, CSV); without one nothing is planned',\n },\n {\n name: 'port',\n summary:\n 'bring a page, a HyperFrames or a Remotion piece: inventory (its words, colours, faces, media, scenes) then scaffold (a data-first program.mjs to translate the motion into)',\n },\n {\n name: 'build',\n summary:\n 'program.mjs (real functions) → config.json, refusing what cannot run on its own (module-scope reads, syntax the page cannot parse, non-JSON data)',\n },\n {\n name: 'compare',\n summary:\n \"a program against the source's own render: per-frame SSIM and source | vos | difference sheets; exits 1 when any frame is under the threshold\",\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:\n 'your files: push or import one, list them (assets ls), see storage (assets usage) and what uses a file (assets why)',\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 name: 'setup',\n summary:\n 'ready this machine: the vos skills into your agent, a browser, one rules block, then doctor',\n },\n {\n name: 'doctor',\n summary:\n 'what is ready, in words: node, browser, ffmpeg, skills, a credential (never printed), the dev server',\n },\n {\n name: 'whoami',\n summary: 'the key’s name and the account it belongs to, never the key',\n },\n { name: 'logout', summary: 'remove ~/.config/vos/credentials' },\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,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,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,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,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,EAAE,MAAM,UAAU,SAAS,mCAAmC;AAAA,EAChE;AACF;","names":[]}
|