@vosjs/cli 0.60.0 → 0.60.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 +1 -1
- package/dist/{assetManifest-MM5WGOWG.js → assetManifest-Y5FYTZW6.js} +3 -3
- package/dist/{chunk-OMDRILG7.js → chunk-LWHVZ5WI.js} +12 -6
- package/dist/chunk-LWHVZ5WI.js.map +1 -0
- package/dist/{chunk-NE5MHC3D.js → chunk-QL25OPZB.js} +6 -6
- package/dist/{chunk-NE5MHC3D.js.map → chunk-QL25OPZB.js.map} +1 -1
- package/dist/{chunk-36P5RREC.js → chunk-WCF6KIOK.js} +240 -118
- package/dist/chunk-WCF6KIOK.js.map +1 -0
- package/dist/{chunk-PUXY32AP.js → chunk-YMVRTZ4L.js} +69 -20
- package/dist/chunk-YMVRTZ4L.js.map +1 -0
- package/dist/cli.js +17 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{run-5DLEKBK2.js → run-BJ77A4G6.js} +5 -5
- package/package.json +5 -5
- package/skills/VERSION +1 -1
- package/skills/vos-authoring/references/schema-reference.md +40 -2
- package/skills/vos-port/SKILL.md +3 -1
- package/skills/vos-port/references/elements-and-context.md +5 -0
- package/skills/vos-port/references/hyperframes.md +1 -1
- package/skills/vos-port/references/remotion.md +1 -1
- package/dist/chunk-36P5RREC.js.map +0 -1
- package/dist/chunk-OMDRILG7.js.map +0 -1
- package/dist/chunk-PUXY32AP.js.map +0 -1
- /package/dist/{assetManifest-MM5WGOWG.js.map → assetManifest-Y5FYTZW6.js.map} +0 -0
- /package/dist/{run-5DLEKBK2.js.map → run-BJ77A4G6.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flagUse.ts","../src/args.ts","../src/programAssets.ts","../src/plugin/container.ts","../src/plugin/args.ts","../src/plugin/platform.ts","../src/plugin/hostAgent.ts"],"sourcesContent":["/**\n * Which of the flags a person gave did the verb actually READ?\n *\n * The parsers accept any `--name`, so a mistyped or misplaced flag used to be\n * swallowed: `vos frames <take> --at 3,5,8` wrote five evenly spread stills\n * and exited 0, because the flag is `--times` and nothing ever looked at\n * `at`. It looked like it worked.\n *\n * A declared list per verb would be the obvious guard and the wrong one here:\n * several verbs read their options through data (a name table, a helper three\n * calls down), so a list drifts, and the day it drifts it REFUSES a real flag\n * for every agent at once. Reading is the truth, so reading is what is\n * recorded: both parsers hand back their flags behind a proxy that notes each\n * name a verb asks for, and the CLI's one exit point compares that with what\n * was given. It cannot reject a flag a verb uses, by construction.\n *\n * The cost of being certain is that the verdict lands AFTER the verb ran, so\n * the message says so plainly: the command ran, without that flag.\n */\n\nconst given = new Set<string>()\nconst used = new Set<string>()\n\n/** A flag bag that remembers which names were asked for. */\nexport function trackFlags<T extends Record<string, unknown>>(bag: T): T {\n return new Proxy(bag, {\n get(target, key, receiver) {\n if (typeof key === 'string') used.add(key)\n return Reflect.get(target, key, receiver)\n },\n has(target, key) {\n if (typeof key === 'string') used.add(key)\n return Reflect.has(target, key)\n },\n // hasOwnProperty.call(flags, name) lands here.\n getOwnPropertyDescriptor(target, key) {\n if (typeof key === 'string') used.add(key)\n return Reflect.getOwnPropertyDescriptor(target, key)\n },\n // A spread, Object.keys or JSON.stringify hands every flag onward; what\n // happens to them after that cannot be seen, so they all count as read.\n ownKeys(target) {\n for (const key of Reflect.ownKeys(target))\n if (typeof key === 'string') used.add(key)\n return Reflect.ownKeys(target)\n },\n })\n}\n\nexport function noteGivenFlag(name: string): void {\n given.add(name)\n}\n\n/** Flags that were given and never read, in the order they were given. */\nexport function unusedFlags(): string[] {\n return [...given].filter((name) => !used.has(name) && name !== 'help')\n}\n\nexport function resetFlagUse(): void {\n given.clear()\n used.clear()\n}\n\n/**\n * The `--flags` a verb documents: its `vos <verb>` line in a help text plus\n * the indented lines that continue it.\n */\nexport function helpFlagsFor(help: string, verb: string): string[] {\n const out = new Set<string>()\n let inVerb = false\n for (const line of help.split('\\n')) {\n const head = /^\\s{2}vos\\s+(\\S+)/.exec(line)\n if (head) inVerb = head[1] === verb\n else if (!/^\\s{3,}\\S/.test(line)) inVerb = false\n if (!inVerb) continue\n for (const m of line.matchAll(/--([a-z][a-z0-9-]*)/g)) out.add(m[1])\n }\n return [...out]\n}\n\nfunction editDistance(a: string, b: string): number {\n const row = Array.from({ length: b.length + 1 }, (_, i) => i)\n for (let i = 1; i <= a.length; i++) {\n let prev = row[0]\n row[0] = i\n for (let j = 1; j <= b.length; j++) {\n const keep = row[j]\n row[j] = Math.min(\n row[j] + 1,\n row[j - 1] + 1,\n prev + (a[i - 1] === b[j - 1] ? 0 : 1),\n )\n prev = keep\n }\n }\n return row[b.length]\n}\n\n/** The documented flag a mistyped one most likely meant, or null. */\nexport function nearestFlag(name: string, candidates: string[]): string | null {\n let best: string | null = null\n let bestScore = Infinity\n for (const c of candidates) {\n if (c === name) continue\n // A candidate that contains the name (or the reverse) is as close as a\n // one-letter slip: `--zooms` for `--at-zooms`, `--body` for `--body-px`.\n const d = c.includes(name) || name.includes(c) ? 1 : editDistance(name, c)\n if (d < bestScore) {\n bestScore = d\n best = c\n }\n }\n return best !== null && bestScore <= Math.max(2, Math.floor(name.length / 3))\n ? best\n : null\n}\n\n/** The sentence for a run that ignored what it was given, or null when nothing was. */\nexport function unusedFlagsMessage(\n verb: string,\n documented: string[],\n): string | null {\n const unused = unusedFlags()\n if (!unused.length) return null\n const parts = unused.map((name) => {\n const near = nearestFlag(name, documented)\n return `--${name}${near ? ` (did you mean --${near}?)` : ''}`\n })\n const list = documented.length\n ? ` It reads: ${documented.map((f) => `--${f}`).join(' ')}.`\n : ''\n return `vos ${verb} RAN, and ignored ${parts.join(', ')}: ${unused.length > 1 ? 'they are' : 'it is'} not something this verb reads, so what it printed above was made without ${unused.length > 1 ? 'them' : 'it'}.${list}`\n}\n","/**\n * Minimal argv parser — positionals + `--flag value` / `--flag=value` /\n * boolean flags. Deliberately tiny: the CLI has a small, stable surface and\n * agents benefit from predictable, dependency-free parsing.\n */\nimport { noteGivenFlag, trackFlags } from './flagUse'\n\nexport interface ParsedArgs {\n positionals: string[]\n flags: Record<string, string | true>\n}\n\nexport class UsageError extends Error {}\n\nexport function parseArgs(\n argv: string[],\n booleanFlags: ReadonlySet<string>,\n): ParsedArgs {\n const positionals: string[] = []\n const flags: Record<string, string | true> = {}\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') {\n positionals.push(...argv.slice(i + 1))\n break\n }\n if (arg.startsWith('--')) {\n const eq = arg.indexOf('=')\n if (eq !== -1) {\n flags[arg.slice(2, eq)] = arg.slice(eq + 1)\n continue\n }\n const name = arg.slice(2)\n if (booleanFlags.has(name)) {\n flags[name] = true\n continue\n }\n const next = argv[i + 1]\n if (next === undefined || next.startsWith('--')) {\n throw new UsageError(`--${name} expects a value`)\n }\n flags[name] = next\n i++\n continue\n }\n positionals.push(arg)\n }\n for (const name of Object.keys(flags)) noteGivenFlag(name)\n return { positionals, flags: trackFlags(flags) }\n}\n\nexport function numFlag(\n flags: ParsedArgs['flags'],\n name: string,\n fallback: number,\n): number {\n const v = flags[name]\n if (v === undefined) return fallback\n const n = Number(v)\n if (!Number.isFinite(n))\n throw new UsageError(`--${name} expects a number, got \"${String(v)}\"`)\n return n\n}\n","/**\n * The files a program declares (`config.assets`), for a LOCAL run of it.\n *\n * A capture page has an origin of its own, so a manifest entry that names a\n * file beside the config (`./logo.png`) or a hosted one (`asset:<id>`)\n * cannot be fetched from inside it as written. Each is made reachable here\n * and handed to the engine as the page's `ctx.assets`:\n *\n * - a local file is SERVED to the page from disk, under a path on the\n * page's own origin, so a video is streamed and never inlined;\n * - a hosted asset is downloaded once with the caller's credential into a\n * cache directory and served the same way;\n * - a URL is used as it is.\n *\n * A file that cannot be reached keeps its ref and is said, so the render\n * shows a missing picture with a reason instead of a silent blank.\n */\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, isAbsolute, join, resolve } from 'node:path'\n\n/** Where served files live on a page's own origin. */\nexport const ASSET_ROUTE = '/__assets/'\n\nexport interface ProgramAssetsOptions {\n /** The directory the manifest's relative refs are read against. */\n baseDir: string\n /** For a hosted ref: where it lives and who may read it. */\n origin?: string\n key?: string | null\n log: (line: string) => void\n /** Where hosted files are kept between runs (default: the OS temp dir). */\n cacheDir?: string\n}\n\nexport interface ProgramAssets {\n /** Name → the URL (or URLs) the page reads as `ctx.assets.<name>`. */\n assets: Record<string, string | string[]>\n /** Served path (`/__assets/…`) → the file on disk behind it. */\n files: Record<string, string>\n}\n\nconst HOSTED_REF =\n /^(?:asset:|(?:https?:\\/\\/[^/]+)?\\/api\\/assets\\/)([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})(?:\\/file(?:\\?.*)?)?$/\n\n/** The hosted asset id a ref names (`asset:<id>` or its file path), or null. */\nexport function hostedAssetId(ref: string): string | null {\n return HOSTED_REF.exec(ref)?.[1] ?? null\n}\n\n/** A ref the page can fetch as written. */\nfunction isUrl(ref: string): boolean {\n return /^(https?:|data:|blob:)/.test(ref)\n}\n\nfunction manifestOf(\n config: Record<string, unknown>,\n): Record<string, { ref: string | string[] }> | null {\n const assets = config.assets\n if (!assets || typeof assets !== 'object' || Array.isArray(assets))\n return null\n return Object.keys(assets).length > 0\n ? (assets as Record<string, { ref: string | string[] }>)\n : null\n}\n\n/** Every ref of a manifest, in declaration order, with where it sits. */\nexport function manifestRefs(\n config: Record<string, unknown>,\n): { name: string; index: number | null; ref: string }[] {\n const out: { name: string; index: number | null; ref: string }[] = []\n for (const [name, decl] of Object.entries(manifestOf(config) ?? {})) {\n if (Array.isArray(decl.ref)) {\n decl.ref.forEach((ref, index) => out.push({ name, index, ref }))\n } else if (typeof decl.ref === 'string') {\n out.push({ name, index: null, ref: decl.ref })\n }\n }\n return out\n}\n\nasync function fetchHosted(\n id: string,\n opts: ProgramAssetsOptions,\n): Promise<string | null> {\n const dir = opts.cacheDir ?? join(tmpdir(), 'vos-assets')\n const file = join(dir, id)\n if (existsSync(file) && statSync(file).size > 0) return file\n if (!opts.origin || !opts.key) {\n opts.log(\n `note: asset ${id} is hosted and no credential is set, so the render cannot load it (vos login, or keep the file beside the config and name its path)`,\n )\n return null\n }\n const res = await fetch(`${opts.origin}/api/assets/${id}/file`, {\n headers: { authorization: `Bearer ${opts.key}` },\n })\n if (!res.ok) {\n opts.log(\n `note: asset ${id} could not be read (${res.status}), so the render cannot load it`,\n )\n return null\n }\n mkdirSync(dir, { recursive: true })\n writeFileSync(file, new Uint8Array(await res.arrayBuffer()))\n return file\n}\n\n/**\n * Make every declared file reachable for a local page. Null when the config\n * declares none, so a caller can leave the engine's defaults alone.\n */\nexport async function programAssets(\n config: Record<string, unknown>,\n opts: ProgramAssetsOptions,\n): Promise<ProgramAssets | null> {\n const refs = manifestRefs(config)\n if (refs.length === 0) return null\n const files: Record<string, string> = {}\n const served = new Map<string, string>()\n const serve = (file: string): string => {\n const known = served.get(file)\n if (known) return known\n // The path is a digest of the file's location, so two files that share\n // a name never collide; the name stays for the type and for reading.\n const tag = createHash('sha256').update(file).digest('hex').slice(0, 10)\n const path = `${ASSET_ROUTE}${tag}/${basename(file)}`\n files[path] = file\n served.set(file, path)\n return path\n }\n\n const reach = async (ref: string, where: string): Promise<string> => {\n if (isUrl(ref)) return ref\n const hosted = hostedAssetId(ref)\n if (hosted) {\n const file = await fetchHosted(hosted, opts)\n return file ? serve(file) : ref\n }\n const file = isAbsolute(ref) ? ref : resolve(opts.baseDir, ref)\n if (!existsSync(file) || !statSync(file).isFile()) {\n opts.log(\n `note: ${where} names ${ref}, which is not a file beside the config, so the render cannot load it`,\n )\n return ref\n }\n return serve(file)\n }\n\n const assets: Record<string, string | string[]> = {}\n for (const { name, index, ref } of refs) {\n const url = await reach(\n ref,\n index === null ? `assets.${name}` : `assets.${name}[${index}]`,\n )\n if (index === null) assets[name] = url\n else {\n const list = (assets[name] ??= []) as string[]\n list[index] = url\n }\n }\n return { assets, files }\n}\n\n/**\n * The manifest entries that name a file which is not there, for a check\n * that knows where the config lives. Hosted refs and URLs are not files.\n */\nexport function missingManifestFiles(\n config: Record<string, unknown>,\n baseDir: string,\n): string[] {\n const out: string[] = []\n for (const { name, index, ref } of manifestRefs(config)) {\n if (isUrl(ref) || hostedAssetId(ref)) continue\n const file = isAbsolute(ref) ? ref : resolve(baseDir, ref)\n if (existsSync(file) && statSync(file).isFile()) continue\n const where = index === null ? `assets.${name}` : `assets.${name}[${index}]`\n out.push(`${where} names ${ref}, which is not a file beside the config`)\n }\n return out\n}\n","/**\n * What a media file IS, read from its first bytes.\n *\n * A file's NAME and its Content-Type are both CLAIMS, and both drift from the\n * bytes: the in-page recorder remuxes a take to mp4 while keeping the name\n * `recording.webm`, and a hosted asset keeps whatever name it was uploaded\n * under. Believing a claim is how an mp4 gets uploaded as `video/webm`, and\n * every render of that version then dies inside the video element with a\n * format error — one machine and one day away from the mislabelling that\n * caused it.\n *\n * So the rule is one line: THE BYTES DECIDE. A claim is the fallback only for\n * bytes that say nothing (an SVG, a recipe, an unknown blob), which keeps the\n * override safe — a name is corrected only when the file positively contradicts\n * it, never on a guess.\n */\n\nconst OCTET_STREAM = 'application/octet-stream'\n\n/** The one extension↔type table. */\nconst TYPES_BY_EXTENSION: Record<string, string> = {\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n avif: 'image/avif',\n webm: 'video/webm',\n mp4: 'video/mp4',\n mov: 'video/quicktime',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n m4a: 'audio/mp4',\n glb: 'model/gltf-binary',\n}\n\n/** The extension each type is written under, where the table is many-to-one. */\nconst EXTENSION_BY_TYPE: Record<string, string> = {\n 'image/jpeg': '.jpg',\n 'video/mp4': '.mp4',\n 'audio/mp4': '.m4a',\n}\n\n/**\n * Every container a take's own media (footage, mic, cam) can come home in —\n * the name `record` writes first, so a resolver is deterministic when a stale\n * sibling is somehow present.\n */\nexport const TAKE_MEDIA_EXTENSIONS = [\n '.webm',\n '.mp4',\n '.mov',\n '.m4a',\n '.mp3',\n '.ogg',\n '.wav',\n] as const\n\n/** Enough bytes for every signature below (the longest reads 12). */\nexport const MEDIA_HEAD_BYTES = 16\n\n/**\n * ISO base media brands that are NOT plain mp4. Everything else carrying an\n * `ftyp` box (isom, mp42, avc1, iso5, dash, M4V…) is video/mp4.\n */\nconst ISO_BRANDS: Record<string, string> = {\n 'qt ': 'video/quicktime',\n 'M4A ': 'audio/mp4',\n 'M4B ': 'audio/mp4',\n avif: 'image/avif',\n avis: 'image/avif',\n}\n\n/** RIFF forms, read from the four bytes after the size field. */\nconst RIFF_FORMS: Record<string, string> = {\n WEBP: 'image/webp',\n WAVE: 'audio/wav',\n 'AVI ': 'video/x-msvideo',\n}\n\n/** The media type a file's first bytes prove, or null when they prove nothing. */\nexport function sniffMediaType(head: Uint8Array): string | null {\n const ascii = (at: number, len: number): string =>\n head.length >= at + len\n ? String.fromCharCode(...head.subarray(at, at + len))\n : ''\n const startsWith = (...bytes: number[]): boolean =>\n bytes.every((b, i) => head[i] === b)\n\n // EBML: Matroska and its WebM profile. A .mkv sniffs as webm, which is what\n // every consumer here wants — the distinction is a codec constraint, not a\n // container one.\n if (startsWith(0x1a, 0x45, 0xdf, 0xa3)) return 'video/webm'\n if (ascii(4, 4) === 'ftyp') return ISO_BRANDS[ascii(8, 4)] ?? 'video/mp4'\n if (ascii(0, 4) === 'RIFF') return RIFF_FORMS[ascii(8, 4)] ?? null\n if (startsWith(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a))\n return 'image/png'\n if (startsWith(0xff, 0xd8, 0xff)) return 'image/jpeg'\n if (ascii(0, 4) === 'GIF8') return 'image/gif'\n if (ascii(0, 4) === 'OggS') return 'audio/ogg'\n if (ascii(0, 3) === 'ID3') return 'audio/mpeg'\n // Binary glTF: the magic is the four ASCII bytes `glTF`.\n if (ascii(0, 4) === 'glTF') return 'model/gltf-binary'\n return null\n}\n\n/** A Content-Type header reduced to its bare type, or '' when it says nothing. */\nfunction bareType(value: string | null | undefined): string {\n const type = (value ?? '').split(';')[0].trim().toLowerCase()\n // `binary/octet-stream` is a common spelling of the same non-answer.\n return !type || type === OCTET_STREAM || type === 'binary/octet-stream'\n ? ''\n : type\n}\n\n/** The content type a file's NAME claims, by extension. */\nexport function mediaContentType(file: string): string {\n const ext = (/\\.([a-z0-9]+)$/i.exec(file)?.[1] ?? '').toLowerCase()\n return TYPES_BY_EXTENSION[ext] ?? OCTET_STREAM\n}\n\n/** The extension a media type is written under ('' when it has none here). */\nexport function extensionFor(contentType: string): string {\n const type = (contentType ?? '').split(';')[0].trim().toLowerCase()\n if (EXTENSION_BY_TYPE[type]) return EXTENSION_BY_TYPE[type]\n const ext = Object.keys(TYPES_BY_EXTENSION).find(\n (e) => TYPES_BY_EXTENSION[e] === type,\n )\n return ext ? `.${ext}` : ''\n}\n\nexport interface MediaTypeClaims {\n /** The file's first bytes — `MEDIA_HEAD_BYTES` is enough. */\n head?: Uint8Array | null\n /** What a server said it was serving. */\n declared?: string | null\n /** What the name claims. */\n filename?: string | null\n}\n\nexport interface ResolvedMediaType {\n type: string\n /** Which claim won, so a caller can SAY when the bytes overruled a name. */\n from: 'bytes' | 'declared' | 'filename' | 'unknown'\n}\n\n/** The type to believe: the bytes, then the server, then the name. */\nexport function resolveMediaType(claims: MediaTypeClaims): ResolvedMediaType {\n const sniffed = claims.head?.length ? sniffMediaType(claims.head) : null\n if (sniffed) return { type: sniffed, from: 'bytes' }\n const declared = bareType(claims.declared)\n if (declared) return { type: declared, from: 'declared' }\n const named = mediaContentType(claims.filename ?? '')\n if (named !== OCTET_STREAM) return { type: named, from: 'filename' }\n return { type: OCTET_STREAM, from: 'unknown' }\n}\n\n/**\n * The same name wearing the extension its type earns. Unchanged when the two\n * already agree (`.jpeg` is not churned into `.jpg`) or when the type has no\n * extension here, so a correction is always a real one.\n */\nexport function nameForType(filename: string, type: string): string {\n const ext = extensionFor(type)\n if (!ext) return filename\n const current = /\\.[a-z0-9]+$/i.exec(filename)?.[0] ?? ''\n if (current && mediaContentType(`x${current}`) === type) return filename\n return `${filename.slice(0, filename.length - current.length)}${ext}`\n}\n\n/**\n * What to say about a file whose name and bytes disagree, or null when they\n * agree or the bytes prove nothing.\n *\n * Every reader in this CLI now believes the bytes (the take server sniffs, a\n * push declares from them), so a mismatch breaks nothing here — it is still a\n * file lying about itself to every player, editor and person who opens it, and\n * the rename is one move.\n */\nexport function containerMismatch(\n filename: string,\n head: Uint8Array,\n): string | null {\n const actual = sniffMediaType(head)\n if (!actual) return null\n const claimed = mediaContentType(filename)\n if (claimed === actual) return null\n return `${filename} is ${actual}, not the ${claimed} its name claims — rename it to ${nameForType(filename, actual)}`\n}\n","/** Minimal argv parser — mirrors @vosjs/cli's conventions. */\nimport { noteGivenFlag, trackFlags } from '../flagUse'\nimport { UsageError } from '../args'\n\nexport interface ParsedArgs {\n positionals: string[]\n flags: Record<string, string | true>\n /** Values of flags declared repeatable (multiFlags) — accumulated in order. */\n multi: Record<string, string[]>\n}\n\n// ONE usage error for the whole CLI: a verb that reaches a shared helper\n// (outputs.ts) must be reported as a usage error on either side.\nexport { UsageError }\n\nexport function parseArgs(\n argv: string[],\n booleanFlags: ReadonlySet<string>,\n multiFlags: ReadonlySet<string> = new Set(),\n): ParsedArgs {\n const positionals: string[] = []\n const flags: Record<string, string | true> = {}\n const multi: Record<string, string[]> = {}\n const put = (name: string, value: string): void => {\n if (multiFlags.has(name)) (multi[name] ??= []).push(value)\n else flags[name] = value\n }\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n if (arg === '--') {\n positionals.push(...argv.slice(i + 1))\n break\n }\n if (arg.startsWith('--')) {\n const eq = arg.indexOf('=')\n if (eq !== -1) {\n put(arg.slice(2, eq), arg.slice(eq + 1))\n continue\n }\n const name = arg.slice(2)\n if (booleanFlags.has(name)) {\n flags[name] = true\n continue\n }\n if (i + 1 >= argv.length || argv[i + 1].startsWith('--')) {\n throw new UsageError(`--${name} expects a value`)\n }\n put(name, argv[i + 1])\n i++\n continue\n }\n positionals.push(arg)\n }\n for (const name of Object.keys(flags)) noteGivenFlag(name)\n for (const name of Object.keys(multi)) noteGivenFlag(name)\n return { positionals, flags: trackFlags(flags), multi: trackFlags(multi) }\n}\n\nexport function numFlag(\n flags: ParsedArgs['flags'],\n name: string,\n fallback: number,\n): number {\n const v = flags[name]\n if (typeof v !== 'string') return fallback\n const n = Number(v)\n if (!Number.isFinite(n))\n throw new UsageError(`--${name} expects a number, got \"${v}\"`)\n return n\n}\n\n/** String flag value, or undefined when absent (or boolean). */\nexport function strFlag(\n flags: ParsedArgs['flags'],\n name: string,\n): string | undefined {\n const v = flags[name]\n return typeof v === 'string' ? v : undefined\n}\n\nexport function hasFlag(flags: ParsedArgs['flags'], name: string): boolean {\n return Object.prototype.hasOwnProperty.call(flags, name)\n}\n","/**\n * vos.so platform client — THE one HTTP surface of the vos CLI.\n * Every platform verb (fetch/push/pull for programs AND takes, login) goes\n * through here: one origin resolution, one credential ladder, one sync-state\n * file, one 409 conflict shape.\n *\n * Contract notes (mirrored at https://vos.so/llms.txt):\n * - public/unlisted programs read with no auth; writes ride a bearer key\n * - credentials resolve --key → VOS_API_KEY → ~/.config/vos/credentials\n * (first line; `vos login` writes it) and are NEVER printed — not in\n * logs, not in errors, not in NDJSON events\n * - pushed configs travel as the MIGRATED object with params/presets\n * untouched; the platform validates and compiles server-side\n */\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { UsageError } from './args'\nimport { detectHostAgent } from './hostAgent'\nimport type { Env } from './hostAgent'\n\n/**\n * Origin resolution: --origin (or legacy --api) → VOS_ORIGIN → legacy\n * VOS_API_BASE (which historically carried a trailing /api) → https://vos.so.\n * Every shape converges on a bare origin; paths below carry the /api prefix.\n */\nexport function platformOrigin(\n flags: { origin?: string; api?: string } = {},\n): string {\n const legacyEnv = process.env.VOS_API_BASE?.trim()\n const raw =\n flags.origin ??\n flags.api ??\n process.env.VOS_ORIGIN?.trim() ??\n legacyEnv ??\n 'https://vos.so'\n if (!flags.origin && !flags.api && !process.env.VOS_ORIGIN && legacyEnv) {\n process.stderr.write(\n 'note: VOS_API_BASE is deprecated — set VOS_ORIGIN (an origin, no /api suffix)\\n',\n )\n }\n return raw.replace(/\\/+$/, '').replace(/\\/api$/, '')\n}\n\n/**\n * Accepts a bare vos id, a watch URL (vos.so/vos/{id}), an embed URL\n * (/embed/vos/{id}), or a studio/stage URL (?vos={id}).\n */\nexport function parseVosId(input: string): string {\n if (!/^https?:\\/\\//.test(input)) {\n if (/^[A-Za-z0-9_-]+$/.test(input)) return input\n throw new UsageError(`\"${input}\" is not a vos id or URL`)\n }\n let url: URL\n try {\n url = new URL(input)\n } catch {\n throw new UsageError(`\"${input}\" is not a valid URL`)\n }\n const query = url.searchParams.get('vos')\n if (query) return query\n const path = url.pathname.match(/\\/vos\\/([A-Za-z0-9_-]+)/)\n if (path) return path[1]\n throw new UsageError(\n `could not find a vos id in ${input} — expected /vos/{id} or ?vos={id}`,\n )\n}\n\nexport function deriveSlug(title: string): string {\n return (\n title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 50)\n .replace(/-+$/, '') || 'remix'\n )\n}\n\nexport const CREDENTIALS_PATH = join(homedir(), '.config', 'vos', 'credentials')\n\n/**\n * The full documented ladder: explicit --key → VOS_API_KEY → the first line\n * of ~/.config/vos/credentials. A vos_rg_ remix grant is just a key here.\n * Returns null when nothing resolves — callers decide whether auth is needed.\n */\nexport function resolveCredential(explicit?: string): string | null {\n const flag = explicit?.trim()\n if (flag) return flag\n const env = process.env.VOS_API_KEY?.trim()\n if (env) return env\n try {\n const first = readFileSync(CREDENTIALS_PATH, 'utf8').split('\\n')[0].trim()\n return first || null\n } catch {\n return null\n }\n}\n\nexport function requireCredential(explicit?: string): string {\n const key = resolveCredential(explicit)\n if (!key) {\n throw new Error(\n 'no credential found — pass --key, set VOS_API_KEY, or run `vos login` ' +\n '(mint a content key at https://vos.so/app/api; a vos_rg_ remix grant works too)',\n )\n }\n return key\n}\n\n/** `vos login` writes here: 0700 dir, 0600 file, first line is the key. */\nexport function writeCredential(key: string): string {\n mkdirSync(join(homedir(), '.config', 'vos'), { recursive: true, mode: 0o700 })\n writeFileSync(CREDENTIALS_PATH, `${key.trim()}\\n`, { mode: 0o600 })\n return CREDENTIALS_PATH\n}\n\n/**\n * The self-reported client string stamped onto pushed versions (User-Agent\n * grammar). Display-only on the platform — attribution trust\n * stays with the credential. The ladder: an explicit VOS_CLIENT (e.g.\n * VOS_CLIENT=claude-code/2.1), else the coding agent this CLI was spawned by\n * (`claude-code vos-cli`, read from the environment by `detectHostAgent`),\n * else the transport alone, so history rows name the tool that pushed.\n */\nexport function clientId(\n env: Env = process.env,\n exists?: (path: string) => boolean,\n): string {\n const explicit = env.VOS_CLIENT?.trim()\n if (explicit && explicit.length <= 60 && !/[\\r\\n]/.test(explicit))\n return explicit\n const host = detectHostAgent(env, exists)\n return host ? `${host} vos-cli` : 'vos-cli'\n}\n\nexport interface ApiResult {\n status: number\n body: Record<string, unknown>\n}\n\nexport async function apiJson(\n origin: string,\n path: string,\n init: {\n method?: string\n key?: string | null\n body?: unknown\n /** Raw request body (uploads) — used verbatim, with `headers`. */\n raw?: Uint8Array\n headers?: Record<string, string>\n } = {},\n): Promise<ApiResult> {\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...init.headers,\n }\n if (init.key) headers.authorization = `Bearer ${init.key}`\n if (init.body !== undefined) headers['content-type'] = 'application/json'\n const reqBody: BodyInit | undefined =\n init.raw !== undefined\n ? (init.raw as unknown as BodyInit)\n : init.body === undefined\n ? undefined\n : JSON.stringify(init.body)\n const res = await fetch(`${origin}${path}`, {\n method: init.method ?? 'GET',\n headers,\n body: reqBody,\n })\n let body: Record<string, unknown> = {}\n try {\n body = (await res.json()) as Record<string, unknown>\n } catch {\n // non-JSON error bodies stay {}\n }\n return { status: res.status, body }\n}\n\n/** Human-readable error line for a failed platform response. Never echoes credentials. */\nexport function apiError(what: string, r: ApiResult): string {\n const detail =\n typeof r.body.error === 'string'\n ? r.body.error\n : r.body.details !== undefined\n ? JSON.stringify(r.body.details)\n : ''\n const hint =\n r.status === 401\n ? ' (run `vos login`, or set VOS_API_KEY — mint a key at https://vos.so/app/api)'\n : ''\n return `${what} → ${r.status}${detail ? `: ${detail}` : ''}${hint}`\n}\n\n// ---------------------------------------------------------------------------\n// Sync state — ONE file (vos.json) for both artifact kinds, legacy-read\n// compatible with the two files it replaces: push.json (take dirs) and\n// meta.json (fetched program dirs).\n// ---------------------------------------------------------------------------\n\nexport const SYNC_STATE_NAME = 'vos.json'\n\nexport interface SyncState {\n vosId: string\n /** The hosted version this directory is based on — the next push's base. */\n versionId: string | null\n pushedAt?: string\n title?: string\n slug?: string\n remixOfId?: string\n}\n\nfunction readJsonFile(file: string): Record<string, unknown> | null {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\n/**\n * vos.json → legacy push.json ({vosId, versionId}) → legacy meta.json\n * ({id, currentVersionId} — guarded on a string `id` so a take dir's\n * RecordingMeta meta.json is never misread as sync state).\n */\nexport function readSyncState(dir: string): SyncState | null {\n const own = readJsonFile(join(dir, SYNC_STATE_NAME))\n if (own && typeof own.vosId === 'string') {\n return {\n vosId: own.vosId,\n versionId: typeof own.versionId === 'string' ? own.versionId : null,\n ...(typeof own.pushedAt === 'string' ? { pushedAt: own.pushedAt } : {}),\n ...(typeof own.title === 'string' ? { title: own.title } : {}),\n ...(typeof own.slug === 'string' ? { slug: own.slug } : {}),\n ...(typeof own.remixOfId === 'string'\n ? { remixOfId: own.remixOfId }\n : {}),\n }\n }\n const push = readJsonFile(join(dir, 'push.json'))\n if (push && typeof push.vosId === 'string') {\n return {\n vosId: push.vosId,\n versionId: typeof push.versionId === 'string' ? push.versionId : null,\n ...(typeof push.pushedAt === 'string' ? { pushedAt: push.pushedAt } : {}),\n }\n }\n const meta = readJsonFile(join(dir, 'meta.json'))\n if (meta && typeof meta.id === 'string') {\n return {\n vosId: meta.id,\n versionId:\n typeof meta.currentVersionId === 'string'\n ? meta.currentVersionId\n : null,\n ...(typeof meta.title === 'string' ? { title: meta.title } : {}),\n ...(typeof meta.slug === 'string' ? { slug: meta.slug } : {}),\n ...(typeof meta.remixOfId === 'string'\n ? { remixOfId: meta.remixOfId }\n : {}),\n }\n }\n return null\n}\n\n/** Merge-writes vos.json; the legacy files are left alone (read-only compat). */\nexport function writeSyncState(\n dir: string,\n patch: Partial<SyncState> & { vosId: string },\n): SyncState {\n const merged = {\n ...(readSyncState(dir) ?? { versionId: null }),\n ...patch,\n } as SyncState\n writeFileSync(\n join(dir, SYNC_STATE_NAME),\n `${JSON.stringify(merged, null, 2)}\\n`,\n )\n return merged\n}\n\n// ---------------------------------------------------------------------------\n// The /changes payload (shape mirrored from the platform)\n// ---------------------------------------------------------------------------\n\nexport interface VersionChange {\n versionId?: string\n versionNumber?: number\n origin?: string\n label?: string | null\n note?: string | null\n summary?: string\n}\n\n/** Human lines for a changes walk — origin/label attributed, note indented. */\nexport function formatChanges(changes: readonly VersionChange[]): string[] {\n const lines: string[] = []\n for (const ch of changes) {\n const label = ch.label ? ` · ${ch.label}` : ''\n const who = ch.origin === 'studio' ? 'human' : (ch.origin ?? 'unknown')\n lines.push(\n `v${String(ch.versionNumber ?? '?')} (${who}${label}): ${ch.summary ?? ''}`,\n )\n if (ch.note) lines.push(` note: ${ch.note}`)\n }\n return lines\n}\n\n/**\n * The warnings a write came back with (a knob or Look the platform dropped),\n * one line each. A push that saved is not a push that kept everything.\n */\nexport function serverWarnings(\n body: Record<string, unknown>,\n log: (line: string) => void,\n): void {\n const warnings = Array.isArray(body.warnings) ? body.warnings : []\n for (const w of warnings) if (typeof w === 'string') log(`warning ${w}`)\n}\n","import { existsSync } from 'node:fs'\n\n/**\n * Which coding agent is driving this CLI, read from the environment the agent\n * spawned it in. Used ONLY to name the tool on a pushed version (`clientId`),\n * which the platform treats as display-only: attribution trust stays with the\n * credential, so a wrong or spoofed answer changes a label and nothing else.\n *\n * The signals follow `@vercel/detect-agent` (Apache-2.0), kept as a small\n * synchronous table because every push reads it: the cross-tool `AI_AGENT`\n * convention first, then each host's own marker. Returns a lowercase slug,\n * or null when nothing says an agent is here.\n */\nexport type Env = Record<string, string | undefined>\n\nconst SLUG = /^[a-z0-9][a-z0-9.-]{0,39}$/\n\n/** The markers each host sets in the shells it runs, in precedence order. */\nconst MARKERS: Array<[slug: string, test: (env: Env) => boolean]> = [\n ['cursor', (e) => !!e.CURSOR_TRACE_ID],\n [\n 'cursor-cli',\n (e) => !!e.CURSOR_AGENT || e.CURSOR_EXTENSION_HOST_ROLE === 'agent-exec',\n ],\n ['gemini-cli', (e) => !!e.GEMINI_CLI],\n ['codex', (e) => !!(e.CODEX_SANDBOX || e.CODEX_CI || e.CODEX_THREAD_ID)],\n ['antigravity', (e) => !!e.ANTIGRAVITY_AGENT],\n ['augment-cli', (e) => !!e.AUGMENT_AGENT],\n ['opencode', (e) => !!e.OPENCODE_CLIENT],\n [\n 'claude-code',\n (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE) && !e.CLAUDE_CODE_IS_COWORK,\n ],\n ['claude-cowork', (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE)],\n ['replit', (e) => !!e.REPL_ID],\n [\n 'github-copilot',\n (e) => !!(e.COPILOT_MODEL || e.COPILOT_ALLOW_ALL || e.COPILOT_GITHUB_TOKEN),\n ],\n]\n\nconst DEVIN_LOCAL_PATH = '/opt/.devin'\n\n/**\n * `AI_AGENT` is free text by convention (`claude-code_2-1-284_agent`,\n * `github-copilot`, `v0`): the tool's name is its leading run of\n * `[a-z0-9.-]`, lowercased, so a version or a role suffix never reaches the\n * label and nothing but a short slug ever leaves the machine.\n */\nexport function slugFromAiAgent(value: string): string | null {\n const head = value.trim().toLowerCase().split('_')[0] ?? ''\n if (head === 'github-copilot-cli') return 'github-copilot'\n return SLUG.test(head) ? head : null\n}\n\nexport function detectHostAgent(\n env: Env = process.env,\n exists: (path: string) => boolean = existsSync,\n): string | null {\n if (env.AI_AGENT) {\n const slug = slugFromAiAgent(env.AI_AGENT)\n if (slug) return slug\n }\n for (const [slug, test] of MARKERS) if (test(env)) return slug\n try {\n if (exists(DEVIN_LOCAL_PATH)) return 'devin'\n } catch {\n // An unreadable path is not an agent.\n }\n return null\n}\n"],"mappings":"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","names":["file","parseArgs","numFlag","mkdirSync","writeFileSync","join","existsSync","join","mkdirSync","writeFileSync"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loadConfig.ts","../src/browser.ts","../src/render.ts","../src/outputs.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport {\n CURRENT_CONFIG_VERSION,\n migrateConfig,\n vosConfigJsonSchema,\n} from '@vosjs/core'\nimport { UsageError } from './args'\n\nexport interface LoadedConfig {\n config: Record<string, unknown>\n warnings: string[]\n}\n\n/**\n * What a directory holds, by one deterministic sniff and never a flag:\n * a TAKE carries a recording document (`doc.json` with `source`); a PROGRAM\n * directory carries `config.json`, optionally beside a program document\n * (`doc.json` without `source`: the shared layers, the tween overlay, the\n * program's own length, with `program.config` omitted on disk). An unparsable\n * doc.json counts as a take so the take path reports the real error.\n */\nexport type DirectoryKind = 'take' | 'program' | 'none'\n\nexport function directoryKind(target: string): DirectoryKind {\n try {\n if (!statSync(target).isDirectory()) return 'none'\n } catch {\n return 'none'\n }\n const docPath = join(target, 'doc.json')\n if (existsSync(docPath)) {\n try {\n const doc: unknown = JSON.parse(readFileSync(docPath, 'utf8'))\n if (typeof doc === 'object' && doc !== null && 'source' in doc)\n return 'take'\n } catch {\n return 'take'\n }\n }\n return existsSync(join(target, 'config.json')) ? 'program' : 'none'\n}\n\n/** A directory holding a PROGRAM document (`doc.json` without `source`). */\nfunction hasProgramDoc(dir: string): boolean {\n const docPath = join(dir, 'doc.json')\n if (!existsSync(docPath)) return false\n try {\n const doc: unknown = JSON.parse(readFileSync(docPath, 'utf8'))\n return typeof doc === 'object' && doc !== null && !('source' in doc)\n } catch {\n return false\n }\n}\n\n/**\n * A program directory's config: `config.json`, composed with the program\n * document beside it when there is one (the layers ride as the studio stack\n * entry, the tween overlay is baked into `createTimeline`), so a render of\n * the directory is a render of what the studio and vos.so play.\n */\nexport async function loadProgramDirectory(\n dir: string,\n warnings: string[],\n): Promise<unknown> {\n const config: unknown = JSON.parse(\n await readFile(join(dir, 'config.json'), 'utf8'),\n )\n const docPath = join(dir, 'doc.json')\n if (!existsSync(docPath)) return config\n const doc: unknown = JSON.parse(await readFile(docPath, 'utf8'))\n if (typeof doc !== 'object' || doc === null || 'source' in doc) return config\n if (typeof config !== 'object' || config === null) {\n throw new UsageError(`${dir}/config.json does not contain a JSON object`)\n }\n const raw = doc as Record<string, unknown>\n const program =\n raw.program && typeof raw.program === 'object'\n ? (raw.program as Record<string, unknown>)\n : {}\n const { lowerProgramDoc } = await import('@vosjs/studio-core')\n const composed = lowerProgramDoc(\n { ...raw, program: { ...program, config } } as never,\n { bake: true },\n ).config\n warnings.push(\n 'composed config.json with doc.json (a program document: its layers and tween edits ride the render)',\n )\n return composed\n}\n\n/**\n * The text of a config source: an http(s) URL or a file. A directory that is\n * neither a take nor a program directory is refused in words, since the raw\n * read would report a bare EISDIR.\n */\nexport async function readSourceText(source: string): Promise<string> {\n if (/^https?:\\/\\//.test(source)) {\n const res = await fetch(source)\n if (!res.ok) throw new Error(`fetch ${source} → ${res.status}`)\n return await res.text()\n }\n let isDir = false\n try {\n isDir = statSync(source).isDirectory()\n } catch {\n /* a missing path reads below and reports itself */\n }\n if (isDir) {\n throw new UsageError(\n `${source} is a directory with no config.json and no doc.json — pass a config file, a URL, a program directory or a take`,\n )\n }\n return await readFile(source, 'utf8')\n}\n\n/**\n * Load a VosConfigJson from a file path, an http(s) URL, or a program\n * directory (config.json, composed with a program document when one sits\n * beside it); unwrap API `{ config }` envelopes, migrate old versions, and\n * validate against the schema. A take directory is refused here in words:\n * takes render through the take pipeline (`vos render <take>`).\n */\nexport async function loadVosConfig(source: string): Promise<LoadedConfig> {\n const warnings: string[] = []\n let parsed: unknown\n const kind = /^https?:\\/\\//.test(source) ? 'none' : directoryKind(source)\n if (kind === 'take') {\n throw new UsageError(\n `${source} is a take (its doc.json is a recording document). Render it through the take pipeline: vos render ${source} [out]; vos frames ${source} for stills.`,\n )\n }\n if (kind === 'program') {\n parsed = await loadProgramDirectory(source, warnings)\n return finish(source, parsed, warnings)\n }\n // A program's config.json named directly, with its document beside it:\n // read the directory, or the document's sound and layers silently drop\n // out of a render that looks complete.\n if (\n !/^https?:\\/\\//.test(source) &&\n basename(source) === 'config.json' &&\n hasProgramDoc(dirname(source))\n ) {\n parsed = await loadProgramDirectory(dirname(source), warnings)\n return finish(source, parsed, warnings)\n }\n const raw = await readSourceText(source)\n\n try {\n parsed = JSON.parse(raw)\n } catch (e) {\n throw new UsageError(`${source} is not valid JSON: ${(e as Error).message}`)\n }\n return finish(source, parsed, warnings)\n}\n\nfunction finish(\n source: string,\n parsed: unknown,\n warnings: string[],\n): LoadedConfig {\n if (typeof parsed !== 'object' || parsed === null) {\n throw new UsageError(`${source} does not contain a JSON object`)\n }\n\n // API endpoints wrap the config: { config: {...} }.\n let obj = parsed as Record<string, unknown>\n if (\n typeof obj.config === 'object' &&\n obj.config !== null &&\n !('createTimeline' in obj)\n ) {\n obj = obj.config as Record<string, unknown>\n warnings.push('unwrapped { config } envelope')\n }\n\n const version = obj.version\n const migrated = migrateConfig(obj)\n if (version === undefined) {\n // Plays fine (the stamp happens above), but a storer refuses it.\n warnings.push(\n `config declares no \"version\". Add \"version\": ${CURRENT_CONFIG_VERSION} before pushing it.`,\n )\n } else if (version !== CURRENT_CONFIG_VERSION) {\n warnings.push(\n `migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`,\n )\n }\n\n const check = vosConfigJsonSchema.safeParse(migrated)\n if (!check.success) {\n const issues = check.error.issues\n .slice(0, 5)\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new UsageError(`invalid vos config:\\n${issues}`)\n }\n\n return { config: migrated, warnings }\n}\n\n/** Best-effort duration from the config (seconds). */\nexport function configDuration(\n config: Record<string, unknown>,\n): number | undefined {\n const d = config.duration\n return typeof d === 'number' && Number.isFinite(d) && d > 0 ? d : undefined\n}\n","import { chromium } from 'playwright'\nimport type { Browser } from 'playwright'\n\nexport class BrowserUnavailableError extends Error {\n constructor(cause: string) {\n super(\n `Could not launch a Chromium-family browser (${cause}).\\n` +\n `Fixes, in order of preference:\\n` +\n ` 1. Install Google Chrome (used automatically), or\\n` +\n ` 2. npx playwright install chromium, or\\n` +\n ` 3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`,\n )\n }\n}\n\n/**\n * Launch headless Chromium: explicit VOS_BROWSER_PATH → system Chrome\n * (no download needed) → Playwright's bundled Chromium.\n *\n * `args` are extra Chromium switches (`--browser-arg` on the verbs that\n * record). Some product surfaces cannot be reached without one: a recorder\n * needs a fake capture device to get past a permission prompt, an extension\n * page needs the extension loaded. Passed through verbatim.\n */\nexport async function launchBrowser(args: string[] = []): Promise<Browser> {\n const opts = args.length ? { args } : {}\n const explicit = process.env.VOS_BROWSER_PATH\n if (explicit) {\n return chromium.launch({ ...opts, executablePath: explicit }).catch((e) => {\n throw new BrowserUnavailableError(\n `VOS_BROWSER_PATH failed: ${(e as Error).message}`,\n )\n })\n }\n try {\n return await chromium.launch({ ...opts, channel: 'chrome' })\n } catch {\n try {\n return await chromium.launch(opts)\n } catch (e) {\n throw new BrowserUnavailableError(\n (e as Error).message.split('\\n')[0] ?? 'unknown',\n )\n }\n }\n}\n","import { compileVosConfig, programSize } from '@vosjs/core'\nimport { generateRenderTemplate } from '@vosjs/core/runtime'\nimport { elementsBundleCode } from '@vosjs/elements/bundle'\nimport { tweenRuntimeCode } from '@vosjs/tween/bundle'\nimport type { Browser, Page } from 'playwright'\nimport { ASSET_ROUTE } from './programAssets'\nimport type { ProgramAssets } from './programAssets'\n\n/** Fake secure origin the render page is served from (WebCodecs needs one). */\nconst RENDER_ORIGIN = 'https://vos-cli.render'\n\nexport interface RenderCommonOptions {\n config: Record<string, unknown>\n width: number\n height: number\n /** Phase callback for progress reporting. */\n onPhase?: (phase: string) => void\n /**\n * The program's declared files made reachable for this page\n * (`programAssets`): handed to the engine as `ctx.assets`, with the local\n * ones served from disk on the page's own origin.\n */\n assets?: ProgramAssets | null\n}\n\nexport interface RenderVideoOptions extends RenderCommonOptions {\n fps: number\n /** Output duration in seconds. */\n duration: number\n format: 'webm' | 'mp4'\n /** The page's audio producer (a program document's sound); absent = silent. */\n audioProducerCode?: string\n}\n\nexport interface RenderStillOptions extends RenderCommonOptions {\n /** Seek time for the captured frame, seconds. */\n time: number\n}\n\nexport interface RenderResult {\n bytes: Uint8Array\n mimeType: string\n /** Uncaught exceptions the page threw while it rendered (it may still have\n * written a frame, so a caller says them instead of hiding them). */\n pageErrors: string[]\n /** The engine's own '[vos] …' warnings: causes a picture cannot show. */\n engineNotes: string[]\n}\n\ninterface RenderComplete {\n success: boolean\n data?: string\n error?: string\n}\n\n/** What a captured still shows, measured from its pixels. */\nexport interface StillPicture {\n /** Every pixel has alpha 0: the frame is empty, whatever the preview showed. */\n transparent: boolean\n /** Every pixel is the same colour. */\n flat: boolean\n}\n\n/**\n * The sentences a still earns before anyone looks at it (pure). A fully\n * transparent frame is the one a custom blend or a cleared alpha leaves, and\n * it reads as black in most viewers while the live preview draws over its\n * page: say so, because nothing else will.\n */\nexport function stillWarnings(\n picture: StillPicture,\n pageErrors: readonly string[],\n engineNotes: readonly string[] = [],\n): string[] {\n const out: string[] = []\n if (picture.transparent) {\n out.push(\n 'the still is fully transparent (every pixel has alpha 0), though the preview may draw it: something wrote alpha 0, most often a custom material blending (blendDst / blendEquation) or a scene with no background. Give the scene a background or keep the destination alpha in the blend.',\n )\n } else if (picture.flat) {\n out.push(\n 'the still is a single flat colour: nothing drew at this time, or everything is hidden; check the time and the program for errors.',\n )\n }\n // The engine's own notes ('[vos] …') name a cause the picture cannot.\n for (const n of new Set(engineNotes)) out.push(n.replace(/^\\[vos\\]\\s*/, ''))\n for (const e of pageErrors.slice(0, 3)) out.push(`the page threw: ${e}`)\n if (pageErrors.length > 3)\n out.push(`the page threw ${pageErrors.length - 3} more errors`)\n return out\n}\n\nfunction compile(\n config: Record<string, unknown>,\n /** Bake these URLs as the program's `ctx.assets` defaults (a preview). */\n assets?: ProgramAssets | null,\n): string {\n if (!assets) return compileVosConfig(config as never, { tweenEngine: 'vos' })\n // The compiler asks per ref; the resolved table is per name, so walk the\n // manifest in the same order to answer.\n const byRef = new Map<string, string>()\n const manifest = (config.assets ?? {}) as Record<\n string,\n { ref: string | string[] }\n >\n for (const [name, decl] of Object.entries(manifest)) {\n const got = assets.assets[name]\n const refs = Array.isArray(decl.ref) ? decl.ref : [decl.ref]\n const urls = Array.isArray(got) ? got : [got]\n refs.forEach((ref, i) => {\n if (typeof urls[i] === 'string') byRef.set(`${name}\\u0000${ref}`, urls[i])\n })\n }\n return compileVosConfig(config as never, {\n tweenEngine: 'vos',\n resolveAssetRef: (ref, name) => byRef.get(`${name}\\u0000${ref}`) ?? ref,\n })\n}\n\n/** A page on the render origin reads a served file by its full URL. */\nfunction captureAssets(assets: ProgramAssets | null | undefined): {\n assets?: Record<string, string | string[]>\n} {\n if (!assets) return {}\n const absolute = (url: string) =>\n url.startsWith(ASSET_ROUTE) ? `${RENDER_ORIGIN}${url}` : url\n const out: Record<string, string | string[]> = {}\n for (const [name, url] of Object.entries(assets.assets)) {\n out[name] = Array.isArray(url) ? url.map(absolute) : absolute(url)\n }\n return { assets: out }\n}\n\nasync function runCapturePage(\n browser: Browser,\n html: string,\n opts: {\n width: number\n height: number\n timeoutMs: number\n /** Served path → file on disk (a program's declared files). */\n files?: Record<string, string>\n },\n): Promise<RenderComplete & { pageErrors: string[]; engineNotes: string[] }> {\n const context = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n })\n const page: Page = await context.newPage()\n const errors: string[] = []\n const pageErrors: string[] = []\n const engineNotes: string[] = []\n page.on('console', (m) => {\n if (m.type() === 'error') errors.push(m.text())\n else if (m.type() === 'warning' && m.text().startsWith('[vos] '))\n engineNotes.push(m.text())\n })\n page.on('pageerror', (e) => {\n pageErrors.push(String(e.message || e))\n })\n try {\n await page.route(`${RENDER_ORIGIN}/**`, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html', body: html }),\n )\n // The program's own files, on the page's origin. Registered after the\n // page route on purpose: the newest matching route answers first.\n const files = opts.files ?? {}\n if (Object.keys(files).length > 0) {\n await page.route(`${RENDER_ORIGIN}${ASSET_ROUTE}**`, (route) => {\n const file = files[new URL(route.request().url()).pathname]\n return file\n ? route.fulfill({ status: 200, path: file })\n : route.fulfill({ status: 404, body: 'no such declared file' })\n })\n }\n await page.goto(`${RENDER_ORIGIN}/render`, {\n waitUntil: 'domcontentloaded',\n })\n const start = Date.now()\n for (;;) {\n const done = (await page.evaluate(\n 'window.__renderComplete ?? null',\n )) as RenderComplete | null\n if (done) return { ...done, pageErrors, engineNotes }\n if (Date.now() - start > opts.timeoutMs) {\n const all = [...pageErrors, ...errors]\n return {\n success: false,\n error: `render timed out after ${Math.round(opts.timeoutMs / 1000)}s${\n all.length ? ` (page errors: ${all.slice(0, 3).join(' | ')})` : ''\n }`,\n pageErrors,\n engineNotes,\n }\n }\n await new Promise((r) => setTimeout(r, 400))\n }\n } finally {\n await context.close()\n }\n}\n\nfunction decodeDataUrl(\n dataUrl: string,\n pageErrors: string[] = [],\n engineNotes: string[] = [],\n): RenderResult {\n const m = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl)\n if (!m) throw new Error('capture page returned an unexpected payload')\n return {\n bytes: Uint8Array.from(Buffer.from(m[2], 'base64')),\n mimeType: m[1],\n pageErrors,\n engineNotes,\n }\n}\n\n/** Render a vos config to a video (WebM/MP4) in a headless browser. */\nexport async function renderVideo(\n browser: Browser,\n opts: RenderVideoOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-video',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: opts.duration,\n fps: opts.fps,\n format: opts.format,\n ...(opts.audioProducerCode\n ? { audioProducerCode: opts.audioProducerCode }\n : {}),\n ...captureAssets(opts.assets),\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n // Generous ceiling: startup + CDN module fetch + ~4× realtime per frame batch.\n const timeoutMs = 90_000 + opts.duration * opts.fps * 400\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs,\n files: opts.assets?.files,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'render failed')\n return decodeDataUrl(done.data, done.pageErrors, done.engineNotes)\n}\n\n/** Render a single frame of a vos config to an image in a headless browser. */\nexport async function renderStill(\n browser: Browser,\n opts: RenderStillOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-thumbnail',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: Math.max(1, opts.time + 1),\n fps: 30,\n thumbnailTime: opts.time,\n ...captureAssets(opts.assets),\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs: 120_000,\n files: opts.assets?.files,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'still render failed')\n return decodeDataUrl(done.data, done.pageErrors, done.engineNotes)\n}\n\n/**\n * Measure a captured still's pixels in a browser page (the canvas is the\n * decoder): whether every pixel is transparent, and whether it is one flat\n * colour. Sampled on a grid, so a large still stays cheap.\n */\nexport async function inspectStill(\n browser: Browser,\n webp: Uint8Array,\n): Promise<StillPicture> {\n const page = await browser.newPage()\n try {\n const src = `data:image/webp;base64,${Buffer.from(webp).toString('base64')}`\n return (await page.evaluate(\n `(async () => {\n const img = new Image()\n img.src = ${JSON.stringify(src)}\n await img.decode()\n const w = img.naturalWidth, h = img.naturalHeight\n const c = document.createElement('canvas')\n c.width = w\n c.height = h\n const g = c.getContext('2d', { willReadFrequently: true })\n g.drawImage(img, 0, 0)\n const d = g.getImageData(0, 0, w, h).data\n const step = Math.max(1, Math.floor(Math.sqrt((w * h) / 40000)))\n let transparent = true, flat = true, first = null\n for (let y = 0; y < h; y += step) {\n for (let x = 0; x < w; x += step) {\n const i = (y * w + x) * 4\n if (d[i + 3] !== 0) transparent = false\n const px = d[i] + ',' + d[i + 1] + ',' + d[i + 2] + ',' + d[i + 3]\n if (first === null) first = px\n else if (px !== first) flat = false\n }\n }\n return { transparent, flat }\n })()`,\n )) as StillPicture\n } finally {\n await page.close()\n }\n}\n\n/**\n * Re-encode a captured WebP still as PNG or JPEG in a browser page, so a\n * still named `.png` holds PNG bytes. The canvas is the encoder; nothing\n * else is installed for it.\n */\nexport async function reencodeStill(\n browser: Browser,\n webp: Uint8Array,\n format: 'png' | 'jpeg',\n): Promise<Uint8Array> {\n const page = await browser.newPage()\n try {\n const src = `data:image/webp;base64,${Buffer.from(webp).toString('base64')}`\n // A string body: a serialized function would carry the bundler's\n // helpers into a page that has none.\n const dataUrl = (await page.evaluate(\n `(async () => {\n const img = new Image()\n img.src = ${JSON.stringify(src)}\n await img.decode()\n const c = document.createElement('canvas')\n c.width = img.naturalWidth\n c.height = img.naturalHeight\n const g = c.getContext('2d')\n ${format === 'jpeg' ? \"g.fillStyle = '#fff'; g.fillRect(0, 0, c.width, c.height)\" : ''}\n g.drawImage(img, 0, 0)\n return c.toDataURL(${JSON.stringify(`image/${format}`)}, 0.92)\n })()`,\n )) as string\n return decodeDataUrl(dataUrl).bytes\n } finally {\n await page.close()\n }\n}\n\nexport interface PreviewPages {\n /** Host page: iframes the player and drives it over the bridge. */\n hostHtml: string\n /** The engine's playback-mode player (waits for a LOAD postMessage). */\n playerHtml: string\n}\n\n/**\n * Pages for `vos preview`. The playback template is the iframe HALF of the\n * player bridge — it renders nothing until a host posts `LOAD`, so the CLI\n * serves a minimal host page that sends `LOAD { code, autoplay }` and shows\n * a transport line (time / duration, click to play-pause).\n */\nexport function previewPages(\n config: Record<string, unknown>,\n /** Declared files, served by the preview server under `ASSET_ROUTE`. */\n assets?: ProgramAssets | null,\n): PreviewPages {\n const code = compile(config, assets)\n const playerHtml = generateRenderTemplate(code, {\n mode: 'playback',\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n const codeJson = JSON.stringify(code).replace(/<\\//g, '<\\\\/')\n // A program that declares its size plays letterboxed at that aspect, the\n // frame it was designed for; one that declares none fills the window.\n const size = programSize(config)\n const frameCss = size\n ? `iframe{position:absolute;inset:0;margin:auto;border:0;width:min(100vw,calc(100vh * ${size.width} / ${size.height}));height:min(100vh,calc(100vw * ${size.height} / ${size.width}))}`\n : 'iframe{position:absolute;inset:0;width:100%;height:100%;border:0}'\n const hostHtml = `<!doctype html><html><head><meta charset=\"utf-8\"><title>vos preview</title>\n<style>\n html,body{margin:0;height:100%;background:#000;overflow:hidden}\n ${frameCss}\n #cover{position:fixed;inset:0;cursor:pointer}\n #hud{position:fixed;left:12px;bottom:10px;color:#fff;opacity:.75;font:12px/1.4 ui-monospace,monospace;\n background:rgba(0,0,0,.45);padding:4px 8px;border-radius:6px;pointer-events:none}\n</style></head><body>\n<iframe id=\"p\" src=\"/player\" allow=\"autoplay\"></iframe>\n<div id=\"cover\"></div>\n<div id=\"hud\">loading…</div>\n<script>\n const code = ${codeJson}\n const frame = document.getElementById('p')\n const hud = document.getElementById('hud')\n let ready = false\n let playing = true\n const post = (msg) => frame.contentWindow && frame.contentWindow.postMessage(msg, '*')\n const load = () => post({ type: 'LOAD', code, autoplay: true })\n window.addEventListener('message', (e) => {\n const msg = e.data || {}\n if (msg.type === 'BRIDGE_READY') load()\n if (msg.type === 'READY') { ready = true; hud.textContent = '0.0 / ' + msg.duration.toFixed(1) + 's — click to pause' }\n if (msg.type === 'UPDATE') hud.textContent = msg.time.toFixed(1) + ' / ' + msg.duration.toFixed(1) + 's — click to ' + (playing ? 'pause' : 'play')\n if (msg.type === 'ERROR') hud.textContent = 'error: ' + msg.error\n })\n // Belt and braces: if BRIDGE_READY raced the host, retry LOAD until READY.\n let tries = 0\n const iv = setInterval(() => {\n if (ready || tries++ > 20) return clearInterval(iv)\n load()\n }, 400)\n // The iframe swallows clicks — capture them on an overlay above it.\n document.getElementById('cover').addEventListener('click', () => {\n if (!ready) return\n playing = !playing\n post({ type: playing ? 'PLAY' : 'PAUSE' })\n hud.textContent = hud.textContent.replace(/click to (pause|play)/, 'click to ' + (playing ? 'pause' : 'play'))\n })\n</script></body></html>`\n return { hostHtml, playerHtml }\n}\n","/**\n * What the output is, decided from what the person asked for.\n *\n * Pure, so the engine verbs (a program) and the take verbs agree: a file\n * named `out.mp4` IS an mp4, an explicit `--format` that contradicts the\n * name is refused instead of writing one container under the other's name,\n * and a still named `.png` is a PNG.\n */\nimport { UsageError } from './args'\n\nexport type VideoFormat = 'webm' | 'mp4'\nexport type StillFormat = 'webp' | 'png' | 'jpeg'\n\nfunction extOf(path: string | undefined): string {\n return /\\.([a-z0-9]+)$/i.exec(path ?? '')?.[1]?.toLowerCase() ?? ''\n}\n\n/** The video container: the flag, else the output's name, else WebM. */\nexport function videoFormat(\n flag: string | undefined,\n out: string | undefined,\n): VideoFormat {\n const named = extOf(out)\n const fromName: VideoFormat | null =\n named === 'mp4' ? 'mp4' : named === 'webm' ? 'webm' : null\n if (flag !== undefined) {\n if (flag !== 'webm' && flag !== 'mp4')\n throw new UsageError('--format must be webm or mp4')\n if (fromName && fromName !== flag)\n throw new UsageError(\n `--format ${flag} contradicts the output name ${out}: name it .${flag}, or drop --format and the name decides`,\n )\n return flag\n }\n return fromName ?? 'webm'\n}\n\n/** The still's encoding, from its name. The engine captures WebP. */\nexport function stillFormat(out: string): StillFormat {\n const ext = extOf(out)\n if (ext === 'png') return 'png'\n if (ext === 'jpg' || ext === 'jpeg') return 'jpeg'\n if (ext === 'webp' || ext === '') return 'webp'\n throw new UsageError(\n `vos still writes .webp, .png or .jpg, not .${ext}: rename ${out}`,\n )\n}\n\n/**\n * `--times 0,1.5,50%,100%`: seconds or percentages of the duration, in the\n * order given. 100% is clamped a hair inside the end, where the last frame is.\n */\nexport function parseTimes(raw: string, duration: number): number[] {\n const times = raw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const pct = /^(\\d+(?:\\.\\d+)?)%$/.exec(s)\n const t = pct ? (Number(pct[1]) / 100) * duration : Number(s)\n if (!Number.isFinite(t) || t < 0)\n throw new UsageError(\n `--times expects seconds or percentages (0,1.5,50%), got \"${s}\"`,\n )\n return Math.min(t, Math.max(0, duration - 1 / 60))\n })\n if (!times.length) throw new UsageError('--times needs at least one time')\n return times\n}\n\n/** Where the still for one time of several goes: `name-1.50s.ext`. */\nexport function stillOutFor(out: string, time: number, many: boolean): string {\n if (!many) return out\n const m = /^(.*?)(\\.[a-z0-9]+)?$/i.exec(out)\n return `${m?.[1] ?? out}-${time.toFixed(2)}s${m?.[2] ?? ''}`\n}\n\n/** Every `--set` value in argv, in order (the parser keeps only the last). */\nexport function setFlags(argv: string[]): string[] {\n const out: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]\n if (a === '--set' && argv[i + 1] !== undefined) out.push(argv[++i])\n else if (a.startsWith('--set=')) out.push(a.slice('--set='.length))\n }\n return out\n}\n\n/**\n * `--set data.<key>=<value>` on a PROGRAM: the knob-honesty check without a\n * second config. The value reads as JSON when it parses (numbers, booleans,\n * arrays) and as a string otherwise. Patched in memory, never written back.\n */\nexport function applyDataSets(\n config: Record<string, unknown>,\n sets: string[],\n): Record<string, unknown> {\n if (!sets.length) return config\n const data: Record<string, unknown> = {\n ...((config.data as Record<string, unknown> | undefined) ?? {}),\n }\n for (const set of sets) {\n const eq = set.indexOf('=')\n const path = eq === -1 ? set : set.slice(0, eq)\n const m = /^data\\.([A-Za-z0-9_$-]+)$/.exec(path)\n if (eq === -1 || !m)\n throw new UsageError(\n `--set on a program takes data.<key>=<value> (a knob's value), got \"${set}\"`,\n )\n const raw = set.slice(eq + 1)\n let value: unknown = raw\n try {\n value = JSON.parse(raw)\n } catch {\n // a bare word is a string: --set data.name=ROBIN\n }\n data[m[1]] = value\n }\n return { ...config, data }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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 { existsSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\nimport { hostedAssetId, 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/** 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)\n if (hosted) {\n setRef(manifest, name, index, `asset:${hosted}`)\n continue\n }\n if (isUrl(ref)) continue\n const file = isAbsolute(ref) ? ref : resolve(dir, ref)\n local.push({\n where: where(name, index),\n // The upload helper reads keys against the directory.\n key: relative(dir, file),\n set: (url) => {\n const id = hostedAssetId(url)\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))\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. A file already there is kept.\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 hosted = manifestRefs(config).filter(({ ref }) => hostedAssetId(ref))\n if (hosted.length === 0) return null\n const manifest = cloneManifest(config)\n const files: string[] = []\n for (const { name, index, ref } of hosted) {\n const id = hostedAssetId(ref)!\n const stem = index === null ? name : `${name}-${index}`\n const present = existsSync(join(dir, 'assets'))\n ? readdirSync(join(dir, 'assets')).find(\n (f) => f === stem || f.startsWith(`${stem}.`),\n )\n : undefined\n let file: string\n if (present) {\n file = `assets/${present}`\n } else {\n const got = await downloadMedia(ctx, `/api/assets/${id}/file`, {\n dir,\n subdir: 'assets',\n stem,\n fallbackExt: '',\n describe: `${where(name, index)} (asset ${id})`,\n notFoundHint: 'a private file needs a content key of its owner',\n })\n file = got.file\n log(\n ` ${file} ← ${where(name, index)}, asset ${id} (${Math.round(got.bytes / 1024)} kB)`,\n )\n }\n files.push(file)\n setRef(manifest, name, index, `./${file}`)\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 recording 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, so there is no file\n // to walk, rewrite or bring home.\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 recording 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 recording 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(` ${ref.where}: ${ref.key} is not beside the document — left as is`)\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 }\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 take's bytes to vos.so, whole or in parts.\n *\n * A request body has a ceiling the platform does not set and cannot raise\n * from inside a handler: the edge refuses anything past it before the\n * request arrives, with its own error page rather than a worded refusal.\n * A screen recording of any real length clears that ceiling at any\n * watchable bitrate, so a whole-file push of a long take failed with a\n * bare status and no explanation.\n *\n * So a file past `SINGLE_SHOT_MAX_BYTES` is declared first, sent as parts\n * that are each a fraction of the ceiling, and sealed. The part size is\n * NOT decided here — the platform returns it from the declaring call, so\n * the split can change server-side without a CLI release. Only the\n * threshold for choosing the chunked door is stated locally, and it is\n * deliberately far below the ceiling.\n *\n * Both doors are content-addressed: the same bytes pushed twice are\n * recognised and never re-sent, which is what keeps an iterating push from\n * re-uploading a take on every round.\n */\nimport { apiJson } from './platform'\nimport type { ApiResult } from './platform'\n\n/**\n * Above this, the upload goes in parts. Well under the edge ceiling, and\n * chosen so a dropped connection costs one part rather than a whole take:\n * re-sending a 40 MB body that died at 90% costs more than re-sending one\n * part of it.\n */\nexport const SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024\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 url: string\n size: number\n /** The platform already had these exact bytes; nothing was sent. */\n reused: boolean\n}\n\nexport interface UploadTarget {\n origin: string\n key: string\n}\n\nexport interface UploadOptions {\n filename: string\n contentType: string\n /** sha256 hex — the platform dedupes on it, per owner. */\n contentHash: string\n /** The take's length, so the platform can hold it to the hosted cap. */\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 (single-shot only). */\n folderId?: string\n}\n\nfunction failed(what: string, r: ApiResult): Error {\n const detail = typeof r.body.error === 'string' ? r.body.error : ''\n return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ''}`)\n}\n\nfunction asAsset(body: Record<string, unknown>): UploadedAsset {\n return {\n id: String(body.id),\n url: String(body.url),\n size: typeof body.size === 'number' ? body.size : 0,\n reused: body.reused === true,\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/**\n * Upload one file as an owned asset, picking the transport by size. The\n * caller never chooses: a small file is one request, a recording is parts.\n */\nexport async function uploadAsset(\n target: UploadTarget,\n bytes: Uint8Array,\n opts: UploadOptions,\n): Promise<UploadedAsset> {\n if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {\n throw new Error(\n `${opts.filename} is over ${SINGLE_SHOT_MAX_BYTES / 1024 / 1024} MB, and a file that large cannot be filed into a project on upload yet. Push it without --folder and reference it from a document, which keeps it`,\n )\n }\n return bytes.length > SINGLE_SHOT_MAX_BYTES\n ? uploadInParts(target, bytes, opts)\n : uploadWhole(target, bytes, opts)\n}\n\nasync function uploadWhole(\n target: UploadTarget,\n bytes: Uint8Array,\n opts: UploadOptions,\n): Promise<UploadedAsset> {\n const res = await apiJson(target.origin, '/api/assets/recording', {\n method: 'POST',\n key: target.key,\n headers: {\n 'Content-Type': opts.contentType,\n 'Content-Length': String(bytes.length),\n 'X-Filename': opts.filename,\n 'X-Content-Hash': opts.contentHash,\n ...(opts.durationSeconds && opts.durationSeconds > 0\n ? { 'X-Content-Duration': opts.durationSeconds.toFixed(3) }\n : {}),\n ...(opts.folderId ? { 'X-Folder-Id': opts.folderId } : {}),\n },\n raw: bytes,\n })\n if (res.status !== 201 && res.status !== 200)\n throw failed('upload failed', res)\n return asAsset(res.body)\n}\n\nasync function uploadInParts(\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 begun = await apiJson(\n target.origin,\n '/api/assets/recording/multipart',\n {\n method: 'POST',\n key: target.key,\n body: {\n filename: opts.filename,\n contentType: opts.contentType,\n size: bytes.length,\n contentHash: opts.contentHash,\n ...(opts.durationSeconds && opts.durationSeconds > 0\n ? { durationSeconds: opts.durationSeconds }\n : {}),\n },\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.reused === true || typeof begun.body.url === 'string') {\n return asAsset(begun.body)\n }\n\n const uploadId = String(begun.body.id)\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 `/api/assets/recording/multipart/${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 const sealed = await apiJson(\n target.origin,\n `/api/assets/recording/multipart/${uploadId}/complete`,\n { method: 'POST', key: target.key, body: { parts: etags } },\n )\n if (sealed.status !== 201 && sealed.status !== 200) {\n throw failed('upload failed', sealed)\n }\n return asAsset(sealed.body)\n } catch (err) {\n // Hand the parts back rather than leaving them held server-side.\n await apiJson(\n target.origin,\n `/api/assets/recording/multipart/${uploadId}`,\n { method: 'DELETE', key: target.key },\n ).catch(() => undefined)\n throw err\n }\n}\n"],"mappings":"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","names":["existsSync","readdirSync","join","existsSync","mkdir","readFile","rename","rm","writeFile","basename","join","where","join","existsSync","readFile","basename","mkdir","rename","rm","writeFile","existsSync","join","readdirSync"]}
|
|
File without changes
|
|
File without changes
|