@vosjs/cli 0.50.4 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,94 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BrowserUnavailableError,
4
+ CREDENTIALS_PATH,
5
+ MEDIA_HEAD_BYTES,
6
+ SYNC_STATE_NAME,
7
+ TAKE_MEDIA_EXTENSIONS,
8
+ UsageError,
9
+ apiError,
10
+ apiJson,
11
+ clientId,
12
+ containerMismatch,
13
+ deriveSlug,
4
14
  directoryKind,
15
+ extensionFor,
16
+ formatChanges,
17
+ hasFlag,
5
18
  launchBrowser,
6
- noteGivenFlag,
7
- trackFlags
8
- } from "./chunk-FACYY7VV.js";
19
+ nameForType,
20
+ numFlag2 as numFlag,
21
+ parseArgs2 as parseArgs,
22
+ parseVosId,
23
+ platformOrigin,
24
+ readSyncState,
25
+ requireCredential,
26
+ resolveCredential,
27
+ resolveMediaType,
28
+ sniffMediaType,
29
+ strFlag,
30
+ videoFormat,
31
+ writeCredential,
32
+ writeSyncState
33
+ } from "./chunk-KSEU3YK5.js";
9
34
  import {
10
35
  deadLine,
11
36
  deadWarns
12
37
  } from "./chunk-6XPFWQKP.js";
13
38
 
39
+ // src/plugin/landed.ts
40
+ var absolute = (origin, url) => typeof url === "string" && url ? /^https?:/.test(url) ? url : `${origin}${url.startsWith("/") ? "" : "/"}${url}` : null;
41
+ async function waitForLanded(opts) {
42
+ const origin = opts.origin.replace(/\/+$/, "");
43
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
44
+ const now = opts.now ?? (() => Date.now());
45
+ const timeoutMs = opts.timeoutMs ?? 3e5;
46
+ const intervalMs = opts.intervalMs ?? 4e3;
47
+ const started = now();
48
+ let still = false;
49
+ if (opts.versionId) {
50
+ opts.log?.("waiting for the still to render\u2026");
51
+ for (; ; ) {
52
+ const res = await fetch(
53
+ `${origin}/api/vos/${opts.vosId}/versions/${opts.versionId}/thumbnail`,
54
+ { headers: { authorization: `Bearer ${opts.key}` } }
55
+ );
56
+ await res.arrayBuffer().catch(() => void 0);
57
+ if (res.ok) {
58
+ still = true;
59
+ break;
60
+ }
61
+ if (now() - started >= timeoutMs) break;
62
+ await sleep(intervalMs);
63
+ }
64
+ }
65
+ const meta = await apiJson(origin, `/api/vos/${opts.vosId}`, {
66
+ key: opts.key
67
+ });
68
+ const vos = meta.body.vos ?? {};
69
+ const urls = vos.contentUrls ?? {};
70
+ return {
71
+ still,
72
+ watchUrl: `${origin}/vos/${opts.vosId}`,
73
+ studioUrl: `${origin}/studio?vos=${opts.vosId}`,
74
+ thumbnailUrl: still ? absolute(origin, urls.thumbnail) : null,
75
+ previewUrl: still ? absolute(origin, urls.preview) : null,
76
+ waitedMs: now() - started
77
+ };
78
+ }
79
+ function landedLines(l) {
80
+ if (!l.still) {
81
+ return ` still: not rendered yet (the fleet renders it after the push; open the watch page in a minute)`;
82
+ }
83
+ return [
84
+ l.thumbnailUrl ? ` still: ${l.thumbnailUrl}` : null,
85
+ l.previewUrl ? ` preview: ${l.previewUrl}` : null
86
+ ].filter(Boolean).join("\n");
87
+ }
88
+
14
89
  // src/plugin/run.ts
15
- import { mkdir as mkdir11, readFile as readFile16, rm as rm5 } from "fs/promises";
16
- import { existsSync as existsSync19 } from "fs";
90
+ import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
91
+ import { existsSync as existsSync20 } from "fs";
17
92
  import { join as join22, resolve as resolve10 } from "path";
18
93
  import {
19
94
  END_CARD_FROM as END_CARD_FROM2,
@@ -21,63 +96,6 @@ import {
21
96
  migrateHostedDoc as migrateHostedDoc5
22
97
  } from "@vosjs/studio-core";
23
98
 
24
- // src/plugin/args.ts
25
- var UsageError = class extends Error {
26
- };
27
- function parseArgs(argv, booleanFlags, multiFlags = /* @__PURE__ */ new Set()) {
28
- const positionals = [];
29
- const flags = {};
30
- const multi = {};
31
- const put = (name, value) => {
32
- if (multiFlags.has(name)) (multi[name] ??= []).push(value);
33
- else flags[name] = value;
34
- };
35
- for (let i = 0; i < argv.length; i++) {
36
- const arg = argv[i];
37
- if (arg === "--") {
38
- positionals.push(...argv.slice(i + 1));
39
- break;
40
- }
41
- if (arg.startsWith("--")) {
42
- const eq = arg.indexOf("=");
43
- if (eq !== -1) {
44
- put(arg.slice(2, eq), arg.slice(eq + 1));
45
- continue;
46
- }
47
- const name = arg.slice(2);
48
- if (booleanFlags.has(name)) {
49
- flags[name] = true;
50
- continue;
51
- }
52
- if (i + 1 >= argv.length || argv[i + 1].startsWith("--")) {
53
- throw new UsageError(`--${name} expects a value`);
54
- }
55
- put(name, argv[i + 1]);
56
- i++;
57
- continue;
58
- }
59
- positionals.push(arg);
60
- }
61
- for (const name of Object.keys(flags)) noteGivenFlag(name);
62
- for (const name of Object.keys(multi)) noteGivenFlag(name);
63
- return { positionals, flags: trackFlags(flags), multi: trackFlags(multi) };
64
- }
65
- function numFlag(flags, name, fallback) {
66
- const v = flags[name];
67
- if (typeof v !== "string") return fallback;
68
- const n = Number(v);
69
- if (!Number.isFinite(n))
70
- throw new UsageError(`--${name} expects a number, got "${v}"`);
71
- return n;
72
- }
73
- function strFlag(flags, name) {
74
- const v = flags[name];
75
- return typeof v === "string" ? v : void 0;
76
- }
77
- function hasFlag(flags, name) {
78
- return Object.prototype.hasOwnProperty.call(flags, name);
79
- }
80
-
81
99
  // src/plugin/output.ts
82
100
  var EXIT_OK = 0;
83
101
  var EXIT_ERROR = 1;
@@ -1634,111 +1652,14 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
1634
1652
  }
1635
1653
  }
1636
1654
 
1637
- // src/plugin/container.ts
1638
- var OCTET_STREAM = "application/octet-stream";
1639
- var TYPES_BY_EXTENSION = {
1640
- svg: "image/svg+xml",
1641
- png: "image/png",
1642
- jpg: "image/jpeg",
1643
- jpeg: "image/jpeg",
1644
- webp: "image/webp",
1645
- gif: "image/gif",
1646
- avif: "image/avif",
1647
- webm: "video/webm",
1648
- mp4: "video/mp4",
1649
- mov: "video/quicktime",
1650
- mp3: "audio/mpeg",
1651
- wav: "audio/wav",
1652
- ogg: "audio/ogg",
1653
- m4a: "audio/mp4"
1654
- };
1655
- var EXTENSION_BY_TYPE = {
1656
- "image/jpeg": ".jpg",
1657
- "video/mp4": ".mp4",
1658
- "audio/mp4": ".m4a"
1659
- };
1660
- var TAKE_MEDIA_EXTENSIONS = [
1661
- ".webm",
1662
- ".mp4",
1663
- ".mov",
1664
- ".m4a",
1665
- ".mp3",
1666
- ".ogg",
1667
- ".wav"
1668
- ];
1669
- var MEDIA_HEAD_BYTES = 16;
1670
- var ISO_BRANDS = {
1671
- "qt ": "video/quicktime",
1672
- "M4A ": "audio/mp4",
1673
- "M4B ": "audio/mp4",
1674
- avif: "image/avif",
1675
- avis: "image/avif"
1676
- };
1677
- var RIFF_FORMS = {
1678
- WEBP: "image/webp",
1679
- WAVE: "audio/wav",
1680
- "AVI ": "video/x-msvideo"
1681
- };
1682
- function sniffMediaType(head) {
1683
- const ascii = (at2, len) => head.length >= at2 + len ? String.fromCharCode(...head.subarray(at2, at2 + len)) : "";
1684
- const startsWith = (...bytes) => bytes.every((b, i) => head[i] === b);
1685
- if (startsWith(26, 69, 223, 163)) return "video/webm";
1686
- if (ascii(4, 4) === "ftyp") return ISO_BRANDS[ascii(8, 4)] ?? "video/mp4";
1687
- if (ascii(0, 4) === "RIFF") return RIFF_FORMS[ascii(8, 4)] ?? null;
1688
- if (startsWith(137, 80, 78, 71, 13, 10, 26, 10))
1689
- return "image/png";
1690
- if (startsWith(255, 216, 255)) return "image/jpeg";
1691
- if (ascii(0, 4) === "GIF8") return "image/gif";
1692
- if (ascii(0, 4) === "OggS") return "audio/ogg";
1693
- if (ascii(0, 3) === "ID3") return "audio/mpeg";
1694
- return null;
1695
- }
1696
- function bareType(value) {
1697
- const type = (value ?? "").split(";")[0].trim().toLowerCase();
1698
- return !type || type === OCTET_STREAM || type === "binary/octet-stream" ? "" : type;
1699
- }
1700
- function mediaContentType(file) {
1701
- const ext = (/\.([a-z0-9]+)$/i.exec(file)?.[1] ?? "").toLowerCase();
1702
- return TYPES_BY_EXTENSION[ext] ?? OCTET_STREAM;
1703
- }
1704
- function extensionFor(contentType) {
1705
- const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
1706
- if (EXTENSION_BY_TYPE[type]) return EXTENSION_BY_TYPE[type];
1707
- const ext = Object.keys(TYPES_BY_EXTENSION).find(
1708
- (e) => TYPES_BY_EXTENSION[e] === type
1709
- );
1710
- return ext ? `.${ext}` : "";
1711
- }
1712
- function resolveMediaType(claims) {
1713
- const sniffed = claims.head?.length ? sniffMediaType(claims.head) : null;
1714
- if (sniffed) return { type: sniffed, from: "bytes" };
1715
- const declared = bareType(claims.declared);
1716
- if (declared) return { type: declared, from: "declared" };
1717
- const named = mediaContentType(claims.filename ?? "");
1718
- if (named !== OCTET_STREAM) return { type: named, from: "filename" };
1719
- return { type: OCTET_STREAM, from: "unknown" };
1720
- }
1721
- function nameForType(filename, type) {
1722
- const ext = extensionFor(type);
1723
- if (!ext) return filename;
1724
- const current = /\.[a-z0-9]+$/i.exec(filename)?.[0] ?? "";
1725
- if (current && mediaContentType(`x${current}`) === type) return filename;
1726
- return `${filename.slice(0, filename.length - current.length)}${ext}`;
1727
- }
1728
- function containerMismatch(filename, head) {
1729
- const actual = sniffMediaType(head);
1730
- if (!actual) return null;
1731
- const claimed = mediaContentType(filename);
1732
- if (claimed === actual) return null;
1733
- return `${filename} is ${actual}, not the ${claimed} its name claims \u2014 rename it to ${nameForType(filename, actual)}`;
1734
- }
1735
-
1736
1655
  // src/plugin/media.ts
1737
1656
  import { isKeyedOverlay } from "@vosjs/studio-core";
1657
+ import { createHash } from "crypto";
1738
1658
  import { createWriteStream, existsSync as existsSync2 } from "fs";
1739
1659
  import {
1740
1660
  mkdir as mkdir2,
1741
1661
  open,
1662
+ readFile as readFile2,
1742
1663
  readdir,
1743
1664
  rename as rename2,
1744
1665
  rm as rm2,
@@ -1847,6 +1768,150 @@ async function loadTake(dir) {
1847
1768
  return { paths, cursor, meta, actions, doc };
1848
1769
  }
1849
1770
 
1771
+ // src/plugin/uploadAsset.ts
1772
+ var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
1773
+ var PART_ATTEMPTS = 3;
1774
+ function planParts(totalBytes, partBytes) {
1775
+ if (totalBytes <= 0 || partBytes <= 0) return [];
1776
+ const parts = [];
1777
+ for (let start = 0; start < totalBytes; start += partBytes) {
1778
+ parts.push({
1779
+ partNumber: parts.length + 1,
1780
+ start,
1781
+ end: Math.min(start + partBytes, totalBytes)
1782
+ });
1783
+ }
1784
+ return parts;
1785
+ }
1786
+ function failed(what, r) {
1787
+ const detail = typeof r.body.error === "string" ? r.body.error : "";
1788
+ return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
1789
+ }
1790
+ function asAsset(body) {
1791
+ return {
1792
+ id: String(body.id),
1793
+ url: String(body.url),
1794
+ size: typeof body.size === "number" ? body.size : 0,
1795
+ reused: body.reused === true
1796
+ };
1797
+ }
1798
+ function transient(status) {
1799
+ return status >= 500 || status === 429;
1800
+ }
1801
+ var wait = (ms) => new Promise((r) => setTimeout(r, ms));
1802
+ async function uploadAsset(target, bytes, opts) {
1803
+ if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {
1804
+ throw new Error(
1805
+ `${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`
1806
+ );
1807
+ }
1808
+ return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
1809
+ }
1810
+ async function uploadWhole(target, bytes, opts) {
1811
+ const res = await apiJson(target.origin, "/api/assets/recording", {
1812
+ method: "POST",
1813
+ key: target.key,
1814
+ headers: {
1815
+ "Content-Type": opts.contentType,
1816
+ "Content-Length": String(bytes.length),
1817
+ "X-Filename": opts.filename,
1818
+ "X-Content-Hash": opts.contentHash,
1819
+ ...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {},
1820
+ ...opts.folderId ? { "X-Folder-Id": opts.folderId } : {}
1821
+ },
1822
+ raw: bytes
1823
+ });
1824
+ if (res.status !== 201 && res.status !== 200)
1825
+ throw failed("upload failed", res);
1826
+ return asAsset(res.body);
1827
+ }
1828
+ async function uploadInParts(target, bytes, opts) {
1829
+ const begun = await apiJson(
1830
+ target.origin,
1831
+ "/api/assets/recording/multipart",
1832
+ {
1833
+ method: "POST",
1834
+ key: target.key,
1835
+ body: {
1836
+ filename: opts.filename,
1837
+ contentType: opts.contentType,
1838
+ size: bytes.length,
1839
+ contentHash: opts.contentHash,
1840
+ ...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
1841
+ }
1842
+ }
1843
+ );
1844
+ if (begun.status !== 201 && begun.status !== 200) {
1845
+ throw failed("upload failed", begun);
1846
+ }
1847
+ if (begun.body.reused === true || typeof begun.body.url === "string") {
1848
+ return asAsset(begun.body);
1849
+ }
1850
+ const uploadId = String(begun.body.id);
1851
+ const partBytes = Number(begun.body.partBytes);
1852
+ if (!Number.isFinite(partBytes) || partBytes <= 0) {
1853
+ throw new Error("upload failed: the platform returned no part size");
1854
+ }
1855
+ const parts = planParts(bytes.length, partBytes);
1856
+ const etags = [];
1857
+ try {
1858
+ for (const part of parts) {
1859
+ const chunk2 = bytes.subarray(part.start, part.end);
1860
+ let sent = null;
1861
+ for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
1862
+ try {
1863
+ sent = await apiJson(
1864
+ target.origin,
1865
+ `/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
1866
+ {
1867
+ method: "PUT",
1868
+ key: target.key,
1869
+ headers: {
1870
+ "Content-Type": "application/octet-stream",
1871
+ "Content-Length": String(chunk2.length)
1872
+ },
1873
+ raw: chunk2
1874
+ }
1875
+ );
1876
+ } catch (networkError) {
1877
+ if (attempt === PART_ATTEMPTS) throw networkError;
1878
+ await wait(500 * 2 ** (attempt - 1));
1879
+ continue;
1880
+ }
1881
+ if (sent.status === 200) break;
1882
+ if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
1883
+ throw failed(`part ${part.partNumber} failed`, sent);
1884
+ }
1885
+ await wait(500 * 2 ** (attempt - 1));
1886
+ }
1887
+ if (!sent || sent.status !== 200) {
1888
+ throw new Error(`part ${part.partNumber} failed`);
1889
+ }
1890
+ etags.push({
1891
+ partNumber: Number(sent.body.partNumber),
1892
+ etag: String(sent.body.etag)
1893
+ });
1894
+ opts.onPart?.(etags.length, parts.length);
1895
+ }
1896
+ const sealed = await apiJson(
1897
+ target.origin,
1898
+ `/api/assets/recording/multipart/${uploadId}/complete`,
1899
+ { method: "POST", key: target.key, body: { parts: etags } }
1900
+ );
1901
+ if (sealed.status !== 201 && sealed.status !== 200) {
1902
+ throw failed("upload failed", sealed);
1903
+ }
1904
+ return asAsset(sealed.body);
1905
+ } catch (err) {
1906
+ await apiJson(
1907
+ target.origin,
1908
+ `/api/assets/recording/multipart/${uploadId}`,
1909
+ { method: "DELETE", key: target.key }
1910
+ ).catch(() => void 0);
1911
+ throw err;
1912
+ }
1913
+ }
1914
+
1850
1915
  // src/plugin/media.ts
1851
1916
  var SOURCE_MEDIA = [
1852
1917
  { key: "videoKey", stem: "recording", fallbackExt: ".webm" },
@@ -1915,8 +1980,58 @@ function docMediaRefs(doc, keep = isTakeRelativeKey) {
1915
1980
  bg.key = next;
1916
1981
  }
1917
1982
  });
1983
+ out.push(...docAudioRefs(doc, keep));
1984
+ return out;
1985
+ }
1986
+ function docAudioRefs(doc, keep = isTakeRelativeKey) {
1987
+ const out = [];
1988
+ for (const clip of doc.audio ?? []) {
1989
+ if (!keep(clip.key)) continue;
1990
+ out.push({
1991
+ where: `audio ${clip.id}`,
1992
+ key: clip.key,
1993
+ set: (next) => {
1994
+ clip.key = next;
1995
+ }
1996
+ });
1997
+ }
1918
1998
  return out;
1919
1999
  }
2000
+ async function uploadDocRefs(refs, dir, target, log) {
2001
+ for (const ref of refs) {
2002
+ const file = join2(dir, takeRelativeFile(ref.key));
2003
+ if (!existsSync2(file)) {
2004
+ log(` ${ref.where}: ${ref.key} is not beside the document \u2014 left as is`);
2005
+ continue;
2006
+ }
2007
+ const media = await readFile2(file);
2008
+ const mediaHash = createHash("sha256").update(media).digest("hex");
2009
+ const { type } = resolveMediaType({
2010
+ head: media.subarray(0, MEDIA_HEAD_BYTES),
2011
+ filename: file
2012
+ });
2013
+ const name = nameForType(basename2(file), type);
2014
+ if (name !== basename2(file)) {
2015
+ log(` ${ref.where}: ${basename2(file)} holds ${type} \u2014 sent as ${name}`);
2016
+ }
2017
+ let put;
2018
+ try {
2019
+ put = await uploadAsset(target, new Uint8Array(media), {
2020
+ filename: name,
2021
+ contentType: type,
2022
+ contentHash: mediaHash
2023
+ });
2024
+ } catch (e) {
2025
+ throw new Error(
2026
+ `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
2027
+ );
2028
+ }
2029
+ ref.set(put.url);
2030
+ log(
2031
+ ` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
2032
+ );
2033
+ }
2034
+ }
1920
2035
  function assetIdOf(url) {
1921
2036
  if (!url) return null;
1922
2037
  const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
@@ -2105,7 +2220,7 @@ function mediaProbeLints(results) {
2105
2220
  }
2106
2221
 
2107
2222
  // src/plugin/framesTake.ts
2108
- import { mkdir as mkdir4, readFile as readFile4, rename as rename4, writeFile as writeFile5 } from "fs/promises";
2223
+ import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
2109
2224
  import { existsSync as existsSync5 } from "fs";
2110
2225
  import { join as join5, resolve as resolve2 } from "path";
2111
2226
  import { compileVosConfig } from "@vosjs/core";
@@ -2246,7 +2361,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
2246
2361
  }
2247
2362
 
2248
2363
  // src/plugin/renderAnimation.ts
2249
- import { readFile as readFile2, rm as rm3 } from "fs/promises";
2364
+ import { readFile as readFile3, rm as rm3 } from "fs/promises";
2250
2365
  import { tweenRuntimeCode } from "@vosjs/tween/bundle";
2251
2366
  import { elementsBundleCode } from "@vosjs/elements/bundle";
2252
2367
  import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
@@ -2541,7 +2656,7 @@ async function renderAnimation(browser, opts) {
2541
2656
  )
2542
2657
  );
2543
2658
  const files = await Promise.all(
2544
- outNames.map((name) => readFile2(`${opts.workDir}/${name}`))
2659
+ outNames.map((name) => readFile3(`${opts.workDir}/${name}`))
2545
2660
  );
2546
2661
  await Promise.all(
2547
2662
  outNames.map((name) => rm3(`${opts.workDir}/${name}`, { force: true }))
@@ -2799,7 +2914,7 @@ function applyAndValidate(doc, o) {
2799
2914
  }
2800
2915
 
2801
2916
  // src/plugin/digestTake.ts
2802
- import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile4 } from "fs/promises";
2917
+ import { mkdir as mkdir3, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
2803
2918
  import { existsSync as existsSync4 } from "fs";
2804
2919
  import { join as join4, resolve } from "path";
2805
2920
  import {
@@ -3199,7 +3314,7 @@ async function discoverTranscript(dir) {
3199
3314
  const file = join4(dir, "transcript.json");
3200
3315
  if (!existsSync4(file)) return null;
3201
3316
  try {
3202
- return parseTranscript(JSON.parse(await readFile3(file, "utf8")));
3317
+ return parseTranscript(JSON.parse(await readFile4(file, "utf8")));
3203
3318
  } catch {
3204
3319
  return null;
3205
3320
  }
@@ -3207,7 +3322,7 @@ async function discoverTranscript(dir) {
3207
3322
  async function readPriorDigest(outDir) {
3208
3323
  try {
3209
3324
  const raw = JSON.parse(
3210
- await readFile3(join4(outDir, "digest.json"), "utf8")
3325
+ await readFile4(join4(outDir, "digest.json"), "utf8")
3211
3326
  );
3212
3327
  return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
3213
3328
  } catch {
@@ -3216,7 +3331,7 @@ async function readPriorDigest(outDir) {
3216
3331
  }
3217
3332
  async function pngSize(file) {
3218
3333
  try {
3219
- const buf = await readFile3(file);
3334
+ const buf = await readFile4(file);
3220
3335
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return null;
3221
3336
  return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
3222
3337
  } catch {
@@ -3456,7 +3571,7 @@ async function momentsFor(dir, doc) {
3456
3571
  const file = join5(dir, DIGEST_DIR, "digest.json");
3457
3572
  if (existsSync5(file)) {
3458
3573
  try {
3459
- const d = JSON.parse(await readFile4(file, "utf8"));
3574
+ const d = JSON.parse(await readFile5(file, "utf8"));
3460
3575
  if (Array.isArray(d.moments)) return d.moments;
3461
3576
  } catch {
3462
3577
  }
@@ -3496,9 +3611,9 @@ async function writeIndexJson(result) {
3496
3611
  // src/plugin/deliver.ts
3497
3612
  import { mkdir as mkdir5, mkdtemp, rename as rename5, rm as rm4, stat as stat2, writeFile as writeFile7 } from "fs/promises";
3498
3613
  import { tmpdir } from "os";
3499
- import { join as join8, relative, resolve as resolve4 } from "path";
3614
+ import { join as join7, relative, resolve as resolve4 } from "path";
3500
3615
  import { existsSync as existsSync7 } from "fs";
3501
- import { readFile as readFile6 } from "fs/promises";
3616
+ import { readFile as readFile7 } from "fs/promises";
3502
3617
  import { parseFrontmatter } from "@vosjs/shared/frontmatter";
3503
3618
  import {
3504
3619
  CHANNEL_SPECS_VERIFIED,
@@ -4478,176 +4593,15 @@ function isLightHexGround(hex2) {
4478
4593
 
4479
4594
  // src/plugin/posterDoc.ts
4480
4595
  import { existsSync as existsSync6 } from "fs";
4481
- import { readFile as readFile5 } from "fs/promises";
4482
- import { isAbsolute, join as join7, resolve as resolve3 } from "path";
4596
+ import { readFile as readFile6 } from "fs/promises";
4597
+ import { isAbsolute, join as join6, resolve as resolve3 } from "path";
4483
4598
  import {
4484
4599
  computeCardLayout,
4485
- docStillTime as docStillTime2,
4486
- migrateHostedDoc,
4487
- overlayRect,
4488
- resolveOverlayStyle
4489
- } from "@vosjs/studio-core";
4490
-
4491
- // src/plugin/platform.ts
4492
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4493
- import { homedir } from "os";
4494
- import { join as join6 } from "path";
4495
- function platformOrigin(flags = {}) {
4496
- const legacyEnv = process.env.VOS_API_BASE?.trim();
4497
- const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
4498
- if (!flags.origin && !flags.api && !process.env.VOS_ORIGIN && legacyEnv) {
4499
- process.stderr.write(
4500
- "note: VOS_API_BASE is deprecated \u2014 set VOS_ORIGIN (an origin, no /api suffix)\n"
4501
- );
4502
- }
4503
- return raw.replace(/\/+$/, "").replace(/\/api$/, "");
4504
- }
4505
- function parseVosId(input) {
4506
- if (!/^https?:\/\//.test(input)) {
4507
- if (/^[A-Za-z0-9_-]+$/.test(input)) return input;
4508
- throw new UsageError(`"${input}" is not a vos id or URL`);
4509
- }
4510
- let url;
4511
- try {
4512
- url = new URL(input);
4513
- } catch {
4514
- throw new UsageError(`"${input}" is not a valid URL`);
4515
- }
4516
- const query = url.searchParams.get("vos");
4517
- if (query) return query;
4518
- const path = url.pathname.match(/\/vos\/([A-Za-z0-9_-]+)/);
4519
- if (path) return path[1];
4520
- throw new UsageError(
4521
- `could not find a vos id in ${input} \u2014 expected /vos/{id} or ?vos={id}`
4522
- );
4523
- }
4524
- function deriveSlug(title) {
4525
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
4526
- }
4527
- var CREDENTIALS_PATH = join6(homedir(), ".config", "vos", "credentials");
4528
- function resolveCredential(explicit) {
4529
- const flag = explicit?.trim();
4530
- if (flag) return flag;
4531
- const env = process.env.VOS_API_KEY?.trim();
4532
- if (env) return env;
4533
- try {
4534
- const first = readFileSync2(CREDENTIALS_PATH, "utf8").split("\n")[0].trim();
4535
- return first || null;
4536
- } catch {
4537
- return null;
4538
- }
4539
- }
4540
- function requireCredential(explicit) {
4541
- const key = resolveCredential(explicit);
4542
- if (!key) {
4543
- throw new Error(
4544
- "no credential found \u2014 pass --key, set VOS_API_KEY, or run `vos login` (mint a content key at https://vos.so/app/api; a vos_rg_ remix grant works too)"
4545
- );
4546
- }
4547
- return key;
4548
- }
4549
- function writeCredential(key) {
4550
- mkdirSync(join6(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
4551
- writeFileSync(CREDENTIALS_PATH, `${key.trim()}
4552
- `, { mode: 384 });
4553
- return CREDENTIALS_PATH;
4554
- }
4555
- function clientId() {
4556
- const env = process.env.VOS_CLIENT?.trim();
4557
- if (env && env.length <= 60 && !/[\r\n]/.test(env)) return env;
4558
- return "vos-cli";
4559
- }
4560
- async function apiJson(origin, path, init = {}) {
4561
- const headers = {
4562
- accept: "application/json",
4563
- ...init.headers
4564
- };
4565
- if (init.key) headers.authorization = `Bearer ${init.key}`;
4566
- if (init.body !== void 0) headers["content-type"] = "application/json";
4567
- const reqBody = init.raw !== void 0 ? init.raw : init.body === void 0 ? void 0 : JSON.stringify(init.body);
4568
- const res = await fetch(`${origin}${path}`, {
4569
- method: init.method ?? "GET",
4570
- headers,
4571
- body: reqBody
4572
- });
4573
- let body = {};
4574
- try {
4575
- body = await res.json();
4576
- } catch {
4577
- }
4578
- return { status: res.status, body };
4579
- }
4580
- function apiError(what, r) {
4581
- const detail = typeof r.body.error === "string" ? r.body.error : r.body.details !== void 0 ? JSON.stringify(r.body.details) : "";
4582
- const hint = r.status === 401 ? " (run `vos login`, or set VOS_API_KEY \u2014 mint a key at https://vos.so/app/api)" : "";
4583
- return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
4584
- }
4585
- var SYNC_STATE_NAME = "vos.json";
4586
- function readJsonFile(file) {
4587
- try {
4588
- return JSON.parse(readFileSync2(file, "utf8"));
4589
- } catch {
4590
- return null;
4591
- }
4592
- }
4593
- function readSyncState(dir) {
4594
- const own = readJsonFile(join6(dir, SYNC_STATE_NAME));
4595
- if (own && typeof own.vosId === "string") {
4596
- return {
4597
- vosId: own.vosId,
4598
- versionId: typeof own.versionId === "string" ? own.versionId : null,
4599
- ...typeof own.pushedAt === "string" ? { pushedAt: own.pushedAt } : {},
4600
- ...typeof own.title === "string" ? { title: own.title } : {},
4601
- ...typeof own.slug === "string" ? { slug: own.slug } : {},
4602
- ...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
4603
- };
4604
- }
4605
- const push = readJsonFile(join6(dir, "push.json"));
4606
- if (push && typeof push.vosId === "string") {
4607
- return {
4608
- vosId: push.vosId,
4609
- versionId: typeof push.versionId === "string" ? push.versionId : null,
4610
- ...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
4611
- };
4612
- }
4613
- const meta = readJsonFile(join6(dir, "meta.json"));
4614
- if (meta && typeof meta.id === "string") {
4615
- return {
4616
- vosId: meta.id,
4617
- versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
4618
- ...typeof meta.title === "string" ? { title: meta.title } : {},
4619
- ...typeof meta.slug === "string" ? { slug: meta.slug } : {},
4620
- ...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
4621
- };
4622
- }
4623
- return null;
4624
- }
4625
- function writeSyncState(dir, patch) {
4626
- const merged = {
4627
- ...readSyncState(dir) ?? { versionId: null },
4628
- ...patch
4629
- };
4630
- writeFileSync(
4631
- join6(dir, SYNC_STATE_NAME),
4632
- `${JSON.stringify(merged, null, 2)}
4633
- `
4634
- );
4635
- return merged;
4636
- }
4637
- function formatChanges(changes) {
4638
- const lines = [];
4639
- for (const ch of changes) {
4640
- const label = ch.label ? ` \xB7 ${ch.label}` : "";
4641
- const who = ch.origin === "studio" ? "human" : ch.origin ?? "unknown";
4642
- lines.push(
4643
- `v${String(ch.versionNumber ?? "?")} (${who}${label}): ${ch.summary ?? ""}`
4644
- );
4645
- if (ch.note) lines.push(` note: ${ch.note}`);
4646
- }
4647
- return lines;
4648
- }
4649
-
4650
- // src/plugin/posterDoc.ts
4600
+ docStillTime as docStillTime2,
4601
+ migrateHostedDoc,
4602
+ overlayRect,
4603
+ resolveOverlayStyle
4604
+ } from "@vosjs/studio-core";
4651
4605
  var POSTER_CLASSES = [
4652
4606
  "landscape",
4653
4607
  "square",
@@ -4667,8 +4621,8 @@ var roleFor = (cls) => `poster-${cls}`;
4667
4621
  async function readPosterAt(cls, path, takeDir, from) {
4668
4622
  let file = path;
4669
4623
  let docDir = path;
4670
- if (existsSync6(join7(path, "doc.json"))) {
4671
- file = join7(path, "doc.json");
4624
+ if (existsSync6(join6(path, "doc.json"))) {
4625
+ file = join6(path, "doc.json");
4672
4626
  } else if (!/\.json$/i.test(path) || !existsSync6(path)) {
4673
4627
  return null;
4674
4628
  } else {
@@ -4676,7 +4630,7 @@ async function readPosterAt(cls, path, takeDir, from) {
4676
4630
  }
4677
4631
  let raw;
4678
4632
  try {
4679
- raw = JSON.parse(await readFile5(file, "utf8"));
4633
+ raw = JSON.parse(await readFile6(file, "utf8"));
4680
4634
  } catch {
4681
4635
  return null;
4682
4636
  }
@@ -4686,7 +4640,7 @@ async function readPosterAt(cls, path, takeDir, from) {
4686
4640
  const doc = migrateHostedDoc(
4687
4641
  raw
4688
4642
  );
4689
- const ownFootage = existsSync6(join7(docDir, "meta.json")) && existsSync6(join7(docDir, source.videoKey));
4643
+ const ownFootage = existsSync6(join6(docDir, "meta.json")) && existsSync6(join6(docDir, source.videoKey));
4690
4644
  const sync = readSyncState(docDir);
4691
4645
  return {
4692
4646
  cls,
@@ -4709,10 +4663,10 @@ async function findPosterDocs(takeDir, launchRoles) {
4709
4663
  candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
4710
4664
  }
4711
4665
  candidates.push({
4712
- path: join7(takeDir, "poster", cls),
4666
+ path: join6(takeDir, "poster", cls),
4713
4667
  from: `poster/${cls}`
4714
4668
  });
4715
- candidates.push({ path: join7(takeDir, "poster"), from: "poster" });
4669
+ candidates.push({ path: join6(takeDir, "poster"), from: "poster" });
4716
4670
  for (const c of candidates) {
4717
4671
  const ref = await readPosterAt(cls, c.path, takeDir, c.from);
4718
4672
  if (ref) {
@@ -4808,19 +4762,19 @@ function resolveChannels(raw) {
4808
4762
  return out;
4809
4763
  }
4810
4764
  async function readBrandBesideTake(dir, explicit) {
4811
- const candidates = explicit ? [explicit] : [join8(dir, "BRAND.md"), join8(dir, "..", "BRAND.md")];
4765
+ const candidates = explicit ? [explicit] : [join7(dir, "BRAND.md"), join7(dir, "..", "BRAND.md")];
4812
4766
  for (const file of candidates) {
4813
4767
  if (!existsSync7(file)) continue;
4814
- const roles = parseFrontmatter(await readFile6(file, "utf8"));
4768
+ const roles = parseFrontmatter(await readFile7(file, "utf8"));
4815
4769
  return { file, roles };
4816
4770
  }
4817
4771
  return null;
4818
4772
  }
4819
4773
  async function readLaunchBesideTake(dir, explicit) {
4820
- const candidates = explicit ? [explicit] : [join8(dir, "LAUNCH.md"), join8(dir, "..", "LAUNCH.md")];
4774
+ const candidates = explicit ? [explicit] : [join7(dir, "LAUNCH.md"), join7(dir, "..", "LAUNCH.md")];
4821
4775
  for (const file of candidates) {
4822
4776
  if (!existsSync7(file)) continue;
4823
- return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
4777
+ return { file, roles: parseFrontmatter(await readFile7(file, "utf8")) };
4824
4778
  }
4825
4779
  return null;
4826
4780
  }
@@ -4884,7 +4838,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4884
4838
  const meta = doc.source.meta;
4885
4839
  const vw = meta.captureWidth ?? meta.width;
4886
4840
  const vh = meta.captureHeight ?? meta.height;
4887
- const probeDir = await mkdtemp(join8(tmpdir(), "vos-moments-"));
4841
+ const probeDir = await mkdtemp(join7(tmpdir(), "vos-moments-"));
4888
4842
  try {
4889
4843
  const probe2 = await framesTake(browser, dir, {
4890
4844
  times: candidates.map((c) => c.time),
@@ -4898,7 +4852,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4898
4852
  });
4899
4853
  const byTime = /* @__PURE__ */ new Map();
4900
4854
  for (const frame of probe2.frames) {
4901
- const img = decodePng(new Uint8Array(await readFile6(frame.file)));
4855
+ const img = decodePng(new Uint8Array(await readFile7(frame.file)));
4902
4856
  if (!img) continue;
4903
4857
  const whole = { x: 0, y: 0, w: img.w, h: img.h };
4904
4858
  byTime.set(+frame.time.toFixed(3), {
@@ -4971,7 +4925,7 @@ async function deliverTake(browser, dir, opts) {
4971
4925
  const doc = take.doc;
4972
4926
  const duration = docOutputDuration2(doc);
4973
4927
  const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
4974
- const outDir = resolve4(opts.outDir ?? join8(dir, "kit"));
4928
+ const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
4975
4929
  await mkdir5(outDir, { recursive: true });
4976
4930
  const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
4977
4931
  const assets = [];
@@ -5032,7 +4986,7 @@ async function deliverTake(browser, dir, opts) {
5032
4986
  opts.onPhase?.(
5033
4987
  `${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
5034
4988
  );
5035
- const shotDir = await mkdtemp(join8(tmpdir(), "vos-poster-"));
4989
+ const shotDir = await mkdtemp(join7(tmpdir(), "vos-poster-"));
5036
4990
  try {
5037
4991
  const captured = await framesTake(browser, ref.takeDir, {
5038
4992
  times: [time],
@@ -5042,7 +4996,7 @@ async function deliverTake(browser, dir, opts) {
5042
4996
  doc: ref.doc,
5043
4997
  overrides: opts.overrides
5044
4998
  });
5045
- const to = join8(outDir, `${d.id}.png`);
4999
+ const to = join7(outDir, `${d.id}.png`);
5046
5000
  await rename5(captured.frames[0].file, to);
5047
5001
  const bytes = (await stat2(to)).size;
5048
5002
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -5114,7 +5068,7 @@ async function deliverTake(browser, dir, opts) {
5114
5068
  }
5115
5069
  }
5116
5070
  opts.onPhase?.(`${label} (${specWords(d)})`);
5117
- const outFile = join8(outDir, `${d.id}.${d.format}`);
5071
+ const outFile = join7(outDir, `${d.id}.${d.format}`);
5118
5072
  const bitrate = d.maxBytes !== void 0 ? Math.min(
5119
5073
  1e7,
5120
5074
  Math.floor(d.maxBytes * 8 / seconds * 0.85)
@@ -5161,7 +5115,7 @@ async function deliverTake(browser, dir, opts) {
5161
5115
  for (let i = 0; i < captured.frames.length; i++) {
5162
5116
  const frame = captured.frames[i];
5163
5117
  const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
5164
- const to = join8(outDir, name);
5118
+ const to = join7(outDir, name);
5165
5119
  await rename5(frame.file, to);
5166
5120
  const bytes = (await stat2(to)).size;
5167
5121
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -5199,15 +5153,15 @@ async function deliverTake(browser, dir, opts) {
5199
5153
  skipped,
5200
5154
  assets
5201
5155
  };
5202
- const kitFile = join8(outDir, "kit.json");
5156
+ const kitFile = join7(outDir, "kit.json");
5203
5157
  await writeFile7(kitFile, JSON.stringify(kit, null, 2));
5204
5158
  return { kit, kitFile, outDir };
5205
5159
  }
5206
5160
 
5207
5161
  // src/plugin/markAsset.ts
5208
- import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
5162
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
5209
5163
  import { existsSync as existsSync8 } from "fs";
5210
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
5164
+ import { isAbsolute as isAbsolute2, join as join8, resolve as resolve5 } from "path";
5211
5165
  function markExtension(url) {
5212
5166
  const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
5213
5167
  if (!m) return null;
@@ -5290,7 +5244,7 @@ async function fetchBrandMarks(takeDir, roles) {
5290
5244
  } else {
5291
5245
  const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
5292
5246
  if (!existsSync8(file)) throw new Error("no such file");
5293
- bytes = new Uint8Array(await readFile7(file));
5247
+ bytes = new Uint8Array(await readFile8(file));
5294
5248
  }
5295
5249
  } catch (e) {
5296
5250
  out.notes.push(
@@ -5305,8 +5259,8 @@ async function fetchBrandMarks(takeDir, roles) {
5305
5259
  );
5306
5260
  return null;
5307
5261
  }
5308
- await mkdir6(join9(takeDir, "brand"), { recursive: true });
5309
- await writeFile8(join9(takeDir, "brand", `${name}.${ext}`), bytes);
5262
+ await mkdir6(join8(takeDir, "brand"), { recursive: true });
5263
+ await writeFile8(join8(takeDir, "brand", `${name}.${ext}`), bytes);
5310
5264
  return { key: `/brand/${name}.${ext}`, aspect };
5311
5265
  };
5312
5266
  out.light = await one("logoUrl", "mark");
@@ -5343,8 +5297,8 @@ async function fetchMusicCatalog(origin) {
5343
5297
 
5344
5298
  // src/plugin/judge.ts
5345
5299
  import { existsSync as existsSync9 } from "fs";
5346
- import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
5347
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join10, resolve as resolve6 } from "path";
5300
+ import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
5301
+ import { dirname as dirname2, isAbsolute as isAbsolute3, join as join9, resolve as resolve6 } from "path";
5348
5302
  import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
5349
5303
 
5350
5304
  // src/plugin/shotBake.ts
@@ -5515,28 +5469,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
5515
5469
  return { w, h, data: out };
5516
5470
  }
5517
5471
  async function judgeKit(kitPath, manifestPath, outDir) {
5518
- const kit = JSON.parse(await readFile8(kitPath, "utf8"));
5472
+ const kit = JSON.parse(await readFile9(kitPath, "utf8"));
5519
5473
  const manifest = JSON.parse(
5520
- await readFile8(manifestPath, "utf8")
5474
+ await readFile9(manifestPath, "utf8")
5521
5475
  );
5522
5476
  const manifestDir = dirname2(resolve6(manifestPath));
5523
5477
  const kitDir = dirname2(resolve6(kitPath));
5524
- const out = resolve6(outDir ?? join10(kitDir, "judge"));
5478
+ const out = resolve6(outDir ?? join9(kitDir, "judge"));
5525
5479
  await mkdir7(out, { recursive: true });
5526
5480
  const sheets = [];
5527
5481
  const skipped = [];
5528
5482
  const refCache = /* @__PURE__ */ new Map();
5529
5483
  const loadRef = async (file) => {
5530
5484
  if (refCache.has(file)) return refCache.get(file) ?? null;
5531
- const p = join10(manifestDir, file);
5532
- const img = existsSync9(p) ? decodePng(new Uint8Array(await readFile8(p))) : null;
5485
+ const p = join9(manifestDir, file);
5486
+ const img = existsSync9(p) ? decodePng(new Uint8Array(await readFile9(p))) : null;
5533
5487
  refCache.set(file, img);
5534
5488
  return img;
5535
5489
  };
5536
5490
  for (const a of kit.assets) {
5537
5491
  if (!/\.png$/i.test(a.path)) continue;
5538
- const file = isAbsolute3(a.path) && existsSync9(a.path) ? a.path : join10(kitDir, a.path.split("/").pop() ?? a.path);
5539
- const img = existsSync9(file) ? decodePng(new Uint8Array(await readFile8(file))) : null;
5492
+ const file = isAbsolute3(a.path) && existsSync9(a.path) ? a.path : join9(kitDir, a.path.split("/").pop() ?? a.path);
5493
+ const img = existsSync9(file) ? decodePng(new Uint8Array(await readFile9(file))) : null;
5540
5494
  if (!img) {
5541
5495
  skipped.push(`${a.destination ?? a.path}: unreadable`);
5542
5496
  continue;
@@ -5567,8 +5521,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5567
5521
  const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
5568
5522
  const nameA = `${id}--A.png`;
5569
5523
  const nameB = `${id}--B.png`;
5570
- await writeFile9(join10(out, nameA), encodePng(composeSheet(img, refImg)));
5571
- await writeFile9(join10(out, nameB), encodePng(composeSheet(refImg, img)));
5524
+ await writeFile9(join9(out, nameA), encodePng(composeSheet(img, refImg)));
5525
+ await writeFile9(join9(out, nameB), encodePng(composeSheet(refImg, img)));
5572
5526
  const rubric = [
5573
5527
  `# ${id} against ${ref.id} (${ref.file})`,
5574
5528
  "",
@@ -5588,7 +5542,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5588
5542
  "In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
5589
5543
  ""
5590
5544
  ].join("\n");
5591
- await writeFile9(join10(out, `${id}.md`), rubric);
5545
+ await writeFile9(join9(out, `${id}.md`), rubric);
5592
5546
  sheets.push({
5593
5547
  asset: id,
5594
5548
  reference: ref.id,
@@ -5597,7 +5551,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5597
5551
  rubric: `${id}.md`
5598
5552
  });
5599
5553
  }
5600
- const verdictFile = join10(out, "judge.json");
5554
+ const verdictFile = join9(out, "judge.json");
5601
5555
  if (!existsSync9(verdictFile)) {
5602
5556
  await writeFile9(
5603
5557
  verdictFile,
@@ -5651,8 +5605,8 @@ function catalogFamily(family) {
5651
5605
 
5652
5606
  // src/plugin/callout.ts
5653
5607
  import { existsSync as existsSync10 } from "fs";
5654
- import { readFile as readFile9 } from "fs/promises";
5655
- import { join as join11 } from "path";
5608
+ import { readFile as readFile10 } from "fs/promises";
5609
+ import { join as join10 } from "path";
5656
5610
  import { lerpArray, sample } from "@vosjs/timeline";
5657
5611
  import {
5658
5612
  CALLOUT_DEFAULT_BODY_PX,
@@ -5849,13 +5803,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5849
5803
  const out = /* @__PURE__ */ new Map();
5850
5804
  if (!times.length) return out;
5851
5805
  const bare = { ...doc, overlays: [] };
5852
- const outDir = join11(dir, ".ground-check");
5806
+ const outDir = join10(dir, ".ground-check");
5853
5807
  const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
5854
5808
  for (const f of res.frames) {
5855
5809
  if (!existsSync10(f.file)) continue;
5856
5810
  out.set(
5857
5811
  +f.time.toFixed(3),
5858
- decodePng(new Uint8Array(await readFile9(f.file)))
5812
+ decodePng(new Uint8Array(await readFile10(f.file)))
5859
5813
  );
5860
5814
  }
5861
5815
  return out;
@@ -5864,7 +5818,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5864
5818
  // src/plugin/kitPicture.ts
5865
5819
  import { execFile } from "child_process";
5866
5820
  import { promisify } from "util";
5867
- import { readFile as readFile10 } from "fs/promises";
5821
+ import { readFile as readFile11 } from "fs/promises";
5868
5822
  var execFileP = promisify(execFile);
5869
5823
  var SUBJECT_BAND = { min: 0.6, max: 0.92 };
5870
5824
  var BLANK_INK = 0.12;
@@ -6140,7 +6094,7 @@ async function pictureChecks(assets) {
6140
6094
  let ffmpeg = null;
6141
6095
  for (const a of assets) {
6142
6096
  if (/\.png$/i.test(a.file)) {
6143
- const img = decodePng(new Uint8Array(await readFile10(a.file)));
6097
+ const img = decodePng(new Uint8Array(await readFile11(a.file)));
6144
6098
  if (!img) {
6145
6099
  findings.push({
6146
6100
  code: "unreadable",
@@ -6234,8 +6188,8 @@ function formatFinding(f) {
6234
6188
 
6235
6189
  // src/plugin/validateKit.ts
6236
6190
  import { existsSync as existsSync11 } from "fs";
6237
- import { readFile as readFile11, stat as stat3 } from "fs/promises";
6238
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join12 } from "path";
6191
+ import { readFile as readFile12, stat as stat3 } from "fs/promises";
6192
+ import { dirname as dirname3, isAbsolute as isAbsolute4, join as join11 } from "path";
6239
6193
  import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
6240
6194
  var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
6241
6195
  function pngDimensions(bytes) {
@@ -6254,7 +6208,7 @@ async function probeVideo(path) {
6254
6208
  const MB = await import("mediabunny");
6255
6209
  const input = new MB.Input({
6256
6210
  formats: MB.ALL_FORMATS,
6257
- source: new MB.BufferSource(new Uint8Array(await readFile11(path)))
6211
+ source: new MB.BufferSource(new Uint8Array(await readFile12(path)))
6258
6212
  });
6259
6213
  try {
6260
6214
  const track = await input.getPrimaryVideoTrack();
@@ -6275,7 +6229,7 @@ async function validateKit(kitPath, opts = {}) {
6275
6229
  const measured = [];
6276
6230
  let kit;
6277
6231
  try {
6278
- kit = JSON.parse(await readFile11(kitPath, "utf8"));
6232
+ kit = JSON.parse(await readFile12(kitPath, "utf8"));
6279
6233
  } catch (e) {
6280
6234
  return {
6281
6235
  valid: false,
@@ -6295,7 +6249,7 @@ async function validateKit(kitPath, opts = {}) {
6295
6249
  const base = dirname3(kitPath);
6296
6250
  const resolvePath = (p) => {
6297
6251
  if (isAbsolute4(p) && existsSync11(p)) return p;
6298
- const beside = join12(base, p.split("/").pop() ?? p);
6252
+ const beside = join11(base, p.split("/").pop() ?? p);
6299
6253
  return beside;
6300
6254
  };
6301
6255
  const perDestination = /* @__PURE__ */ new Map();
@@ -6321,7 +6275,7 @@ async function validateKit(kitPath, opts = {}) {
6321
6275
  let h = null;
6322
6276
  let seconds = null;
6323
6277
  if (/\.(png|jpe?g|webp)$/i.test(file)) {
6324
- const head = Buffer.from(await readFile11(file));
6278
+ const head = Buffer.from(await readFile12(file));
6325
6279
  const kind = sniffImage(head);
6326
6280
  if (file.toLowerCase().endsWith(".png") && kind !== "png")
6327
6281
  problems.push(
@@ -6430,8 +6384,8 @@ async function validateKit(kitPath, opts = {}) {
6430
6384
  }
6431
6385
 
6432
6386
  // src/plugin/recorder.ts
6433
- import { writeFileSync as writeFileSync2 } from "fs";
6434
- import { join as join13 } from "path";
6387
+ import { writeFileSync } from "fs";
6388
+ import { join as join12 } from "path";
6435
6389
  import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
6436
6390
  async function recordTake(browser, url, actions, paths, log, opts = {}) {
6437
6391
  const dry = opts.dryRun === true;
@@ -6441,7 +6395,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6441
6395
  actions,
6442
6396
  {
6443
6397
  frame: (file, bytes) => {
6444
- writeFileSync2(join13(paths.framesDir, file), bytes);
6398
+ writeFileSync(join12(paths.framesDir, file), bytes);
6445
6399
  }
6446
6400
  },
6447
6401
  log,
@@ -6493,11 +6447,11 @@ import {
6493
6447
 
6494
6448
  // src/plugin/session.ts
6495
6449
  import { spawn } from "child_process";
6496
- import { existsSync as existsSync12, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
6497
- import { homedir as homedir2 } from "os";
6498
- import { join as join14 } from "path";
6450
+ import { existsSync as existsSync12, mkdirSync, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
6451
+ import { homedir } from "os";
6452
+ import { join as join13 } from "path";
6499
6453
  import { chromium } from "playwright";
6500
- var SESSIONS_DIR = join14(homedir2(), ".config", "vos", "sessions");
6454
+ var SESSIONS_DIR = join13(homedir(), ".config", "vos", "sessions");
6501
6455
  var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
6502
6456
  function sessionNameVerdict(name) {
6503
6457
  if (!name) return "a session needs a name: --name <app>";
@@ -6506,20 +6460,20 @@ function sessionNameVerdict(name) {
6506
6460
  return null;
6507
6461
  }
6508
6462
  function sessionDir(name) {
6509
- return join14(SESSIONS_DIR, name);
6463
+ return join13(SESSIONS_DIR, name);
6510
6464
  }
6511
6465
  function systemChromePath() {
6512
6466
  const explicit = process.env.VOS_BROWSER_PATH;
6513
6467
  if (explicit) return explicit;
6514
6468
  const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
6515
- join14(
6469
+ join13(
6516
6470
  process.env["PROGRAMFILES"] ?? "C:\\Program Files",
6517
6471
  "Google",
6518
6472
  "Chrome",
6519
6473
  "Application",
6520
6474
  "chrome.exe"
6521
6475
  ),
6522
- join14(
6476
+ join13(
6523
6477
  process.env["LOCALAPPDATA"] ?? "",
6524
6478
  "Google",
6525
6479
  "Chrome",
@@ -6541,7 +6495,7 @@ function systemChromePath() {
6541
6495
  async function openSession(name, url, opts = { log: () => {
6542
6496
  } }) {
6543
6497
  const dir = sessionDir(name);
6544
- mkdirSync2(dir, { recursive: true, mode: 448 });
6498
+ mkdirSync(dir, { recursive: true, mode: 448 });
6545
6499
  const exe = systemChromePath();
6546
6500
  const args = [
6547
6501
  `--user-data-dir=${dir}`,
@@ -6613,7 +6567,7 @@ function summaryLine(s) {
6613
6567
  function listSessions() {
6614
6568
  if (!existsSync12(SESSIONS_DIR)) return [];
6615
6569
  return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
6616
- const st = statSync3(join14(SESSIONS_DIR, d.name));
6570
+ const st = statSync3(join13(SESSIONS_DIR, d.name));
6617
6571
  return {
6618
6572
  name: d.name,
6619
6573
  ageDays: Math.floor(
@@ -6690,8 +6644,8 @@ async function encodeRecording(browser, takeDir, onProgress) {
6690
6644
 
6691
6645
  // src/plugin/plan.ts
6692
6646
  import { existsSync as existsSync13 } from "fs";
6693
- import { readFile as readFile12 } from "fs/promises";
6694
- import { join as join15 } from "path";
6647
+ import { readFile as readFile13 } from "fs/promises";
6648
+ import { join as join14 } from "path";
6695
6649
  import {
6696
6650
  DEFAULT_FRAME_STYLE,
6697
6651
  STYLE_FIELDS,
@@ -6913,10 +6867,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
6913
6867
  // src/plugin/plan.ts
6914
6868
  var overlaps = (a, b) => a.in < b.out && b.in < a.out;
6915
6869
  async function readDigestActivity(dir) {
6916
- const file = join15(dir, "digest", "digest.json");
6870
+ const file = join14(dir, "digest", "digest.json");
6917
6871
  if (!existsSync13(file)) return null;
6918
6872
  try {
6919
- const d = JSON.parse(await readFile12(file, "utf8"));
6873
+ const d = JSON.parse(await readFile13(file, "utf8"));
6920
6874
  return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
6921
6875
  } catch {
6922
6876
  return null;
@@ -6977,7 +6931,7 @@ async function planTake(dir, opts = {}) {
6977
6931
  const take = await loadTake(dir);
6978
6932
  const { meta, cursor } = take;
6979
6933
  const activity = await readDigestActivity(dir);
6980
- const ingest = opts.backdrop ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) } : {};
6934
+ const ingest = opts.footage ? { footage: true } : opts.backdrop ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) } : {};
6981
6935
  let doc;
6982
6936
  let fresh;
6983
6937
  let layout;
@@ -7129,16 +7083,16 @@ async function planTake(dir, opts = {}) {
7129
7083
  }
7130
7084
 
7131
7085
  // src/plugin/sync.ts
7132
- import { createHash } from "crypto";
7133
- import { existsSync as existsSync14, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
7134
- import { readFile as readFile13 } from "fs/promises";
7086
+ import { createHash as createHash2 } from "crypto";
7087
+ import { existsSync as existsSync14, readFileSync as readFileSync2, readdirSync as readdirSync3 } from "fs";
7088
+ import { readFile as readFile14 } from "fs/promises";
7135
7089
  import { createInterface } from "readline/promises";
7136
- import { basename as basename3, join as join17 } from "path";
7090
+ import { basename as basename3, join as join16 } from "path";
7137
7091
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
7138
7092
 
7139
7093
  // src/plugin/folder.ts
7140
7094
  import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
7141
- import { join as join16 } from "path";
7095
+ import { join as join15 } from "path";
7142
7096
  import { recipeHints } from "@vosjs/shared/frontmatter";
7143
7097
  import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7144
7098
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
@@ -7314,18 +7268,18 @@ async function cmdPull(argv) {
7314
7268
  throw new Error(apiError(`pull folder ${folder.slug}`, res));
7315
7269
  const payload = res.body;
7316
7270
  const out = strFlag(flags, "out") ?? folder.slug;
7317
- await mkdir8(join16(out, "recipes"), { recursive: true });
7271
+ await mkdir8(join15(out, "recipes"), { recursive: true });
7318
7272
  const lines = [];
7319
7273
  const recipes = [];
7320
7274
  for (const rec of payload.recipes) {
7321
- const path = join16(out, "recipes", safeName(rec.filename));
7275
+ const path = join15(out, "recipes", safeName(rec.filename));
7322
7276
  await writeFile10(path, rec.body ?? "");
7323
7277
  recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
7324
7278
  }
7325
7279
  for (const rec of payload.inheritedRecipes) {
7326
7280
  const from = safeName(rec.folderSlug ?? "inherited");
7327
- await mkdir8(join16(out, "recipes", "_inherited", from), { recursive: true });
7328
- const path = join16(
7281
+ await mkdir8(join15(out, "recipes", "_inherited", from), { recursive: true });
7282
+ const path = join15(
7329
7283
  out,
7330
7284
  "recipes",
7331
7285
  "_inherited",
@@ -7340,7 +7294,7 @@ async function cmdPull(argv) {
7340
7294
  }
7341
7295
  const members = [];
7342
7296
  for (const v of payload.voses) {
7343
- const dir = join16(out, "members", safeName(v.slug || v.title || v.id));
7297
+ const dir = join15(out, "members", safeName(v.slug || v.title || v.id));
7344
7298
  await mkdir8(dir, { recursive: true });
7345
7299
  const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
7346
7300
  if (cfg.status !== 200) {
@@ -7348,7 +7302,7 @@ async function cmdPull(argv) {
7348
7302
  continue;
7349
7303
  }
7350
7304
  await writeFile10(
7351
- join16(dir, "config.json"),
7305
+ join15(dir, "config.json"),
7352
7306
  JSON.stringify(cfg.body.config, null, 2)
7353
7307
  );
7354
7308
  let take = false;
@@ -7360,7 +7314,7 @@ async function cmdPull(argv) {
7360
7314
  );
7361
7315
  if (doc.status === 200) {
7362
7316
  await writeFile10(
7363
- join16(dir, "doc.json"),
7317
+ join15(dir, "doc.json"),
7364
7318
  JSON.stringify(doc.body, null, 2)
7365
7319
  );
7366
7320
  take = true;
@@ -7430,144 +7384,6 @@ async function cmdFolder(argv) {
7430
7384
  }
7431
7385
  }
7432
7386
 
7433
- // src/plugin/uploadAsset.ts
7434
- var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
7435
- var PART_ATTEMPTS = 3;
7436
- function planParts(totalBytes, partBytes) {
7437
- if (totalBytes <= 0 || partBytes <= 0) return [];
7438
- const parts = [];
7439
- for (let start = 0; start < totalBytes; start += partBytes) {
7440
- parts.push({
7441
- partNumber: parts.length + 1,
7442
- start,
7443
- end: Math.min(start + partBytes, totalBytes)
7444
- });
7445
- }
7446
- return parts;
7447
- }
7448
- function failed(what, r) {
7449
- const detail = typeof r.body.error === "string" ? r.body.error : "";
7450
- return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
7451
- }
7452
- function asAsset(body) {
7453
- return {
7454
- id: String(body.id),
7455
- url: String(body.url),
7456
- size: typeof body.size === "number" ? body.size : 0,
7457
- reused: body.reused === true
7458
- };
7459
- }
7460
- function transient(status) {
7461
- return status >= 500 || status === 429;
7462
- }
7463
- var wait = (ms) => new Promise((r) => setTimeout(r, ms));
7464
- async function uploadAsset(target, bytes, opts) {
7465
- return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
7466
- }
7467
- async function uploadWhole(target, bytes, opts) {
7468
- const res = await apiJson(target.origin, "/api/assets/recording", {
7469
- method: "POST",
7470
- key: target.key,
7471
- headers: {
7472
- "Content-Type": opts.contentType,
7473
- "Content-Length": String(bytes.length),
7474
- "X-Filename": opts.filename,
7475
- "X-Content-Hash": opts.contentHash,
7476
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
7477
- },
7478
- raw: bytes
7479
- });
7480
- if (res.status !== 201 && res.status !== 200)
7481
- throw failed("upload failed", res);
7482
- return asAsset(res.body);
7483
- }
7484
- async function uploadInParts(target, bytes, opts) {
7485
- const begun = await apiJson(
7486
- target.origin,
7487
- "/api/assets/recording/multipart",
7488
- {
7489
- method: "POST",
7490
- key: target.key,
7491
- body: {
7492
- filename: opts.filename,
7493
- contentType: opts.contentType,
7494
- size: bytes.length,
7495
- contentHash: opts.contentHash,
7496
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
7497
- }
7498
- }
7499
- );
7500
- if (begun.status !== 201 && begun.status !== 200) {
7501
- throw failed("upload failed", begun);
7502
- }
7503
- if (begun.body.reused === true || typeof begun.body.url === "string") {
7504
- return asAsset(begun.body);
7505
- }
7506
- const uploadId = String(begun.body.id);
7507
- const partBytes = Number(begun.body.partBytes);
7508
- if (!Number.isFinite(partBytes) || partBytes <= 0) {
7509
- throw new Error("upload failed: the platform returned no part size");
7510
- }
7511
- const parts = planParts(bytes.length, partBytes);
7512
- const etags = [];
7513
- try {
7514
- for (const part of parts) {
7515
- const chunk2 = bytes.subarray(part.start, part.end);
7516
- let sent = null;
7517
- for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
7518
- try {
7519
- sent = await apiJson(
7520
- target.origin,
7521
- `/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
7522
- {
7523
- method: "PUT",
7524
- key: target.key,
7525
- headers: {
7526
- "Content-Type": "application/octet-stream",
7527
- "Content-Length": String(chunk2.length)
7528
- },
7529
- raw: chunk2
7530
- }
7531
- );
7532
- } catch (networkError) {
7533
- if (attempt === PART_ATTEMPTS) throw networkError;
7534
- await wait(500 * 2 ** (attempt - 1));
7535
- continue;
7536
- }
7537
- if (sent.status === 200) break;
7538
- if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
7539
- throw failed(`part ${part.partNumber} failed`, sent);
7540
- }
7541
- await wait(500 * 2 ** (attempt - 1));
7542
- }
7543
- if (!sent || sent.status !== 200) {
7544
- throw new Error(`part ${part.partNumber} failed`);
7545
- }
7546
- etags.push({
7547
- partNumber: Number(sent.body.partNumber),
7548
- etag: String(sent.body.etag)
7549
- });
7550
- opts.onPart?.(etags.length, parts.length);
7551
- }
7552
- const sealed = await apiJson(
7553
- target.origin,
7554
- `/api/assets/recording/multipart/${uploadId}/complete`,
7555
- { method: "POST", key: target.key, body: { parts: etags } }
7556
- );
7557
- if (sealed.status !== 201 && sealed.status !== 200) {
7558
- throw failed("upload failed", sealed);
7559
- }
7560
- return asAsset(sealed.body);
7561
- } catch (err) {
7562
- await apiJson(
7563
- target.origin,
7564
- `/api/assets/recording/multipart/${uploadId}`,
7565
- { method: "DELETE", key: target.key }
7566
- ).catch(() => void 0);
7567
- throw err;
7568
- }
7569
- }
7570
-
7571
7387
  // src/plugin/sync.ts
7572
7388
  function apiContext(flags) {
7573
7389
  return {
@@ -7634,8 +7450,8 @@ function storageStateInDir(dir) {
7634
7450
  if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
7635
7451
  continue;
7636
7452
  try {
7637
- const v = JSON.parse(readFileSync3(join17(dir, f), "utf8"));
7638
- if (looksLikeStorageState(v)) return join17(dir, f);
7453
+ const v = JSON.parse(readFileSync2(join16(dir, f), "utf8"));
7454
+ if (looksLikeStorageState(v)) return join16(dir, f);
7639
7455
  } catch {
7640
7456
  }
7641
7457
  }
@@ -7705,8 +7521,8 @@ async function pushTake(dir, flags, r) {
7705
7521
  throw new Error("push cancelled");
7706
7522
  }
7707
7523
  }
7708
- const bytes = await readFile13(take.paths.recording);
7709
- const hash = createHash("sha256").update(bytes).digest("hex");
7524
+ const bytes = await readFile14(take.paths.recording);
7525
+ const hash = createHash2("sha256").update(bytes).digest("hex");
7710
7526
  const { type: contentType } = resolveMediaType({
7711
7527
  head: bytes.subarray(0, MEDIA_HEAD_BYTES),
7712
7528
  filename: take.paths.recordingName
@@ -7747,39 +7563,7 @@ async function pushTake(dir, flags, r) {
7747
7563
  r.log(" mic track is local-only \u2014 dropped from the push");
7748
7564
  delete docForPush.source.micKey;
7749
7565
  }
7750
- for (const ref of docMediaRefs(docForPush)) {
7751
- const file = join17(dir, takeRelativeFile(ref.key));
7752
- if (!existsSync14(file)) {
7753
- r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
7754
- continue;
7755
- }
7756
- const media = await readFile13(file);
7757
- const mediaHash = createHash("sha256").update(media).digest("hex");
7758
- const { type } = resolveMediaType({
7759
- head: media.subarray(0, MEDIA_HEAD_BYTES),
7760
- filename: file
7761
- });
7762
- const name = nameForType(basename3(file), type);
7763
- if (name !== basename3(file)) {
7764
- r.log(` ${ref.where}: ${basename3(file)} holds ${type} \u2014 sent as ${name}`);
7765
- }
7766
- let put;
7767
- try {
7768
- put = await uploadAsset(ctx, new Uint8Array(media), {
7769
- filename: name,
7770
- contentType: type,
7771
- contentHash: mediaHash
7772
- });
7773
- } catch (e) {
7774
- throw new Error(
7775
- `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
7776
- );
7777
- }
7778
- ref.set(put.url);
7779
- r.log(
7780
- ` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
7781
- );
7782
- }
7566
+ await uploadDocRefs(docMediaRefs(docForPush), dir, ctx, (l) => r.log(l));
7783
7567
  const lowered = lowerToComposition3(docForPush);
7784
7568
  const config = { ...lowered.config, data: lowered.data };
7785
7569
  if (!state) {
@@ -7893,9 +7677,9 @@ async function pullTake(dir, flags, r) {
7893
7677
  };
7894
7678
  }
7895
7679
  let media2;
7896
- if (flags.media && existsSync14(join17(dir, "doc.json"))) {
7680
+ if (flags.media && existsSync14(join16(dir, "doc.json"))) {
7897
7681
  const local = JSON.parse(
7898
- await readFile13(join17(dir, "doc.json"), "utf8")
7682
+ await readFile14(join16(dir, "doc.json"), "utf8")
7899
7683
  );
7900
7684
  media2 = await pullMedia(ctx, dir, local, r.log);
7901
7685
  }
@@ -7959,7 +7743,7 @@ async function pullTake(dir, flags, r) {
7959
7743
  if (existsSync14(localRecording)) {
7960
7744
  doc.source.videoKey = basename3(localRecording);
7961
7745
  }
7962
- await writeJson(join17(dir, "doc.json"), doc, true);
7746
+ await writeJson(join16(dir, "doc.json"), doc, true);
7963
7747
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
7964
7748
  const versions = await api(ctx, `/vos/${vosId}/versions`);
7965
7749
  const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
@@ -7979,9 +7763,9 @@ async function pullTake(dir, flags, r) {
7979
7763
 
7980
7764
  // src/plugin/program.ts
7981
7765
  import { existsSync as existsSync15 } from "fs";
7982
- import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
7766
+ import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
7983
7767
  import { createInterface as createInterface2 } from "readline/promises";
7984
- import { basename as basename4, dirname as dirname4, join as join18 } from "path";
7768
+ import { basename as basename4, dirname as dirname4, join as join17 } from "path";
7985
7769
  import {
7986
7770
  CURRENT_CONFIG_VERSION,
7987
7771
  migrateConfig,
@@ -8121,7 +7905,8 @@ var BOOLEAN_FLAGS2 = /* @__PURE__ */ new Set([
8121
7905
  "check",
8122
7906
  "yes",
8123
7907
  "claimable",
8124
- "no-browser"
7908
+ "no-browser",
7909
+ "wait"
8125
7910
  ]);
8126
7911
  var MULTI_FLAGS = /* @__PURE__ */ new Set(["override"]);
8127
7912
  function preflightConfig(parsed) {
@@ -8186,7 +7971,7 @@ function preflightConfig(parsed) {
8186
7971
  }
8187
7972
  function resolveConfigPath(target) {
8188
7973
  if (target.endsWith(".json")) return target;
8189
- const inDir = join18(target, "config.json");
7974
+ const inDir = join17(target, "config.json");
8190
7975
  if (existsSync15(inDir)) return inDir;
8191
7976
  throw new UsageError(
8192
7977
  `${target} has no config.json (and is not a take \u2014 no doc.json)`
@@ -8215,7 +8000,7 @@ async function cmdFetch(argv) {
8215
8000
  const out = strFlag(flags, "out") ?? slug;
8216
8001
  await mkdir9(out, { recursive: true });
8217
8002
  await writeFile11(
8218
- join18(out, "config.json"),
8003
+ join17(out, "config.json"),
8219
8004
  JSON.stringify(cfg.body.config, null, 2)
8220
8005
  );
8221
8006
  const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
@@ -8240,12 +8025,12 @@ async function cmdFetch(argv) {
8240
8025
  const hosted = migrateHostedDoc4(doc.body);
8241
8026
  const { config: own } = await writeProgramDoc(out, hosted);
8242
8027
  if (own) {
8243
- await writeFile11(join18(out, "config.json"), JSON.stringify(own, null, 2));
8028
+ await writeFile11(join17(out, "config.json"), JSON.stringify(own, null, 2));
8244
8029
  }
8245
8030
  } else if (doc.status === 200) {
8246
8031
  take = true;
8247
8032
  const hosted = migrateHostedDoc4(doc.body);
8248
- await writeFile11(join18(out, "doc.json"), JSON.stringify(hosted, null, 2));
8033
+ await writeFile11(join17(out, "doc.json"), JSON.stringify(hosted, null, 2));
8249
8034
  if (flags.media === true) {
8250
8035
  const media = await pullMedia({ origin, key }, out, hosted, r.log);
8251
8036
  mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
@@ -8358,7 +8143,7 @@ async function cmdPushProgram(argv) {
8358
8143
  api: strFlag(flags, "api")
8359
8144
  });
8360
8145
  const dir = dirname4(source);
8361
- const parsed = JSON.parse(await readFile14(source, "utf8"));
8146
+ const parsed = JSON.parse(await readFile15(source, "utf8"));
8362
8147
  const pre = preflightConfig(parsed);
8363
8148
  if (!pre.ok || !pre.config) {
8364
8149
  for (const issue of pre.issues) r.log(`error ${issue}`);
@@ -8387,7 +8172,25 @@ async function cmdPushProgram(argv) {
8387
8172
  }
8388
8173
  const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
8389
8174
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8390
- const body = { title: title2, config };
8175
+ let claimDoc = programDoc;
8176
+ if (programDoc) {
8177
+ const sound = soundOf(programDoc);
8178
+ const local = docAudioRefs(sound);
8179
+ if (local.length) {
8180
+ const dropped = new Set(local.map((ref) => ref.key));
8181
+ claimDoc = {
8182
+ ...programDoc,
8183
+ audio: (sound.audio ?? []).filter((a) => !dropped.has(a.key))
8184
+ };
8185
+ r.log(
8186
+ `warning a claimable push carries no files, so ${local.length} local sound${local.length === 1 ? "" : "s"} (${[...dropped].join(", ")}) ${local.length === 1 ? "was" : "were"} left out. After the claim, push again with a key to add ${local.length === 1 ? "it" : "them"}`
8187
+ );
8188
+ }
8189
+ }
8190
+ const body = {
8191
+ title: title2,
8192
+ config: storedProgramConfig(config, claimDoc)
8193
+ };
8391
8194
  const slug = strFlag(flags, "slug");
8392
8195
  if (slug) body.slug = slug;
8393
8196
  const res = await apiJson(origin, "/api/claim", { method: "POST", body });
@@ -8407,6 +8210,15 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8407
8210
  return EXIT_OK;
8408
8211
  }
8409
8212
  const key = requireCredential(strFlag(flags, "key"));
8213
+ void flags.yes;
8214
+ if (programDoc) {
8215
+ await uploadDocRefs(
8216
+ docAudioRefs(soundOf(programDoc)),
8217
+ dir,
8218
+ { origin, key },
8219
+ (l) => r.log(l)
8220
+ );
8221
+ }
8410
8222
  const overrides = [
8411
8223
  ...multi.override ?? [],
8412
8224
  ...strFlag(flags, "overrides")?.split(",") ?? []
@@ -8476,6 +8288,13 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8476
8288
  }
8477
8289
  const watchUrl2 = `${origin}/vos/${vosId}`;
8478
8290
  const studioUrl2 = `${origin}/studio?vos=${vosId}`;
8291
+ const landed2 = flags.wait === true ? await waitForLanded({
8292
+ origin,
8293
+ key,
8294
+ vosId,
8295
+ versionId: typeof version.id === "string" ? version.id : null,
8296
+ log: (l) => r.log(l)
8297
+ }) : null;
8479
8298
  r.done(
8480
8299
  {
8481
8300
  id: vosId,
@@ -8483,11 +8302,17 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8483
8302
  versionNumber: version.versionNumber ?? null,
8484
8303
  base: base ?? null,
8485
8304
  watchUrl: watchUrl2,
8486
- studioUrl: studioUrl2
8305
+ studioUrl: studioUrl2,
8306
+ ...landed2 ? {
8307
+ still: landed2.still,
8308
+ thumbnailUrl: landed2.thumbnailUrl,
8309
+ previewUrl: landed2.previewUrl
8310
+ } : {}
8487
8311
  },
8488
8312
  `Pushed version ${String(version.versionNumber ?? "?")} of ${vosId}
8489
8313
  watch: ${watchUrl2}
8490
- studio: ${studioUrl2}`
8314
+ studio: ${studioUrl2}` + (landed2 ? `
8315
+ ${landedLines(landed2)}` : "")
8491
8316
  );
8492
8317
  return EXIT_OK;
8493
8318
  }
@@ -8523,20 +8348,35 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8523
8348
  });
8524
8349
  const watchUrl = `${origin}/vos/${created.id}`;
8525
8350
  const studioUrl = `${origin}/studio?vos=${created.id}`;
8351
+ const landed = flags.wait === true ? await waitForLanded({
8352
+ origin,
8353
+ key,
8354
+ vosId: created.id,
8355
+ versionId: created.currentVersionId,
8356
+ log: (l) => r.log(l)
8357
+ }) : null;
8526
8358
  r.done(
8527
8359
  {
8528
8360
  id: created.id,
8529
8361
  slug: created.slug,
8530
8362
  title,
8531
8363
  visibility: "private",
8364
+ folder: folderRef ?? null,
8532
8365
  remixOfId: remixOfId ?? null,
8533
8366
  currentVersionId: created.currentVersionId,
8534
8367
  watchUrl,
8535
- studioUrl
8368
+ studioUrl,
8369
+ ...landed ? {
8370
+ still: landed.still,
8371
+ thumbnailUrl: landed.thumbnailUrl,
8372
+ previewUrl: landed.previewUrl
8373
+ } : {}
8536
8374
  },
8537
8375
  `Created private vos ${created.id} (${title})
8538
8376
  watch: ${watchUrl}
8539
8377
  studio: ${studioUrl}
8378
+ ` + (landed ? `${landedLines(landed)}
8379
+ ` : "") + ` shelf: ${folderRef ? `in ${folderRef}` : `unfiled, at the root of your shelf (file it: vos folder move ${created.id} --to <slug>)`}
8540
8380
  Iterate with: vos push ${source} --vos ${created.id}`
8541
8381
  );
8542
8382
  return EXIT_OK;
@@ -8608,10 +8448,10 @@ async function cmdPullProgram(argv) {
8608
8448
  const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
8609
8449
  if (cfg.status !== 200)
8610
8450
  throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
8611
- const configPath = join18(dir, "config.json");
8451
+ const configPath = join17(dir, "config.json");
8612
8452
  let backedUp = false;
8613
8453
  if (existsSync15(configPath)) {
8614
- await writeFile11(join18(dir, "config.backup.json"), await readFile14(configPath));
8454
+ await writeFile11(join17(dir, "config.backup.json"), await readFile15(configPath));
8615
8455
  backedUp = true;
8616
8456
  }
8617
8457
  let headConfig = cfg.body.config;
@@ -8644,7 +8484,7 @@ async function cmdPullProgram(argv) {
8644
8484
  protected: protectedIds,
8645
8485
  changes,
8646
8486
  out: configPath,
8647
- backup: backedUp ? join18(dir, "config.backup.json") : null
8487
+ backup: backedUp ? join17(dir, "config.backup.json") : null
8648
8488
  },
8649
8489
  `Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
8650
8490
  Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
@@ -8715,15 +8555,18 @@ async function storeKey(key, origin, r) {
8715
8555
  function isTakeDir(target) {
8716
8556
  return directoryKind(target) === "take";
8717
8557
  }
8558
+ function soundOf(doc) {
8559
+ return { audio: Array.isArray(doc.audio) ? doc.audio : [] };
8560
+ }
8718
8561
  function storedProgramConfig(config, programDoc) {
8719
8562
  if (!programDoc) return config;
8720
8563
  const doc = { audio: [], ...programDoc };
8721
8564
  return lowerProgramDoc(doc).config;
8722
8565
  }
8723
8566
  async function readProgramDoc(dir, config) {
8724
- const docPath = join18(dir, "doc.json");
8567
+ const docPath = join17(dir, "doc.json");
8725
8568
  if (!existsSync15(docPath)) return null;
8726
- const parsed = JSON.parse(await readFile14(docPath, "utf8"));
8569
+ const parsed = JSON.parse(await readFile15(docPath, "utf8"));
8727
8570
  if (typeof parsed !== "object" || parsed === null || "source" in parsed)
8728
8571
  return null;
8729
8572
  const raw = parsed;
@@ -8734,14 +8577,15 @@ async function writeProgramDoc(dir, hosted) {
8734
8577
  const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
8735
8578
  const { config, ...rest } = program;
8736
8579
  const onDisk = { ...hosted, program: rest };
8737
- await writeFile11(join18(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8580
+ await writeFile11(join17(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8738
8581
  return {
8739
8582
  config: config && typeof config === "object" ? config : null
8740
8583
  };
8741
8584
  }
8742
8585
 
8743
8586
  // src/plugin/asset.ts
8744
- import { readFileSync as readFileSync4 } from "fs";
8587
+ import { createHash as createHash3 } from "crypto";
8588
+ import { readFileSync as readFileSync3 } from "fs";
8745
8589
  import { basename as basename5, extname as extname2 } from "path";
8746
8590
  var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
8747
8591
  var MIME_BY_EXT = {
@@ -8792,10 +8636,38 @@ async function cmdPush(argv) {
8792
8636
  const folderRef = strFlag(flags, "folder");
8793
8637
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
8794
8638
  const uploaded = [];
8639
+ let unfiledMedia = 0;
8795
8640
  for (const file of positionals) {
8796
8641
  const name = basename5(file);
8642
+ const body = readFileSync3(file);
8643
+ const media = resolveMediaType({
8644
+ head: body.subarray(0, MEDIA_HEAD_BYTES),
8645
+ filename: name
8646
+ });
8647
+ if (/^(audio|video)\//.test(media.type)) {
8648
+ const sent = nameForType(name, media.type);
8649
+ let put;
8650
+ try {
8651
+ put = await uploadAsset({ origin, key }, new Uint8Array(body), {
8652
+ filename: sent,
8653
+ contentType: media.type,
8654
+ contentHash: createHash3("sha256").update(body).digest("hex"),
8655
+ ...folder ? { folderId: folder.id } : {}
8656
+ });
8657
+ } catch (e) {
8658
+ const landed = uploaded.length ? ` (${uploaded.length} of ${positionals.length} landed before it)` : "";
8659
+ throw new Error(
8660
+ `push ${name}${landed}: ${e instanceof Error ? e.message : String(e)}`
8661
+ );
8662
+ }
8663
+ if (!folder && !put.reused) unfiledMedia += 1;
8664
+ uploaded.push({ id: put.id, filename: sent });
8665
+ r.log(
8666
+ `uploaded ${sent} (${put.id})${put.reused ? " \u2014 already on your shelf" : ""} ${put.url}`
8667
+ );
8668
+ continue;
8669
+ }
8797
8670
  const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
8798
- const body = readFileSync4(file);
8799
8671
  const form = new FormData();
8800
8672
  form.set("file", new Blob([body], { type: mime }), name);
8801
8673
  if (folder) form.set("folderId", folder.id);
@@ -8829,7 +8701,8 @@ async function cmdPush(argv) {
8829
8701
  count: uploaded.length,
8830
8702
  folder: folder?.slug ?? null
8831
8703
  },
8832
- `pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}`
8704
+ `pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}` + (unfiledMedia ? `
8705
+ note: an unfiled sound or video that no document uses is removed after 7 days. Reference it from a doc (doc.audio key = its url) or push it with --folder <slug>` : "")
8833
8706
  );
8834
8707
  return EXIT_OK;
8835
8708
  }
@@ -8848,10 +8721,69 @@ async function cmdAsset(argv) {
8848
8721
  }
8849
8722
  }
8850
8723
 
8724
+ // src/plugin/remove.ts
8725
+ import { existsSync as existsSync16, rmSync as rmSync2, statSync as statSync4 } from "fs";
8726
+ import { join as join18 } from "path";
8727
+ import { createInterface as createInterface3 } from "readline/promises";
8728
+ var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help", "yes"]);
8729
+ async function cmdDelete(argv) {
8730
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
8731
+ const target = positionals[0];
8732
+ if (!target)
8733
+ throw new UsageError("vos delete <vosId|watch-url|dir> [--yes] [--json]");
8734
+ const r = createReporter(flags.json === true);
8735
+ const origin = platformOrigin({
8736
+ origin: strFlag(flags, "origin"),
8737
+ api: strFlag(flags, "api")
8738
+ }).replace(/\/+$/, "");
8739
+ const key = requireCredential(strFlag(flags, "key"));
8740
+ const dir = existsSync16(target) && statSync4(target).isDirectory() ? target : null;
8741
+ const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
8742
+ if (!vosId)
8743
+ throw new UsageError(
8744
+ `${target} tracks no vos (no ${SYNC_STATE_NAME}): pass the vos id or its watch URL`
8745
+ );
8746
+ const meta = await apiJson(origin, `/api/vos/${vosId}`, { key });
8747
+ if (meta.status !== 200) throw new Error(apiError(`read ${vosId}`, meta));
8748
+ const vos = meta.body.vos ?? {};
8749
+ const title = typeof vos.title === "string" ? vos.title : vosId;
8750
+ if (flags.yes !== true) {
8751
+ if (!process.stdin.isTTY)
8752
+ throw new UsageError(
8753
+ `deleting "${title}" (${vosId}) cannot be undone: re-run with --yes to confirm`
8754
+ );
8755
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
8756
+ const answer = await rl.question(
8757
+ `Delete "${title}" (${vosId}) and every version of it? This cannot be undone. [y/N] `
8758
+ );
8759
+ rl.close();
8760
+ if (!/^y(es)?$/i.test(answer.trim())) {
8761
+ r.done({ id: vosId, deleted: false }, "kept \u2014 nothing was deleted");
8762
+ return EXIT_OK;
8763
+ }
8764
+ }
8765
+ const res = await apiJson(origin, `/api/vos/${vosId}`, {
8766
+ method: "DELETE",
8767
+ key
8768
+ });
8769
+ if (res.status !== 200 && res.status !== 204)
8770
+ throw new Error(apiError(`delete ${vosId}`, res));
8771
+ let unlinked = false;
8772
+ if (dir) {
8773
+ rmSync2(join18(dir, SYNC_STATE_NAME), { force: true });
8774
+ unlinked = true;
8775
+ }
8776
+ r.done(
8777
+ { id: vosId, title, deleted: true, unlinked },
8778
+ `deleted "${title}" (${vosId})${unlinked ? `; ${dir} no longer tracks it` : ""}`
8779
+ );
8780
+ return EXIT_OK;
8781
+ }
8782
+
8851
8783
  // src/plugin/recipe.ts
8852
- import { readFileSync as readFileSync5 } from "fs";
8784
+ import { readFileSync as readFileSync4 } from "fs";
8853
8785
  import { basename as basename6 } from "path";
8854
- var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
8786
+ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
8855
8787
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
8856
8788
  function recipeFilename(file) {
8857
8789
  const name = basename6(file);
@@ -8861,7 +8793,7 @@ function recipeFilename(file) {
8861
8793
  return name;
8862
8794
  }
8863
8795
  async function cmdPush2(argv) {
8864
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
8796
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
8865
8797
  const [file] = positionals;
8866
8798
  const folderRef = strFlag(flags, "folder");
8867
8799
  const assetId = strFlag(flags, "asset");
@@ -8869,7 +8801,7 @@ async function cmdPush2(argv) {
8869
8801
  throw new UsageError(USAGE);
8870
8802
  }
8871
8803
  const filename = recipeFilename(file);
8872
- const body = readFileSync5(file);
8804
+ const body = readFileSync4(file);
8873
8805
  const r = createReporter(flags.json === true);
8874
8806
  const origin = platformOrigin({
8875
8807
  origin: strFlag(flags, "origin"),
@@ -9890,8 +9822,8 @@ async function cmdActions(argv) {
9890
9822
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
9891
9823
  viewport = { width: Number(m[1]), height: Number(m[2]) };
9892
9824
  }
9893
- const { readFile: readFile17 } = await import("fs/promises");
9894
- const text = await readFile17(resolve8(input), "utf8");
9825
+ const { readFile: readFile18 } = await import("fs/promises");
9826
+ const text = await readFile18(resolve8(input), "utf8");
9895
9827
  const { records, problems } = parseAgentBrowserLog(text);
9896
9828
  for (const p of problems) r.log(` ${p}`);
9897
9829
  const result = convertAgentBrowser(records, {
@@ -9928,9 +9860,9 @@ function summary(result) {
9928
9860
  }
9929
9861
  async function cmdActionsScript(input, flags) {
9930
9862
  const r = createReporter(flags.json === true);
9931
- const { readFile: readFile17 } = await import("fs/promises");
9863
+ const { readFile: readFile18 } = await import("fs/promises");
9932
9864
  const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
9933
- const raw = JSON.parse(await readFile17(resolve8(input), "utf8"));
9865
+ const raw = JSON.parse(await readFile18(resolve8(input), "utf8"));
9934
9866
  const errors = validateActions(raw);
9935
9867
  if (errors.length) throw new UsageError(`${input}:
9936
9868
  ${errors.join("\n ")}`);
@@ -9940,8 +9872,8 @@ async function cmdActionsScript(input, flags) {
9940
9872
  }
9941
9873
 
9942
9874
  // src/plugin/ingest.ts
9943
- import { existsSync as existsSync16 } from "fs";
9944
- import { copyFile, mkdir as mkdir10, readFile as readFile15, stat as stat4 } from "fs/promises";
9875
+ import { existsSync as existsSync17 } from "fs";
9876
+ import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
9945
9877
  import { basename as basename7, join as join19, resolve as resolve9 } from "path";
9946
9878
  import { inflateRawSync } from "zlib";
9947
9879
  import {
@@ -9962,7 +9894,7 @@ import {
9962
9894
  Output,
9963
9895
  WebMOutputFormat
9964
9896
  } from "mediabunny";
9965
- var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
9897
+ var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set(["json", "help"]);
9966
9898
  async function probeVideoFile(path) {
9967
9899
  const input = new Input({
9968
9900
  source: new FilePathSource(path),
@@ -10030,7 +9962,7 @@ async function readTraceFile(path, offsetMs) {
10030
9962
  );
10031
9963
  }
10032
9964
  if (kind === "zip") {
10033
- const bytes = new Uint8Array(await readFile15(path));
9965
+ const bytes = new Uint8Array(await readFile16(path));
10034
9966
  const entry = playwrightTraceEntry(zipEntries(bytes));
10035
9967
  if (!entry) {
10036
9968
  throw new UsageError(
@@ -10043,7 +9975,7 @@ async function readTraceFile(path, offsetMs) {
10043
9975
  );
10044
9976
  return cursorFromPlaywrightTrace(text2, { offsetMs });
10045
9977
  }
10046
- const text = await readFile15(path, "utf8");
9978
+ const text = await readFile16(path, "utf8");
10047
9979
  return cursorFromTrace(basename7(path), text, { offsetMs });
10048
9980
  }
10049
9981
  function parseViewport(v) {
@@ -10053,20 +9985,28 @@ function parseViewport(v) {
10053
9985
  return { width: Number(m[1]), height: Number(m[2]) };
10054
9986
  }
10055
9987
  async function cmdIngest(argv) {
10056
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5, MULTI_FLAGS2);
9988
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS6, MULTI_FLAGS2);
10057
9989
  const source = positionals[0];
10058
9990
  if (!source)
10059
9991
  throw new UsageError(
10060
- "vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
9992
+ "vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
10061
9993
  );
10062
9994
  const videoPath = resolve9(source);
10063
- if (!existsSync16(videoPath)) throw new UsageError(`${source}: no such file`);
9995
+ if (!existsSync17(videoPath)) throw new UsageError(`${source}: no such file`);
10064
9996
  const r = createReporter(flags.json === true);
10065
9997
  const outDir = resolve9(strFlag(flags, "out") ?? "take");
10066
9998
  const offsetMs = numFlag(flags, "offset", 0);
10067
9999
  const viewportFlag = parseViewport(strFlag(flags, "viewport"));
10068
10000
  const cursorPath = strFlag(flags, "cursor");
10069
- const backdrop = await takeBackdrop(flags, r);
10001
+ const asFlag = strFlag(flags, "as");
10002
+ if (asFlag !== void 0 && asFlag !== "footage" && asFlag !== "take")
10003
+ throw new UsageError("--as expects footage or take");
10004
+ const footage = asFlag ? asFlag === "footage" : !cursorPath;
10005
+ if (footage && strFlag(flags, "background") !== void 0)
10006
+ r.log(
10007
+ "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
10008
+ );
10009
+ const backdrop = footage ? null : await takeBackdrop(flags, r);
10070
10010
  r.log(`probing ${basename7(videoPath)}\u2026`);
10071
10011
  const probed = await probeVideoFile(videoPath);
10072
10012
  if (!(probed.durationMs > 0)) {
@@ -10079,7 +10019,7 @@ async function cmdIngest(argv) {
10079
10019
  trace = await readTraceFile(resolve9(cursorPath), offsetMs);
10080
10020
  for (const n of trace.notes) r.log(`note: ${n}`);
10081
10021
  }
10082
- if (existsSync16(join19(outDir, "meta.json"))) {
10022
+ if (existsSync17(join19(outDir, "meta.json"))) {
10083
10023
  const { prevDoc, kept } = await prepareReRecord(outDir);
10084
10024
  r.log(
10085
10025
  `note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
@@ -10112,12 +10052,13 @@ async function cmdIngest(argv) {
10112
10052
  };
10113
10053
  await writeJson(paths.meta, meta, true);
10114
10054
  if (events.length) await writeJson(paths.cursor, events);
10115
- const plan = await planTake(outDir, { backdrop });
10055
+ const plan = await planTake(outDir, { backdrop, footage });
10116
10056
  const clicks = events.filter((e) => e.type === "down").length;
10117
10057
  const seconds = (probed.durationMs / 1e3).toFixed(1);
10118
10058
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
10119
10059
  const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename7(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
10120
- const next = trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
10060
+ const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
10061
+ const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
10121
10062
  r.done(
10122
10063
  {
10123
10064
  out: outDir,
@@ -10139,9 +10080,11 @@ async function cmdIngest(argv) {
10139
10080
  notes: trace.notes
10140
10081
  } : null,
10141
10082
  zooms: plan.zoomAuto,
10142
- backdrop: plan.backdrop ?? null
10083
+ backdrop: plan.backdrop ?? null,
10084
+ as: footage ? "footage" : "take"
10143
10085
  },
10144
10086
  `Ingested ${basename7(videoPath)} \u2192 ${outDir}/${basename7(recording)} (${facts})
10087
+ ${opened}
10145
10088
  ${cursorLine}
10146
10089
  ${next}`
10147
10090
  );
@@ -10152,18 +10095,18 @@ async function cmdIngest(argv) {
10152
10095
  import { spawnSync } from "child_process";
10153
10096
  import {
10154
10097
  cpSync,
10155
- existsSync as existsSync18,
10156
- mkdirSync as mkdirSync3,
10157
- readFileSync as readFileSync7,
10158
- rmSync as rmSync2,
10159
- writeFileSync as writeFileSync3
10098
+ existsSync as existsSync19,
10099
+ mkdirSync as mkdirSync2,
10100
+ readFileSync as readFileSync6,
10101
+ rmSync as rmSync3,
10102
+ writeFileSync as writeFileSync2
10160
10103
  } from "fs";
10161
- import { homedir as homedir3 } from "os";
10104
+ import { homedir as homedir2 } from "os";
10162
10105
  import { join as join21 } from "path";
10163
10106
  import { chromium as chromium2 } from "playwright";
10164
10107
 
10165
10108
  // src/plugin/machine.ts
10166
- import { existsSync as existsSync17, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
10109
+ import { existsSync as existsSync18, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
10167
10110
  import { dirname as dirname5, join as join20 } from "path";
10168
10111
  var AGENT_NAMES = [
10169
10112
  "claude",
@@ -10184,7 +10127,7 @@ var HOME_DIRS = {
10184
10127
  copilot: [".copilot"]
10185
10128
  };
10186
10129
  function detectAgents(root, opts = {}) {
10187
- const exists = opts.exists ?? existsSync17;
10130
+ const exists = opts.exists ?? existsSync18;
10188
10131
  const out = [];
10189
10132
  for (const agent of AGENT_NAMES) {
10190
10133
  for (const rel of PROJECT_DIRS[agent]) {
@@ -10231,7 +10174,7 @@ function skillsDirFor(home) {
10231
10174
  function fallbackSkillsHome(root) {
10232
10175
  return { agent: "codex", dir: join20(root, ".agents"), scope: "project" };
10233
10176
  }
10234
- function installedSkills(dir, exists = existsSync17, list = (p) => {
10177
+ function installedSkills(dir, exists = existsSync18, list = (p) => {
10235
10178
  try {
10236
10179
  return readdirSync4(p);
10237
10180
  } catch {
@@ -10271,7 +10214,7 @@ ${RULES_BLOCK}
10271
10214
  }
10272
10215
  return { text, changed: text !== current };
10273
10216
  }
10274
- function rulesFileFor(root, exists = existsSync17) {
10217
+ function rulesFileFor(root, exists = existsSync18) {
10275
10218
  const agents = join20(root, "AGENTS.md");
10276
10219
  if (exists(agents)) return agents;
10277
10220
  const claude = join20(root, "CLAUDE.md");
@@ -10378,12 +10321,12 @@ function bundledSkillsDir(from) {
10378
10321
  let dir = dirname5(from);
10379
10322
  for (let i = 0; i < 6; i++) {
10380
10323
  const pkg = join20(dir, "package.json");
10381
- if (existsSync17(pkg)) {
10324
+ if (existsSync18(pkg)) {
10382
10325
  try {
10383
- const name = JSON.parse(readFileSync6(pkg, "utf8")).name;
10326
+ const name = JSON.parse(readFileSync5(pkg, "utf8")).name;
10384
10327
  if (name === "@vosjs/cli") {
10385
10328
  const skills = join20(dir, "skills");
10386
- return existsSync17(skills) && statSync4(skills).isDirectory() ? skills : null;
10329
+ return existsSync18(skills) && statSync5(skills).isDirectory() ? skills : null;
10387
10330
  }
10388
10331
  } catch {
10389
10332
  }
@@ -10396,7 +10339,7 @@ function bundledSkillsDir(from) {
10396
10339
  }
10397
10340
 
10398
10341
  // src/plugin/machineCmd.ts
10399
- var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set([
10342
+ var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set([
10400
10343
  "json",
10401
10344
  "help",
10402
10345
  "global",
@@ -10414,7 +10357,7 @@ function npx() {
10414
10357
  function bundledChromium() {
10415
10358
  try {
10416
10359
  const p = chromium2.executablePath();
10417
- return p && existsSync18(p) ? p : null;
10360
+ return p && existsSync19(p) ? p : null;
10418
10361
  } catch {
10419
10362
  return null;
10420
10363
  }
@@ -10423,7 +10366,7 @@ function probe() {
10423
10366
  return probeBrowser({
10424
10367
  platform: process.platform,
10425
10368
  env: process.env,
10426
- exists: existsSync18,
10369
+ exists: existsSync19,
10427
10370
  bundled: bundledChromium
10428
10371
  });
10429
10372
  }
@@ -10474,7 +10417,7 @@ async function doctorFacts(opts) {
10474
10417
  };
10475
10418
  }
10476
10419
  async function cmdSetup(argv) {
10477
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10420
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10478
10421
  const r = reporterFor(flags);
10479
10422
  const root = process.cwd();
10480
10423
  const global = flags.global === true;
@@ -10489,7 +10432,7 @@ async function cmdSetup(argv) {
10489
10432
  if ("error" in v) throw new UsageError(v.error);
10490
10433
  homes = v.homes;
10491
10434
  } else {
10492
- homes = detectAgents(root, { home: homedir3(), global });
10435
+ homes = detectAgents(root, { home: homedir2(), global });
10493
10436
  }
10494
10437
  const agents = [...new Set(homes.map((h) => h.agent))];
10495
10438
  r.event({ event: "agents", agents, global });
@@ -10525,7 +10468,7 @@ async function cmdSetup(argv) {
10525
10468
  const targets = homes.length ? homes : [fallbackSkillsHome(root)];
10526
10469
  for (const home of targets) {
10527
10470
  const dest = skillsDirFor(home);
10528
- mkdirSync3(dest, { recursive: true });
10471
+ mkdirSync2(dest, { recursive: true });
10529
10472
  for (const name of installedSkills(bundled)) {
10530
10473
  cpSync(join21(bundled, name), join21(dest, name), { recursive: true });
10531
10474
  }
@@ -10562,9 +10505,9 @@ async function cmdSetup(argv) {
10562
10505
  let rules = null;
10563
10506
  if (!noRules) {
10564
10507
  const file = rulesFileFor(root);
10565
- const existing = existsSync18(file) ? readFileSync7(file, "utf8") : null;
10508
+ const existing = existsSync19(file) ? readFileSync6(file, "utf8") : null;
10566
10509
  const { text, changed } = applyRulesBlock(existing);
10567
- if (changed) writeFileSync3(file, text);
10510
+ if (changed) writeFileSync2(file, text);
10568
10511
  rules = { file, changed };
10569
10512
  r.log(
10570
10513
  changed ? `rules: wrote the vosso block into ${file}` : `rules: ${file} already carries the block`
@@ -10593,10 +10536,10 @@ Next: ${report.next_step}`
10593
10536
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
10594
10537
  }
10595
10538
  async function cmdDoctor(argv) {
10596
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10539
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10597
10540
  const r = reporterFor(flags);
10598
10541
  const root = process.cwd();
10599
- const homes = detectAgents(root, { home: homedir3(), global: true });
10542
+ const homes = detectAgents(root, { home: homedir2(), global: true });
10600
10543
  const report = doctorReport(
10601
10544
  await doctorFacts({
10602
10545
  homes: homes.length ? homes : [fallbackSkillsHome(root)],
@@ -10612,7 +10555,7 @@ Next: ${report.next_step}`
10612
10555
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
10613
10556
  }
10614
10557
  async function cmdWhoami(argv) {
10615
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10558
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10616
10559
  const r = reporterFor(flags);
10617
10560
  const origin = platformOrigin({
10618
10561
  origin: strFlag(flags, "origin"),
@@ -10674,10 +10617,10 @@ async function cmdWhoami(argv) {
10674
10617
  return EXIT_OK;
10675
10618
  }
10676
10619
  function cmdLogout(argv) {
10677
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10620
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10678
10621
  const r = reporterFor(flags);
10679
- const had = existsSync18(CREDENTIALS_PATH);
10680
- rmSync2(CREDENTIALS_PATH, { force: true });
10622
+ const had = existsSync19(CREDENTIALS_PATH);
10623
+ rmSync3(CREDENTIALS_PATH, { force: true });
10681
10624
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
10682
10625
  r.done(
10683
10626
  {
@@ -10692,9 +10635,10 @@ function cmdLogout(argv) {
10692
10635
  }
10693
10636
 
10694
10637
  // src/plugin/run.ts
10695
- var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set([
10638
+ var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set([
10696
10639
  "json",
10697
10640
  "help",
10641
+ "wait",
10698
10642
  "picture",
10699
10643
  "fresh",
10700
10644
  "reuse",
@@ -10735,8 +10679,8 @@ Take pipeline
10735
10679
  vos validate <actions.json|take|kit.json> [--picture] [--json]
10736
10680
  vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
10737
10681
  vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
10738
- vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
10739
- a take from a recording someone else made (a cloud agent's PR video, a Playwright run's video, a Loom export): the file becomes recording.<container>, meta.json from its own dimensions and duration, and a trace beside it becomes cursor.json so the planner has clicks to zoom on; without one, nothing is planned and the done event says so
10682
+ vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
10683
+ a take from a recording someone else made (a cloud agent's PR video, a Playwright run's video, a Loom export): the file becomes recording.<container>, meta.json from its own dimensions and duration, and a trace beside it becomes cursor.json so the planner has clicks to zoom on; without one, nothing is planned and the done event says so. Without a trace the file opens as FINISHED footage (a render from HyperFrames or Remotion, a film): no card, no browser bar, no drawn cursor, its own frame rate; --as take gives a screen demo the card
10740
10684
  vos session open <url> --name <app> a plain Chrome window on a vos-owned profile; sign in, close it
10741
10685
  vos session check <name> [--url <url>] headless: does the session still open the page? exit 0/4
10742
10686
  vos session list | rm <name> what exists and how old; delete one
@@ -10772,7 +10716,7 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10772
10716
  vos push <config.json|take> [--vos id] [--title t] [--slug s] [--remix-of id]
10773
10717
  [--desc d] [--tags a,b] [--folder <folderId|slug>]
10774
10718
  [--note n] [--label l] [--base versionId] [--override <id>]... [--yes] [--json]
10775
- [--claimable]
10719
+ [--claimable] [--wait]
10776
10720
  a take DIRECTORY (doc.json) pushes recording + doc; a config.json
10777
10721
  pushes the program. No --vos: create a PRIVATE vos; with --vos:
10778
10722
  add a version against the tracked base \u2014 a stale push 409s WITH
@@ -10784,6 +10728,14 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10784
10728
  user and nowhere else; unclaimed work is deleted after 72h.
10785
10729
  --override consents to touching
10786
10730
  protected (human-edited) nodes, ONLY when the user asked.
10731
+ Without --folder a new vos lands unfiled, at the root of your
10732
+ shelf. A document's local sound (doc.json audio keys) uploads
10733
+ with the push. --wait stays until the version's still has
10734
+ rendered and prints absolute still and preview links.
10735
+ vos delete <vosId|watch-url|dir> [--yes] [--json]
10736
+ take a vos off vos.so, every version of it, for good. Asks on a
10737
+ terminal; headless it needs --yes. A dir that tracked it is
10738
+ unlinked (its vos.json removed)
10787
10739
  vos pull [dir|take] [--vos <id>] [--since versionId] [--check] [--media] [--json]
10788
10740
  what changed on vos.so since your base; syncs config.json (backup
10789
10741
  kept) or the take's doc.json. ALWAYS pull before editing pushed work.
@@ -10805,7 +10757,10 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10805
10757
  vos asset push <file...> [--folder <folderId|slug>]
10806
10758
  upload files onto your shelf (a key takes models .glb/.gltf,
10807
10759
  recipes .md and images .png/.jpg/.webp/.gif \u2014 posters and
10808
- store stills file into the release's project; 40 images/24h)
10760
+ store stills file into the release's project; 40 images/24h \u2014
10761
+ and sound and video, which land in Sound > Uploads and print
10762
+ the url a doc.json audio key takes; an unfiled one that no
10763
+ document uses is removed after 7 days, so pass --folder)
10809
10764
  vos recipe push <file.md> --folder <folderId|slug>
10810
10765
  vos recipe push <file.md> --asset <assetId>
10811
10766
  put a recipe (a plain .md) on the shelf: --folder creates it
@@ -10994,7 +10949,7 @@ lint-gated, so a bad override fails like a bad doc.json):
10994
10949
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
10995
10950
  `;
10996
10951
  async function loadActions(file) {
10997
- const raw = JSON.parse(await readFile16(file, "utf8"));
10952
+ const raw = JSON.parse(await readFile17(file, "utf8"));
10998
10953
  const errors = validateActions(raw);
10999
10954
  if (errors.length)
11000
10955
  throw new UsageError(`invalid actions file:
@@ -11057,7 +11012,7 @@ function takeStorageState(flags) {
11057
11012
  const raw = strFlag(flags, "storage-state");
11058
11013
  if (!raw) return void 0;
11059
11014
  const path = resolve10(raw);
11060
- if (!existsSync19(path))
11015
+ if (!existsSync20(path))
11061
11016
  throw new UsageError(`--storage-state: no such file: ${path}`);
11062
11017
  return path;
11063
11018
  }
@@ -11070,7 +11025,7 @@ function takeSessionName(flags, storageState) {
11070
11025
  throw new UsageError(
11071
11026
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
11072
11027
  );
11073
- if (!existsSync19(sessionDir(name)))
11028
+ if (!existsSync20(sessionDir(name)))
11074
11029
  throw new UsageError(
11075
11030
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
11076
11031
  );
@@ -11126,7 +11081,7 @@ function wallGate(flags, r, opts) {
11126
11081
  async function cmdRecord(argv) {
11127
11082
  const { positionals, flags, multi } = parseArgs(
11128
11083
  argv,
11129
- BOOLEAN_FLAGS7,
11084
+ BOOLEAN_FLAGS8,
11130
11085
  MULTI_FLAGS2
11131
11086
  );
11132
11087
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -11293,7 +11248,7 @@ ${lines.join("\n")}
11293
11248
  }
11294
11249
  const paths = takePaths(outDir);
11295
11250
  const prepare = async () => {
11296
- if (existsSync19(join22(outDir, "meta.json"))) {
11251
+ if (existsSync20(join22(outDir, "meta.json"))) {
11297
11252
  const { prevDoc, kept } = await prepareReRecord(outDir);
11298
11253
  r.log(
11299
11254
  `note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
@@ -11368,7 +11323,7 @@ ${lines.join("\n")}
11368
11323
  async function cmdCreate2(argv) {
11369
11324
  const { positionals, flags, multi } = parseArgs(
11370
11325
  argv,
11371
- BOOLEAN_FLAGS7,
11326
+ BOOLEAN_FLAGS8,
11372
11327
  MULTI_FLAGS2
11373
11328
  );
11374
11329
  const actionsPath = strFlag(flags, "actions");
@@ -11383,10 +11338,7 @@ async function cmdCreate2(argv) {
11383
11338
  if (!url)
11384
11339
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
11385
11340
  const outDir = resolve10(strFlag(flags, "out") ?? "take");
11386
- const fmtRaw = strFlag(flags, "format") ?? "webm";
11387
- if (fmtRaw !== "webm" && fmtRaw !== "mp4")
11388
- throw new UsageError("--format must be webm or mp4");
11389
- const format = fmtRaw;
11341
+ const format = videoFormat(strFlag(flags, "format"), positionals[0]);
11390
11342
  const out = positionals[0] ?? join22(outDir, `out.${format}`);
11391
11343
  const parallel = numFlag(flags, "parallel", 1);
11392
11344
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
@@ -11400,7 +11352,7 @@ async function cmdCreate2(argv) {
11400
11352
  const headers = takeHeaders(multi);
11401
11353
  const paths = takePaths(outDir);
11402
11354
  const prepare = async () => {
11403
- if (existsSync19(join22(outDir, "meta.json"))) {
11355
+ if (existsSync20(join22(outDir, "meta.json"))) {
11404
11356
  const { prevDoc, kept } = await prepareReRecord(outDir);
11405
11357
  r.log(
11406
11358
  `note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
@@ -11497,7 +11449,7 @@ async function cmdCreate2(argv) {
11497
11449
  async function cmdPlan(argv) {
11498
11450
  const { positionals, flags, multi } = parseArgs(
11499
11451
  argv,
11500
- BOOLEAN_FLAGS7,
11452
+ BOOLEAN_FLAGS8,
11501
11453
  /* @__PURE__ */ new Set(["with"])
11502
11454
  );
11503
11455
  const dir = positionals[0];
@@ -11513,18 +11465,18 @@ async function cmdPlan(argv) {
11513
11465
  let reuse;
11514
11466
  if (flags.reuse === true) {
11515
11467
  const from = resolve10(strFlag(flags, "from") ?? join22(dir, PREV_DOC_NAME));
11516
- if (!existsSync19(from)) {
11468
+ if (!existsSync20(from)) {
11517
11469
  throw new UsageError(
11518
11470
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
11519
11471
  );
11520
11472
  }
11521
11473
  reuse = {
11522
11474
  from,
11523
- doc: JSON.parse(await readFile16(from, "utf8"))
11475
+ doc: JSON.parse(await readFile17(from, "utf8"))
11524
11476
  };
11525
11477
  }
11526
11478
  const style = await resolveStyleRef(flags);
11527
- const hasDoc = existsSync19(join22(dir, "doc.json"));
11479
+ const hasDoc = existsSync20(join22(dir, "doc.json"));
11528
11480
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
11529
11481
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
11530
11482
  const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
@@ -11695,7 +11647,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
11695
11647
  async function cmdRender(argv) {
11696
11648
  const { positionals, flags, multi } = parseArgs(
11697
11649
  argv,
11698
- BOOLEAN_FLAGS7,
11650
+ BOOLEAN_FLAGS8,
11699
11651
  MULTI_FLAGS2
11700
11652
  );
11701
11653
  const dir = positionals[0];
@@ -11704,10 +11656,7 @@ async function cmdRender(argv) {
11704
11656
  "vos render <take> [out] [--width] [--height] [--fps] [--format] [--parallel]"
11705
11657
  );
11706
11658
  const r = createReporter(flags.json === true);
11707
- const fmtRaw = strFlag(flags, "format") ?? "webm";
11708
- if (fmtRaw !== "webm" && fmtRaw !== "mp4")
11709
- throw new UsageError("--format must be webm or mp4");
11710
- const format = fmtRaw;
11659
+ const format = videoFormat(strFlag(flags, "format"), positionals[1]);
11711
11660
  const out = positionals[1] ?? join22(dir, `out.${format}`);
11712
11661
  const parallel = numFlag(flags, "parallel", 1);
11713
11662
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
@@ -11766,7 +11715,7 @@ async function cmdRender(argv) {
11766
11715
  async function cmdFrames(argv) {
11767
11716
  const { positionals, flags, multi } = parseArgs(
11768
11717
  argv,
11769
- BOOLEAN_FLAGS7,
11718
+ BOOLEAN_FLAGS8,
11770
11719
  MULTI_FLAGS2
11771
11720
  );
11772
11721
  const dir = positionals[0];
@@ -11839,7 +11788,7 @@ async function cmdFrames(argv) {
11839
11788
  async function cmdDeliver(argv) {
11840
11789
  const { positionals, flags, multi } = parseArgs(
11841
11790
  argv,
11842
- BOOLEAN_FLAGS7,
11791
+ BOOLEAN_FLAGS8,
11843
11792
  MULTI_FLAGS2
11844
11793
  );
11845
11794
  const dir = positionals[0];
@@ -11955,15 +11904,15 @@ async function resolveStyleRef(flags) {
11955
11904
  }
11956
11905
  async function resolveDocRef(ref, flags, what) {
11957
11906
  const styleRef = ref;
11958
- const file = existsSync19(styleRef) ? resolve10(
11907
+ const file = existsSync20(styleRef) ? resolve10(
11959
11908
  styleRef,
11960
- existsSync19(join22(styleRef, "doc.json")) ? "doc.json" : ""
11909
+ existsSync20(join22(styleRef, "doc.json")) ? "doc.json" : ""
11961
11910
  ) : null;
11962
- if (file && existsSync19(file)) {
11911
+ if (file && existsSync20(file)) {
11963
11912
  return {
11964
11913
  from: file,
11965
11914
  doc: migrateHostedDoc5(
11966
- JSON.parse(await readFile16(file, "utf8"))
11915
+ JSON.parse(await readFile17(file, "utf8"))
11967
11916
  )
11968
11917
  };
11969
11918
  }
@@ -12005,7 +11954,7 @@ async function officialTemplate(origin, ref) {
12005
11954
  }
12006
11955
  }
12007
11956
  async function cmdDigest(argv) {
12008
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
11957
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12009
11958
  const dir = positionals[0];
12010
11959
  if (!dir)
12011
11960
  throw new UsageError(
@@ -12015,7 +11964,7 @@ async function cmdDigest(argv) {
12015
11964
  const take = await loadTake(dir);
12016
11965
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
12017
11966
  const transcriptPath = strFlag(flags, "transcript");
12018
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile16(transcriptPath, "utf8"))) : null;
11967
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile17(transcriptPath, "utf8"))) : null;
12019
11968
  const style = await resolveStyleRef(flags);
12020
11969
  const noFrames = flags["no-frames"] === true;
12021
11970
  let browser = null;
@@ -12069,12 +12018,12 @@ async function cmdDigest(argv) {
12069
12018
  }
12070
12019
  }
12071
12020
  async function cmdOpen(argv) {
12072
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12021
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12073
12022
  const dir = positionals[0];
12074
12023
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
12075
12024
  const r = createReporter(flags.json === true);
12076
12025
  const take = await loadTake(dir);
12077
- if (!existsSync19(take.paths.recording)) {
12026
+ if (!existsSync20(take.paths.recording)) {
12078
12027
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
12079
12028
  }
12080
12029
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
@@ -12107,7 +12056,7 @@ async function cmdOpen(argv) {
12107
12056
  return EXIT_OK;
12108
12057
  }
12109
12058
  async function cmdJudge(argv) {
12110
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12059
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12111
12060
  const target = positionals[0];
12112
12061
  const against = strFlag(flags, "against");
12113
12062
  if (!target || !against)
@@ -12116,11 +12065,11 @@ async function cmdJudge(argv) {
12116
12065
  );
12117
12066
  const r = createReporter(flags.json === true);
12118
12067
  const kitFile = target.endsWith("kit.json") ? target : join22(target, "kit.json");
12119
- if (!existsSync19(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
12120
- if (!existsSync19(against))
12068
+ if (!existsSync20(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
12069
+ if (!existsSync20(against))
12121
12070
  throw new UsageError(`${against}: no reference manifest`);
12122
12071
  const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
12123
- const verdicts = JSON.parse(await readFile16(result.verdictFile, "utf8")).verdicts;
12072
+ const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
12124
12073
  const rate = winRate(verdicts);
12125
12074
  const lines = result.sheets.map(
12126
12075
  (s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
@@ -12136,7 +12085,7 @@ Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judg
12136
12085
  return EXIT_OK;
12137
12086
  }
12138
12087
  async function cmdCallout(argv) {
12139
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12088
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12140
12089
  const dir = positionals[0];
12141
12090
  const shape = positionals[1];
12142
12091
  if (!dir || !shape || !isCalloutShape(shape))
@@ -12160,7 +12109,7 @@ async function cmdCallout(argv) {
12160
12109
  `no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
12161
12110
  );
12162
12111
  const codeFile = strFlag(flags, "code-file");
12163
- const code = codeFile ? await readFile16(codeFile, "utf8") : strFlag(flags, "code");
12112
+ const code = codeFile ? await readFile17(codeFile, "utf8") : strFlag(flags, "code");
12164
12113
  const words = {
12165
12114
  kicker: strFlag(flags, "kicker"),
12166
12115
  title: strFlag(flags, "title"),
@@ -12235,11 +12184,11 @@ async function cmdCallout(argv) {
12235
12184
  return EXIT_OK;
12236
12185
  }
12237
12186
  async function cmdValidate(argv) {
12238
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12187
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12239
12188
  const target = positionals[0];
12240
12189
  if (!target) throw new UsageError("vos validate <actions.json|take>");
12241
12190
  const r = createReporter(flags.json === true);
12242
- const kitTarget = target.endsWith("kit.json") ? target : existsSync19(join22(target, "kit.json")) && !isTakeDir(target) ? join22(target, "kit.json") : null;
12191
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync20(join22(target, "kit.json")) && !isTakeDir(target) ? join22(target, "kit.json") : null;
12243
12192
  if (kitTarget) {
12244
12193
  const picture = flags.picture === true;
12245
12194
  const verdict = await validateKit(kitTarget, { picture });
@@ -12271,9 +12220,9 @@ async function cmdValidate(argv) {
12271
12220
  r.done({ valid: true, target }, `${target}: valid actions file`);
12272
12221
  return EXIT_OK;
12273
12222
  }
12274
- if (!isTakeDir(target) && existsSync19(join22(target, "config.json"))) {
12223
+ if (!isTakeDir(target) && existsSync20(join22(target, "config.json"))) {
12275
12224
  const parsed = JSON.parse(
12276
- await readFile16(join22(target, "config.json"), "utf8")
12225
+ await readFile17(join22(target, "config.json"), "utf8")
12277
12226
  );
12278
12227
  const pre = preflightConfig(parsed);
12279
12228
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -12314,7 +12263,7 @@ async function cmdValidate(argv) {
12314
12263
  const take = await loadTake(target);
12315
12264
  const problems = [];
12316
12265
  const warnings = [];
12317
- if (!existsSync19(take.paths.recording)) {
12266
+ if (!existsSync20(take.paths.recording)) {
12318
12267
  problems.push("missing recording (re-run record)");
12319
12268
  } else {
12320
12269
  const mismatch = containerMismatch(
@@ -12397,7 +12346,7 @@ async function cmdValidate(argv) {
12397
12346
  async function cmdPush3(argv) {
12398
12347
  const { positionals, flags, multi } = parseArgs(
12399
12348
  argv,
12400
- BOOLEAN_FLAGS7,
12349
+ BOOLEAN_FLAGS8,
12401
12350
  MULTI_FLAGS2
12402
12351
  );
12403
12352
  const dir = positionals[0];
@@ -12435,16 +12384,33 @@ async function cmdPush3(argv) {
12435
12384
  origin: strFlag(flags, "origin"),
12436
12385
  api: strFlag(flags, "api")
12437
12386
  }).replace(/\/+$/, "");
12387
+ const landed = flags.wait === true ? await waitForLanded({
12388
+ origin: pushedTo,
12389
+ key: requireCredential(strFlag(flags, "key")),
12390
+ vosId: result.vosId,
12391
+ versionId: result.versionId ?? null,
12392
+ log: (l) => r.log(l)
12393
+ }) : null;
12394
+ const filed = result.versionNumber === 1 ? `
12395
+ shelf: ${strFlag(flags, "folder") ? `in ${strFlag(flags, "folder")}` : `unfiled, at the root of your shelf (file it: vos folder move ${result.vosId} --to <slug>)`}` : "";
12438
12396
  r.done(
12439
- { ...result },
12397
+ {
12398
+ ...result,
12399
+ ...landed ? {
12400
+ still: landed.still,
12401
+ thumbnailUrl: landed.thumbnailUrl,
12402
+ previewUrl: landed.previewUrl
12403
+ } : {}
12404
+ },
12440
12405
  `pushed v${result.versionNumber} \u2192 vos ${result.vosId}
12441
12406
  review: ${pushedTo}/vos/${result.vosId}
12442
- studio: ${pushedTo}/studio?vos=${result.vosId}`
12407
+ studio: ${pushedTo}/studio?vos=${result.vosId}` + filed + (landed ? `
12408
+ ${landedLines(landed)}` : "")
12443
12409
  );
12444
12410
  return EXIT_OK;
12445
12411
  }
12446
12412
  async function cmdPull2(argv) {
12447
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7, MULTI_FLAGS2);
12413
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8, MULTI_FLAGS2);
12448
12414
  const dir = positionals[0] ?? ".";
12449
12415
  const r = createReporter(flags.json === true);
12450
12416
  const result = await pullTake(
@@ -12555,6 +12521,8 @@ async function run(argv) {
12555
12521
  const target = rest.find((a) => !a.startsWith("-")) ?? ".";
12556
12522
  return isTakeDir(target) ? await cmdPull2(rest) : await cmdPullProgram(rest);
12557
12523
  }
12524
+ case "delete":
12525
+ return await cmdDelete(rest);
12558
12526
  case "folder":
12559
12527
  return await cmdFolder(rest);
12560
12528
  case "asset":
@@ -12601,7 +12569,7 @@ async function run(argv) {
12601
12569
  }
12602
12570
  }
12603
12571
  async function cmdSession(argv) {
12604
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12572
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12605
12573
  const [sub, arg] = positionals;
12606
12574
  const r = createReporter(flags.json === true);
12607
12575
  const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
@@ -12637,7 +12605,7 @@ async function cmdSession(argv) {
12637
12605
  if (!arg) throw new UsageError(usage);
12638
12606
  const bad = sessionNameVerdict(arg);
12639
12607
  if (bad) throw new UsageError(bad);
12640
- if (!existsSync19(sessionDir(arg)))
12608
+ if (!existsSync20(sessionDir(arg)))
12641
12609
  throw new UsageError(`no session "${arg}" (vos session list)`);
12642
12610
  const summary2 = await summarizeSession(arg);
12643
12611
  const url = strFlag(flags, "url");
@@ -12728,13 +12696,6 @@ export {
12728
12696
  digestTake,
12729
12697
  parseTranscript,
12730
12698
  renderTake,
12731
- platformOrigin,
12732
- parseVosId,
12733
- resolveCredential,
12734
- apiJson,
12735
- apiError,
12736
- readSyncState,
12737
- writeSyncState,
12738
12699
  recordTake,
12739
12700
  encodeRecording,
12740
12701
  planTake,
@@ -12748,4 +12709,4 @@ export {
12748
12709
  verbHelp,
12749
12710
  run
12750
12711
  };
12751
- //# sourceMappingURL=chunk-D6AP7Y3G.js.map
12712
+ //# sourceMappingURL=chunk-F2QCWV4U.js.map