@vosjs/cli 0.51.0 → 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,18 +1,93 @@
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";
90
+ import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
16
91
  import { existsSync as existsSync20 } from "fs";
17
92
  import { join as join22, resolve as resolve10 } from "path";
18
93
  import {
@@ -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));
1918
1984
  return out;
1919
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
+ }
1998
+ return out;
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";
3500
- import { existsSync as existsSync8 } from "fs";
3501
- import { readFile as readFile6 } from "fs/promises";
3614
+ import { join as join7, relative, resolve as resolve4 } from "path";
3615
+ import { existsSync as existsSync7 } from "fs";
3616
+ import { readFile as readFile7 } from "fs/promises";
3502
3617
  import { parseFrontmatter } from "@vosjs/shared/frontmatter";
3503
3618
  import {
3504
3619
  CHANNEL_SPECS_VERIFIED,
@@ -4477,9 +4592,9 @@ function isLightHexGround(hex2) {
4477
4592
  }
4478
4593
 
4479
4594
  // src/plugin/posterDoc.ts
4480
- import { existsSync as existsSync7 } from "fs";
4481
- import { readFile as readFile5 } from "fs/promises";
4482
- import { isAbsolute, join as join7, resolve as resolve3 } from "path";
4595
+ import { existsSync as existsSync6 } from "fs";
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
4600
  docStillTime as docStillTime2,
@@ -4487,215 +4602,6 @@ import {
4487
4602
  overlayRect,
4488
4603
  resolveOverlayStyle
4489
4604
  } 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
-
4496
- // src/plugin/hostAgent.ts
4497
- import { existsSync as existsSync6 } from "fs";
4498
- var SLUG = /^[a-z0-9][a-z0-9.-]{0,39}$/;
4499
- var MARKERS = [
4500
- ["cursor", (e) => !!e.CURSOR_TRACE_ID],
4501
- [
4502
- "cursor-cli",
4503
- (e) => !!e.CURSOR_AGENT || e.CURSOR_EXTENSION_HOST_ROLE === "agent-exec"
4504
- ],
4505
- ["gemini-cli", (e) => !!e.GEMINI_CLI],
4506
- ["codex", (e) => !!(e.CODEX_SANDBOX || e.CODEX_CI || e.CODEX_THREAD_ID)],
4507
- ["antigravity", (e) => !!e.ANTIGRAVITY_AGENT],
4508
- ["augment-cli", (e) => !!e.AUGMENT_AGENT],
4509
- ["opencode", (e) => !!e.OPENCODE_CLIENT],
4510
- [
4511
- "claude-code",
4512
- (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE) && !e.CLAUDE_CODE_IS_COWORK
4513
- ],
4514
- ["claude-cowork", (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE)],
4515
- ["replit", (e) => !!e.REPL_ID],
4516
- [
4517
- "github-copilot",
4518
- (e) => !!(e.COPILOT_MODEL || e.COPILOT_ALLOW_ALL || e.COPILOT_GITHUB_TOKEN)
4519
- ]
4520
- ];
4521
- var DEVIN_LOCAL_PATH = "/opt/.devin";
4522
- function slugFromAiAgent(value) {
4523
- const head = value.trim().toLowerCase().split("_")[0] ?? "";
4524
- if (head === "github-copilot-cli") return "github-copilot";
4525
- return SLUG.test(head) ? head : null;
4526
- }
4527
- function detectHostAgent(env = process.env, exists = existsSync6) {
4528
- if (env.AI_AGENT) {
4529
- const slug = slugFromAiAgent(env.AI_AGENT);
4530
- if (slug) return slug;
4531
- }
4532
- for (const [slug, test] of MARKERS) if (test(env)) return slug;
4533
- try {
4534
- if (exists(DEVIN_LOCAL_PATH)) return "devin";
4535
- } catch {
4536
- }
4537
- return null;
4538
- }
4539
-
4540
- // src/plugin/platform.ts
4541
- function platformOrigin(flags = {}) {
4542
- const legacyEnv = process.env.VOS_API_BASE?.trim();
4543
- const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
4544
- if (!flags.origin && !flags.api && !process.env.VOS_ORIGIN && legacyEnv) {
4545
- process.stderr.write(
4546
- "note: VOS_API_BASE is deprecated \u2014 set VOS_ORIGIN (an origin, no /api suffix)\n"
4547
- );
4548
- }
4549
- return raw.replace(/\/+$/, "").replace(/\/api$/, "");
4550
- }
4551
- function parseVosId(input) {
4552
- if (!/^https?:\/\//.test(input)) {
4553
- if (/^[A-Za-z0-9_-]+$/.test(input)) return input;
4554
- throw new UsageError(`"${input}" is not a vos id or URL`);
4555
- }
4556
- let url;
4557
- try {
4558
- url = new URL(input);
4559
- } catch {
4560
- throw new UsageError(`"${input}" is not a valid URL`);
4561
- }
4562
- const query = url.searchParams.get("vos");
4563
- if (query) return query;
4564
- const path = url.pathname.match(/\/vos\/([A-Za-z0-9_-]+)/);
4565
- if (path) return path[1];
4566
- throw new UsageError(
4567
- `could not find a vos id in ${input} \u2014 expected /vos/{id} or ?vos={id}`
4568
- );
4569
- }
4570
- function deriveSlug(title) {
4571
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
4572
- }
4573
- var CREDENTIALS_PATH = join6(homedir(), ".config", "vos", "credentials");
4574
- function resolveCredential(explicit) {
4575
- const flag = explicit?.trim();
4576
- if (flag) return flag;
4577
- const env = process.env.VOS_API_KEY?.trim();
4578
- if (env) return env;
4579
- try {
4580
- const first = readFileSync2(CREDENTIALS_PATH, "utf8").split("\n")[0].trim();
4581
- return first || null;
4582
- } catch {
4583
- return null;
4584
- }
4585
- }
4586
- function requireCredential(explicit) {
4587
- const key = resolveCredential(explicit);
4588
- if (!key) {
4589
- throw new Error(
4590
- "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)"
4591
- );
4592
- }
4593
- return key;
4594
- }
4595
- function writeCredential(key) {
4596
- mkdirSync(join6(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
4597
- writeFileSync(CREDENTIALS_PATH, `${key.trim()}
4598
- `, { mode: 384 });
4599
- return CREDENTIALS_PATH;
4600
- }
4601
- function clientId(env = process.env, exists) {
4602
- const explicit = env.VOS_CLIENT?.trim();
4603
- if (explicit && explicit.length <= 60 && !/[\r\n]/.test(explicit))
4604
- return explicit;
4605
- const host = detectHostAgent(env, exists);
4606
- return host ? `${host} vos-cli` : "vos-cli";
4607
- }
4608
- async function apiJson(origin, path, init = {}) {
4609
- const headers = {
4610
- accept: "application/json",
4611
- ...init.headers
4612
- };
4613
- if (init.key) headers.authorization = `Bearer ${init.key}`;
4614
- if (init.body !== void 0) headers["content-type"] = "application/json";
4615
- const reqBody = init.raw !== void 0 ? init.raw : init.body === void 0 ? void 0 : JSON.stringify(init.body);
4616
- const res = await fetch(`${origin}${path}`, {
4617
- method: init.method ?? "GET",
4618
- headers,
4619
- body: reqBody
4620
- });
4621
- let body = {};
4622
- try {
4623
- body = await res.json();
4624
- } catch {
4625
- }
4626
- return { status: res.status, body };
4627
- }
4628
- function apiError(what, r) {
4629
- const detail = typeof r.body.error === "string" ? r.body.error : r.body.details !== void 0 ? JSON.stringify(r.body.details) : "";
4630
- const hint = r.status === 401 ? " (run `vos login`, or set VOS_API_KEY \u2014 mint a key at https://vos.so/app/api)" : "";
4631
- return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
4632
- }
4633
- var SYNC_STATE_NAME = "vos.json";
4634
- function readJsonFile(file) {
4635
- try {
4636
- return JSON.parse(readFileSync2(file, "utf8"));
4637
- } catch {
4638
- return null;
4639
- }
4640
- }
4641
- function readSyncState(dir) {
4642
- const own = readJsonFile(join6(dir, SYNC_STATE_NAME));
4643
- if (own && typeof own.vosId === "string") {
4644
- return {
4645
- vosId: own.vosId,
4646
- versionId: typeof own.versionId === "string" ? own.versionId : null,
4647
- ...typeof own.pushedAt === "string" ? { pushedAt: own.pushedAt } : {},
4648
- ...typeof own.title === "string" ? { title: own.title } : {},
4649
- ...typeof own.slug === "string" ? { slug: own.slug } : {},
4650
- ...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
4651
- };
4652
- }
4653
- const push = readJsonFile(join6(dir, "push.json"));
4654
- if (push && typeof push.vosId === "string") {
4655
- return {
4656
- vosId: push.vosId,
4657
- versionId: typeof push.versionId === "string" ? push.versionId : null,
4658
- ...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
4659
- };
4660
- }
4661
- const meta = readJsonFile(join6(dir, "meta.json"));
4662
- if (meta && typeof meta.id === "string") {
4663
- return {
4664
- vosId: meta.id,
4665
- versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
4666
- ...typeof meta.title === "string" ? { title: meta.title } : {},
4667
- ...typeof meta.slug === "string" ? { slug: meta.slug } : {},
4668
- ...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
4669
- };
4670
- }
4671
- return null;
4672
- }
4673
- function writeSyncState(dir, patch) {
4674
- const merged = {
4675
- ...readSyncState(dir) ?? { versionId: null },
4676
- ...patch
4677
- };
4678
- writeFileSync(
4679
- join6(dir, SYNC_STATE_NAME),
4680
- `${JSON.stringify(merged, null, 2)}
4681
- `
4682
- );
4683
- return merged;
4684
- }
4685
- function formatChanges(changes) {
4686
- const lines = [];
4687
- for (const ch of changes) {
4688
- const label = ch.label ? ` \xB7 ${ch.label}` : "";
4689
- const who = ch.origin === "studio" ? "human" : ch.origin ?? "unknown";
4690
- lines.push(
4691
- `v${String(ch.versionNumber ?? "?")} (${who}${label}): ${ch.summary ?? ""}`
4692
- );
4693
- if (ch.note) lines.push(` note: ${ch.note}`);
4694
- }
4695
- return lines;
4696
- }
4697
-
4698
- // src/plugin/posterDoc.ts
4699
4605
  var POSTER_CLASSES = [
4700
4606
  "landscape",
4701
4607
  "square",
@@ -4715,16 +4621,16 @@ var roleFor = (cls) => `poster-${cls}`;
4715
4621
  async function readPosterAt(cls, path, takeDir, from) {
4716
4622
  let file = path;
4717
4623
  let docDir = path;
4718
- if (existsSync7(join7(path, "doc.json"))) {
4719
- file = join7(path, "doc.json");
4720
- } else if (!/\.json$/i.test(path) || !existsSync7(path)) {
4624
+ if (existsSync6(join6(path, "doc.json"))) {
4625
+ file = join6(path, "doc.json");
4626
+ } else if (!/\.json$/i.test(path) || !existsSync6(path)) {
4721
4627
  return null;
4722
4628
  } else {
4723
4629
  docDir = resolve3(path, "..");
4724
4630
  }
4725
4631
  let raw;
4726
4632
  try {
4727
- raw = JSON.parse(await readFile5(file, "utf8"));
4633
+ raw = JSON.parse(await readFile6(file, "utf8"));
4728
4634
  } catch {
4729
4635
  return null;
4730
4636
  }
@@ -4734,7 +4640,7 @@ async function readPosterAt(cls, path, takeDir, from) {
4734
4640
  const doc = migrateHostedDoc(
4735
4641
  raw
4736
4642
  );
4737
- const ownFootage = existsSync7(join7(docDir, "meta.json")) && existsSync7(join7(docDir, source.videoKey));
4643
+ const ownFootage = existsSync6(join6(docDir, "meta.json")) && existsSync6(join6(docDir, source.videoKey));
4738
4644
  const sync = readSyncState(docDir);
4739
4645
  return {
4740
4646
  cls,
@@ -4757,10 +4663,10 @@ async function findPosterDocs(takeDir, launchRoles) {
4757
4663
  candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
4758
4664
  }
4759
4665
  candidates.push({
4760
- path: join7(takeDir, "poster", cls),
4666
+ path: join6(takeDir, "poster", cls),
4761
4667
  from: `poster/${cls}`
4762
4668
  });
4763
- candidates.push({ path: join7(takeDir, "poster"), from: "poster" });
4669
+ candidates.push({ path: join6(takeDir, "poster"), from: "poster" });
4764
4670
  for (const c of candidates) {
4765
4671
  const ref = await readPosterAt(cls, c.path, takeDir, c.from);
4766
4672
  if (ref) {
@@ -4856,19 +4762,19 @@ function resolveChannels(raw) {
4856
4762
  return out;
4857
4763
  }
4858
4764
  async function readBrandBesideTake(dir, explicit) {
4859
- 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")];
4860
4766
  for (const file of candidates) {
4861
- if (!existsSync8(file)) continue;
4862
- const roles = parseFrontmatter(await readFile6(file, "utf8"));
4767
+ if (!existsSync7(file)) continue;
4768
+ const roles = parseFrontmatter(await readFile7(file, "utf8"));
4863
4769
  return { file, roles };
4864
4770
  }
4865
4771
  return null;
4866
4772
  }
4867
4773
  async function readLaunchBesideTake(dir, explicit) {
4868
- 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")];
4869
4775
  for (const file of candidates) {
4870
- if (!existsSync8(file)) continue;
4871
- return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
4776
+ if (!existsSync7(file)) continue;
4777
+ return { file, roles: parseFrontmatter(await readFile7(file, "utf8")) };
4872
4778
  }
4873
4779
  return null;
4874
4780
  }
@@ -4932,7 +4838,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4932
4838
  const meta = doc.source.meta;
4933
4839
  const vw = meta.captureWidth ?? meta.width;
4934
4840
  const vh = meta.captureHeight ?? meta.height;
4935
- const probeDir = await mkdtemp(join8(tmpdir(), "vos-moments-"));
4841
+ const probeDir = await mkdtemp(join7(tmpdir(), "vos-moments-"));
4936
4842
  try {
4937
4843
  const probe2 = await framesTake(browser, dir, {
4938
4844
  times: candidates.map((c) => c.time),
@@ -4946,7 +4852,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4946
4852
  });
4947
4853
  const byTime = /* @__PURE__ */ new Map();
4948
4854
  for (const frame of probe2.frames) {
4949
- const img = decodePng(new Uint8Array(await readFile6(frame.file)));
4855
+ const img = decodePng(new Uint8Array(await readFile7(frame.file)));
4950
4856
  if (!img) continue;
4951
4857
  const whole = { x: 0, y: 0, w: img.w, h: img.h };
4952
4858
  byTime.set(+frame.time.toFixed(3), {
@@ -5019,7 +4925,7 @@ async function deliverTake(browser, dir, opts) {
5019
4925
  const doc = take.doc;
5020
4926
  const duration = docOutputDuration2(doc);
5021
4927
  const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
5022
- const outDir = resolve4(opts.outDir ?? join8(dir, "kit"));
4928
+ const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
5023
4929
  await mkdir5(outDir, { recursive: true });
5024
4930
  const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
5025
4931
  const assets = [];
@@ -5080,7 +4986,7 @@ async function deliverTake(browser, dir, opts) {
5080
4986
  opts.onPhase?.(
5081
4987
  `${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
5082
4988
  );
5083
- const shotDir = await mkdtemp(join8(tmpdir(), "vos-poster-"));
4989
+ const shotDir = await mkdtemp(join7(tmpdir(), "vos-poster-"));
5084
4990
  try {
5085
4991
  const captured = await framesTake(browser, ref.takeDir, {
5086
4992
  times: [time],
@@ -5090,7 +4996,7 @@ async function deliverTake(browser, dir, opts) {
5090
4996
  doc: ref.doc,
5091
4997
  overrides: opts.overrides
5092
4998
  });
5093
- const to = join8(outDir, `${d.id}.png`);
4999
+ const to = join7(outDir, `${d.id}.png`);
5094
5000
  await rename5(captured.frames[0].file, to);
5095
5001
  const bytes = (await stat2(to)).size;
5096
5002
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -5162,7 +5068,7 @@ async function deliverTake(browser, dir, opts) {
5162
5068
  }
5163
5069
  }
5164
5070
  opts.onPhase?.(`${label} (${specWords(d)})`);
5165
- const outFile = join8(outDir, `${d.id}.${d.format}`);
5071
+ const outFile = join7(outDir, `${d.id}.${d.format}`);
5166
5072
  const bitrate = d.maxBytes !== void 0 ? Math.min(
5167
5073
  1e7,
5168
5074
  Math.floor(d.maxBytes * 8 / seconds * 0.85)
@@ -5209,7 +5115,7 @@ async function deliverTake(browser, dir, opts) {
5209
5115
  for (let i = 0; i < captured.frames.length; i++) {
5210
5116
  const frame = captured.frames[i];
5211
5117
  const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
5212
- const to = join8(outDir, name);
5118
+ const to = join7(outDir, name);
5213
5119
  await rename5(frame.file, to);
5214
5120
  const bytes = (await stat2(to)).size;
5215
5121
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -5247,15 +5153,15 @@ async function deliverTake(browser, dir, opts) {
5247
5153
  skipped,
5248
5154
  assets
5249
5155
  };
5250
- const kitFile = join8(outDir, "kit.json");
5156
+ const kitFile = join7(outDir, "kit.json");
5251
5157
  await writeFile7(kitFile, JSON.stringify(kit, null, 2));
5252
5158
  return { kit, kitFile, outDir };
5253
5159
  }
5254
5160
 
5255
5161
  // src/plugin/markAsset.ts
5256
- import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
5257
- import { existsSync as existsSync9 } from "fs";
5258
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve5 } from "path";
5162
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
5163
+ import { existsSync as existsSync8 } from "fs";
5164
+ import { isAbsolute as isAbsolute2, join as join8, resolve as resolve5 } from "path";
5259
5165
  function markExtension(url) {
5260
5166
  const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
5261
5167
  if (!m) return null;
@@ -5337,8 +5243,8 @@ async function fetchBrandMarks(takeDir, roles) {
5337
5243
  bytes = new Uint8Array(await res.arrayBuffer());
5338
5244
  } else {
5339
5245
  const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
5340
- if (!existsSync9(file)) throw new Error("no such file");
5341
- bytes = new Uint8Array(await readFile7(file));
5246
+ if (!existsSync8(file)) throw new Error("no such file");
5247
+ bytes = new Uint8Array(await readFile8(file));
5342
5248
  }
5343
5249
  } catch (e) {
5344
5250
  out.notes.push(
@@ -5353,8 +5259,8 @@ async function fetchBrandMarks(takeDir, roles) {
5353
5259
  );
5354
5260
  return null;
5355
5261
  }
5356
- await mkdir6(join9(takeDir, "brand"), { recursive: true });
5357
- 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);
5358
5264
  return { key: `/brand/${name}.${ext}`, aspect };
5359
5265
  };
5360
5266
  out.light = await one("logoUrl", "mark");
@@ -5390,9 +5296,9 @@ async function fetchMusicCatalog(origin) {
5390
5296
  }
5391
5297
 
5392
5298
  // src/plugin/judge.ts
5393
- import { existsSync as existsSync10 } from "fs";
5394
- import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
5395
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join10, resolve as resolve6 } from "path";
5299
+ import { existsSync as existsSync9 } from "fs";
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";
5396
5302
  import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
5397
5303
 
5398
5304
  // src/plugin/shotBake.ts
@@ -5563,28 +5469,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
5563
5469
  return { w, h, data: out };
5564
5470
  }
5565
5471
  async function judgeKit(kitPath, manifestPath, outDir) {
5566
- const kit = JSON.parse(await readFile8(kitPath, "utf8"));
5472
+ const kit = JSON.parse(await readFile9(kitPath, "utf8"));
5567
5473
  const manifest = JSON.parse(
5568
- await readFile8(manifestPath, "utf8")
5474
+ await readFile9(manifestPath, "utf8")
5569
5475
  );
5570
5476
  const manifestDir = dirname2(resolve6(manifestPath));
5571
5477
  const kitDir = dirname2(resolve6(kitPath));
5572
- const out = resolve6(outDir ?? join10(kitDir, "judge"));
5478
+ const out = resolve6(outDir ?? join9(kitDir, "judge"));
5573
5479
  await mkdir7(out, { recursive: true });
5574
5480
  const sheets = [];
5575
5481
  const skipped = [];
5576
5482
  const refCache = /* @__PURE__ */ new Map();
5577
5483
  const loadRef = async (file) => {
5578
5484
  if (refCache.has(file)) return refCache.get(file) ?? null;
5579
- const p = join10(manifestDir, file);
5580
- const img = existsSync10(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;
5581
5487
  refCache.set(file, img);
5582
5488
  return img;
5583
5489
  };
5584
5490
  for (const a of kit.assets) {
5585
5491
  if (!/\.png$/i.test(a.path)) continue;
5586
- const file = isAbsolute3(a.path) && existsSync10(a.path) ? a.path : join10(kitDir, a.path.split("/").pop() ?? a.path);
5587
- const img = existsSync10(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;
5588
5494
  if (!img) {
5589
5495
  skipped.push(`${a.destination ?? a.path}: unreadable`);
5590
5496
  continue;
@@ -5615,8 +5521,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5615
5521
  const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
5616
5522
  const nameA = `${id}--A.png`;
5617
5523
  const nameB = `${id}--B.png`;
5618
- await writeFile9(join10(out, nameA), encodePng(composeSheet(img, refImg)));
5619
- 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)));
5620
5526
  const rubric = [
5621
5527
  `# ${id} against ${ref.id} (${ref.file})`,
5622
5528
  "",
@@ -5636,7 +5542,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5636
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.",
5637
5543
  ""
5638
5544
  ].join("\n");
5639
- await writeFile9(join10(out, `${id}.md`), rubric);
5545
+ await writeFile9(join9(out, `${id}.md`), rubric);
5640
5546
  sheets.push({
5641
5547
  asset: id,
5642
5548
  reference: ref.id,
@@ -5645,8 +5551,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5645
5551
  rubric: `${id}.md`
5646
5552
  });
5647
5553
  }
5648
- const verdictFile = join10(out, "judge.json");
5649
- if (!existsSync10(verdictFile)) {
5554
+ const verdictFile = join9(out, "judge.json");
5555
+ if (!existsSync9(verdictFile)) {
5650
5556
  await writeFile9(
5651
5557
  verdictFile,
5652
5558
  JSON.stringify(
@@ -5698,9 +5604,9 @@ function catalogFamily(family) {
5698
5604
  }
5699
5605
 
5700
5606
  // src/plugin/callout.ts
5701
- import { existsSync as existsSync11 } from "fs";
5702
- import { readFile as readFile9 } from "fs/promises";
5703
- import { join as join11 } from "path";
5607
+ import { existsSync as existsSync10 } from "fs";
5608
+ import { readFile as readFile10 } from "fs/promises";
5609
+ import { join as join10 } from "path";
5704
5610
  import { lerpArray, sample } from "@vosjs/timeline";
5705
5611
  import {
5706
5612
  CALLOUT_DEFAULT_BODY_PX,
@@ -5897,13 +5803,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5897
5803
  const out = /* @__PURE__ */ new Map();
5898
5804
  if (!times.length) return out;
5899
5805
  const bare = { ...doc, overlays: [] };
5900
- const outDir = join11(dir, ".ground-check");
5806
+ const outDir = join10(dir, ".ground-check");
5901
5807
  const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
5902
5808
  for (const f of res.frames) {
5903
- if (!existsSync11(f.file)) continue;
5809
+ if (!existsSync10(f.file)) continue;
5904
5810
  out.set(
5905
5811
  +f.time.toFixed(3),
5906
- decodePng(new Uint8Array(await readFile9(f.file)))
5812
+ decodePng(new Uint8Array(await readFile10(f.file)))
5907
5813
  );
5908
5814
  }
5909
5815
  return out;
@@ -5912,7 +5818,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5912
5818
  // src/plugin/kitPicture.ts
5913
5819
  import { execFile } from "child_process";
5914
5820
  import { promisify } from "util";
5915
- import { readFile as readFile10 } from "fs/promises";
5821
+ import { readFile as readFile11 } from "fs/promises";
5916
5822
  var execFileP = promisify(execFile);
5917
5823
  var SUBJECT_BAND = { min: 0.6, max: 0.92 };
5918
5824
  var BLANK_INK = 0.12;
@@ -6188,7 +6094,7 @@ async function pictureChecks(assets) {
6188
6094
  let ffmpeg = null;
6189
6095
  for (const a of assets) {
6190
6096
  if (/\.png$/i.test(a.file)) {
6191
- const img = decodePng(new Uint8Array(await readFile10(a.file)));
6097
+ const img = decodePng(new Uint8Array(await readFile11(a.file)));
6192
6098
  if (!img) {
6193
6099
  findings.push({
6194
6100
  code: "unreadable",
@@ -6281,9 +6187,9 @@ function formatFinding(f) {
6281
6187
  }
6282
6188
 
6283
6189
  // src/plugin/validateKit.ts
6284
- import { existsSync as existsSync12 } from "fs";
6285
- import { readFile as readFile11, stat as stat3 } from "fs/promises";
6286
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join12 } from "path";
6190
+ import { existsSync as existsSync11 } from "fs";
6191
+ import { readFile as readFile12, stat as stat3 } from "fs/promises";
6192
+ import { dirname as dirname3, isAbsolute as isAbsolute4, join as join11 } from "path";
6287
6193
  import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
6288
6194
  var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
6289
6195
  function pngDimensions(bytes) {
@@ -6302,7 +6208,7 @@ async function probeVideo(path) {
6302
6208
  const MB = await import("mediabunny");
6303
6209
  const input = new MB.Input({
6304
6210
  formats: MB.ALL_FORMATS,
6305
- source: new MB.BufferSource(new Uint8Array(await readFile11(path)))
6211
+ source: new MB.BufferSource(new Uint8Array(await readFile12(path)))
6306
6212
  });
6307
6213
  try {
6308
6214
  const track = await input.getPrimaryVideoTrack();
@@ -6323,7 +6229,7 @@ async function validateKit(kitPath, opts = {}) {
6323
6229
  const measured = [];
6324
6230
  let kit;
6325
6231
  try {
6326
- kit = JSON.parse(await readFile11(kitPath, "utf8"));
6232
+ kit = JSON.parse(await readFile12(kitPath, "utf8"));
6327
6233
  } catch (e) {
6328
6234
  return {
6329
6235
  valid: false,
@@ -6342,8 +6248,8 @@ async function validateKit(kitPath, opts = {}) {
6342
6248
  }
6343
6249
  const base = dirname3(kitPath);
6344
6250
  const resolvePath = (p) => {
6345
- if (isAbsolute4(p) && existsSync12(p)) return p;
6346
- const beside = join12(base, p.split("/").pop() ?? p);
6251
+ if (isAbsolute4(p) && existsSync11(p)) return p;
6252
+ const beside = join11(base, p.split("/").pop() ?? p);
6347
6253
  return beside;
6348
6254
  };
6349
6255
  const perDestination = /* @__PURE__ */ new Map();
@@ -6369,7 +6275,7 @@ async function validateKit(kitPath, opts = {}) {
6369
6275
  let h = null;
6370
6276
  let seconds = null;
6371
6277
  if (/\.(png|jpe?g|webp)$/i.test(file)) {
6372
- const head = Buffer.from(await readFile11(file));
6278
+ const head = Buffer.from(await readFile12(file));
6373
6279
  const kind = sniffImage(head);
6374
6280
  if (file.toLowerCase().endsWith(".png") && kind !== "png")
6375
6281
  problems.push(
@@ -6478,8 +6384,8 @@ async function validateKit(kitPath, opts = {}) {
6478
6384
  }
6479
6385
 
6480
6386
  // src/plugin/recorder.ts
6481
- import { writeFileSync as writeFileSync2 } from "fs";
6482
- import { join as join13 } from "path";
6387
+ import { writeFileSync } from "fs";
6388
+ import { join as join12 } from "path";
6483
6389
  import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
6484
6390
  async function recordTake(browser, url, actions, paths, log, opts = {}) {
6485
6391
  const dry = opts.dryRun === true;
@@ -6489,7 +6395,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6489
6395
  actions,
6490
6396
  {
6491
6397
  frame: (file, bytes) => {
6492
- writeFileSync2(join13(paths.framesDir, file), bytes);
6398
+ writeFileSync(join12(paths.framesDir, file), bytes);
6493
6399
  }
6494
6400
  },
6495
6401
  log,
@@ -6541,11 +6447,11 @@ import {
6541
6447
 
6542
6448
  // src/plugin/session.ts
6543
6449
  import { spawn } from "child_process";
6544
- import { existsSync as existsSync13, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
6545
- import { homedir as homedir2 } from "os";
6546
- 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";
6547
6453
  import { chromium } from "playwright";
6548
- var SESSIONS_DIR = join14(homedir2(), ".config", "vos", "sessions");
6454
+ var SESSIONS_DIR = join13(homedir(), ".config", "vos", "sessions");
6549
6455
  var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
6550
6456
  function sessionNameVerdict(name) {
6551
6457
  if (!name) return "a session needs a name: --name <app>";
@@ -6554,20 +6460,20 @@ function sessionNameVerdict(name) {
6554
6460
  return null;
6555
6461
  }
6556
6462
  function sessionDir(name) {
6557
- return join14(SESSIONS_DIR, name);
6463
+ return join13(SESSIONS_DIR, name);
6558
6464
  }
6559
6465
  function systemChromePath() {
6560
6466
  const explicit = process.env.VOS_BROWSER_PATH;
6561
6467
  if (explicit) return explicit;
6562
6468
  const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
6563
- join14(
6469
+ join13(
6564
6470
  process.env["PROGRAMFILES"] ?? "C:\\Program Files",
6565
6471
  "Google",
6566
6472
  "Chrome",
6567
6473
  "Application",
6568
6474
  "chrome.exe"
6569
6475
  ),
6570
- join14(
6476
+ join13(
6571
6477
  process.env["LOCALAPPDATA"] ?? "",
6572
6478
  "Google",
6573
6479
  "Chrome",
@@ -6579,7 +6485,7 @@ function systemChromePath() {
6579
6485
  "/usr/bin/google-chrome-stable",
6580
6486
  "/opt/google/chrome/chrome"
6581
6487
  ];
6582
- const found = candidates.find((c) => existsSync13(c));
6488
+ const found = candidates.find((c) => existsSync12(c));
6583
6489
  if (!found)
6584
6490
  throw new Error(
6585
6491
  "no Google Chrome found: a session needs the system Chrome (the bundled Chromium cannot read a profile Chrome wrote). Install Chrome, or set VOS_BROWSER_PATH"
@@ -6589,7 +6495,7 @@ function systemChromePath() {
6589
6495
  async function openSession(name, url, opts = { log: () => {
6590
6496
  } }) {
6591
6497
  const dir = sessionDir(name);
6592
- mkdirSync2(dir, { recursive: true, mode: 448 });
6498
+ mkdirSync(dir, { recursive: true, mode: 448 });
6593
6499
  const exe = systemChromePath();
6594
6500
  const args = [
6595
6501
  `--user-data-dir=${dir}`,
@@ -6610,7 +6516,7 @@ async function openSession(name, url, opts = { log: () => {
6610
6516
  }
6611
6517
  async function withSession(name, fn, opts = {}) {
6612
6518
  const dir = sessionDir(name);
6613
- if (!existsSync13(dir))
6519
+ if (!existsSync12(dir))
6614
6520
  throw new Error(`no session "${name}" (vos session list)`);
6615
6521
  const explicit = process.env.VOS_BROWSER_PATH;
6616
6522
  const ctx = await chromium.launchPersistentContext(dir, {
@@ -6659,9 +6565,9 @@ function summaryLine(s) {
6659
6565
  return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
6660
6566
  }
6661
6567
  function listSessions() {
6662
- if (!existsSync13(SESSIONS_DIR)) return [];
6568
+ if (!existsSync12(SESSIONS_DIR)) return [];
6663
6569
  return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
6664
- const st = statSync3(join14(SESSIONS_DIR, d.name));
6570
+ const st = statSync3(join13(SESSIONS_DIR, d.name));
6665
6571
  return {
6666
6572
  name: d.name,
6667
6573
  ageDays: Math.floor(
@@ -6672,7 +6578,7 @@ function listSessions() {
6672
6578
  }
6673
6579
  function removeSession(name) {
6674
6580
  const dir = sessionDir(name);
6675
- if (!existsSync13(dir)) return false;
6581
+ if (!existsSync12(dir)) return false;
6676
6582
  rmSync(dir, { recursive: true, force: true });
6677
6583
  return true;
6678
6584
  }
@@ -6737,9 +6643,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
6737
6643
  }
6738
6644
 
6739
6645
  // src/plugin/plan.ts
6740
- import { existsSync as existsSync14 } from "fs";
6741
- import { readFile as readFile12 } from "fs/promises";
6742
- import { join as join15 } from "path";
6646
+ import { existsSync as existsSync13 } from "fs";
6647
+ import { readFile as readFile13 } from "fs/promises";
6648
+ import { join as join14 } from "path";
6743
6649
  import {
6744
6650
  DEFAULT_FRAME_STYLE,
6745
6651
  STYLE_FIELDS,
@@ -6961,10 +6867,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
6961
6867
  // src/plugin/plan.ts
6962
6868
  var overlaps = (a, b) => a.in < b.out && b.in < a.out;
6963
6869
  async function readDigestActivity(dir) {
6964
- const file = join15(dir, "digest", "digest.json");
6965
- if (!existsSync14(file)) return null;
6870
+ const file = join14(dir, "digest", "digest.json");
6871
+ if (!existsSync13(file)) return null;
6966
6872
  try {
6967
- const d = JSON.parse(await readFile12(file, "utf8"));
6873
+ const d = JSON.parse(await readFile13(file, "utf8"));
6968
6874
  return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
6969
6875
  } catch {
6970
6876
  return null;
@@ -7025,7 +6931,7 @@ async function planTake(dir, opts = {}) {
7025
6931
  const take = await loadTake(dir);
7026
6932
  const { meta, cursor } = take;
7027
6933
  const activity = await readDigestActivity(dir);
7028
- 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) } : {};
7029
6935
  let doc;
7030
6936
  let fresh;
7031
6937
  let layout;
@@ -7177,16 +7083,16 @@ async function planTake(dir, opts = {}) {
7177
7083
  }
7178
7084
 
7179
7085
  // src/plugin/sync.ts
7180
- import { createHash } from "crypto";
7181
- import { existsSync as existsSync15, readFileSync as readFileSync3, readdirSync as readdirSync3 } from "fs";
7182
- 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";
7183
7089
  import { createInterface } from "readline/promises";
7184
- import { basename as basename3, join as join17 } from "path";
7090
+ import { basename as basename3, join as join16 } from "path";
7185
7091
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
7186
7092
 
7187
7093
  // src/plugin/folder.ts
7188
7094
  import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
7189
- import { join as join16 } from "path";
7095
+ import { join as join15 } from "path";
7190
7096
  import { recipeHints } from "@vosjs/shared/frontmatter";
7191
7097
  import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7192
7098
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
@@ -7362,18 +7268,18 @@ async function cmdPull(argv) {
7362
7268
  throw new Error(apiError(`pull folder ${folder.slug}`, res));
7363
7269
  const payload = res.body;
7364
7270
  const out = strFlag(flags, "out") ?? folder.slug;
7365
- await mkdir8(join16(out, "recipes"), { recursive: true });
7271
+ await mkdir8(join15(out, "recipes"), { recursive: true });
7366
7272
  const lines = [];
7367
7273
  const recipes = [];
7368
7274
  for (const rec of payload.recipes) {
7369
- const path = join16(out, "recipes", safeName(rec.filename));
7275
+ const path = join15(out, "recipes", safeName(rec.filename));
7370
7276
  await writeFile10(path, rec.body ?? "");
7371
7277
  recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
7372
7278
  }
7373
7279
  for (const rec of payload.inheritedRecipes) {
7374
7280
  const from = safeName(rec.folderSlug ?? "inherited");
7375
- await mkdir8(join16(out, "recipes", "_inherited", from), { recursive: true });
7376
- const path = join16(
7281
+ await mkdir8(join15(out, "recipes", "_inherited", from), { recursive: true });
7282
+ const path = join15(
7377
7283
  out,
7378
7284
  "recipes",
7379
7285
  "_inherited",
@@ -7388,7 +7294,7 @@ async function cmdPull(argv) {
7388
7294
  }
7389
7295
  const members = [];
7390
7296
  for (const v of payload.voses) {
7391
- 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));
7392
7298
  await mkdir8(dir, { recursive: true });
7393
7299
  const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
7394
7300
  if (cfg.status !== 200) {
@@ -7396,7 +7302,7 @@ async function cmdPull(argv) {
7396
7302
  continue;
7397
7303
  }
7398
7304
  await writeFile10(
7399
- join16(dir, "config.json"),
7305
+ join15(dir, "config.json"),
7400
7306
  JSON.stringify(cfg.body.config, null, 2)
7401
7307
  );
7402
7308
  let take = false;
@@ -7408,7 +7314,7 @@ async function cmdPull(argv) {
7408
7314
  );
7409
7315
  if (doc.status === 200) {
7410
7316
  await writeFile10(
7411
- join16(dir, "doc.json"),
7317
+ join15(dir, "doc.json"),
7412
7318
  JSON.stringify(doc.body, null, 2)
7413
7319
  );
7414
7320
  take = true;
@@ -7478,144 +7384,6 @@ async function cmdFolder(argv) {
7478
7384
  }
7479
7385
  }
7480
7386
 
7481
- // src/plugin/uploadAsset.ts
7482
- var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
7483
- var PART_ATTEMPTS = 3;
7484
- function planParts(totalBytes, partBytes) {
7485
- if (totalBytes <= 0 || partBytes <= 0) return [];
7486
- const parts = [];
7487
- for (let start = 0; start < totalBytes; start += partBytes) {
7488
- parts.push({
7489
- partNumber: parts.length + 1,
7490
- start,
7491
- end: Math.min(start + partBytes, totalBytes)
7492
- });
7493
- }
7494
- return parts;
7495
- }
7496
- function failed(what, r) {
7497
- const detail = typeof r.body.error === "string" ? r.body.error : "";
7498
- return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
7499
- }
7500
- function asAsset(body) {
7501
- return {
7502
- id: String(body.id),
7503
- url: String(body.url),
7504
- size: typeof body.size === "number" ? body.size : 0,
7505
- reused: body.reused === true
7506
- };
7507
- }
7508
- function transient(status) {
7509
- return status >= 500 || status === 429;
7510
- }
7511
- var wait = (ms) => new Promise((r) => setTimeout(r, ms));
7512
- async function uploadAsset(target, bytes, opts) {
7513
- return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
7514
- }
7515
- async function uploadWhole(target, bytes, opts) {
7516
- const res = await apiJson(target.origin, "/api/assets/recording", {
7517
- method: "POST",
7518
- key: target.key,
7519
- headers: {
7520
- "Content-Type": opts.contentType,
7521
- "Content-Length": String(bytes.length),
7522
- "X-Filename": opts.filename,
7523
- "X-Content-Hash": opts.contentHash,
7524
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
7525
- },
7526
- raw: bytes
7527
- });
7528
- if (res.status !== 201 && res.status !== 200)
7529
- throw failed("upload failed", res);
7530
- return asAsset(res.body);
7531
- }
7532
- async function uploadInParts(target, bytes, opts) {
7533
- const begun = await apiJson(
7534
- target.origin,
7535
- "/api/assets/recording/multipart",
7536
- {
7537
- method: "POST",
7538
- key: target.key,
7539
- body: {
7540
- filename: opts.filename,
7541
- contentType: opts.contentType,
7542
- size: bytes.length,
7543
- contentHash: opts.contentHash,
7544
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
7545
- }
7546
- }
7547
- );
7548
- if (begun.status !== 201 && begun.status !== 200) {
7549
- throw failed("upload failed", begun);
7550
- }
7551
- if (begun.body.reused === true || typeof begun.body.url === "string") {
7552
- return asAsset(begun.body);
7553
- }
7554
- const uploadId = String(begun.body.id);
7555
- const partBytes = Number(begun.body.partBytes);
7556
- if (!Number.isFinite(partBytes) || partBytes <= 0) {
7557
- throw new Error("upload failed: the platform returned no part size");
7558
- }
7559
- const parts = planParts(bytes.length, partBytes);
7560
- const etags = [];
7561
- try {
7562
- for (const part of parts) {
7563
- const chunk2 = bytes.subarray(part.start, part.end);
7564
- let sent = null;
7565
- for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
7566
- try {
7567
- sent = await apiJson(
7568
- target.origin,
7569
- `/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
7570
- {
7571
- method: "PUT",
7572
- key: target.key,
7573
- headers: {
7574
- "Content-Type": "application/octet-stream",
7575
- "Content-Length": String(chunk2.length)
7576
- },
7577
- raw: chunk2
7578
- }
7579
- );
7580
- } catch (networkError) {
7581
- if (attempt === PART_ATTEMPTS) throw networkError;
7582
- await wait(500 * 2 ** (attempt - 1));
7583
- continue;
7584
- }
7585
- if (sent.status === 200) break;
7586
- if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
7587
- throw failed(`part ${part.partNumber} failed`, sent);
7588
- }
7589
- await wait(500 * 2 ** (attempt - 1));
7590
- }
7591
- if (!sent || sent.status !== 200) {
7592
- throw new Error(`part ${part.partNumber} failed`);
7593
- }
7594
- etags.push({
7595
- partNumber: Number(sent.body.partNumber),
7596
- etag: String(sent.body.etag)
7597
- });
7598
- opts.onPart?.(etags.length, parts.length);
7599
- }
7600
- const sealed = await apiJson(
7601
- target.origin,
7602
- `/api/assets/recording/multipart/${uploadId}/complete`,
7603
- { method: "POST", key: target.key, body: { parts: etags } }
7604
- );
7605
- if (sealed.status !== 201 && sealed.status !== 200) {
7606
- throw failed("upload failed", sealed);
7607
- }
7608
- return asAsset(sealed.body);
7609
- } catch (err) {
7610
- await apiJson(
7611
- target.origin,
7612
- `/api/assets/recording/multipart/${uploadId}`,
7613
- { method: "DELETE", key: target.key }
7614
- ).catch(() => void 0);
7615
- throw err;
7616
- }
7617
- }
7618
-
7619
7387
  // src/plugin/sync.ts
7620
7388
  function apiContext(flags) {
7621
7389
  return {
@@ -7682,8 +7450,8 @@ function storageStateInDir(dir) {
7682
7450
  if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
7683
7451
  continue;
7684
7452
  try {
7685
- const v = JSON.parse(readFileSync3(join17(dir, f), "utf8"));
7686
- 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);
7687
7455
  } catch {
7688
7456
  }
7689
7457
  }
@@ -7704,7 +7472,7 @@ async function pushTake(dir, flags, r) {
7704
7472
  throw new Error(`doc.json fails lint:
7705
7473
  ${lint.problems.join("\n ")}`);
7706
7474
  }
7707
- if (!existsSync15(take.paths.recording)) {
7475
+ if (!existsSync14(take.paths.recording)) {
7708
7476
  throw new Error(
7709
7477
  "no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
7710
7478
  );
@@ -7753,8 +7521,8 @@ async function pushTake(dir, flags, r) {
7753
7521
  throw new Error("push cancelled");
7754
7522
  }
7755
7523
  }
7756
- const bytes = await readFile13(take.paths.recording);
7757
- 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");
7758
7526
  const { type: contentType } = resolveMediaType({
7759
7527
  head: bytes.subarray(0, MEDIA_HEAD_BYTES),
7760
7528
  filename: take.paths.recordingName
@@ -7795,39 +7563,7 @@ async function pushTake(dir, flags, r) {
7795
7563
  r.log(" mic track is local-only \u2014 dropped from the push");
7796
7564
  delete docForPush.source.micKey;
7797
7565
  }
7798
- for (const ref of docMediaRefs(docForPush)) {
7799
- const file = join17(dir, takeRelativeFile(ref.key));
7800
- if (!existsSync15(file)) {
7801
- r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
7802
- continue;
7803
- }
7804
- const media = await readFile13(file);
7805
- const mediaHash = createHash("sha256").update(media).digest("hex");
7806
- const { type } = resolveMediaType({
7807
- head: media.subarray(0, MEDIA_HEAD_BYTES),
7808
- filename: file
7809
- });
7810
- const name = nameForType(basename3(file), type);
7811
- if (name !== basename3(file)) {
7812
- r.log(` ${ref.where}: ${basename3(file)} holds ${type} \u2014 sent as ${name}`);
7813
- }
7814
- let put;
7815
- try {
7816
- put = await uploadAsset(ctx, new Uint8Array(media), {
7817
- filename: name,
7818
- contentType: type,
7819
- contentHash: mediaHash
7820
- });
7821
- } catch (e) {
7822
- throw new Error(
7823
- `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
7824
- );
7825
- }
7826
- ref.set(put.url);
7827
- r.log(
7828
- ` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
7829
- );
7830
- }
7566
+ await uploadDocRefs(docMediaRefs(docForPush), dir, ctx, (l) => r.log(l));
7831
7567
  const lowered = lowerToComposition3(docForPush);
7832
7568
  const config = { ...lowered.config, data: lowered.data };
7833
7569
  if (!state) {
@@ -7941,9 +7677,9 @@ async function pullTake(dir, flags, r) {
7941
7677
  };
7942
7678
  }
7943
7679
  let media2;
7944
- if (flags.media && existsSync15(join17(dir, "doc.json"))) {
7680
+ if (flags.media && existsSync14(join16(dir, "doc.json"))) {
7945
7681
  const local = JSON.parse(
7946
- await readFile13(join17(dir, "doc.json"), "utf8")
7682
+ await readFile14(join16(dir, "doc.json"), "utf8")
7947
7683
  );
7948
7684
  media2 = await pullMedia(ctx, dir, local, r.log);
7949
7685
  }
@@ -8004,10 +7740,10 @@ async function pullTake(dir, flags, r) {
8004
7740
  const hostedDoc = migrateHostedDoc3(docRes.json);
8005
7741
  const doc = hostedDoc;
8006
7742
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
8007
- if (existsSync15(localRecording)) {
7743
+ if (existsSync14(localRecording)) {
8008
7744
  doc.source.videoKey = basename3(localRecording);
8009
7745
  }
8010
- await writeJson(join17(dir, "doc.json"), doc, true);
7746
+ await writeJson(join16(dir, "doc.json"), doc, true);
8011
7747
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
8012
7748
  const versions = await api(ctx, `/vos/${vosId}/versions`);
8013
7749
  const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
@@ -8026,10 +7762,10 @@ async function pullTake(dir, flags, r) {
8026
7762
  }
8027
7763
 
8028
7764
  // src/plugin/program.ts
8029
- import { existsSync as existsSync16 } from "fs";
8030
- import { mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
7765
+ import { existsSync as existsSync15 } from "fs";
7766
+ import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
8031
7767
  import { createInterface as createInterface2 } from "readline/promises";
8032
- 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";
8033
7769
  import {
8034
7770
  CURRENT_CONFIG_VERSION,
8035
7771
  migrateConfig,
@@ -8169,7 +7905,8 @@ var BOOLEAN_FLAGS2 = /* @__PURE__ */ new Set([
8169
7905
  "check",
8170
7906
  "yes",
8171
7907
  "claimable",
8172
- "no-browser"
7908
+ "no-browser",
7909
+ "wait"
8173
7910
  ]);
8174
7911
  var MULTI_FLAGS = /* @__PURE__ */ new Set(["override"]);
8175
7912
  function preflightConfig(parsed) {
@@ -8234,8 +7971,8 @@ function preflightConfig(parsed) {
8234
7971
  }
8235
7972
  function resolveConfigPath(target) {
8236
7973
  if (target.endsWith(".json")) return target;
8237
- const inDir = join18(target, "config.json");
8238
- if (existsSync16(inDir)) return inDir;
7974
+ const inDir = join17(target, "config.json");
7975
+ if (existsSync15(inDir)) return inDir;
8239
7976
  throw new UsageError(
8240
7977
  `${target} has no config.json (and is not a take \u2014 no doc.json)`
8241
7978
  );
@@ -8263,7 +8000,7 @@ async function cmdFetch(argv) {
8263
8000
  const out = strFlag(flags, "out") ?? slug;
8264
8001
  await mkdir9(out, { recursive: true });
8265
8002
  await writeFile11(
8266
- join18(out, "config.json"),
8003
+ join17(out, "config.json"),
8267
8004
  JSON.stringify(cfg.body.config, null, 2)
8268
8005
  );
8269
8006
  const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
@@ -8288,12 +8025,12 @@ async function cmdFetch(argv) {
8288
8025
  const hosted = migrateHostedDoc4(doc.body);
8289
8026
  const { config: own } = await writeProgramDoc(out, hosted);
8290
8027
  if (own) {
8291
- await writeFile11(join18(out, "config.json"), JSON.stringify(own, null, 2));
8028
+ await writeFile11(join17(out, "config.json"), JSON.stringify(own, null, 2));
8292
8029
  }
8293
8030
  } else if (doc.status === 200) {
8294
8031
  take = true;
8295
8032
  const hosted = migrateHostedDoc4(doc.body);
8296
- await writeFile11(join18(out, "doc.json"), JSON.stringify(hosted, null, 2));
8033
+ await writeFile11(join17(out, "doc.json"), JSON.stringify(hosted, null, 2));
8297
8034
  if (flags.media === true) {
8298
8035
  const media = await pullMedia({ origin, key }, out, hosted, r.log);
8299
8036
  mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
@@ -8406,7 +8143,7 @@ async function cmdPushProgram(argv) {
8406
8143
  api: strFlag(flags, "api")
8407
8144
  });
8408
8145
  const dir = dirname4(source);
8409
- const parsed = JSON.parse(await readFile14(source, "utf8"));
8146
+ const parsed = JSON.parse(await readFile15(source, "utf8"));
8410
8147
  const pre = preflightConfig(parsed);
8411
8148
  if (!pre.ok || !pre.config) {
8412
8149
  for (const issue of pre.issues) r.log(`error ${issue}`);
@@ -8435,7 +8172,25 @@ async function cmdPushProgram(argv) {
8435
8172
  }
8436
8173
  const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
8437
8174
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8438
- 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
+ };
8439
8194
  const slug = strFlag(flags, "slug");
8440
8195
  if (slug) body.slug = slug;
8441
8196
  const res = await apiJson(origin, "/api/claim", { method: "POST", body });
@@ -8455,6 +8210,15 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8455
8210
  return EXIT_OK;
8456
8211
  }
8457
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
+ }
8458
8222
  const overrides = [
8459
8223
  ...multi.override ?? [],
8460
8224
  ...strFlag(flags, "overrides")?.split(",") ?? []
@@ -8524,6 +8288,13 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8524
8288
  }
8525
8289
  const watchUrl2 = `${origin}/vos/${vosId}`;
8526
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;
8527
8298
  r.done(
8528
8299
  {
8529
8300
  id: vosId,
@@ -8531,11 +8302,17 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8531
8302
  versionNumber: version.versionNumber ?? null,
8532
8303
  base: base ?? null,
8533
8304
  watchUrl: watchUrl2,
8534
- studioUrl: studioUrl2
8305
+ studioUrl: studioUrl2,
8306
+ ...landed2 ? {
8307
+ still: landed2.still,
8308
+ thumbnailUrl: landed2.thumbnailUrl,
8309
+ previewUrl: landed2.previewUrl
8310
+ } : {}
8535
8311
  },
8536
8312
  `Pushed version ${String(version.versionNumber ?? "?")} of ${vosId}
8537
8313
  watch: ${watchUrl2}
8538
- studio: ${studioUrl2}`
8314
+ studio: ${studioUrl2}` + (landed2 ? `
8315
+ ${landedLines(landed2)}` : "")
8539
8316
  );
8540
8317
  return EXIT_OK;
8541
8318
  }
@@ -8571,20 +8348,35 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8571
8348
  });
8572
8349
  const watchUrl = `${origin}/vos/${created.id}`;
8573
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;
8574
8358
  r.done(
8575
8359
  {
8576
8360
  id: created.id,
8577
8361
  slug: created.slug,
8578
8362
  title,
8579
8363
  visibility: "private",
8364
+ folder: folderRef ?? null,
8580
8365
  remixOfId: remixOfId ?? null,
8581
8366
  currentVersionId: created.currentVersionId,
8582
8367
  watchUrl,
8583
- studioUrl
8368
+ studioUrl,
8369
+ ...landed ? {
8370
+ still: landed.still,
8371
+ thumbnailUrl: landed.thumbnailUrl,
8372
+ previewUrl: landed.previewUrl
8373
+ } : {}
8584
8374
  },
8585
8375
  `Created private vos ${created.id} (${title})
8586
8376
  watch: ${watchUrl}
8587
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>)`}
8588
8380
  Iterate with: vos push ${source} --vos ${created.id}`
8589
8381
  );
8590
8382
  return EXIT_OK;
@@ -8656,10 +8448,10 @@ async function cmdPullProgram(argv) {
8656
8448
  const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
8657
8449
  if (cfg.status !== 200)
8658
8450
  throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
8659
- const configPath = join18(dir, "config.json");
8451
+ const configPath = join17(dir, "config.json");
8660
8452
  let backedUp = false;
8661
- if (existsSync16(configPath)) {
8662
- await writeFile11(join18(dir, "config.backup.json"), await readFile14(configPath));
8453
+ if (existsSync15(configPath)) {
8454
+ await writeFile11(join17(dir, "config.backup.json"), await readFile15(configPath));
8663
8455
  backedUp = true;
8664
8456
  }
8665
8457
  let headConfig = cfg.body.config;
@@ -8692,7 +8484,7 @@ async function cmdPullProgram(argv) {
8692
8484
  protected: protectedIds,
8693
8485
  changes,
8694
8486
  out: configPath,
8695
- backup: backedUp ? join18(dir, "config.backup.json") : null
8487
+ backup: backedUp ? join17(dir, "config.backup.json") : null
8696
8488
  },
8697
8489
  `Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
8698
8490
  Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
@@ -8763,15 +8555,18 @@ async function storeKey(key, origin, r) {
8763
8555
  function isTakeDir(target) {
8764
8556
  return directoryKind(target) === "take";
8765
8557
  }
8558
+ function soundOf(doc) {
8559
+ return { audio: Array.isArray(doc.audio) ? doc.audio : [] };
8560
+ }
8766
8561
  function storedProgramConfig(config, programDoc) {
8767
8562
  if (!programDoc) return config;
8768
8563
  const doc = { audio: [], ...programDoc };
8769
8564
  return lowerProgramDoc(doc).config;
8770
8565
  }
8771
8566
  async function readProgramDoc(dir, config) {
8772
- const docPath = join18(dir, "doc.json");
8773
- if (!existsSync16(docPath)) return null;
8774
- const parsed = JSON.parse(await readFile14(docPath, "utf8"));
8567
+ const docPath = join17(dir, "doc.json");
8568
+ if (!existsSync15(docPath)) return null;
8569
+ const parsed = JSON.parse(await readFile15(docPath, "utf8"));
8775
8570
  if (typeof parsed !== "object" || parsed === null || "source" in parsed)
8776
8571
  return null;
8777
8572
  const raw = parsed;
@@ -8782,14 +8577,15 @@ async function writeProgramDoc(dir, hosted) {
8782
8577
  const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
8783
8578
  const { config, ...rest } = program;
8784
8579
  const onDisk = { ...hosted, program: rest };
8785
- await writeFile11(join18(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8580
+ await writeFile11(join17(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8786
8581
  return {
8787
8582
  config: config && typeof config === "object" ? config : null
8788
8583
  };
8789
8584
  }
8790
8585
 
8791
8586
  // src/plugin/asset.ts
8792
- import { readFileSync as readFileSync4 } from "fs";
8587
+ import { createHash as createHash3 } from "crypto";
8588
+ import { readFileSync as readFileSync3 } from "fs";
8793
8589
  import { basename as basename5, extname as extname2 } from "path";
8794
8590
  var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
8795
8591
  var MIME_BY_EXT = {
@@ -8840,10 +8636,38 @@ async function cmdPush(argv) {
8840
8636
  const folderRef = strFlag(flags, "folder");
8841
8637
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
8842
8638
  const uploaded = [];
8639
+ let unfiledMedia = 0;
8843
8640
  for (const file of positionals) {
8844
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
+ }
8845
8670
  const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
8846
- const body = readFileSync4(file);
8847
8671
  const form = new FormData();
8848
8672
  form.set("file", new Blob([body], { type: mime }), name);
8849
8673
  if (folder) form.set("folderId", folder.id);
@@ -8877,7 +8701,8 @@ async function cmdPush(argv) {
8877
8701
  count: uploaded.length,
8878
8702
  folder: folder?.slug ?? null
8879
8703
  },
8880
- `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>` : "")
8881
8706
  );
8882
8707
  return EXIT_OK;
8883
8708
  }
@@ -8896,10 +8721,69 @@ async function cmdAsset(argv) {
8896
8721
  }
8897
8722
  }
8898
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
+
8899
8783
  // src/plugin/recipe.ts
8900
- import { readFileSync as readFileSync5 } from "fs";
8784
+ import { readFileSync as readFileSync4 } from "fs";
8901
8785
  import { basename as basename6 } from "path";
8902
- var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
8786
+ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
8903
8787
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
8904
8788
  function recipeFilename(file) {
8905
8789
  const name = basename6(file);
@@ -8909,7 +8793,7 @@ function recipeFilename(file) {
8909
8793
  return name;
8910
8794
  }
8911
8795
  async function cmdPush2(argv) {
8912
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
8796
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
8913
8797
  const [file] = positionals;
8914
8798
  const folderRef = strFlag(flags, "folder");
8915
8799
  const assetId = strFlag(flags, "asset");
@@ -8917,7 +8801,7 @@ async function cmdPush2(argv) {
8917
8801
  throw new UsageError(USAGE);
8918
8802
  }
8919
8803
  const filename = recipeFilename(file);
8920
- const body = readFileSync5(file);
8804
+ const body = readFileSync4(file);
8921
8805
  const r = createReporter(flags.json === true);
8922
8806
  const origin = platformOrigin({
8923
8807
  origin: strFlag(flags, "origin"),
@@ -9938,8 +9822,8 @@ async function cmdActions(argv) {
9938
9822
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
9939
9823
  viewport = { width: Number(m[1]), height: Number(m[2]) };
9940
9824
  }
9941
- const { readFile: readFile17 } = await import("fs/promises");
9942
- const text = await readFile17(resolve8(input), "utf8");
9825
+ const { readFile: readFile18 } = await import("fs/promises");
9826
+ const text = await readFile18(resolve8(input), "utf8");
9943
9827
  const { records, problems } = parseAgentBrowserLog(text);
9944
9828
  for (const p of problems) r.log(` ${p}`);
9945
9829
  const result = convertAgentBrowser(records, {
@@ -9976,9 +9860,9 @@ function summary(result) {
9976
9860
  }
9977
9861
  async function cmdActionsScript(input, flags) {
9978
9862
  const r = createReporter(flags.json === true);
9979
- const { readFile: readFile17 } = await import("fs/promises");
9863
+ const { readFile: readFile18 } = await import("fs/promises");
9980
9864
  const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
9981
- const raw = JSON.parse(await readFile17(resolve8(input), "utf8"));
9865
+ const raw = JSON.parse(await readFile18(resolve8(input), "utf8"));
9982
9866
  const errors = validateActions(raw);
9983
9867
  if (errors.length) throw new UsageError(`${input}:
9984
9868
  ${errors.join("\n ")}`);
@@ -9989,7 +9873,7 @@ async function cmdActionsScript(input, flags) {
9989
9873
 
9990
9874
  // src/plugin/ingest.ts
9991
9875
  import { existsSync as existsSync17 } from "fs";
9992
- import { copyFile, mkdir as mkdir10, readFile as readFile15, stat as stat4 } from "fs/promises";
9876
+ import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
9993
9877
  import { basename as basename7, join as join19, resolve as resolve9 } from "path";
9994
9878
  import { inflateRawSync } from "zlib";
9995
9879
  import {
@@ -10010,7 +9894,7 @@ import {
10010
9894
  Output,
10011
9895
  WebMOutputFormat
10012
9896
  } from "mediabunny";
10013
- var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
9897
+ var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set(["json", "help"]);
10014
9898
  async function probeVideoFile(path) {
10015
9899
  const input = new Input({
10016
9900
  source: new FilePathSource(path),
@@ -10078,7 +9962,7 @@ async function readTraceFile(path, offsetMs) {
10078
9962
  );
10079
9963
  }
10080
9964
  if (kind === "zip") {
10081
- const bytes = new Uint8Array(await readFile15(path));
9965
+ const bytes = new Uint8Array(await readFile16(path));
10082
9966
  const entry = playwrightTraceEntry(zipEntries(bytes));
10083
9967
  if (!entry) {
10084
9968
  throw new UsageError(
@@ -10091,7 +9975,7 @@ async function readTraceFile(path, offsetMs) {
10091
9975
  );
10092
9976
  return cursorFromPlaywrightTrace(text2, { offsetMs });
10093
9977
  }
10094
- const text = await readFile15(path, "utf8");
9978
+ const text = await readFile16(path, "utf8");
10095
9979
  return cursorFromTrace(basename7(path), text, { offsetMs });
10096
9980
  }
10097
9981
  function parseViewport(v) {
@@ -10101,11 +9985,11 @@ function parseViewport(v) {
10101
9985
  return { width: Number(m[1]), height: Number(m[2]) };
10102
9986
  }
10103
9987
  async function cmdIngest(argv) {
10104
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5, MULTI_FLAGS2);
9988
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS6, MULTI_FLAGS2);
10105
9989
  const source = positionals[0];
10106
9990
  if (!source)
10107
9991
  throw new UsageError(
10108
- "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]"
10109
9993
  );
10110
9994
  const videoPath = resolve9(source);
10111
9995
  if (!existsSync17(videoPath)) throw new UsageError(`${source}: no such file`);
@@ -10114,7 +9998,15 @@ async function cmdIngest(argv) {
10114
9998
  const offsetMs = numFlag(flags, "offset", 0);
10115
9999
  const viewportFlag = parseViewport(strFlag(flags, "viewport"));
10116
10000
  const cursorPath = strFlag(flags, "cursor");
10117
- 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);
10118
10010
  r.log(`probing ${basename7(videoPath)}\u2026`);
10119
10011
  const probed = await probeVideoFile(videoPath);
10120
10012
  if (!(probed.durationMs > 0)) {
@@ -10160,12 +10052,13 @@ async function cmdIngest(argv) {
10160
10052
  };
10161
10053
  await writeJson(paths.meta, meta, true);
10162
10054
  if (events.length) await writeJson(paths.cursor, events);
10163
- const plan = await planTake(outDir, { backdrop });
10055
+ const plan = await planTake(outDir, { backdrop, footage });
10164
10056
  const clicks = events.filter((e) => e.type === "down").length;
10165
10057
  const seconds = (probed.durationMs / 1e3).toFixed(1);
10166
10058
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
10167
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";
10168
- 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`;
10169
10062
  r.done(
10170
10063
  {
10171
10064
  out: outDir,
@@ -10187,9 +10080,11 @@ async function cmdIngest(argv) {
10187
10080
  notes: trace.notes
10188
10081
  } : null,
10189
10082
  zooms: plan.zoomAuto,
10190
- backdrop: plan.backdrop ?? null
10083
+ backdrop: plan.backdrop ?? null,
10084
+ as: footage ? "footage" : "take"
10191
10085
  },
10192
10086
  `Ingested ${basename7(videoPath)} \u2192 ${outDir}/${basename7(recording)} (${facts})
10087
+ ${opened}
10193
10088
  ${cursorLine}
10194
10089
  ${next}`
10195
10090
  );
@@ -10201,17 +10096,17 @@ import { spawnSync } from "child_process";
10201
10096
  import {
10202
10097
  cpSync,
10203
10098
  existsSync as existsSync19,
10204
- mkdirSync as mkdirSync3,
10205
- readFileSync as readFileSync7,
10206
- rmSync as rmSync2,
10207
- writeFileSync as writeFileSync3
10099
+ mkdirSync as mkdirSync2,
10100
+ readFileSync as readFileSync6,
10101
+ rmSync as rmSync3,
10102
+ writeFileSync as writeFileSync2
10208
10103
  } from "fs";
10209
- import { homedir as homedir3 } from "os";
10104
+ import { homedir as homedir2 } from "os";
10210
10105
  import { join as join21 } from "path";
10211
10106
  import { chromium as chromium2 } from "playwright";
10212
10107
 
10213
10108
  // src/plugin/machine.ts
10214
- import { existsSync as existsSync18, 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";
10215
10110
  import { dirname as dirname5, join as join20 } from "path";
10216
10111
  var AGENT_NAMES = [
10217
10112
  "claude",
@@ -10428,10 +10323,10 @@ function bundledSkillsDir(from) {
10428
10323
  const pkg = join20(dir, "package.json");
10429
10324
  if (existsSync18(pkg)) {
10430
10325
  try {
10431
- const name = JSON.parse(readFileSync6(pkg, "utf8")).name;
10326
+ const name = JSON.parse(readFileSync5(pkg, "utf8")).name;
10432
10327
  if (name === "@vosjs/cli") {
10433
10328
  const skills = join20(dir, "skills");
10434
- return existsSync18(skills) && statSync4(skills).isDirectory() ? skills : null;
10329
+ return existsSync18(skills) && statSync5(skills).isDirectory() ? skills : null;
10435
10330
  }
10436
10331
  } catch {
10437
10332
  }
@@ -10444,7 +10339,7 @@ function bundledSkillsDir(from) {
10444
10339
  }
10445
10340
 
10446
10341
  // src/plugin/machineCmd.ts
10447
- var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set([
10342
+ var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set([
10448
10343
  "json",
10449
10344
  "help",
10450
10345
  "global",
@@ -10522,7 +10417,7 @@ async function doctorFacts(opts) {
10522
10417
  };
10523
10418
  }
10524
10419
  async function cmdSetup(argv) {
10525
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10420
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10526
10421
  const r = reporterFor(flags);
10527
10422
  const root = process.cwd();
10528
10423
  const global = flags.global === true;
@@ -10537,7 +10432,7 @@ async function cmdSetup(argv) {
10537
10432
  if ("error" in v) throw new UsageError(v.error);
10538
10433
  homes = v.homes;
10539
10434
  } else {
10540
- homes = detectAgents(root, { home: homedir3(), global });
10435
+ homes = detectAgents(root, { home: homedir2(), global });
10541
10436
  }
10542
10437
  const agents = [...new Set(homes.map((h) => h.agent))];
10543
10438
  r.event({ event: "agents", agents, global });
@@ -10573,7 +10468,7 @@ async function cmdSetup(argv) {
10573
10468
  const targets = homes.length ? homes : [fallbackSkillsHome(root)];
10574
10469
  for (const home of targets) {
10575
10470
  const dest = skillsDirFor(home);
10576
- mkdirSync3(dest, { recursive: true });
10471
+ mkdirSync2(dest, { recursive: true });
10577
10472
  for (const name of installedSkills(bundled)) {
10578
10473
  cpSync(join21(bundled, name), join21(dest, name), { recursive: true });
10579
10474
  }
@@ -10610,9 +10505,9 @@ async function cmdSetup(argv) {
10610
10505
  let rules = null;
10611
10506
  if (!noRules) {
10612
10507
  const file = rulesFileFor(root);
10613
- const existing = existsSync19(file) ? readFileSync7(file, "utf8") : null;
10508
+ const existing = existsSync19(file) ? readFileSync6(file, "utf8") : null;
10614
10509
  const { text, changed } = applyRulesBlock(existing);
10615
- if (changed) writeFileSync3(file, text);
10510
+ if (changed) writeFileSync2(file, text);
10616
10511
  rules = { file, changed };
10617
10512
  r.log(
10618
10513
  changed ? `rules: wrote the vosso block into ${file}` : `rules: ${file} already carries the block`
@@ -10641,10 +10536,10 @@ Next: ${report.next_step}`
10641
10536
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
10642
10537
  }
10643
10538
  async function cmdDoctor(argv) {
10644
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10539
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10645
10540
  const r = reporterFor(flags);
10646
10541
  const root = process.cwd();
10647
- const homes = detectAgents(root, { home: homedir3(), global: true });
10542
+ const homes = detectAgents(root, { home: homedir2(), global: true });
10648
10543
  const report = doctorReport(
10649
10544
  await doctorFacts({
10650
10545
  homes: homes.length ? homes : [fallbackSkillsHome(root)],
@@ -10660,7 +10555,7 @@ Next: ${report.next_step}`
10660
10555
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
10661
10556
  }
10662
10557
  async function cmdWhoami(argv) {
10663
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10558
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10664
10559
  const r = reporterFor(flags);
10665
10560
  const origin = platformOrigin({
10666
10561
  origin: strFlag(flags, "origin"),
@@ -10722,10 +10617,10 @@ async function cmdWhoami(argv) {
10722
10617
  return EXIT_OK;
10723
10618
  }
10724
10619
  function cmdLogout(argv) {
10725
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS6);
10620
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10726
10621
  const r = reporterFor(flags);
10727
10622
  const had = existsSync19(CREDENTIALS_PATH);
10728
- rmSync2(CREDENTIALS_PATH, { force: true });
10623
+ rmSync3(CREDENTIALS_PATH, { force: true });
10729
10624
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
10730
10625
  r.done(
10731
10626
  {
@@ -10740,9 +10635,10 @@ function cmdLogout(argv) {
10740
10635
  }
10741
10636
 
10742
10637
  // src/plugin/run.ts
10743
- var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set([
10638
+ var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set([
10744
10639
  "json",
10745
10640
  "help",
10641
+ "wait",
10746
10642
  "picture",
10747
10643
  "fresh",
10748
10644
  "reuse",
@@ -10783,8 +10679,8 @@ Take pipeline
10783
10679
  vos validate <actions.json|take|kit.json> [--picture] [--json]
10784
10680
  vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
10785
10681
  vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
10786
- vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
10787
- 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
10788
10684
  vos session open <url> --name <app> a plain Chrome window on a vos-owned profile; sign in, close it
10789
10685
  vos session check <name> [--url <url>] headless: does the session still open the page? exit 0/4
10790
10686
  vos session list | rm <name> what exists and how old; delete one
@@ -10820,7 +10716,7 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10820
10716
  vos push <config.json|take> [--vos id] [--title t] [--slug s] [--remix-of id]
10821
10717
  [--desc d] [--tags a,b] [--folder <folderId|slug>]
10822
10718
  [--note n] [--label l] [--base versionId] [--override <id>]... [--yes] [--json]
10823
- [--claimable]
10719
+ [--claimable] [--wait]
10824
10720
  a take DIRECTORY (doc.json) pushes recording + doc; a config.json
10825
10721
  pushes the program. No --vos: create a PRIVATE vos; with --vos:
10826
10722
  add a version against the tracked base \u2014 a stale push 409s WITH
@@ -10832,6 +10728,14 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10832
10728
  user and nowhere else; unclaimed work is deleted after 72h.
10833
10729
  --override consents to touching
10834
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)
10835
10739
  vos pull [dir|take] [--vos <id>] [--since versionId] [--check] [--media] [--json]
10836
10740
  what changed on vos.so since your base; syncs config.json (backup
10837
10741
  kept) or the take's doc.json. ALWAYS pull before editing pushed work.
@@ -10853,7 +10757,10 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10853
10757
  vos asset push <file...> [--folder <folderId|slug>]
10854
10758
  upload files onto your shelf (a key takes models .glb/.gltf,
10855
10759
  recipes .md and images .png/.jpg/.webp/.gif \u2014 posters and
10856
- 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)
10857
10764
  vos recipe push <file.md> --folder <folderId|slug>
10858
10765
  vos recipe push <file.md> --asset <assetId>
10859
10766
  put a recipe (a plain .md) on the shelf: --folder creates it
@@ -11042,7 +10949,7 @@ lint-gated, so a bad override fails like a bad doc.json):
11042
10949
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
11043
10950
  `;
11044
10951
  async function loadActions(file) {
11045
- const raw = JSON.parse(await readFile16(file, "utf8"));
10952
+ const raw = JSON.parse(await readFile17(file, "utf8"));
11046
10953
  const errors = validateActions(raw);
11047
10954
  if (errors.length)
11048
10955
  throw new UsageError(`invalid actions file:
@@ -11174,7 +11081,7 @@ function wallGate(flags, r, opts) {
11174
11081
  async function cmdRecord(argv) {
11175
11082
  const { positionals, flags, multi } = parseArgs(
11176
11083
  argv,
11177
- BOOLEAN_FLAGS7,
11084
+ BOOLEAN_FLAGS8,
11178
11085
  MULTI_FLAGS2
11179
11086
  );
11180
11087
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -11416,7 +11323,7 @@ ${lines.join("\n")}
11416
11323
  async function cmdCreate2(argv) {
11417
11324
  const { positionals, flags, multi } = parseArgs(
11418
11325
  argv,
11419
- BOOLEAN_FLAGS7,
11326
+ BOOLEAN_FLAGS8,
11420
11327
  MULTI_FLAGS2
11421
11328
  );
11422
11329
  const actionsPath = strFlag(flags, "actions");
@@ -11431,10 +11338,7 @@ async function cmdCreate2(argv) {
11431
11338
  if (!url)
11432
11339
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
11433
11340
  const outDir = resolve10(strFlag(flags, "out") ?? "take");
11434
- const fmtRaw = strFlag(flags, "format") ?? "webm";
11435
- if (fmtRaw !== "webm" && fmtRaw !== "mp4")
11436
- throw new UsageError("--format must be webm or mp4");
11437
- const format = fmtRaw;
11341
+ const format = videoFormat(strFlag(flags, "format"), positionals[0]);
11438
11342
  const out = positionals[0] ?? join22(outDir, `out.${format}`);
11439
11343
  const parallel = numFlag(flags, "parallel", 1);
11440
11344
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
@@ -11545,7 +11449,7 @@ async function cmdCreate2(argv) {
11545
11449
  async function cmdPlan(argv) {
11546
11450
  const { positionals, flags, multi } = parseArgs(
11547
11451
  argv,
11548
- BOOLEAN_FLAGS7,
11452
+ BOOLEAN_FLAGS8,
11549
11453
  /* @__PURE__ */ new Set(["with"])
11550
11454
  );
11551
11455
  const dir = positionals[0];
@@ -11568,7 +11472,7 @@ async function cmdPlan(argv) {
11568
11472
  }
11569
11473
  reuse = {
11570
11474
  from,
11571
- doc: JSON.parse(await readFile16(from, "utf8"))
11475
+ doc: JSON.parse(await readFile17(from, "utf8"))
11572
11476
  };
11573
11477
  }
11574
11478
  const style = await resolveStyleRef(flags);
@@ -11743,7 +11647,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
11743
11647
  async function cmdRender(argv) {
11744
11648
  const { positionals, flags, multi } = parseArgs(
11745
11649
  argv,
11746
- BOOLEAN_FLAGS7,
11650
+ BOOLEAN_FLAGS8,
11747
11651
  MULTI_FLAGS2
11748
11652
  );
11749
11653
  const dir = positionals[0];
@@ -11752,10 +11656,7 @@ async function cmdRender(argv) {
11752
11656
  "vos render <take> [out] [--width] [--height] [--fps] [--format] [--parallel]"
11753
11657
  );
11754
11658
  const r = createReporter(flags.json === true);
11755
- const fmtRaw = strFlag(flags, "format") ?? "webm";
11756
- if (fmtRaw !== "webm" && fmtRaw !== "mp4")
11757
- throw new UsageError("--format must be webm or mp4");
11758
- const format = fmtRaw;
11659
+ const format = videoFormat(strFlag(flags, "format"), positionals[1]);
11759
11660
  const out = positionals[1] ?? join22(dir, `out.${format}`);
11760
11661
  const parallel = numFlag(flags, "parallel", 1);
11761
11662
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
@@ -11814,7 +11715,7 @@ async function cmdRender(argv) {
11814
11715
  async function cmdFrames(argv) {
11815
11716
  const { positionals, flags, multi } = parseArgs(
11816
11717
  argv,
11817
- BOOLEAN_FLAGS7,
11718
+ BOOLEAN_FLAGS8,
11818
11719
  MULTI_FLAGS2
11819
11720
  );
11820
11721
  const dir = positionals[0];
@@ -11887,7 +11788,7 @@ async function cmdFrames(argv) {
11887
11788
  async function cmdDeliver(argv) {
11888
11789
  const { positionals, flags, multi } = parseArgs(
11889
11790
  argv,
11890
- BOOLEAN_FLAGS7,
11791
+ BOOLEAN_FLAGS8,
11891
11792
  MULTI_FLAGS2
11892
11793
  );
11893
11794
  const dir = positionals[0];
@@ -12011,7 +11912,7 @@ async function resolveDocRef(ref, flags, what) {
12011
11912
  return {
12012
11913
  from: file,
12013
11914
  doc: migrateHostedDoc5(
12014
- JSON.parse(await readFile16(file, "utf8"))
11915
+ JSON.parse(await readFile17(file, "utf8"))
12015
11916
  )
12016
11917
  };
12017
11918
  }
@@ -12053,7 +11954,7 @@ async function officialTemplate(origin, ref) {
12053
11954
  }
12054
11955
  }
12055
11956
  async function cmdDigest(argv) {
12056
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
11957
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12057
11958
  const dir = positionals[0];
12058
11959
  if (!dir)
12059
11960
  throw new UsageError(
@@ -12063,7 +11964,7 @@ async function cmdDigest(argv) {
12063
11964
  const take = await loadTake(dir);
12064
11965
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
12065
11966
  const transcriptPath = strFlag(flags, "transcript");
12066
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile16(transcriptPath, "utf8"))) : null;
11967
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile17(transcriptPath, "utf8"))) : null;
12067
11968
  const style = await resolveStyleRef(flags);
12068
11969
  const noFrames = flags["no-frames"] === true;
12069
11970
  let browser = null;
@@ -12117,7 +12018,7 @@ async function cmdDigest(argv) {
12117
12018
  }
12118
12019
  }
12119
12020
  async function cmdOpen(argv) {
12120
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12021
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12121
12022
  const dir = positionals[0];
12122
12023
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
12123
12024
  const r = createReporter(flags.json === true);
@@ -12155,7 +12056,7 @@ async function cmdOpen(argv) {
12155
12056
  return EXIT_OK;
12156
12057
  }
12157
12058
  async function cmdJudge(argv) {
12158
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12059
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12159
12060
  const target = positionals[0];
12160
12061
  const against = strFlag(flags, "against");
12161
12062
  if (!target || !against)
@@ -12168,7 +12069,7 @@ async function cmdJudge(argv) {
12168
12069
  if (!existsSync20(against))
12169
12070
  throw new UsageError(`${against}: no reference manifest`);
12170
12071
  const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
12171
- const verdicts = JSON.parse(await readFile16(result.verdictFile, "utf8")).verdicts;
12072
+ const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
12172
12073
  const rate = winRate(verdicts);
12173
12074
  const lines = result.sheets.map(
12174
12075
  (s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
@@ -12184,7 +12085,7 @@ Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judg
12184
12085
  return EXIT_OK;
12185
12086
  }
12186
12087
  async function cmdCallout(argv) {
12187
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12088
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12188
12089
  const dir = positionals[0];
12189
12090
  const shape = positionals[1];
12190
12091
  if (!dir || !shape || !isCalloutShape(shape))
@@ -12208,7 +12109,7 @@ async function cmdCallout(argv) {
12208
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`
12209
12110
  );
12210
12111
  const codeFile = strFlag(flags, "code-file");
12211
- const code = codeFile ? await readFile16(codeFile, "utf8") : strFlag(flags, "code");
12112
+ const code = codeFile ? await readFile17(codeFile, "utf8") : strFlag(flags, "code");
12212
12113
  const words = {
12213
12114
  kicker: strFlag(flags, "kicker"),
12214
12115
  title: strFlag(flags, "title"),
@@ -12283,7 +12184,7 @@ async function cmdCallout(argv) {
12283
12184
  return EXIT_OK;
12284
12185
  }
12285
12186
  async function cmdValidate(argv) {
12286
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12187
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12287
12188
  const target = positionals[0];
12288
12189
  if (!target) throw new UsageError("vos validate <actions.json|take>");
12289
12190
  const r = createReporter(flags.json === true);
@@ -12321,7 +12222,7 @@ async function cmdValidate(argv) {
12321
12222
  }
12322
12223
  if (!isTakeDir(target) && existsSync20(join22(target, "config.json"))) {
12323
12224
  const parsed = JSON.parse(
12324
- await readFile16(join22(target, "config.json"), "utf8")
12225
+ await readFile17(join22(target, "config.json"), "utf8")
12325
12226
  );
12326
12227
  const pre = preflightConfig(parsed);
12327
12228
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -12445,7 +12346,7 @@ async function cmdValidate(argv) {
12445
12346
  async function cmdPush3(argv) {
12446
12347
  const { positionals, flags, multi } = parseArgs(
12447
12348
  argv,
12448
- BOOLEAN_FLAGS7,
12349
+ BOOLEAN_FLAGS8,
12449
12350
  MULTI_FLAGS2
12450
12351
  );
12451
12352
  const dir = positionals[0];
@@ -12483,16 +12384,33 @@ async function cmdPush3(argv) {
12483
12384
  origin: strFlag(flags, "origin"),
12484
12385
  api: strFlag(flags, "api")
12485
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>)`}` : "";
12486
12396
  r.done(
12487
- { ...result },
12397
+ {
12398
+ ...result,
12399
+ ...landed ? {
12400
+ still: landed.still,
12401
+ thumbnailUrl: landed.thumbnailUrl,
12402
+ previewUrl: landed.previewUrl
12403
+ } : {}
12404
+ },
12488
12405
  `pushed v${result.versionNumber} \u2192 vos ${result.vosId}
12489
12406
  review: ${pushedTo}/vos/${result.vosId}
12490
- studio: ${pushedTo}/studio?vos=${result.vosId}`
12407
+ studio: ${pushedTo}/studio?vos=${result.vosId}` + filed + (landed ? `
12408
+ ${landedLines(landed)}` : "")
12491
12409
  );
12492
12410
  return EXIT_OK;
12493
12411
  }
12494
12412
  async function cmdPull2(argv) {
12495
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7, MULTI_FLAGS2);
12413
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8, MULTI_FLAGS2);
12496
12414
  const dir = positionals[0] ?? ".";
12497
12415
  const r = createReporter(flags.json === true);
12498
12416
  const result = await pullTake(
@@ -12603,6 +12521,8 @@ async function run(argv) {
12603
12521
  const target = rest.find((a) => !a.startsWith("-")) ?? ".";
12604
12522
  return isTakeDir(target) ? await cmdPull2(rest) : await cmdPullProgram(rest);
12605
12523
  }
12524
+ case "delete":
12525
+ return await cmdDelete(rest);
12606
12526
  case "folder":
12607
12527
  return await cmdFolder(rest);
12608
12528
  case "asset":
@@ -12649,7 +12569,7 @@ async function run(argv) {
12649
12569
  }
12650
12570
  }
12651
12571
  async function cmdSession(argv) {
12652
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
12572
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
12653
12573
  const [sub, arg] = positionals;
12654
12574
  const r = createReporter(flags.json === true);
12655
12575
  const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
@@ -12776,13 +12696,6 @@ export {
12776
12696
  digestTake,
12777
12697
  parseTranscript,
12778
12698
  renderTake,
12779
- platformOrigin,
12780
- parseVosId,
12781
- resolveCredential,
12782
- apiJson,
12783
- apiError,
12784
- readSyncState,
12785
- writeSyncState,
12786
12699
  recordTake,
12787
12700
  encodeRecording,
12788
12701
  planTake,
@@ -12796,4 +12709,4 @@ export {
12796
12709
  verbHelp,
12797
12710
  run
12798
12711
  };
12799
- //# sourceMappingURL=chunk-JVINOGJG.js.map
12712
+ //# sourceMappingURL=chunk-F2QCWV4U.js.map