@vosjs/cli 0.59.0 → 0.60.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,42 +1,70 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BrowserUnavailableError,
4
+ configDuration,
5
+ directoryKind,
6
+ launchBrowser,
7
+ loadVosConfig,
8
+ parseTimes,
9
+ reencodeStill,
10
+ renderStill,
11
+ videoFormat
12
+ } from "./chunk-OMDRILG7.js";
13
+ import {
14
+ PREV_DOC_NAME,
15
+ RECORDING_NAME,
16
+ docAudioRefs,
17
+ docLayerRefs,
18
+ docMediaRefs,
19
+ dropScreencastFrames,
20
+ ensureTakeDir,
21
+ hostedLiteralWarnings,
22
+ isTakeRelativeKey,
23
+ loadTake,
24
+ localManifestRefs,
25
+ prepareReRecord,
26
+ pullManifest,
27
+ pullMedia,
28
+ readJson,
29
+ readMediaHead,
30
+ sourceSidecarRefs,
31
+ takeMediaFile,
32
+ takePaths,
33
+ uploadAsset,
34
+ uploadDocRefs,
35
+ uploadManifest,
36
+ writeJson
37
+ } from "./chunk-PUXY32AP.js";
38
+ import {
4
39
  CREDENTIALS_PATH,
5
40
  MEDIA_HEAD_BYTES,
6
41
  SYNC_STATE_NAME,
7
- TAKE_MEDIA_EXTENSIONS,
8
42
  UsageError,
9
43
  apiError,
10
44
  apiJson,
11
45
  clientId,
12
- configDuration,
13
46
  containerMismatch,
14
47
  deriveSlug,
15
- directoryKind,
16
- extensionFor,
17
48
  formatChanges,
18
49
  hasFlag,
19
- launchBrowser,
20
- loadVosConfig,
50
+ hostedAssetId,
51
+ manifestRefs,
21
52
  nameForType,
22
53
  numFlag2 as numFlag,
23
54
  parseArgs2 as parseArgs,
24
- parseTimes,
25
55
  parseVosId,
26
56
  platformOrigin,
57
+ programAssets,
27
58
  readSyncState,
28
- reencodeStill,
29
- renderStill,
30
59
  requireCredential,
31
60
  resolveCredential,
32
61
  resolveMediaType,
33
62
  serverWarnings,
34
63
  sniffMediaType,
35
64
  strFlag,
36
- videoFormat,
37
65
  writeCredential,
38
66
  writeSyncState
39
- } from "./chunk-YC3LY4LW.js";
67
+ } from "./chunk-36P5RREC.js";
40
68
  import {
41
69
  deadLine,
42
70
  deadWarns
@@ -98,9 +126,9 @@ function landedLines(l) {
98
126
  }
99
127
 
100
128
  // src/plugin/run.ts
101
- import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
102
- import { existsSync as existsSync24 } from "fs";
103
- import { join as join28, resolve as resolve15 } from "path";
129
+ import { mkdir as mkdir9, readFile as readFile15, rm as rm3 } from "fs/promises";
130
+ import { existsSync as existsSync23 } from "fs";
131
+ import { join as join26, resolve as resolve15 } from "path";
104
132
  import {
105
133
  END_CARD_FROM as END_CARD_FROM2,
106
134
  docOutputDuration as docOutputDuration3,
@@ -176,6 +204,7 @@ import {
176
204
  pinCandidates,
177
205
  pinRectOnScreen,
178
206
  pinReferent,
207
+ parseEmphasis,
179
208
  ratedSegments,
180
209
  recommendedExportResolution,
181
210
  resolvePins,
@@ -479,6 +508,23 @@ function lintDoc(docIn) {
479
508
  if (z.focusMode !== void 0 && z.focusMode !== "manual" && z.focusMode !== "auto") {
480
509
  problems.push(`${name}: focusMode must be 'manual' or 'auto'`);
481
510
  }
511
+ if (z.screen !== void 0) {
512
+ const sp = z.screen;
513
+ const ok = !!sp && typeof sp === "object" && isNum(sp.x) && isNum(sp.y) && sp.x >= 0 && sp.x <= 1 && sp.y >= 0 && sp.y <= 1;
514
+ if (!ok) {
515
+ problems.push(
516
+ `${name}: screen must be { x, y } in 0..1 \u2014 where the target lands as FRACTIONS of the frame (0.35, 0.5 = a third across, centred), not pixels`
517
+ );
518
+ } else if (z.focusMode === "auto") {
519
+ warnings.push(
520
+ `${name}: screen is ignored on a span that follows the cursor (focusMode 'auto'); drop one or the other`
521
+ );
522
+ } else if (cameraModel(doc.frame) !== "stage") {
523
+ warnings.push(
524
+ `${name}: screen places the target under the stage camera only (frame.camera: 'stage'); the magnifier keeps the target where it is`
525
+ );
526
+ }
527
+ }
482
528
  if (z.source !== void 0 && z.source !== "manual" && z.source !== "auto") {
483
529
  problems.push(`${name}: source must be 'manual' or 'auto'`);
484
530
  }
@@ -1000,6 +1046,23 @@ function lintDoc(docIn) {
1000
1046
  `${name}.preset "${String(o.preset)}" is not a known preset (${PRESETS.join("|")}) \u2014 falls back to "title"`
1001
1047
  );
1002
1048
  }
1049
+ if (o.emphasis !== void 0) {
1050
+ const em = o.emphasis;
1051
+ if (!em || typeof em !== "object" || Array.isArray(em)) {
1052
+ problems.push(
1053
+ `${name}.emphasis must be { weight?, color? } ({} turns *markers* on)`
1054
+ );
1055
+ } else {
1056
+ if (em.weight !== void 0 && (!isNum(em.weight) || em.weight < 100 || em.weight > 900))
1057
+ problems.push(`${name}.emphasis.weight must be 100..900`);
1058
+ if (em.color !== void 0 && typeof em.color !== "string")
1059
+ problems.push(`${name}.emphasis.color must be a CSS colour`);
1060
+ }
1061
+ } else if (typeof o.text === "string" && parseEmphasis(o.text) !== null) {
1062
+ warnings.push(
1063
+ `${name}.text has *marked* words but no emphasis, so the asterisks show as typed; add emphasis: {} to set them bold (\\* keeps one literal)`
1064
+ );
1065
+ }
1003
1066
  if (o.size !== void 0 && (!isNum(o.size) || o.size < 12 || o.size > 200)) {
1004
1067
  problems.push(`${name}.size must be design px in 12..200`);
1005
1068
  }
@@ -1632,7 +1695,13 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
1632
1695
  }
1633
1696
  const rect = { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
1634
1697
  if (!zoomCoversRect(
1635
- { level: z.level, cx: z.cx, cy: z.cy },
1698
+ {
1699
+ level: z.level,
1700
+ cx: z.cx,
1701
+ cy: z.cy,
1702
+ screen: z.screen,
1703
+ focusMode: z.focusMode
1704
+ },
1636
1705
  rect,
1637
1706
  layout,
1638
1707
  0.02,
@@ -1772,535 +1841,6 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
1772
1841
  }
1773
1842
  }
1774
1843
 
1775
- // src/plugin/media.ts
1776
- import { isKeyedOverlay } from "@vosjs/studio-core";
1777
- import { createHash } from "crypto";
1778
- import { createWriteStream, existsSync as existsSync2 } from "fs";
1779
- import {
1780
- mkdir as mkdir2,
1781
- open,
1782
- readFile as readFile2,
1783
- readdir,
1784
- rename as rename2,
1785
- rm as rm2,
1786
- stat,
1787
- writeFile as writeFile2
1788
- } from "fs/promises";
1789
- import { basename as basename2, dirname, join as join2 } from "path";
1790
- import { Readable } from "stream";
1791
- import { pipeline } from "stream/promises";
1792
-
1793
- // src/plugin/take.ts
1794
- import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
1795
- import { existsSync, readdirSync, statSync } from "fs";
1796
- import { basename, join } from "path";
1797
- var RECORDING_NAME = "recording.webm";
1798
- function takeMediaFiles(dir, stem) {
1799
- return TAKE_MEDIA_EXTENSIONS.map((ext) => join(dir, `${stem}${ext}`)).filter(
1800
- (file) => existsSync(file)
1801
- );
1802
- }
1803
- function takeMediaFile(dir, stem, fallback) {
1804
- return takeMediaFiles(dir, stem)[0] ?? join(dir, fallback);
1805
- }
1806
- var PREV_DOC_NAME = "doc.prev.json";
1807
- function takePaths(dir) {
1808
- const recording = takeMediaFile(dir, "recording", RECORDING_NAME);
1809
- return {
1810
- dir,
1811
- recording,
1812
- recordingName: basename(recording),
1813
- framesDir: join(dir, "frames"),
1814
- framesIndex: join(dir, "frames.json"),
1815
- cursor: join(dir, "cursor.json"),
1816
- meta: join(dir, "meta.json"),
1817
- actions: join(dir, "actions.json"),
1818
- doc: join(dir, "doc.json")
1819
- };
1820
- }
1821
- async function ensureTakeDir(dir) {
1822
- const p = takePaths(dir);
1823
- await mkdir(p.framesDir, { recursive: true });
1824
- return p;
1825
- }
1826
- async function dropScreencastFrames(p) {
1827
- if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0;
1828
- if (!existsSync(p.framesDir)) return 0;
1829
- let freed = 0;
1830
- for (const name of readdirSync(p.framesDir)) {
1831
- try {
1832
- freed += statSync(join(p.framesDir, name)).size;
1833
- } catch {
1834
- }
1835
- }
1836
- await rm(p.framesDir, { recursive: true, force: true });
1837
- await rm(p.framesIndex, { force: true });
1838
- return freed;
1839
- }
1840
- async function prepareReRecord(dir) {
1841
- const p = takePaths(dir);
1842
- const kept = [];
1843
- let prevDoc = false;
1844
- if (existsSync(p.doc)) {
1845
- await rename(p.doc, join(dir, PREV_DOC_NAME));
1846
- prevDoc = true;
1847
- kept.push(PREV_DOC_NAME);
1848
- }
1849
- if (existsSync(p.actions)) kept.push("actions.json");
1850
- if (existsSync(join(dir, "vos.json"))) kept.push("vos.json");
1851
- for (const stale of [
1852
- // Every container, not just the one `record` writes: a pulled take's
1853
- // footage may be mp4, and a survivor would outrank the new recording.
1854
- ...takeMediaFiles(dir, "recording"),
1855
- ...takeMediaFiles(dir, "mic"),
1856
- ...takeMediaFiles(dir, "cam"),
1857
- p.cursor,
1858
- p.meta,
1859
- p.framesIndex,
1860
- p.framesDir,
1861
- join(dir, "stills"),
1862
- join(dir, "digest")
1863
- ]) {
1864
- await rm(stale, { recursive: true, force: true });
1865
- }
1866
- return { prevDoc, kept };
1867
- }
1868
- async function readJson(file) {
1869
- return JSON.parse(await readFile(file, "utf8"));
1870
- }
1871
- async function writeJson(file, value, pretty = false) {
1872
- await writeFile(
1873
- file,
1874
- pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)
1875
- );
1876
- }
1877
- async function loadTake(dir) {
1878
- const paths = takePaths(dir);
1879
- if (!existsSync(paths.meta)) {
1880
- throw new Error(
1881
- `${dir} is not a take directory (missing meta.json) \u2014 run record first`
1882
- );
1883
- }
1884
- const meta = await readJson(paths.meta);
1885
- const cursor = existsSync(paths.cursor) ? await readJson(paths.cursor) : [];
1886
- const actions = existsSync(paths.actions) ? await readJson(paths.actions) : null;
1887
- const doc = existsSync(paths.doc) ? await readJson(paths.doc) : null;
1888
- return { paths, cursor, meta, actions, doc };
1889
- }
1890
-
1891
- // src/plugin/uploadAsset.ts
1892
- var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
1893
- var PART_ATTEMPTS = 3;
1894
- function planParts(totalBytes, partBytes) {
1895
- if (totalBytes <= 0 || partBytes <= 0) return [];
1896
- const parts = [];
1897
- for (let start = 0; start < totalBytes; start += partBytes) {
1898
- parts.push({
1899
- partNumber: parts.length + 1,
1900
- start,
1901
- end: Math.min(start + partBytes, totalBytes)
1902
- });
1903
- }
1904
- return parts;
1905
- }
1906
- function failed(what, r) {
1907
- const detail = typeof r.body.error === "string" ? r.body.error : "";
1908
- return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
1909
- }
1910
- function asAsset(body) {
1911
- return {
1912
- id: String(body.id),
1913
- url: String(body.url),
1914
- size: typeof body.size === "number" ? body.size : 0,
1915
- reused: body.reused === true
1916
- };
1917
- }
1918
- function transient(status) {
1919
- return status >= 500 || status === 429;
1920
- }
1921
- var wait = (ms) => new Promise((r) => setTimeout(r, ms));
1922
- async function uploadAsset(target, bytes, opts) {
1923
- if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {
1924
- throw new Error(
1925
- `${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`
1926
- );
1927
- }
1928
- return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
1929
- }
1930
- async function uploadWhole(target, bytes, opts) {
1931
- const res = await apiJson(target.origin, "/api/assets/recording", {
1932
- method: "POST",
1933
- key: target.key,
1934
- headers: {
1935
- "Content-Type": opts.contentType,
1936
- "Content-Length": String(bytes.length),
1937
- "X-Filename": opts.filename,
1938
- "X-Content-Hash": opts.contentHash,
1939
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {},
1940
- ...opts.folderId ? { "X-Folder-Id": opts.folderId } : {}
1941
- },
1942
- raw: bytes
1943
- });
1944
- if (res.status !== 201 && res.status !== 200)
1945
- throw failed("upload failed", res);
1946
- return asAsset(res.body);
1947
- }
1948
- async function uploadInParts(target, bytes, opts) {
1949
- const begun = await apiJson(
1950
- target.origin,
1951
- "/api/assets/recording/multipart",
1952
- {
1953
- method: "POST",
1954
- key: target.key,
1955
- body: {
1956
- filename: opts.filename,
1957
- contentType: opts.contentType,
1958
- size: bytes.length,
1959
- contentHash: opts.contentHash,
1960
- ...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
1961
- }
1962
- }
1963
- );
1964
- if (begun.status !== 201 && begun.status !== 200) {
1965
- throw failed("upload failed", begun);
1966
- }
1967
- if (begun.body.reused === true || typeof begun.body.url === "string") {
1968
- return asAsset(begun.body);
1969
- }
1970
- const uploadId = String(begun.body.id);
1971
- const partBytes = Number(begun.body.partBytes);
1972
- if (!Number.isFinite(partBytes) || partBytes <= 0) {
1973
- throw new Error("upload failed: the platform returned no part size");
1974
- }
1975
- const parts = planParts(bytes.length, partBytes);
1976
- const etags = [];
1977
- try {
1978
- for (const part of parts) {
1979
- const chunk2 = bytes.subarray(part.start, part.end);
1980
- let sent = null;
1981
- for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
1982
- try {
1983
- sent = await apiJson(
1984
- target.origin,
1985
- `/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
1986
- {
1987
- method: "PUT",
1988
- key: target.key,
1989
- headers: {
1990
- "Content-Type": "application/octet-stream",
1991
- "Content-Length": String(chunk2.length)
1992
- },
1993
- raw: chunk2
1994
- }
1995
- );
1996
- } catch (networkError) {
1997
- if (attempt === PART_ATTEMPTS) throw networkError;
1998
- await wait(500 * 2 ** (attempt - 1));
1999
- continue;
2000
- }
2001
- if (sent.status === 200) break;
2002
- if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
2003
- throw failed(`part ${part.partNumber} failed`, sent);
2004
- }
2005
- await wait(500 * 2 ** (attempt - 1));
2006
- }
2007
- if (!sent || sent.status !== 200) {
2008
- throw new Error(`part ${part.partNumber} failed`);
2009
- }
2010
- etags.push({
2011
- partNumber: Number(sent.body.partNumber),
2012
- etag: String(sent.body.etag)
2013
- });
2014
- opts.onPart?.(etags.length, parts.length);
2015
- }
2016
- const sealed = await apiJson(
2017
- target.origin,
2018
- `/api/assets/recording/multipart/${uploadId}/complete`,
2019
- { method: "POST", key: target.key, body: { parts: etags } }
2020
- );
2021
- if (sealed.status !== 201 && sealed.status !== 200) {
2022
- throw failed("upload failed", sealed);
2023
- }
2024
- return asAsset(sealed.body);
2025
- } catch (err) {
2026
- await apiJson(
2027
- target.origin,
2028
- `/api/assets/recording/multipart/${uploadId}`,
2029
- { method: "DELETE", key: target.key }
2030
- ).catch(() => void 0);
2031
- throw err;
2032
- }
2033
- }
2034
-
2035
- // src/plugin/media.ts
2036
- var SOURCE_MEDIA = [
2037
- { key: "videoKey", stem: "recording", fallbackExt: ".webm" },
2038
- { key: "micKey", stem: "mic", fallbackExt: ".webm" },
2039
- { key: "camKey", stem: "cam", fallbackExt: ".webm" }
2040
- ];
2041
- function isTakeRelativeKey(key) {
2042
- return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
2043
- }
2044
- var takeRelativeFile = (key) => key.replace(/^\/+/, "");
2045
- function sourceSidecarRefs(doc, keep = isTakeRelativeKey) {
2046
- const out = [];
2047
- for (const [field, where] of [
2048
- ["micKey", "mic track"],
2049
- ["camKey", "cam track"]
2050
- ]) {
2051
- const key = doc.source[field];
2052
- if (!key || !keep(key)) continue;
2053
- out.push({
2054
- where,
2055
- key,
2056
- set: (next) => {
2057
- doc.source[field] = next;
2058
- }
2059
- });
2060
- }
2061
- return out;
2062
- }
2063
- function docObjectRefs(doc, keep = isTakeRelativeKey) {
2064
- const out = [];
2065
- for (const clip of doc.objects ?? []) {
2066
- const asset = clip.asset;
2067
- if (asset.kind !== "gltf" || !keep(asset.key)) continue;
2068
- out.push({
2069
- where: `object ${clip.id}`,
2070
- key: asset.key,
2071
- set: (next) => {
2072
- asset.key = next;
2073
- }
2074
- });
2075
- }
2076
- return out;
2077
- }
2078
- function docOverlayRefs(doc, keep = isTakeRelativeKey) {
2079
- const out = [];
2080
- for (const clip of doc.overlays ?? []) {
2081
- if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
2082
- out.push({
2083
- where: `overlay ${clip.id}`,
2084
- key: clip.key,
2085
- set: (next) => {
2086
- clip.key = next;
2087
- }
2088
- });
2089
- }
2090
- return out;
2091
- }
2092
- function docLayerRefs(doc, keep = isTakeRelativeKey) {
2093
- return [
2094
- ...docOverlayRefs(doc, keep),
2095
- ...docObjectRefs(doc, keep),
2096
- ...docAudioRefs(doc, keep)
2097
- ];
2098
- }
2099
- function docMediaRefs(doc, keep = isTakeRelativeKey) {
2100
- const out = [];
2101
- for (const m of doc.media ?? []) {
2102
- if (keep(m.videoKey))
2103
- out.push({
2104
- where: `media ${m.id}`,
2105
- key: m.videoKey,
2106
- set: (next) => {
2107
- m.videoKey = next;
2108
- }
2109
- });
2110
- const mic = m.micKey;
2111
- if (mic && keep(mic))
2112
- out.push({
2113
- where: `media ${m.id} mic`,
2114
- key: mic,
2115
- set: (next) => {
2116
- m.micKey = next;
2117
- }
2118
- });
2119
- const cam = m.camKey;
2120
- if (cam && keep(cam))
2121
- out.push({
2122
- where: `media ${m.id} cam`,
2123
- key: cam,
2124
- set: (next) => {
2125
- m.camKey = next;
2126
- }
2127
- });
2128
- }
2129
- out.push(...docOverlayRefs(doc, keep));
2130
- out.push(...docObjectRefs(doc, keep));
2131
- const mark = doc.endCard?.mark;
2132
- if (mark && keep(mark.key))
2133
- out.push({
2134
- where: "end card mark",
2135
- key: mark.key,
2136
- set: (next) => {
2137
- mark.key = next;
2138
- }
2139
- });
2140
- const bg = doc.frame.backgroundMedia;
2141
- if (bg && keep(bg.key))
2142
- out.push({
2143
- where: "background",
2144
- key: bg.key,
2145
- set: (next) => {
2146
- bg.key = next;
2147
- }
2148
- });
2149
- out.push(...docAudioRefs(doc, keep));
2150
- return out;
2151
- }
2152
- function docAudioRefs(doc, keep = isTakeRelativeKey) {
2153
- const out = [];
2154
- for (const clip of doc.audio ?? []) {
2155
- if (!keep(clip.key)) continue;
2156
- out.push({
2157
- where: `audio ${clip.id}`,
2158
- key: clip.key,
2159
- set: (next) => {
2160
- clip.key = next;
2161
- }
2162
- });
2163
- }
2164
- return out;
2165
- }
2166
- async function uploadDocRefs(refs, dir, target, log) {
2167
- for (const ref of refs) {
2168
- const file = join2(dir, takeRelativeFile(ref.key));
2169
- if (!existsSync2(file)) {
2170
- log(` ${ref.where}: ${ref.key} is not beside the document \u2014 left as is`);
2171
- continue;
2172
- }
2173
- const media = await readFile2(file);
2174
- const mediaHash = createHash("sha256").update(media).digest("hex");
2175
- const { type } = resolveMediaType({
2176
- head: media.subarray(0, MEDIA_HEAD_BYTES),
2177
- filename: file
2178
- });
2179
- const name = nameForType(basename2(file), type);
2180
- if (name !== basename2(file)) {
2181
- log(` ${ref.where}: ${basename2(file)} holds ${type} \u2014 sent as ${name}`);
2182
- }
2183
- let put;
2184
- try {
2185
- put = await uploadAsset(target, new Uint8Array(media), {
2186
- filename: name,
2187
- contentType: type,
2188
- contentHash: mediaHash
2189
- });
2190
- } catch (e) {
2191
- throw new Error(
2192
- `${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
2193
- );
2194
- }
2195
- ref.set(put.url);
2196
- log(
2197
- ` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
2198
- );
2199
- }
2200
- }
2201
- function assetIdOf(url) {
2202
- if (!url) return null;
2203
- const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
2204
- return m ? m[1] : null;
2205
- }
2206
- async function readMediaHead(file) {
2207
- const handle = await open(file, "r");
2208
- try {
2209
- const buf = Buffer.alloc(MEDIA_HEAD_BYTES);
2210
- const { bytesRead } = await handle.read(buf, 0, MEDIA_HEAD_BYTES, 0);
2211
- return new Uint8Array(buf.subarray(0, bytesRead));
2212
- } finally {
2213
- await handle.close();
2214
- }
2215
- }
2216
- async function downloadMedia(ctx, url, target) {
2217
- const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
2218
- const res = await fetch(abs, {
2219
- headers: ctx.key ? { authorization: `Bearer ${ctx.key}` } : {}
2220
- });
2221
- if (!res.ok || !res.body) {
2222
- throw new Error(
2223
- `download of ${target.describe} failed (${res.status})${res.status === 404 && target.notFoundHint ? ` \u2014 ${target.notFoundHint}` : ""}`
2224
- );
2225
- }
2226
- const into = target.subdir ? join2(target.dir, target.subdir) : target.dir;
2227
- const partial = join2(into, `${target.stem}.part`);
2228
- await mkdir2(dirname(partial), { recursive: true });
2229
- try {
2230
- await pipeline(
2231
- Readable.fromWeb(res.body),
2232
- createWriteStream(partial)
2233
- );
2234
- const { type } = resolveMediaType({
2235
- head: await readMediaHead(partial),
2236
- declared: res.headers.get("content-type"),
2237
- filename: `${target.stem}${target.fallbackExt}`
2238
- });
2239
- const name = `${target.stem}${extensionFor(type) || target.fallbackExt}`;
2240
- await rename2(partial, join2(into, name));
2241
- const file = target.subdir ? `${target.subdir}/${name}` : name;
2242
- return { file, bytes: (await stat(join2(target.dir, file))).size };
2243
- } finally {
2244
- await rm2(partial, { force: true });
2245
- }
2246
- }
2247
- async function pullMedia(ctx, dir, doc, log) {
2248
- const result = { downloaded: [], kept: [] };
2249
- for (const { key, stem, fallbackExt } of SOURCE_MEDIA) {
2250
- const url = doc.source[key];
2251
- const assetId = assetIdOf(url);
2252
- if (!assetId) continue;
2253
- const present = takeMediaFiles(dir, stem)[0];
2254
- if (present) {
2255
- result.kept.push(basename2(present));
2256
- doc.source[key] = basename2(present);
2257
- continue;
2258
- }
2259
- const got = await downloadMedia(ctx, url, {
2260
- dir,
2261
- stem,
2262
- fallbackExt,
2263
- describe: stem,
2264
- notFoundHint: "a private recording needs a content key of its owner"
2265
- });
2266
- result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
2267
- log(` ${got.file} \u2190 asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`);
2268
- doc.source[key] = got.file;
2269
- }
2270
- for (const ref of docMediaRefs(doc, (k) => assetIdOf(k) !== null)) {
2271
- const assetId = assetIdOf(ref.key);
2272
- const existing = existsSync2(join2(dir, "media")) ? (await readdir(join2(dir, "media"))).find(
2273
- (f) => f === assetId || f.startsWith(`${assetId}.`)
2274
- ) : void 0;
2275
- if (existing) {
2276
- const file = `media/${existing}`;
2277
- result.kept.push(file);
2278
- ref.set(file);
2279
- continue;
2280
- }
2281
- const got = await downloadMedia(ctx, ref.key, {
2282
- dir,
2283
- subdir: "media",
2284
- stem: assetId,
2285
- // A type with no extension here (an svg served as such) keeps the bare
2286
- // asset id, exactly as before.
2287
- fallbackExt: "",
2288
- describe: `${ref.where} (${ref.key})`
2289
- });
2290
- result.downloaded.push({ file: got.file, assetId, bytes: got.bytes });
2291
- log(
2292
- ` ${got.file} \u2190 ${ref.where}, asset ${assetId} (${Math.round(got.bytes / 1024)} kB)`
2293
- );
2294
- ref.set(got.file);
2295
- }
2296
- const metaPath = join2(dir, "meta.json");
2297
- if (!existsSync2(metaPath)) await writeJson(metaPath, doc.source.meta, true);
2298
- const cursorPath = join2(dir, "cursor.json");
2299
- if (!existsSync2(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
2300
- await writeFile2(join2(dir, "doc.json"), JSON.stringify(doc, null, 2));
2301
- return result;
2302
- }
2303
-
2304
1844
  // src/plugin/mediaProbe.ts
2305
1845
  var MEDIA_EXT = /\.(png|jpe?g|webp|gif|webm|mp4|mov|mp3|wav|m4a|ogg|glb|gltf|hdr|exr|woff2?)($|\?)/i;
2306
1846
  var isRemote = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
@@ -2386,9 +1926,9 @@ function mediaProbeLints(results) {
2386
1926
  }
2387
1927
 
2388
1928
  // src/plugin/framesTake.ts
2389
- import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
2390
- import { existsSync as existsSync5 } from "fs";
2391
- import { join as join5, resolve as resolve2 } from "path";
1929
+ import { mkdir as mkdir2, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
1930
+ import { existsSync as existsSync3 } from "fs";
1931
+ import { join as join3, resolve as resolve2 } from "path";
2392
1932
  import { compileVosConfig } from "@vosjs/core";
2393
1933
  import {
2394
1934
  docStillTime,
@@ -2402,9 +1942,9 @@ import {
2402
1942
 
2403
1943
  // src/plugin/server.ts
2404
1944
  import { createServer } from "http";
2405
- import { existsSync as existsSync3, readFileSync, statSync as statSync2 } from "fs";
2406
- import { writeFile as writeFile3 } from "fs/promises";
2407
- import { extname, join as join3 } from "path";
1945
+ import { existsSync, readFileSync, statSync } from "fs";
1946
+ import { writeFile } from "fs/promises";
1947
+ import { extname, join } from "path";
2408
1948
  var MIME = {
2409
1949
  ".html": "text/html",
2410
1950
  ".js": "text/javascript",
@@ -2448,7 +1988,7 @@ function startTakeServer(rootDir, pages) {
2448
1988
  const chunks = [];
2449
1989
  req.on("data", (c) => chunks.push(c));
2450
1990
  req.on("end", () => {
2451
- void writeFile3(join3(rootDir, name), Buffer.concat(chunks)).then(
1991
+ void writeFile(join(rootDir, name), Buffer.concat(chunks)).then(
2452
1992
  () => res.writeHead(200).end("ok"),
2453
1993
  (e) => res.writeHead(500).end(String(e))
2454
1994
  );
@@ -2460,9 +2000,9 @@ function startTakeServer(rootDir, pages) {
2460
2000
  res.end(pages[url.pathname]);
2461
2001
  return;
2462
2002
  }
2463
- const file = join3(rootDir, decodeURIComponent(url.pathname));
2464
- if (existsSync3(file) && statSync2(file).isFile()) {
2465
- const size = statSync2(file).size;
2003
+ const file = join(rootDir, decodeURIComponent(url.pathname));
2004
+ if (existsSync(file) && statSync(file).isFile()) {
2005
+ const size = statSync(file).size;
2466
2006
  const buf = readFileSync(file);
2467
2007
  const type = sniffMediaType(buf.subarray(0, MEDIA_HEAD_BYTES)) ?? MIME[extname(file)] ?? "application/octet-stream";
2468
2008
  const range = req.headers.range;
@@ -2527,7 +2067,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
2527
2067
  }
2528
2068
 
2529
2069
  // src/plugin/renderAnimation.ts
2530
- import { readFile as readFile3, rm as rm3 } from "fs/promises";
2070
+ import { readFile, rm } from "fs/promises";
2531
2071
  import { tweenRuntimeCode } from "@vosjs/tween/bundle";
2532
2072
  import { elementsBundleCode } from "@vosjs/elements/bundle";
2533
2073
  import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
@@ -2822,10 +2362,10 @@ async function renderAnimation(browser, opts) {
2822
2362
  )
2823
2363
  );
2824
2364
  const files = await Promise.all(
2825
- outNames.map((name) => readFile3(`${opts.workDir}/${name}`))
2365
+ outNames.map((name) => readFile(`${opts.workDir}/${name}`))
2826
2366
  );
2827
2367
  await Promise.all(
2828
- outNames.map((name) => rm3(`${opts.workDir}/${name}`, { force: true }))
2368
+ outNames.map((name) => rm(`${opts.workDir}/${name}`, { force: true }))
2829
2369
  );
2830
2370
  if (chunks.length === 1) {
2831
2371
  return { bytes: new Uint8Array(files[0]), totalFrames, chunks: 1 };
@@ -3080,9 +2620,9 @@ function applyAndValidate(doc, o) {
3080
2620
  }
3081
2621
 
3082
2622
  // src/plugin/digestTake.ts
3083
- import { mkdir as mkdir3, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
3084
- import { existsSync as existsSync4 } from "fs";
3085
- import { join as join4, resolve } from "path";
2623
+ import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
2624
+ import { existsSync as existsSync2 } from "fs";
2625
+ import { join as join2, resolve } from "path";
3086
2626
  import {
3087
2627
  DIGEST_CROP_MAX,
3088
2628
  DIGEST_FULL_MAX,
@@ -3304,8 +2844,8 @@ async function digestTake(browser, dir, opts = {}) {
3304
2844
  const meta = doc.source.meta;
3305
2845
  let plan = planForDigest(doc);
3306
2846
  const transcript = opts.transcript ?? await discoverTranscript(dir);
3307
- const outDir = resolve(opts.outDir ?? join4(dir, DIGEST_DIR));
3308
- await mkdir3(outDir, { recursive: true });
2847
+ const outDir = resolve(opts.outDir ?? join2(dir, DIGEST_DIR));
2848
+ await mkdir(outDir, { recursive: true });
3309
2849
  const fullMax = opts.full ?? DIGEST_FULL_MAX;
3310
2850
  const cropMax = opts.crop ?? DIGEST_CROP_MAX;
3311
2851
  const wantFrames = opts.frames !== false && browser !== null;
@@ -3321,7 +2861,7 @@ async function digestTake(browser, dir, opts = {}) {
3321
2861
  kind: "video",
3322
2862
  url: `/${take.paths.recordingName}`
3323
2863
  };
3324
- if (existsSync4(take.paths.framesIndex) && existsSync4(take.paths.framesDir)) {
2864
+ if (existsSync2(take.paths.framesIndex) && existsSync2(take.paths.framesDir)) {
3325
2865
  const index = await readJson(
3326
2866
  take.paths.framesIndex
3327
2867
  );
@@ -3336,7 +2876,7 @@ async function digestTake(browser, dir, opts = {}) {
3336
2876
  }
3337
2877
  }
3338
2878
  if (wantFrames) {
3339
- if (source.kind === "video" && !existsSync4(take.paths.recording))
2879
+ if (source.kind === "video" && !existsSync2(take.paths.recording))
3340
2880
  throw new Error(
3341
2881
  `${dir} has no recording \u2014 pull it with \`vos pull --media\` first`
3342
2882
  );
@@ -3409,9 +2949,9 @@ async function digestTake(browser, dir, opts = {}) {
3409
2949
  );
3410
2950
  sizes = f.sizes;
3411
2951
  for (const name of Object.keys(sizes)) {
3412
- const from = join4(dir, name);
3413
- const to = join4(outDir, name);
3414
- if (from !== to && existsSync4(from)) await rename3(from, to);
2952
+ const from = join2(dir, name);
2953
+ const to = join2(outDir, name);
2954
+ if (from !== to && existsSync2(from)) await rename(from, to);
3415
2955
  }
3416
2956
  if (sizes["sheet.png"]) sheet = "sheet.png";
3417
2957
  } finally {
@@ -3437,12 +2977,12 @@ async function digestTake(browser, dir, opts = {}) {
3437
2977
  if (!p) continue;
3438
2978
  for (const f of [p.full, p.crop]) {
3439
2979
  if (!f) continue;
3440
- const size = await pngSize(join4(outDir, f));
2980
+ const size = await pngSize(join2(outDir, f));
3441
2981
  if (size) sizes[f] = size;
3442
2982
  }
3443
2983
  if (p.box) boxes.set(m.id, p.box);
3444
2984
  }
3445
- if (prior.images.sheet && existsSync4(join4(outDir, prior.images.sheet)))
2985
+ if (prior.images.sheet && existsSync2(join2(outDir, prior.images.sheet)))
3446
2986
  sheet = prior.images.sheet;
3447
2987
  }
3448
2988
  }
@@ -3471,16 +3011,16 @@ async function digestTake(browser, dir, opts = {}) {
3471
3011
  style: opts.style ?? null,
3472
3012
  transcript
3473
3013
  });
3474
- const file = join4(outDir, "digest.json");
3014
+ const file = join2(outDir, "digest.json");
3475
3015
  const body = JSON.stringify(digest, null, 2);
3476
- await writeFile4(file, body);
3016
+ await writeFile2(file, body);
3477
3017
  return { file, outDir, digest, bytes: Buffer.byteLength(body) };
3478
3018
  }
3479
3019
  async function discoverTranscript(dir) {
3480
- const file = join4(dir, "transcript.json");
3481
- if (!existsSync4(file)) return null;
3020
+ const file = join2(dir, "transcript.json");
3021
+ if (!existsSync2(file)) return null;
3482
3022
  try {
3483
- return parseTranscript(JSON.parse(await readFile4(file, "utf8")));
3023
+ return parseTranscript(JSON.parse(await readFile2(file, "utf8")));
3484
3024
  } catch {
3485
3025
  return null;
3486
3026
  }
@@ -3488,7 +3028,7 @@ async function discoverTranscript(dir) {
3488
3028
  async function readPriorDigest(outDir) {
3489
3029
  try {
3490
3030
  const raw = JSON.parse(
3491
- await readFile4(join4(outDir, "digest.json"), "utf8")
3031
+ await readFile2(join2(outDir, "digest.json"), "utf8")
3492
3032
  );
3493
3033
  return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
3494
3034
  } catch {
@@ -3497,7 +3037,7 @@ async function readPriorDigest(outDir) {
3497
3037
  }
3498
3038
  async function pngSize(file) {
3499
3039
  try {
3500
- const buf = await readFile4(file);
3040
+ const buf = await readFile2(file);
3501
3041
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return null;
3502
3042
  return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
3503
3043
  } catch {
@@ -3672,8 +3212,8 @@ async function framesTake(browser, dir, opts) {
3672
3212
  throw new Error(
3673
3213
  "no frame times \u2014 pass --times/--frame, --at-zooms, --at-moments or --at-still"
3674
3214
  );
3675
- const outDir = resolve2(opts.outDir ?? join5(dir, "stills"));
3676
- await mkdir4(outDir, { recursive: true });
3215
+ const outDir = resolve2(opts.outDir ?? join3(dir, "stills"));
3216
+ await mkdir2(outDir, { recursive: true });
3677
3217
  const named = shots.map((s, i) => ({
3678
3218
  ...s,
3679
3219
  name: s.kind === "moment" ? `moment-${s.momentId}-${s.time.toFixed(2)}s.png` : `${s.kind === "zoom" ? "zoom" : "frame"}-${String(i).padStart(2, "0")}-${s.time.toFixed(2)}s.png`
@@ -3717,9 +3257,9 @@ async function framesTake(browser, dir, opts) {
3717
3257
  );
3718
3258
  const frames = [];
3719
3259
  for (const n of named) {
3720
- const from = join5(dir, n.name);
3721
- const to = join5(outDir, n.name);
3722
- if (from !== to) await rename4(from, to);
3260
+ const from = join3(dir, n.name);
3261
+ const to = join3(outDir, n.name);
3262
+ if (from !== to) await rename2(from, to);
3723
3263
  frames.push({
3724
3264
  file: to,
3725
3265
  time: n.time,
@@ -3734,10 +3274,10 @@ async function framesTake(browser, dir, opts) {
3734
3274
  }
3735
3275
  }
3736
3276
  async function momentsFor(dir, doc) {
3737
- const file = join5(dir, DIGEST_DIR, "digest.json");
3738
- if (existsSync5(file)) {
3277
+ const file = join3(dir, DIGEST_DIR, "digest.json");
3278
+ if (existsSync3(file)) {
3739
3279
  try {
3740
- const d = JSON.parse(await readFile5(file, "utf8"));
3280
+ const d = JSON.parse(await readFile3(file, "utf8"));
3741
3281
  if (Array.isArray(d.moments)) return d.moments;
3742
3282
  } catch {
3743
3283
  }
@@ -3757,8 +3297,8 @@ function parseTimes2(spec, duration) {
3757
3297
  });
3758
3298
  }
3759
3299
  async function writeIndexJson(result) {
3760
- const index = join5(result.outDir, "stills.json");
3761
- await writeFile5(
3300
+ const index = join3(result.outDir, "stills.json");
3301
+ await writeFile3(
3762
3302
  index,
3763
3303
  JSON.stringify(
3764
3304
  {
@@ -3775,11 +3315,11 @@ async function writeIndexJson(result) {
3775
3315
  }
3776
3316
 
3777
3317
  // src/plugin/deliver.ts
3778
- import { mkdir as mkdir5, mkdtemp, rename as rename5, rm as rm4, stat as stat2, writeFile as writeFile7 } from "fs/promises";
3318
+ import { mkdir as mkdir3, mkdtemp, rename as rename3, rm as rm2, stat, writeFile as writeFile5 } from "fs/promises";
3779
3319
  import { tmpdir } from "os";
3780
- import { join as join7, relative, resolve as resolve4 } from "path";
3781
- import { existsSync as existsSync7 } from "fs";
3782
- import { readFile as readFile7 } from "fs/promises";
3320
+ import { join as join5, relative, resolve as resolve4 } from "path";
3321
+ import { existsSync as existsSync5 } from "fs";
3322
+ import { readFile as readFile5 } from "fs/promises";
3783
3323
  import { parseFrontmatter } from "@vosjs/shared/frontmatter";
3784
3324
  import {
3785
3325
  CHANNEL_SPECS_VERIFIED,
@@ -3793,7 +3333,7 @@ import {
3793
3333
  } from "@vosjs/studio-core";
3794
3334
 
3795
3335
  // src/plugin/renderTake.ts
3796
- import { writeFile as writeFile6 } from "fs/promises";
3336
+ import { writeFile as writeFile4 } from "fs/promises";
3797
3337
  import { compileVosConfig as compileVosConfig2 } from "@vosjs/core";
3798
3338
  import {
3799
3339
  audioProducerCode,
@@ -3882,7 +3422,7 @@ async function renderTake(browser, dir, outFile, opts) {
3882
3422
  } : void 0,
3883
3423
  onProgress: opts.onProgress
3884
3424
  });
3885
- await writeFile6(outFile, result.bytes);
3425
+ await writeFile4(outFile, result.bytes);
3886
3426
  return {
3887
3427
  out: outFile,
3888
3428
  bytes: result.bytes.length,
@@ -4437,9 +3977,9 @@ function proposeTransitions(input, kind) {
4437
3977
  for (const step of steps) {
4438
3978
  if (!step.navigated || step.skipped) continue;
4439
3979
  const name = step.id ?? String(step.step);
4440
- const wait2 = step.do === "wait";
4441
- const outEnd = round3(wait2 ? step.tStart : step.tEnd);
4442
- const inStart = round3(wait2 ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
3980
+ const wait = step.do === "wait";
3981
+ const outEnd = round3(wait ? step.tStart : step.tEnd);
3982
+ const inStart = round3(wait ? step.tEnd : step.tEnd + STEP_SETTLE_SECONDS);
4443
3983
  const segs = doc.segments;
4444
3984
  if (segs.some(
4445
3985
  (s) => !s.media && Math.abs(s.out - outEnd) < 0.05 && s !== segs.at(-1)
@@ -4758,9 +4298,9 @@ function isLightHexGround(hex2) {
4758
4298
  }
4759
4299
 
4760
4300
  // src/plugin/posterDoc.ts
4761
- import { existsSync as existsSync6 } from "fs";
4762
- import { readFile as readFile6 } from "fs/promises";
4763
- import { isAbsolute, join as join6, resolve as resolve3 } from "path";
4301
+ import { existsSync as existsSync4 } from "fs";
4302
+ import { readFile as readFile4 } from "fs/promises";
4303
+ import { isAbsolute, join as join4, resolve as resolve3 } from "path";
4764
4304
  import {
4765
4305
  computeCardLayout,
4766
4306
  docStillTime as docStillTime2,
@@ -4787,16 +4327,16 @@ var roleFor = (cls) => `poster-${cls}`;
4787
4327
  async function readPosterAt(cls, path, takeDir, from) {
4788
4328
  let file = path;
4789
4329
  let docDir = path;
4790
- if (existsSync6(join6(path, "doc.json"))) {
4791
- file = join6(path, "doc.json");
4792
- } else if (!/\.json$/i.test(path) || !existsSync6(path)) {
4330
+ if (existsSync4(join4(path, "doc.json"))) {
4331
+ file = join4(path, "doc.json");
4332
+ } else if (!/\.json$/i.test(path) || !existsSync4(path)) {
4793
4333
  return null;
4794
4334
  } else {
4795
4335
  docDir = resolve3(path, "..");
4796
4336
  }
4797
4337
  let raw;
4798
4338
  try {
4799
- raw = JSON.parse(await readFile6(file, "utf8"));
4339
+ raw = JSON.parse(await readFile4(file, "utf8"));
4800
4340
  } catch {
4801
4341
  return null;
4802
4342
  }
@@ -4806,7 +4346,7 @@ async function readPosterAt(cls, path, takeDir, from) {
4806
4346
  const doc = migrateHostedDoc(
4807
4347
  raw
4808
4348
  );
4809
- const ownFootage = existsSync6(join6(docDir, "meta.json")) && existsSync6(join6(docDir, source.videoKey));
4349
+ const ownFootage = existsSync4(join4(docDir, "meta.json")) && existsSync4(join4(docDir, source.videoKey));
4810
4350
  const sync = readSyncState(docDir);
4811
4351
  return {
4812
4352
  cls,
@@ -4829,10 +4369,10 @@ async function findPosterDocs(takeDir, launchRoles) {
4829
4369
  candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
4830
4370
  }
4831
4371
  candidates.push({
4832
- path: join6(takeDir, "poster", cls),
4372
+ path: join4(takeDir, "poster", cls),
4833
4373
  from: `poster/${cls}`
4834
4374
  });
4835
- candidates.push({ path: join6(takeDir, "poster"), from: "poster" });
4375
+ candidates.push({ path: join4(takeDir, "poster"), from: "poster" });
4836
4376
  for (const c of candidates) {
4837
4377
  const ref = await readPosterAt(cls, c.path, takeDir, c.from);
4838
4378
  if (ref) {
@@ -4928,19 +4468,19 @@ function resolveChannels(raw) {
4928
4468
  return out;
4929
4469
  }
4930
4470
  async function readBrandBesideTake(dir, explicit) {
4931
- const candidates = explicit ? [explicit] : [join7(dir, "BRAND.md"), join7(dir, "..", "BRAND.md")];
4471
+ const candidates = explicit ? [explicit] : [join5(dir, "BRAND.md"), join5(dir, "..", "BRAND.md")];
4932
4472
  for (const file of candidates) {
4933
- if (!existsSync7(file)) continue;
4934
- const roles = parseFrontmatter(await readFile7(file, "utf8"));
4473
+ if (!existsSync5(file)) continue;
4474
+ const roles = parseFrontmatter(await readFile5(file, "utf8"));
4935
4475
  return { file, roles };
4936
4476
  }
4937
4477
  return null;
4938
4478
  }
4939
4479
  async function readLaunchBesideTake(dir, explicit) {
4940
- const candidates = explicit ? [explicit] : [join7(dir, "LAUNCH.md"), join7(dir, "..", "LAUNCH.md")];
4480
+ const candidates = explicit ? [explicit] : [join5(dir, "LAUNCH.md"), join5(dir, "..", "LAUNCH.md")];
4941
4481
  for (const file of candidates) {
4942
- if (!existsSync7(file)) continue;
4943
- return { file, roles: parseFrontmatter(await readFile7(file, "utf8")) };
4482
+ if (!existsSync5(file)) continue;
4483
+ return { file, roles: parseFrontmatter(await readFile5(file, "utf8")) };
4944
4484
  }
4945
4485
  return null;
4946
4486
  }
@@ -5004,7 +4544,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
5004
4544
  const meta = doc.source.meta;
5005
4545
  const vw = meta.captureWidth ?? meta.width;
5006
4546
  const vh = meta.captureHeight ?? meta.height;
5007
- const probeDir = await mkdtemp(join7(tmpdir(), "vos-moments-"));
4547
+ const probeDir = await mkdtemp(join5(tmpdir(), "vos-moments-"));
5008
4548
  try {
5009
4549
  const probe3 = await framesTake(browser, dir, {
5010
4550
  times: candidates.map((c) => c.time),
@@ -5018,7 +4558,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
5018
4558
  });
5019
4559
  const byTime = /* @__PURE__ */ new Map();
5020
4560
  for (const frame of probe3.frames) {
5021
- const img = decodePng(new Uint8Array(await readFile7(frame.file)));
4561
+ const img = decodePng(new Uint8Array(await readFile5(frame.file)));
5022
4562
  if (!img) continue;
5023
4563
  const whole = { x: 0, y: 0, w: img.w, h: img.h };
5024
4564
  byTime.set(+frame.time.toFixed(3), {
@@ -5043,7 +4583,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
5043
4583
  notes: [...notes, ...pick.dropped.map((d) => `moment: ${d}`)]
5044
4584
  };
5045
4585
  } finally {
5046
- await rm4(probeDir, { recursive: true, force: true });
4586
+ await rm2(probeDir, { recursive: true, force: true });
5047
4587
  }
5048
4588
  }
5049
4589
  var overCeiling = (bytes, maxBytes) => `${Math.ceil(bytes / 1024)} KB exceeds the ${Math.floor(maxBytes / 1024)} KB ceiling`;
@@ -5091,8 +4631,8 @@ async function deliverTake(browser, dir, opts) {
5091
4631
  const doc = take.doc;
5092
4632
  const duration = docOutputDuration2(doc);
5093
4633
  const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
5094
- const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
5095
- await mkdir5(outDir, { recursive: true });
4634
+ const outDir = resolve4(opts.outDir ?? join5(dir, "kit"));
4635
+ await mkdir3(outDir, { recursive: true });
5096
4636
  const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
5097
4637
  const assets = [];
5098
4638
  const skipped = [];
@@ -5152,7 +4692,7 @@ async function deliverTake(browser, dir, opts) {
5152
4692
  opts.onPhase?.(
5153
4693
  `${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
5154
4694
  );
5155
- const shotDir = await mkdtemp(join7(tmpdir(), "vos-poster-"));
4695
+ const shotDir = await mkdtemp(join5(tmpdir(), "vos-poster-"));
5156
4696
  try {
5157
4697
  const captured = await framesTake(browser, ref.takeDir, {
5158
4698
  times: [time],
@@ -5162,9 +4702,9 @@ async function deliverTake(browser, dir, opts) {
5162
4702
  doc: ref.doc,
5163
4703
  overrides: opts.overrides
5164
4704
  });
5165
- const to = join7(outDir, `${d.id}.png`);
5166
- await rename5(captured.frames[0].file, to);
5167
- const bytes = (await stat2(to)).size;
4705
+ const to = join5(outDir, `${d.id}.png`);
4706
+ await rename3(captured.frames[0].file, to);
4707
+ const bytes = (await stat(to)).size;
5168
4708
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
5169
4709
  skipped.push(
5170
4710
  `${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
@@ -5193,7 +4733,7 @@ async function deliverTake(browser, dir, opts) {
5193
4733
  ...shot.x < 0 && shot.x + shot.w > 1 ? { crop: true } : {}
5194
4734
  });
5195
4735
  } finally {
5196
- await rm4(shotDir, { recursive: true, force: true });
4736
+ await rm2(shotDir, { recursive: true, force: true });
5197
4737
  }
5198
4738
  }
5199
4739
  if (missing.size) {
@@ -5234,7 +4774,7 @@ async function deliverTake(browser, dir, opts) {
5234
4774
  }
5235
4775
  }
5236
4776
  opts.onPhase?.(`${label} (${specWords(d)})`);
5237
- const outFile = join7(outDir, `${d.id}.${d.format}`);
4777
+ const outFile = join5(outDir, `${d.id}.${d.format}`);
5238
4778
  const bitrate = d.maxBytes !== void 0 ? Math.min(
5239
4779
  1e7,
5240
4780
  Math.floor(d.maxBytes * 8 / seconds * 0.85)
@@ -5281,9 +4821,9 @@ async function deliverTake(browser, dir, opts) {
5281
4821
  for (let i = 0; i < captured.frames.length; i++) {
5282
4822
  const frame = captured.frames[i];
5283
4823
  const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
5284
- const to = join7(outDir, name);
5285
- await rename5(frame.file, to);
5286
- const bytes = (await stat2(to)).size;
4824
+ const to = join5(outDir, name);
4825
+ await rename3(frame.file, to);
4826
+ const bytes = (await stat(to)).size;
5287
4827
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
5288
4828
  skipped.push(
5289
4829
  `${label}: ${overCeiling(bytes, d.maxBytes)} (kept at ${to})`
@@ -5319,15 +4859,15 @@ async function deliverTake(browser, dir, opts) {
5319
4859
  skipped,
5320
4860
  assets
5321
4861
  };
5322
- const kitFile = join7(outDir, "kit.json");
5323
- await writeFile7(kitFile, JSON.stringify(kit, null, 2));
4862
+ const kitFile = join5(outDir, "kit.json");
4863
+ await writeFile5(kitFile, JSON.stringify(kit, null, 2));
5324
4864
  return { kit, kitFile, outDir };
5325
4865
  }
5326
4866
 
5327
4867
  // src/plugin/markAsset.ts
5328
- import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
5329
- import { existsSync as existsSync8 } from "fs";
5330
- import { isAbsolute as isAbsolute2, join as join8, resolve as resolve5 } from "path";
4868
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4869
+ import { existsSync as existsSync6 } from "fs";
4870
+ import { isAbsolute as isAbsolute2, join as join6, resolve as resolve5 } from "path";
5331
4871
  function markExtension(url) {
5332
4872
  const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
5333
4873
  if (!m) return null;
@@ -5409,8 +4949,8 @@ async function fetchBrandMarks(takeDir, roles) {
5409
4949
  bytes = new Uint8Array(await res.arrayBuffer());
5410
4950
  } else {
5411
4951
  const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
5412
- if (!existsSync8(file)) throw new Error("no such file");
5413
- bytes = new Uint8Array(await readFile8(file));
4952
+ if (!existsSync6(file)) throw new Error("no such file");
4953
+ bytes = new Uint8Array(await readFile6(file));
5414
4954
  }
5415
4955
  } catch (e) {
5416
4956
  out.notes.push(
@@ -5425,8 +4965,8 @@ async function fetchBrandMarks(takeDir, roles) {
5425
4965
  );
5426
4966
  return null;
5427
4967
  }
5428
- await mkdir6(join8(takeDir, "brand"), { recursive: true });
5429
- await writeFile8(join8(takeDir, "brand", `${name}.${ext}`), bytes);
4968
+ await mkdir4(join6(takeDir, "brand"), { recursive: true });
4969
+ await writeFile6(join6(takeDir, "brand", `${name}.${ext}`), bytes);
5430
4970
  return { key: `/brand/${name}.${ext}`, aspect };
5431
4971
  };
5432
4972
  out.light = await one("logoUrl", "mark");
@@ -5462,9 +5002,9 @@ async function fetchMusicCatalog(origin) {
5462
5002
  }
5463
5003
 
5464
5004
  // src/plugin/judge.ts
5465
- import { existsSync as existsSync9 } from "fs";
5466
- import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
5467
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join9, resolve as resolve6 } from "path";
5005
+ import { existsSync as existsSync7 } from "fs";
5006
+ import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
5007
+ import { dirname, isAbsolute as isAbsolute3, join as join7, resolve as resolve6 } from "path";
5468
5008
  import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
5469
5009
 
5470
5010
  // src/plugin/shotBake.ts
@@ -5635,28 +5175,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
5635
5175
  return { w, h, data: out };
5636
5176
  }
5637
5177
  async function judgeKit(kitPath, manifestPath, outDir) {
5638
- const kit = JSON.parse(await readFile9(kitPath, "utf8"));
5178
+ const kit = JSON.parse(await readFile7(kitPath, "utf8"));
5639
5179
  const manifest = JSON.parse(
5640
- await readFile9(manifestPath, "utf8")
5180
+ await readFile7(manifestPath, "utf8")
5641
5181
  );
5642
- const manifestDir = dirname2(resolve6(manifestPath));
5643
- const kitDir = dirname2(resolve6(kitPath));
5644
- const out = resolve6(outDir ?? join9(kitDir, "judge"));
5645
- await mkdir7(out, { recursive: true });
5182
+ const manifestDir = dirname(resolve6(manifestPath));
5183
+ const kitDir = dirname(resolve6(kitPath));
5184
+ const out = resolve6(outDir ?? join7(kitDir, "judge"));
5185
+ await mkdir5(out, { recursive: true });
5646
5186
  const sheets = [];
5647
5187
  const skipped = [];
5648
5188
  const refCache = /* @__PURE__ */ new Map();
5649
5189
  const loadRef = async (file) => {
5650
5190
  if (refCache.has(file)) return refCache.get(file) ?? null;
5651
- const p = join9(manifestDir, file);
5652
- const img = existsSync9(p) ? decodePng(new Uint8Array(await readFile9(p))) : null;
5191
+ const p = join7(manifestDir, file);
5192
+ const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
5653
5193
  refCache.set(file, img);
5654
5194
  return img;
5655
5195
  };
5656
5196
  for (const a of kit.assets) {
5657
5197
  if (!/\.png$/i.test(a.path)) continue;
5658
- const file = isAbsolute3(a.path) && existsSync9(a.path) ? a.path : join9(kitDir, a.path.split("/").pop() ?? a.path);
5659
- const img = existsSync9(file) ? decodePng(new Uint8Array(await readFile9(file))) : null;
5198
+ const file = isAbsolute3(a.path) && existsSync7(a.path) ? a.path : join7(kitDir, a.path.split("/").pop() ?? a.path);
5199
+ const img = existsSync7(file) ? decodePng(new Uint8Array(await readFile7(file))) : null;
5660
5200
  if (!img) {
5661
5201
  skipped.push(`${a.destination ?? a.path}: unreadable`);
5662
5202
  continue;
@@ -5687,8 +5227,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5687
5227
  const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
5688
5228
  const nameA = `${id}--A.png`;
5689
5229
  const nameB = `${id}--B.png`;
5690
- await writeFile9(join9(out, nameA), encodePng(composeSheet(img, refImg)));
5691
- await writeFile9(join9(out, nameB), encodePng(composeSheet(refImg, img)));
5230
+ await writeFile7(join7(out, nameA), encodePng(composeSheet(img, refImg)));
5231
+ await writeFile7(join7(out, nameB), encodePng(composeSheet(refImg, img)));
5692
5232
  const rubric = [
5693
5233
  `# ${id} against ${ref.id} (${ref.file})`,
5694
5234
  "",
@@ -5708,7 +5248,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5708
5248
  "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.",
5709
5249
  ""
5710
5250
  ].join("\n");
5711
- await writeFile9(join9(out, `${id}.md`), rubric);
5251
+ await writeFile7(join7(out, `${id}.md`), rubric);
5712
5252
  sheets.push({
5713
5253
  asset: id,
5714
5254
  reference: ref.id,
@@ -5717,9 +5257,9 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5717
5257
  rubric: `${id}.md`
5718
5258
  });
5719
5259
  }
5720
- const verdictFile = join9(out, "judge.json");
5721
- if (!existsSync9(verdictFile)) {
5722
- await writeFile9(
5260
+ const verdictFile = join7(out, "judge.json");
5261
+ if (!existsSync7(verdictFile)) {
5262
+ await writeFile7(
5723
5263
  verdictFile,
5724
5264
  JSON.stringify(
5725
5265
  {
@@ -5770,9 +5310,9 @@ function catalogFamily(family) {
5770
5310
  }
5771
5311
 
5772
5312
  // src/plugin/callout.ts
5773
- import { existsSync as existsSync10 } from "fs";
5774
- import { readFile as readFile10 } from "fs/promises";
5775
- import { join as join10 } from "path";
5313
+ import { existsSync as existsSync8 } from "fs";
5314
+ import { readFile as readFile8 } from "fs/promises";
5315
+ import { join as join8 } from "path";
5776
5316
  import { lerpArray, sample } from "@vosjs/timeline";
5777
5317
  import {
5778
5318
  CALLOUT_DEFAULT_BODY_PX,
@@ -5969,13 +5509,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5969
5509
  const out = /* @__PURE__ */ new Map();
5970
5510
  if (!times.length) return out;
5971
5511
  const bare = { ...doc, overlays: [] };
5972
- const outDir = join10(dir, ".ground-check");
5512
+ const outDir = join8(dir, ".ground-check");
5973
5513
  const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
5974
5514
  for (const f of res.frames) {
5975
- if (!existsSync10(f.file)) continue;
5515
+ if (!existsSync8(f.file)) continue;
5976
5516
  out.set(
5977
5517
  +f.time.toFixed(3),
5978
- decodePng(new Uint8Array(await readFile10(f.file)))
5518
+ decodePng(new Uint8Array(await readFile8(f.file)))
5979
5519
  );
5980
5520
  }
5981
5521
  return out;
@@ -5984,7 +5524,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
5984
5524
  // src/plugin/kitPicture.ts
5985
5525
  import { execFile } from "child_process";
5986
5526
  import { promisify } from "util";
5987
- import { readFile as readFile11 } from "fs/promises";
5527
+ import { readFile as readFile9 } from "fs/promises";
5988
5528
  var execFileP = promisify(execFile);
5989
5529
  var SUBJECT_BAND = { min: 0.6, max: 0.92 };
5990
5530
  var BLANK_INK = 0.12;
@@ -6260,7 +5800,7 @@ async function pictureChecks(assets) {
6260
5800
  let ffmpeg2 = null;
6261
5801
  for (const a of assets) {
6262
5802
  if (/\.png$/i.test(a.file)) {
6263
- const img = decodePng(new Uint8Array(await readFile11(a.file)));
5803
+ const img = decodePng(new Uint8Array(await readFile9(a.file)));
6264
5804
  if (!img) {
6265
5805
  findings.push({
6266
5806
  code: "unreadable",
@@ -6353,9 +5893,9 @@ function formatFinding(f) {
6353
5893
  }
6354
5894
 
6355
5895
  // src/plugin/validateKit.ts
6356
- import { existsSync as existsSync11 } from "fs";
6357
- import { readFile as readFile12, stat as stat3 } from "fs/promises";
6358
- import { dirname as dirname3, isAbsolute as isAbsolute4, join as join11 } from "path";
5896
+ import { existsSync as existsSync9 } from "fs";
5897
+ import { readFile as readFile10, stat as stat2 } from "fs/promises";
5898
+ import { dirname as dirname2, isAbsolute as isAbsolute4, join as join9 } from "path";
6359
5899
  import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
6360
5900
  var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
6361
5901
  function pngDimensions(bytes) {
@@ -6374,7 +5914,7 @@ async function probeVideo(path) {
6374
5914
  const MB = await import("mediabunny");
6375
5915
  const input = new MB.Input({
6376
5916
  formats: MB.ALL_FORMATS,
6377
- source: new MB.BufferSource(new Uint8Array(await readFile12(path)))
5917
+ source: new MB.BufferSource(new Uint8Array(await readFile10(path)))
6378
5918
  });
6379
5919
  try {
6380
5920
  const track = await input.getPrimaryVideoTrack();
@@ -6395,7 +5935,7 @@ async function validateKit(kitPath, opts = {}) {
6395
5935
  const measured = [];
6396
5936
  let kit;
6397
5937
  try {
6398
- kit = JSON.parse(await readFile12(kitPath, "utf8"));
5938
+ kit = JSON.parse(await readFile10(kitPath, "utf8"));
6399
5939
  } catch (e) {
6400
5940
  return {
6401
5941
  valid: false,
@@ -6412,10 +5952,10 @@ async function validateKit(kitPath, opts = {}) {
6412
5952
  measured
6413
5953
  };
6414
5954
  }
6415
- const base = dirname3(kitPath);
5955
+ const base = dirname2(kitPath);
6416
5956
  const resolvePath = (p) => {
6417
- if (isAbsolute4(p) && existsSync11(p)) return p;
6418
- const beside = join11(base, p.split("/").pop() ?? p);
5957
+ if (isAbsolute4(p) && existsSync9(p)) return p;
5958
+ const beside = join9(base, p.split("/").pop() ?? p);
6419
5959
  return beside;
6420
5960
  };
6421
5961
  const perDestination = /* @__PURE__ */ new Map();
@@ -6426,7 +5966,7 @@ async function validateKit(kitPath, opts = {}) {
6426
5966
  const file = resolvePath(a.path);
6427
5967
  let bytes;
6428
5968
  try {
6429
- bytes = (await stat3(file)).size;
5969
+ bytes = (await stat2(file)).size;
6430
5970
  } catch {
6431
5971
  problems.push(`${label}: ${a.path} is missing`);
6432
5972
  continue;
@@ -6441,7 +5981,7 @@ async function validateKit(kitPath, opts = {}) {
6441
5981
  let h = null;
6442
5982
  let seconds = null;
6443
5983
  if (/\.(png|jpe?g|webp)$/i.test(file)) {
6444
- const head = Buffer.from(await readFile12(file));
5984
+ const head = Buffer.from(await readFile10(file));
6445
5985
  const kind = sniffImage(head);
6446
5986
  if (file.toLowerCase().endsWith(".png") && kind !== "png")
6447
5987
  problems.push(
@@ -6551,7 +6091,7 @@ async function validateKit(kitPath, opts = {}) {
6551
6091
 
6552
6092
  // src/plugin/recorder.ts
6553
6093
  import { writeFileSync } from "fs";
6554
- import { join as join12 } from "path";
6094
+ import { join as join10 } from "path";
6555
6095
  import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
6556
6096
  async function recordTake(browser, url, actions, paths, log, opts = {}) {
6557
6097
  const dry = opts.dryRun === true;
@@ -6561,7 +6101,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6561
6101
  actions,
6562
6102
  {
6563
6103
  frame: (file, bytes) => {
6564
- writeFileSync(join12(paths.framesDir, file), bytes);
6104
+ writeFileSync(join10(paths.framesDir, file), bytes);
6565
6105
  }
6566
6106
  },
6567
6107
  log,
@@ -6613,11 +6153,11 @@ import {
6613
6153
 
6614
6154
  // src/plugin/session.ts
6615
6155
  import { spawn } from "child_process";
6616
- import { existsSync as existsSync12, mkdirSync, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
6156
+ import { existsSync as existsSync10, mkdirSync, readdirSync, rmSync, statSync as statSync2 } from "fs";
6617
6157
  import { homedir } from "os";
6618
- import { join as join13 } from "path";
6158
+ import { join as join11 } from "path";
6619
6159
  import { chromium } from "playwright";
6620
- var SESSIONS_DIR = join13(homedir(), ".config", "vos", "sessions");
6160
+ var SESSIONS_DIR = join11(homedir(), ".config", "vos", "sessions");
6621
6161
  var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
6622
6162
  function sessionNameVerdict(name) {
6623
6163
  if (!name) return "a session needs a name: --name <app>";
@@ -6626,20 +6166,20 @@ function sessionNameVerdict(name) {
6626
6166
  return null;
6627
6167
  }
6628
6168
  function sessionDir(name) {
6629
- return join13(SESSIONS_DIR, name);
6169
+ return join11(SESSIONS_DIR, name);
6630
6170
  }
6631
6171
  function systemChromePath() {
6632
6172
  const explicit = process.env.VOS_BROWSER_PATH;
6633
6173
  if (explicit) return explicit;
6634
6174
  const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
6635
- join13(
6175
+ join11(
6636
6176
  process.env["PROGRAMFILES"] ?? "C:\\Program Files",
6637
6177
  "Google",
6638
6178
  "Chrome",
6639
6179
  "Application",
6640
6180
  "chrome.exe"
6641
6181
  ),
6642
- join13(
6182
+ join11(
6643
6183
  process.env["LOCALAPPDATA"] ?? "",
6644
6184
  "Google",
6645
6185
  "Chrome",
@@ -6651,7 +6191,7 @@ function systemChromePath() {
6651
6191
  "/usr/bin/google-chrome-stable",
6652
6192
  "/opt/google/chrome/chrome"
6653
6193
  ];
6654
- const found = candidates.find((c) => existsSync12(c));
6194
+ const found = candidates.find((c) => existsSync10(c));
6655
6195
  if (!found)
6656
6196
  throw new Error(
6657
6197
  "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"
@@ -6682,7 +6222,7 @@ async function openSession(name, url, opts = { log: () => {
6682
6222
  }
6683
6223
  async function withSession(name, fn, opts = {}) {
6684
6224
  const dir = sessionDir(name);
6685
- if (!existsSync12(dir))
6225
+ if (!existsSync10(dir))
6686
6226
  throw new Error(`no session "${name}" (vos session list)`);
6687
6227
  const explicit = process.env.VOS_BROWSER_PATH;
6688
6228
  const ctx = await chromium.launchPersistentContext(dir, {
@@ -6701,7 +6241,7 @@ async function withSession(name, fn, opts = {}) {
6701
6241
  }
6702
6242
  async function summarizeSession(name) {
6703
6243
  const dir = sessionDir(name);
6704
- const created = statSync3(dir).birthtimeMs || statSync3(dir).mtimeMs;
6244
+ const created = statSync2(dir).birthtimeMs || statSync2(dir).mtimeMs;
6705
6245
  return withSession(name, async (ctx) => {
6706
6246
  const cookies = await ctx.cookies();
6707
6247
  const byHost = /* @__PURE__ */ new Map();
@@ -6731,9 +6271,9 @@ function summaryLine(s) {
6731
6271
  return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
6732
6272
  }
6733
6273
  function listSessions() {
6734
- if (!existsSync12(SESSIONS_DIR)) return [];
6735
- return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
6736
- const st = statSync3(join13(SESSIONS_DIR, d.name));
6274
+ if (!existsSync10(SESSIONS_DIR)) return [];
6275
+ return readdirSync(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
6276
+ const st = statSync2(join11(SESSIONS_DIR, d.name));
6737
6277
  return {
6738
6278
  name: d.name,
6739
6279
  ageDays: Math.floor(
@@ -6744,7 +6284,7 @@ function listSessions() {
6744
6284
  }
6745
6285
  function removeSession(name) {
6746
6286
  const dir = sessionDir(name);
6747
- if (!existsSync12(dir)) return false;
6287
+ if (!existsSync10(dir)) return false;
6748
6288
  rmSync(dir, { recursive: true, force: true });
6749
6289
  return true;
6750
6290
  }
@@ -6809,9 +6349,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
6809
6349
  }
6810
6350
 
6811
6351
  // src/plugin/plan.ts
6812
- import { existsSync as existsSync13 } from "fs";
6813
- import { readFile as readFile13 } from "fs/promises";
6814
- import { join as join14 } from "path";
6352
+ import { existsSync as existsSync11 } from "fs";
6353
+ import { readFile as readFile11 } from "fs/promises";
6354
+ import { join as join12 } from "path";
6815
6355
  import {
6816
6356
  DEFAULT_FRAME_STYLE,
6817
6357
  STYLE_FIELDS,
@@ -7033,10 +6573,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
7033
6573
  // src/plugin/plan.ts
7034
6574
  var overlaps = (a, b) => a.in < b.out && b.in < a.out;
7035
6575
  async function readDigestActivity(dir) {
7036
- const file = join14(dir, "digest", "digest.json");
7037
- if (!existsSync13(file)) return null;
6576
+ const file = join12(dir, "digest", "digest.json");
6577
+ if (!existsSync11(file)) return null;
7038
6578
  try {
7039
- const d = JSON.parse(await readFile13(file, "utf8"));
6579
+ const d = JSON.parse(await readFile11(file, "utf8"));
7040
6580
  return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
7041
6581
  } catch {
7042
6582
  return null;
@@ -7249,16 +6789,16 @@ async function planTake(dir, opts = {}) {
7249
6789
  }
7250
6790
 
7251
6791
  // src/plugin/sync.ts
7252
- import { createHash as createHash2 } from "crypto";
7253
- import { existsSync as existsSync14, readFileSync as readFileSync2, readdirSync as readdirSync3 } from "fs";
7254
- import { readFile as readFile14 } from "fs/promises";
6792
+ import { createHash } from "crypto";
6793
+ import { existsSync as existsSync12, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
6794
+ import { readFile as readFile12 } from "fs/promises";
7255
6795
  import { createInterface } from "readline/promises";
7256
- import { basename as basename3, join as join16 } from "path";
6796
+ import { basename, join as join14 } from "path";
7257
6797
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
7258
6798
 
7259
6799
  // src/plugin/folder.ts
7260
- import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
7261
- import { join as join15 } from "path";
6800
+ import { mkdir as mkdir6, writeFile as writeFile8 } from "fs/promises";
6801
+ import { join as join13 } from "path";
7262
6802
  import { recipeHints } from "@vosjs/shared/frontmatter";
7263
6803
  import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7264
6804
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
@@ -7368,7 +6908,7 @@ async function cmdMove(argv) {
7368
6908
  });
7369
6909
  const key = requireCredential(strFlag(flags, "key"));
7370
6910
  const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
7371
- let failed2 = 0;
6911
+ let failed = 0;
7372
6912
  const moved = [];
7373
6913
  for (const raw of positionals) {
7374
6914
  const id = parseVosId(raw);
@@ -7383,28 +6923,28 @@ async function cmdMove(argv) {
7383
6923
  continue;
7384
6924
  }
7385
6925
  if (asVos.status === 404) {
7386
- const asAsset2 = await apiJson(origin, `/api/assets/${id}`, {
6926
+ const asAsset = await apiJson(origin, `/api/assets/${id}`, {
7387
6927
  method: "PATCH",
7388
6928
  key,
7389
6929
  body: { folderId }
7390
6930
  });
7391
- if (asAsset2.status === 200) {
6931
+ if (asAsset.status === 200) {
7392
6932
  moved.push({ id, kind: "asset" });
7393
6933
  r.log(`moved asset ${id}`);
7394
6934
  continue;
7395
6935
  }
7396
- failed2++;
7397
- r.log(apiError(`move ${id}`, asAsset2));
6936
+ failed++;
6937
+ r.log(apiError(`move ${id}`, asAsset));
7398
6938
  continue;
7399
6939
  }
7400
- failed2++;
6940
+ failed++;
7401
6941
  r.log(apiError(`move ${id}`, asVos));
7402
6942
  }
7403
6943
  r.done(
7404
- { moved, failed: failed2, folderId },
7405
- `moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed2 ? ` \u2014 ${failed2} failed` : ""}`
6944
+ { moved, failed, folderId },
6945
+ `moved ${moved.length}/${positionals.length} into ${toRef === "none" ? "the top level" : toRef}${failed ? ` \u2014 ${failed} failed` : ""}`
7406
6946
  );
7407
- return failed2 ? EXIT_ERROR : EXIT_OK;
6947
+ return failed ? EXIT_ERROR : EXIT_OK;
7408
6948
  }
7409
6949
  function recipeLine(filename, body) {
7410
6950
  const h = recipeHints(body);
@@ -7434,25 +6974,25 @@ async function cmdPull(argv) {
7434
6974
  throw new Error(apiError(`pull folder ${folder.slug}`, res));
7435
6975
  const payload = res.body;
7436
6976
  const out = strFlag(flags, "out") ?? folder.slug;
7437
- await mkdir8(join15(out, "recipes"), { recursive: true });
6977
+ await mkdir6(join13(out, "recipes"), { recursive: true });
7438
6978
  const lines = [];
7439
6979
  const recipes = [];
7440
6980
  for (const rec of payload.recipes) {
7441
- const path = join15(out, "recipes", safeName(rec.filename));
7442
- await writeFile10(path, rec.body ?? "");
6981
+ const path = join13(out, "recipes", safeName(rec.filename));
6982
+ await writeFile8(path, rec.body ?? "");
7443
6983
  recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
7444
6984
  }
7445
6985
  for (const rec of payload.inheritedRecipes) {
7446
6986
  const from = safeName(rec.folderSlug ?? "inherited");
7447
- await mkdir8(join15(out, "recipes", "_inherited", from), { recursive: true });
7448
- const path = join15(
6987
+ await mkdir6(join13(out, "recipes", "_inherited", from), { recursive: true });
6988
+ const path = join13(
7449
6989
  out,
7450
6990
  "recipes",
7451
6991
  "_inherited",
7452
6992
  from,
7453
6993
  safeName(rec.filename)
7454
6994
  );
7455
- await writeFile10(path, rec.body ?? "");
6995
+ await writeFile8(path, rec.body ?? "");
7456
6996
  recipes.push({
7457
6997
  path,
7458
6998
  line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
@@ -7460,15 +7000,15 @@ async function cmdPull(argv) {
7460
7000
  }
7461
7001
  const members = [];
7462
7002
  for (const v of payload.voses) {
7463
- const dir = join15(out, "members", safeName(v.slug || v.title || v.id));
7464
- await mkdir8(dir, { recursive: true });
7003
+ const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
7004
+ await mkdir6(dir, { recursive: true });
7465
7005
  const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
7466
7006
  if (cfg.status !== 200) {
7467
7007
  lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
7468
7008
  continue;
7469
7009
  }
7470
- await writeFile10(
7471
- join15(dir, "config.json"),
7010
+ await writeFile8(
7011
+ join13(dir, "config.json"),
7472
7012
  JSON.stringify(cfg.body.config, null, 2)
7473
7013
  );
7474
7014
  let take = false;
@@ -7479,8 +7019,8 @@ async function cmdPull(argv) {
7479
7019
  { key }
7480
7020
  );
7481
7021
  if (doc.status === 200) {
7482
- await writeFile10(
7483
- join15(dir, "doc.json"),
7022
+ await writeFile8(
7023
+ join13(dir, "doc.json"),
7484
7024
  JSON.stringify(doc.body, null, 2)
7485
7025
  );
7486
7026
  take = true;
@@ -7612,12 +7152,12 @@ function programPushTarget(ask) {
7612
7152
  return { kind: "version", vosId: tracked, tracked: true };
7613
7153
  }
7614
7154
  function storageStateInDir(dir) {
7615
- for (const f of readdirSync3(dir)) {
7155
+ for (const f of readdirSync2(dir)) {
7616
7156
  if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
7617
7157
  continue;
7618
7158
  try {
7619
- const v = JSON.parse(readFileSync2(join16(dir, f), "utf8"));
7620
- if (looksLikeStorageState(v)) return join16(dir, f);
7159
+ const v = JSON.parse(readFileSync2(join14(dir, f), "utf8"));
7160
+ if (looksLikeStorageState(v)) return join14(dir, f);
7621
7161
  } catch {
7622
7162
  }
7623
7163
  }
@@ -7638,7 +7178,7 @@ async function pushTake(dir, flags, r) {
7638
7178
  throw new Error(`doc.json fails lint:
7639
7179
  ${lint.problems.join("\n ")}`);
7640
7180
  }
7641
- if (!existsSync14(take.paths.recording)) {
7181
+ if (!existsSync12(take.paths.recording)) {
7642
7182
  throw new Error(
7643
7183
  "no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
7644
7184
  );
@@ -7687,8 +7227,8 @@ async function pushTake(dir, flags, r) {
7687
7227
  throw new Error("push cancelled");
7688
7228
  }
7689
7229
  }
7690
- const bytes = await readFile14(take.paths.recording);
7691
- const hash = createHash2("sha256").update(bytes).digest("hex");
7230
+ const bytes = await readFile12(take.paths.recording);
7231
+ const hash = createHash("sha256").update(bytes).digest("hex");
7692
7232
  const { type: contentType } = resolveMediaType({
7693
7233
  head: bytes.subarray(0, MEDIA_HEAD_BYTES),
7694
7234
  filename: take.paths.recordingName
@@ -7735,7 +7275,7 @@ async function pushTake(dir, flags, r) {
7735
7275
  const lowered = lowerToComposition3(docForPush);
7736
7276
  const config = { ...lowered.config, data: lowered.data };
7737
7277
  if (!state) {
7738
- const title = flags.title ?? `Take ${basename3(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
7278
+ const title = flags.title ?? `Take ${basename(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
7739
7279
  const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
7740
7280
  const created = await api(ctx, "/vos", {
7741
7281
  method: "POST",
@@ -7847,9 +7387,9 @@ async function pullTake(dir, flags, r) {
7847
7387
  };
7848
7388
  }
7849
7389
  let media2;
7850
- if (flags.media && existsSync14(join16(dir, "doc.json"))) {
7390
+ if (flags.media && existsSync12(join14(dir, "doc.json"))) {
7851
7391
  const local = JSON.parse(
7852
- await readFile14(join16(dir, "doc.json"), "utf8")
7392
+ await readFile12(join14(dir, "doc.json"), "utf8")
7853
7393
  );
7854
7394
  media2 = await pullMedia(ctx, dir, local, r.log);
7855
7395
  }
@@ -7910,10 +7450,10 @@ async function pullTake(dir, flags, r) {
7910
7450
  const hostedDoc = migrateHostedDoc3(docRes.json);
7911
7451
  const doc = hostedDoc;
7912
7452
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
7913
- if (existsSync14(localRecording)) {
7914
- doc.source.videoKey = basename3(localRecording);
7453
+ if (existsSync12(localRecording)) {
7454
+ doc.source.videoKey = basename(localRecording);
7915
7455
  }
7916
- await writeJson(join16(dir, "doc.json"), doc, true);
7456
+ await writeJson(join14(dir, "doc.json"), doc, true);
7917
7457
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
7918
7458
  const versions = await api(ctx, `/vos/${vosId}/versions`);
7919
7459
  const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
@@ -7932,10 +7472,10 @@ async function pullTake(dir, flags, r) {
7932
7472
  }
7933
7473
 
7934
7474
  // src/plugin/program.ts
7935
- import { existsSync as existsSync15 } from "fs";
7936
- import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
7475
+ import { existsSync as existsSync13 } from "fs";
7476
+ import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile9 } from "fs/promises";
7937
7477
  import { createInterface as createInterface2 } from "readline/promises";
7938
- import { basename as basename4, dirname as dirname4, join as join17 } from "path";
7478
+ import { basename as basename2, dirname as dirname3, join as join15 } from "path";
7939
7479
  import {
7940
7480
  CURRENT_CONFIG_VERSION,
7941
7481
  migrateConfig,
@@ -8141,8 +7681,8 @@ function preflightConfig(parsed) {
8141
7681
  }
8142
7682
  function resolveConfigPath(target) {
8143
7683
  if (target.endsWith(".json")) return target;
8144
- const inDir = join17(target, "config.json");
8145
- if (existsSync15(inDir)) return inDir;
7684
+ const inDir = join15(target, "config.json");
7685
+ if (existsSync13(inDir)) return inDir;
8146
7686
  throw new UsageError(
8147
7687
  `${target} has no config.json (and is not a take \u2014 no doc.json)`
8148
7688
  );
@@ -8168,9 +7708,9 @@ async function cmdFetch(argv) {
8168
7708
  throw new Error(apiError(`fetch config for ${vosId}`, cfg));
8169
7709
  const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
8170
7710
  const out = strFlag(flags, "out") ?? slug;
8171
- await mkdir9(out, { recursive: true });
8172
- await writeFile11(
8173
- join17(out, "config.json"),
7711
+ await mkdir7(out, { recursive: true });
7712
+ await writeFile9(
7713
+ join15(out, "config.json"),
8174
7714
  JSON.stringify(cfg.body.config, null, 2)
8175
7715
  );
8176
7716
  const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
@@ -8195,18 +7735,40 @@ async function cmdFetch(argv) {
8195
7735
  const hosted = migrateHostedDoc4(doc.body);
8196
7736
  const { config: own } = await writeProgramDoc(out, hosted);
8197
7737
  if (own) {
8198
- await writeFile11(join17(out, "config.json"), JSON.stringify(own, null, 2));
7738
+ await writeFile9(join15(out, "config.json"), JSON.stringify(own, null, 2));
8199
7739
  }
8200
7740
  } else if (doc.status === 200) {
8201
7741
  take = true;
8202
7742
  const hosted = migrateHostedDoc4(doc.body);
8203
- await writeFile11(join17(out, "doc.json"), JSON.stringify(hosted, null, 2));
7743
+ await writeFile9(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
8204
7744
  if (flags.media === true) {
8205
7745
  const media = await pullMedia({ origin, key }, out, hosted, r.log);
8206
7746
  mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
8207
7747
  }
8208
7748
  }
8209
7749
  }
7750
+ let assetsLine = "";
7751
+ if (!take) {
7752
+ const configPath = join15(out, "config.json");
7753
+ const written = JSON.parse(await readFile13(configPath, "utf8"));
7754
+ const hostedCount = manifestRefs(written).filter(
7755
+ ({ ref }) => hostedAssetId(ref)
7756
+ ).length;
7757
+ if (hostedCount && flags.media === true) {
7758
+ const pulled = await pullManifest({ origin, key }, out, written, r.log);
7759
+ if (pulled) {
7760
+ await writeFile9(
7761
+ configPath,
7762
+ JSON.stringify({ ...written, assets: pulled.assets }, null, 2)
7763
+ );
7764
+ assetsLine = `
7765
+ ${pulled.files.length} declared file${pulled.files.length === 1 ? "" : "s"} in ${out}/assets`;
7766
+ }
7767
+ } else if (hostedCount) {
7768
+ assetsLine = `
7769
+ ${hostedCount} declared file${hostedCount === 1 ? " is" : "s are"} hosted (a render reads ${hostedCount === 1 ? "it" : "them"} with your key). Add --media to bring ${hostedCount === 1 ? "it" : "them"} home`;
7770
+ }
7771
+ }
8210
7772
  r.done(
8211
7773
  {
8212
7774
  out,
@@ -8217,7 +7779,7 @@ async function cmdFetch(argv) {
8217
7779
  take
8218
7780
  },
8219
7781
  take ? `Wrote ${out}/doc.json + config.json + vos.json${mediaLine} (${title || id}, a take)
8220
- ` + (flags.media === true ? `Then: vos digest ${out} \u2014 look before you cut; edit doc.json; vos validate ${out}; vos push ${out}` : `Add --media to bring the recording home (digest/frames/render need it); then edit doc.json and vos push ${out}`) : `Wrote ${out}/config.json + vos.json (${title || id})
7782
+ ` + (flags.media === true ? `Then: vos digest ${out} \u2014 look before you cut; edit doc.json; vos validate ${out}; vos push ${out}` : `Add --media to bring the recording home (digest/frames/render need it); then edit doc.json and vos push ${out}`) : `Wrote ${out}/config.json + vos.json (${title || id})${assetsLine}
8221
7783
  Edit config.json, then: vos check ${out}/config.json && vos push ${out}/config.json`
8222
7784
  );
8223
7785
  return EXIT_OK;
@@ -8313,8 +7875,8 @@ async function cmdPushProgram(argv) {
8313
7875
  origin: strFlag(flags, "origin"),
8314
7876
  api: strFlag(flags, "api")
8315
7877
  });
8316
- const dir = dirname4(source);
8317
- const parsed = JSON.parse(await readFile15(source, "utf8"));
7878
+ const dir = dirname3(source);
7879
+ const parsed = JSON.parse(await readFile13(source, "utf8"));
8318
7880
  const pre = preflightConfig(parsed);
8319
7881
  if (!pre.ok || !pre.config) {
8320
7882
  for (const issue of pre.issues) r.log(`error ${issue}`);
@@ -8334,6 +7896,7 @@ async function cmdPushProgram(argv) {
8334
7896
  await setProgramStill(dir, t);
8335
7897
  r.log(`cover: ${t}s (doc.json "still")`);
8336
7898
  }
7899
+ for (const w of hostedLiteralWarnings(config)) r.log(`warning ${w}`);
8337
7900
  const programDoc = await readProgramDoc(dir, config);
8338
7901
  if (programDoc) {
8339
7902
  const lint = lintDoc(programDoc);
@@ -8349,7 +7912,7 @@ async function cmdPushProgram(argv) {
8349
7912
  "--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
8350
7913
  );
8351
7914
  }
8352
- const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
7915
+ const fallback = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos program";
8353
7916
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8354
7917
  let claimDoc = programDoc;
8355
7918
  if (programDoc) {
@@ -8366,6 +7929,12 @@ async function cmdPushProgram(argv) {
8366
7929
  );
8367
7930
  }
8368
7931
  }
7932
+ const stay = localManifestRefs(config);
7933
+ if (stay.length) {
7934
+ throw new UsageError(
7935
+ `a claimable push carries no files, and this program declares ${stay.length} local file${stay.length === 1 ? "" : "s"} (${stay.join(", ")}). Push with a key instead (vos login, then vos push ${source}), which uploads ${stay.length === 1 ? "it" : "them"}`
7936
+ );
7937
+ }
8369
7938
  const body = {
8370
7939
  title: title2,
8371
7940
  config: storedProgramConfig(config, claimDoc)
@@ -8399,6 +7968,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8399
7968
  (l) => r.log(l)
8400
7969
  );
8401
7970
  }
7971
+ await uploadManifest(config, dir, { origin, key }, (l) => r.log(l));
8402
7972
  const overrides = [
8403
7973
  ...multi.override ?? [],
8404
7974
  ...strFlag(flags, "overrides")?.split(",") ?? []
@@ -8498,7 +8068,7 @@ ${landedLines(landed2)}` : "")
8498
8068
  return EXIT_OK;
8499
8069
  }
8500
8070
  const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
8501
- const fallbackTitle = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos remix";
8071
+ const fallbackTitle = state?.title ? `${state.title} remix` : basename2(source).replace(/\.json$/i, "") || "vos remix";
8502
8072
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
8503
8073
  const folderId = folderRef ? resolveFolder(await listFolders(origin, key), folderRef).id : void 0;
8504
8074
  const created = await createProgramVos({
@@ -8566,7 +8136,7 @@ Iterate with: vos push ${source} --vos ${created.id}`
8566
8136
  async function cmdPullProgram(argv) {
8567
8137
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS2);
8568
8138
  const target = positionals[0] ?? ".";
8569
- const dir = target.endsWith(".json") ? dirname4(target) : target;
8139
+ const dir = target.endsWith(".json") ? dirname3(target) : target;
8570
8140
  const r = createReporter(flags.json === true);
8571
8141
  const origin = platformOrigin({
8572
8142
  origin: strFlag(flags, "origin"),
@@ -8630,10 +8200,10 @@ async function cmdPullProgram(argv) {
8630
8200
  const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
8631
8201
  if (cfg.status !== 200)
8632
8202
  throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
8633
- const configPath = join17(dir, "config.json");
8203
+ const configPath = join15(dir, "config.json");
8634
8204
  let backedUp = false;
8635
- if (existsSync15(configPath)) {
8636
- await writeFile11(join17(dir, "config.backup.json"), await readFile15(configPath));
8205
+ if (existsSync13(configPath)) {
8206
+ await writeFile9(join15(dir, "config.backup.json"), await readFile13(configPath));
8637
8207
  backedUp = true;
8638
8208
  }
8639
8209
  let headConfig = cfg.body.config;
@@ -8652,7 +8222,7 @@ async function cmdPullProgram(argv) {
8652
8222
  if (own) headConfig = own;
8653
8223
  }
8654
8224
  }
8655
- await writeFile11(configPath, JSON.stringify(headConfig, null, 2));
8225
+ await writeFile9(configPath, JSON.stringify(headConfig, null, 2));
8656
8226
  writeSyncState(dir, {
8657
8227
  vosId,
8658
8228
  versionId: typeof head.id === "string" ? head.id : since
@@ -8666,7 +8236,7 @@ async function cmdPullProgram(argv) {
8666
8236
  protected: protectedIds,
8667
8237
  changes,
8668
8238
  out: configPath,
8669
- backup: backedUp ? join17(dir, "config.backup.json") : null
8239
+ backup: backedUp ? join15(dir, "config.backup.json") : null
8670
8240
  },
8671
8241
  `Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
8672
8242
  Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
@@ -8753,9 +8323,9 @@ function storedProgramConfig(config, programDoc) {
8753
8323
  return lowerProgramDoc(doc).config;
8754
8324
  }
8755
8325
  async function readProgramDoc(dir, config) {
8756
- const docPath = join17(dir, "doc.json");
8757
- if (!existsSync15(docPath)) return null;
8758
- const parsed = JSON.parse(await readFile15(docPath, "utf8"));
8326
+ const docPath = join15(dir, "doc.json");
8327
+ if (!existsSync13(docPath)) return null;
8328
+ const parsed = JSON.parse(await readFile13(docPath, "utf8"));
8759
8329
  if (typeof parsed !== "object" || parsed === null || "source" in parsed)
8760
8330
  return null;
8761
8331
  const raw = parsed;
@@ -8763,32 +8333,32 @@ async function readProgramDoc(dir, config) {
8763
8333
  return { ...raw, program: { ...program, config } };
8764
8334
  }
8765
8335
  async function setProgramStill(dir, t) {
8766
- const docPath = join17(dir, "doc.json");
8336
+ const docPath = join15(dir, "doc.json");
8767
8337
  let doc = { program: {}, audio: [] };
8768
- if (existsSync15(docPath)) {
8769
- const parsed = JSON.parse(await readFile15(docPath, "utf8"));
8338
+ if (existsSync13(docPath)) {
8339
+ const parsed = JSON.parse(await readFile13(docPath, "utf8"));
8770
8340
  if (!parsed || typeof parsed !== "object" || "source" in parsed)
8771
8341
  throw new UsageError(
8772
8342
  `${docPath} is a take's document; a take's cover is its own "still" field`
8773
8343
  );
8774
8344
  doc = parsed;
8775
8345
  }
8776
- await writeFile11(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
8346
+ await writeFile9(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
8777
8347
  }
8778
8348
  async function writeProgramDoc(dir, hosted) {
8779
8349
  const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
8780
8350
  const { config, ...rest } = program;
8781
8351
  const onDisk = { ...hosted, program: rest };
8782
- await writeFile11(join17(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8352
+ await writeFile9(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8783
8353
  return {
8784
8354
  config: config && typeof config === "object" ? config : null
8785
8355
  };
8786
8356
  }
8787
8357
 
8788
8358
  // src/plugin/asset.ts
8789
- import { createHash as createHash3 } from "crypto";
8359
+ import { createHash as createHash2 } from "crypto";
8790
8360
  import { readFileSync as readFileSync3 } from "fs";
8791
- import { basename as basename5, extname as extname2 } from "path";
8361
+ import { basename as basename3, extname as extname2 } from "path";
8792
8362
  var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
8793
8363
  var MIME_BY_EXT = {
8794
8364
  ".png": "image/png",
@@ -8840,7 +8410,7 @@ async function cmdPush(argv) {
8840
8410
  const uploaded = [];
8841
8411
  let unfiledMedia = 0;
8842
8412
  for (const file of positionals) {
8843
- const name = basename5(file);
8413
+ const name = basename3(file);
8844
8414
  const body = readFileSync3(file);
8845
8415
  const media = resolveMediaType({
8846
8416
  head: body.subarray(0, MEDIA_HEAD_BYTES),
@@ -8853,7 +8423,7 @@ async function cmdPush(argv) {
8853
8423
  put = await uploadAsset({ origin, key }, new Uint8Array(body), {
8854
8424
  filename: sent,
8855
8425
  contentType: media.type,
8856
- contentHash: createHash3("sha256").update(body).digest("hex"),
8426
+ contentHash: createHash2("sha256").update(body).digest("hex"),
8857
8427
  ...folder ? { folderId: folder.id } : {}
8858
8428
  });
8859
8429
  } catch (e) {
@@ -8924,8 +8494,8 @@ async function cmdAsset(argv) {
8924
8494
  }
8925
8495
 
8926
8496
  // src/plugin/remove.ts
8927
- import { existsSync as existsSync16, rmSync as rmSync2, statSync as statSync4 } from "fs";
8928
- import { join as join18 } from "path";
8497
+ import { existsSync as existsSync14, rmSync as rmSync2, statSync as statSync3 } from "fs";
8498
+ import { join as join16 } from "path";
8929
8499
  import { createInterface as createInterface3 } from "readline/promises";
8930
8500
 
8931
8501
  // src/plugin/trash.ts
@@ -9104,7 +8674,7 @@ async function cmdDelete(argv) {
9104
8674
  api: strFlag(flags, "api")
9105
8675
  }).replace(/\/+$/, "");
9106
8676
  const key = requireCredential(strFlag(flags, "key"));
9107
- const dir = existsSync16(target) && statSync4(target).isDirectory() ? target : null;
8677
+ const dir = existsSync14(target) && statSync3(target).isDirectory() ? target : null;
9108
8678
  const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
9109
8679
  if (!vosId)
9110
8680
  throw new UsageError(
@@ -9146,7 +8716,7 @@ async function cmdDelete(argv) {
9146
8716
  throw new Error(apiError(`delete ${vosId}`, res));
9147
8717
  let unlinked = false;
9148
8718
  if (dir) {
9149
- rmSync2(join18(dir, SYNC_STATE_NAME), { force: true });
8719
+ rmSync2(join16(dir, SYNC_STATE_NAME), { force: true });
9150
8720
  unlinked = true;
9151
8721
  }
9152
8722
  const trashed = res.body.trashed === true;
@@ -9172,7 +8742,7 @@ async function cmdDelete(argv) {
9172
8742
 
9173
8743
  // src/plugin/buildProgram.ts
9174
8744
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
9175
- import { basename as basename6, dirname as dirname5, join as join19, resolve as resolve7 } from "path";
8745
+ import { basename as basename4, dirname as dirname4, join as join17, resolve as resolve7 } from "path";
9176
8746
  import { pathToFileURL } from "url";
9177
8747
  import { parse } from "acorn";
9178
8748
  import { fullAncestor } from "acorn-walk";
@@ -9380,7 +8950,7 @@ async function cmdBuild(argv) {
9380
8950
  throw new UsageError(
9381
8951
  `${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
9382
8952
  );
9383
- const built = buildProgram(program, basename6(file), text);
8953
+ const built = buildProgram(program, basename4(file), text);
9384
8954
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
9385
8955
  if (built.problems.length) {
9386
8956
  r.done(
@@ -9390,7 +8960,7 @@ async function cmdBuild(argv) {
9390
8960
  return EXIT_ERROR;
9391
8961
  }
9392
8962
  const out = resolve7(
9393
- strFlag(flags, "out") ?? join19(dirname5(file), "config.json")
8963
+ strFlag(flags, "out") ?? join17(dirname4(file), "config.json")
9394
8964
  );
9395
8965
  writeFileSync2(out, JSON.stringify(built.config, null, 2) + "\n");
9396
8966
  const sidecar = out.replace(/\.json$/, "") + ".sources.json";
@@ -9407,8 +8977,8 @@ async function cmdBuild(argv) {
9407
8977
 
9408
8978
  // src/plugin/compare.ts
9409
8979
  import { spawnSync } from "child_process";
9410
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
9411
- import { join as join20, resolve as resolve8 } from "path";
8980
+ import { existsSync as existsSync15, mkdirSync as mkdirSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
8981
+ import { dirname as dirname5, join as join18, resolve as resolve8 } from "path";
9412
8982
  var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
9413
8983
  function sampleTimes(duration, every) {
9414
8984
  const step = every > 0 ? every : 1;
@@ -9474,6 +9044,12 @@ async function cmdCompare(argv) {
9474
9044
  for (const w of loaded.warnings) r.log(`note: ${w}`);
9475
9045
  const config = loaded.config;
9476
9046
  const duration = configDuration(config) ?? 5;
9047
+ const assets = await programAssets(config, {
9048
+ baseDir: existsSync15(source) && statSync4(source).isDirectory() ? source : dirname5(source),
9049
+ origin: platformOrigin({}),
9050
+ key: resolveCredential(),
9051
+ log: (line) => r.log(line)
9052
+ });
9477
9053
  const probed = probeSize(ref);
9478
9054
  const half = probed && probed.width > 1280 ? 2 : 1;
9479
9055
  const width = Number(
@@ -9492,9 +9068,9 @@ async function cmdCompare(argv) {
9492
9068
  try {
9493
9069
  for (const t of times) {
9494
9070
  const tag = t.toFixed(2);
9495
- const refPng = join20(outDir, `src-${tag}.png`);
9496
- const vosPng = join20(outDir, `vos-${tag}.png`);
9497
- const sheet = join20(outDir, `sheet-${tag}.png`);
9071
+ const refPng = join18(outDir, `src-${tag}.png`);
9072
+ const vosPng = join18(outDir, `vos-${tag}.png`);
9073
+ const sheet = join18(outDir, `sheet-${tag}.png`);
9498
9074
  const grab = ffmpeg([
9499
9075
  "-v",
9500
9076
  "error",
@@ -9515,6 +9091,7 @@ async function cmdCompare(argv) {
9515
9091
  );
9516
9092
  const still = await renderStill(browser, {
9517
9093
  config,
9094
+ assets,
9518
9095
  width,
9519
9096
  height,
9520
9097
  time: t
@@ -9550,7 +9127,7 @@ async function cmdCompare(argv) {
9550
9127
  } finally {
9551
9128
  await browser.close();
9552
9129
  }
9553
- const overview = join20(outDir, "overview.png");
9130
+ const overview = join18(outDir, "overview.png");
9554
9131
  if (scores.length) {
9555
9132
  const inputs = scores.flatMap((s) => ["-i", s.sheet]);
9556
9133
  const scaled = scores.map((_, i) => `[${i}:v]scale=iw/3:-2[s${i}]`).join(";");
@@ -9576,7 +9153,7 @@ async function cmdCompare(argv) {
9576
9153
  overview
9577
9154
  };
9578
9155
  writeFileSync3(
9579
- join20(outDir, "report.json"),
9156
+ join18(outDir, "report.json"),
9580
9157
  JSON.stringify(report, null, 2) + "\n"
9581
9158
  );
9582
9159
  r.done(
@@ -9587,28 +9164,28 @@ async function cmdCompare(argv) {
9587
9164
  }
9588
9165
 
9589
9166
  // src/plugin/port.ts
9590
- import { existsSync as existsSync20, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
9591
- import { dirname as dirname8, join as join24, resolve as resolve11 } from "path";
9167
+ import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
9168
+ import { dirname as dirname8, join as join22, resolve as resolve11 } from "path";
9592
9169
  import { pathToFileURL as pathToFileURL3 } from "url";
9593
9170
 
9594
9171
  // src/plugin/portInventory.ts
9595
9172
  import { spawnSync as spawnSync2 } from "child_process";
9596
9173
  import {
9597
- existsSync as existsSync18,
9174
+ existsSync as existsSync17,
9598
9175
  mkdirSync as mkdirSync3,
9599
9176
  readFileSync as readFileSync6,
9600
- readdirSync as readdirSync5,
9177
+ readdirSync as readdirSync4,
9601
9178
  statSync as statSync6
9602
9179
  } from "fs";
9603
- import { dirname as dirname6, join as join22, relative as relative2, resolve as resolve9 } from "path";
9180
+ import { dirname as dirname6, join as join20, relative as relative2, resolve as resolve9 } from "path";
9604
9181
  import { pathToFileURL as pathToFileURL2 } from "url";
9605
9182
  import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
9606
9183
 
9607
9184
  // src/plugin/portRemotion.ts
9608
- import { existsSync as existsSync17, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
9185
+ import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
9609
9186
  import { createServer as createServer2 } from "http";
9610
9187
  import { createRequire } from "module";
9611
- import { basename as basename7, extname as extname3, join as join21 } from "path";
9188
+ import { basename as basename5, extname as extname3, join as join19 } from "path";
9612
9189
  function themeColors(sources) {
9613
9190
  const out = {};
9614
9191
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -9672,16 +9249,16 @@ function sourceTexts(dir) {
9672
9249
  const out = [];
9673
9250
  const walk = (d, depth) => {
9674
9251
  if (depth > 4 || out.length > 200) return;
9675
- for (const name of readdirSync4(d)) {
9252
+ for (const name of readdirSync3(d)) {
9676
9253
  if (name === "node_modules" || name.startsWith(".")) continue;
9677
- const p = join21(d, name);
9254
+ const p = join19(d, name);
9678
9255
  if (statSync5(p).isDirectory()) walk(p, depth + 1);
9679
9256
  else if (/\.(tsx?|jsx?|mjs)$/.test(name))
9680
9257
  out.push(readFileSync5(p, "utf8"));
9681
9258
  }
9682
9259
  };
9683
- const src = join21(dir, "src");
9684
- walk(existsSync17(src) ? src : dir, 0);
9260
+ const src = join19(dir, "src");
9261
+ walk(existsSync16(src) ? src : dir, 0);
9685
9262
  return out;
9686
9263
  }
9687
9264
  function entryPoint(dir) {
@@ -9693,7 +9270,7 @@ function entryPoint(dir) {
9693
9270
  "remotion/index.ts",
9694
9271
  "remotion/index.tsx"
9695
9272
  ])
9696
- if (existsSync17(join21(dir, p))) return join21(dir, p);
9273
+ if (existsSync16(join19(dir, p))) return join19(dir, p);
9697
9274
  return null;
9698
9275
  }
9699
9276
  var TYPES = {
@@ -9768,7 +9345,7 @@ async function readRemotion(dir, log) {
9768
9345
  const entry = entryPoint(dir);
9769
9346
  let bundler;
9770
9347
  try {
9771
- bundler = createRequire(join21(dir, "package.json"))("@remotion/bundler");
9348
+ bundler = createRequire(join19(dir, "package.json"))("@remotion/bundler");
9772
9349
  } catch {
9773
9350
  log?.(
9774
9351
  "a Remotion project whose dependencies are not installed: run `npm install` in it so vos can read its compositions (words, scenes, faces); reading only the render"
@@ -9785,9 +9362,9 @@ async function readRemotion(dir, log) {
9785
9362
  } });
9786
9363
  const server = createServer2((rq, rs) => {
9787
9364
  const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
9788
- let f = join21(out, path);
9789
- if (!f.startsWith(out) || !existsSync17(f) || statSync5(f).isDirectory())
9790
- f = join21(out, "index.html");
9365
+ let f = join19(out, path);
9366
+ if (!f.startsWith(out) || !existsSync16(f) || statSync5(f).isDirectory())
9367
+ f = join19(out, "index.html");
9791
9368
  rs.writeHead(200, {
9792
9369
  "content-type": TYPES[extname3(f)] ?? "application/octet-stream"
9793
9370
  });
@@ -9886,10 +9463,10 @@ async function readRemotion(dir, log) {
9886
9463
  perScene.push({ scene: s.name, texts: read.texts ?? [] });
9887
9464
  }
9888
9465
  const sources = sourceTexts(dir);
9889
- const publicDir = join21(dir, "public");
9466
+ const publicDir = join19(dir, "public");
9890
9467
  const media = tree.audio.map((a) => {
9891
- const name = basename7(a.src.split("?")[0]);
9892
- const local = existsSync17(join21(publicDir, name)) ? `public/${name}` : a.src;
9468
+ const name = basename5(a.src.split("?")[0]);
9469
+ const local = existsSync16(join19(publicDir, name)) ? `public/${name}` : a.src;
9893
9470
  return {
9894
9471
  kind: "audio",
9895
9472
  src: local,
@@ -9930,9 +9507,9 @@ async function readRemotion(dir, log) {
9930
9507
 
9931
9508
  // src/plugin/portInventory.ts
9932
9509
  function detectEngine(dir) {
9933
- if (existsSync18(join22(dir, "hyperframes.json"))) return "hyperframes";
9934
- const pkg = join22(dir, "package.json");
9935
- if (existsSync18(pkg)) {
9510
+ if (existsSync17(join20(dir, "hyperframes.json"))) return "hyperframes";
9511
+ const pkg = join20(dir, "package.json");
9512
+ if (existsSync17(pkg)) {
9936
9513
  try {
9937
9514
  const p = JSON.parse(readFileSync6(pkg, "utf8"));
9938
9515
  const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
@@ -9946,14 +9523,14 @@ function detectEngine(dir) {
9946
9523
  return "page";
9947
9524
  }
9948
9525
  function sourcePage(source) {
9949
- if (!existsSync18(source)) return null;
9526
+ if (!existsSync17(source)) return null;
9950
9527
  if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
9951
- const index = join22(source, "index.html");
9952
- return existsSync18(index) ? index : null;
9528
+ const index = join20(source, "index.html");
9529
+ return existsSync17(index) ? index : null;
9953
9530
  }
9954
9531
  function findRender(dir) {
9955
- const videos = (d) => existsSync18(d) ? readdirSync5(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join22(d, f)) : [];
9956
- for (const d of [join22(dir, "renders"), join22(dir, "out"), dir]) {
9532
+ const videos = (d) => existsSync17(d) ? readdirSync4(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join20(d, f)) : [];
9533
+ for (const d of [join20(dir, "renders"), join20(dir, "out"), dir]) {
9957
9534
  const found = videos(d);
9958
9535
  if (found.length) return found[0];
9959
9536
  }
@@ -10224,7 +9801,7 @@ var SEEK_AND_READ = `((args) => {
10224
9801
  })`;
10225
9802
  async function readInventory(source, opts) {
10226
9803
  const abs = resolve9(source);
10227
- const dir = existsSync18(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
9804
+ const dir = existsSync17(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
10228
9805
  const engine = detectEngine(dir);
10229
9806
  const page = sourcePage(abs);
10230
9807
  const render = opts.render === void 0 ? findRender(dir) : opts.render;
@@ -10327,7 +9904,7 @@ async function readInventory(source, opts) {
10327
9904
  if (render && duration) {
10328
9905
  const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
10329
9906
  if (has) {
10330
- const refDir = join22(opts.outDir, "ref");
9907
+ const refDir = join20(opts.outDir, "ref");
10331
9908
  mkdirSync3(refDir, { recursive: true });
10332
9909
  const times = read.scenes.length >= 3 ? read.scenes.map(
10333
9910
  (s) => s.start + (s.duration ?? 1) / 2
@@ -10336,7 +9913,7 @@ async function readInventory(source, opts) {
10336
9913
  (_, i) => i + 0.5
10337
9914
  );
10338
9915
  for (const t of times.filter((t2) => t2 < duration)) {
10339
- const file = join22(refDir, `src-${t.toFixed(2)}.png`);
9916
+ const file = join20(refDir, `src-${t.toFixed(2)}.png`);
10340
9917
  const ok = spawnSync2("ffmpeg", [
10341
9918
  "-v",
10342
9919
  "error",
@@ -10390,8 +9967,8 @@ async function readInventory(source, opts) {
10390
9967
 
10391
9968
  // src/plugin/portScaffold.ts
10392
9969
  import { spawnSync as spawnSync3 } from "child_process";
10393
- import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10394
- import { basename as basename8, dirname as dirname7, join as join23, resolve as resolve10 } from "path";
9970
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
9971
+ import { basename as basename6, dirname as dirname7, join as join21, resolve as resolve10 } from "path";
10395
9972
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
10396
9973
 
10397
9974
  // src/plugin/painterLib.ts
@@ -10801,8 +10378,8 @@ function planScaffold(inv, opts) {
10801
10378
  const copies = [];
10802
10379
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
10803
10380
  const from = resolve10(opts.sourceDir, m.src);
10804
- const name = basename8(from);
10805
- if (!existsSync19(from)) {
10381
+ const name = basename6(from);
10382
+ if (!existsSync18(from)) {
10806
10383
  report.push(
10807
10384
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
10808
10385
  );
@@ -10945,7 +10522,7 @@ ${sceneTodos}${springHint}
10945
10522
  }
10946
10523
  function reportText(inv, r) {
10947
10524
  const lines = [
10948
- `# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
10525
+ `# Port report: ${inv.source === "." && inv.root ? basename6(inv.root) : inv.source}`,
10949
10526
  "",
10950
10527
  `${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
10951
10528
  "",
@@ -11000,13 +10577,13 @@ function reportText(inv, r) {
11000
10577
  }
11001
10578
  function writeScaffold(plan, outDir) {
11002
10579
  mkdirSync4(outDir, { recursive: true });
11003
- writeFileSync4(join23(outDir, "program.mjs"), plan.program);
10580
+ writeFileSync4(join21(outDir, "program.mjs"), plan.program);
11004
10581
  writeFileSync4(
11005
- join23(outDir, "doc.json"),
10582
+ join21(outDir, "doc.json"),
11006
10583
  JSON.stringify(plan.doc, null, 2) + "\n"
11007
10584
  );
11008
- writeFileSync4(join23(dirname7(outDir), "REPORT.md"), plan.report);
11009
- for (const [from, name] of plan.copies) copyFileSync(from, join23(outDir, name));
10585
+ writeFileSync4(join21(dirname7(outDir), "REPORT.md"), plan.report);
10586
+ for (const [from, name] of plan.copies) copyFileSync(from, join21(outDir, name));
11010
10587
  }
11011
10588
  function probeAudioDuration(file) {
11012
10589
  const res = spawnSync3(
@@ -11040,7 +10617,7 @@ async function inventory(argv) {
11040
10617
  outDir,
11041
10618
  log: (l) => r.log(`note: ${l}`)
11042
10619
  });
11043
- const file = join24(outDir, "inventory.json");
10620
+ const file = join22(outDir, "inventory.json");
11044
10621
  writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
11045
10622
  const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
11046
10623
  r.log(
@@ -11072,32 +10649,32 @@ function summary(inv) {
11072
10649
  async function scaffold(argv) {
11073
10650
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS8);
11074
10651
  const r = createReporter(flags.json === true);
11075
- const from = resolve11(strFlag(flags, "from") ?? join24("port", "inventory.json"));
11076
- if (!existsSync20(from))
10652
+ const from = resolve11(strFlag(flags, "from") ?? join22("port", "inventory.json"));
10653
+ if (!existsSync19(from))
11077
10654
  throw new UsageError(
11078
10655
  `no inventory at ${from}: run npx vos port inventory <source> first`
11079
10656
  );
11080
10657
  const inv = JSON.parse(readFileSync7(from, "utf8"));
11081
10658
  const outDir = resolve11(
11082
- strFlag(flags, "out") ?? join24(dirname8(from), "program")
10659
+ strFlag(flags, "out") ?? join22(dirname8(from), "program")
11083
10660
  );
11084
10661
  const plan = planScaffold(inv, {
11085
10662
  sourceDir: scaffoldSourceDir(inv),
11086
10663
  probeDuration: probeAudioDuration
11087
10664
  });
11088
10665
  writeScaffold(plan, outDir);
11089
- const programFile = join24(outDir, "program.mjs");
10666
+ const programFile = join22(outDir, "program.mjs");
11090
10667
  const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
11091
10668
  const built = buildProgram(mod.default, "program.mjs", plan.program);
11092
10669
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
11093
10670
  if (built.problems.length) return EXIT_ERROR;
11094
10671
  writeFileSync5(
11095
- join24(outDir, "config.json"),
10672
+ join22(outDir, "config.json"),
11096
10673
  JSON.stringify(built.config, null, 2) + "\n"
11097
10674
  );
11098
10675
  r.done(
11099
- { program: outDir, report: join24(dirname8(outDir), "REPORT.md") },
11100
- `Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join24(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
10676
+ { program: outDir, report: join22(dirname8(outDir), "REPORT.md") },
10677
+ `Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join22(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
11101
10678
  );
11102
10679
  return EXIT_OK;
11103
10680
  }
@@ -11108,11 +10685,11 @@ function scaffoldSourceDir(inv) {
11108
10685
 
11109
10686
  // src/plugin/recipe.ts
11110
10687
  import { readFileSync as readFileSync8 } from "fs";
11111
- import { basename as basename9 } from "path";
10688
+ import { basename as basename7 } from "path";
11112
10689
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
11113
10690
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
11114
10691
  function recipeFilename(file) {
11115
- const name = basename9(file);
10692
+ const name = basename7(file);
11116
10693
  if (!/\.md$/i.test(name)) {
11117
10694
  throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
11118
10695
  }
@@ -11199,7 +10776,7 @@ async function cmdRecipe(argv) {
11199
10776
  }
11200
10777
 
11201
10778
  // src/plugin/brand.ts
11202
- import { writeFile as writeFile12 } from "fs/promises";
10779
+ import { writeFile as writeFile10 } from "fs/promises";
11203
10780
  import { resolve as resolve12 } from "path";
11204
10781
  import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
11205
10782
  import { lookKindForGround } from "@vosjs/studio-core";
@@ -11553,7 +11130,7 @@ async function cmdBrand(argv) {
11553
11130
  composition,
11554
11131
  llms?.claim ?? design?.description ?? null
11555
11132
  );
11556
- await writeFile12(out, md);
11133
+ await writeFile10(out, md);
11557
11134
  const k = composition.kit;
11558
11135
  r.done(
11559
11136
  {
@@ -11668,7 +11245,7 @@ async function recordHosted(input) {
11668
11245
  }
11669
11246
 
11670
11247
  // src/plugin/agentBrowser.ts
11671
- import { writeFile as writeFile13 } from "fs/promises";
11248
+ import { writeFile as writeFile11 } from "fs/promises";
11672
11249
  import { resolve as resolve13 } from "path";
11673
11250
  function parseAgentBrowserLog(text) {
11674
11251
  const records = [];
@@ -12148,8 +11725,8 @@ async function cmdActions(argv) {
12148
11725
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
12149
11726
  viewport = { width: Number(m[1]), height: Number(m[2]) };
12150
11727
  }
12151
- const { readFile: readFile18 } = await import("fs/promises");
12152
- const text = await readFile18(resolve13(input), "utf8");
11728
+ const { readFile: readFile16 } = await import("fs/promises");
11729
+ const text = await readFile16(resolve13(input), "utf8");
12153
11730
  const { records, problems } = parseAgentBrowserLog(text);
12154
11731
  for (const p of problems) r.log(` ${p}`);
12155
11732
  const result = convertAgentBrowser(records, {
@@ -12166,7 +11743,7 @@ async function cmdActions(argv) {
12166
11743
  );
12167
11744
  return EXIT_ERROR;
12168
11745
  }
12169
- await writeFile13(out, `${JSON.stringify(result.actions, null, 2)}
11746
+ await writeFile11(out, `${JSON.stringify(result.actions, null, 2)}
12170
11747
  `);
12171
11748
  r.done(
12172
11749
  { ok: true, out, url: result.actions.url ?? null, ...summary2(result) },
@@ -12186,9 +11763,9 @@ function summary2(result) {
12186
11763
  }
12187
11764
  async function cmdActionsScript(input, flags) {
12188
11765
  const r = createReporter(flags.json === true);
12189
- const { readFile: readFile18 } = await import("fs/promises");
11766
+ const { readFile: readFile16 } = await import("fs/promises");
12190
11767
  const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
12191
- const raw = JSON.parse(await readFile18(resolve13(input), "utf8"));
11768
+ const raw = JSON.parse(await readFile16(resolve13(input), "utf8"));
12192
11769
  const errors = validateActions(raw);
12193
11770
  if (errors.length) throw new UsageError(`${input}:
12194
11771
  ${errors.join("\n ")}`);
@@ -12198,9 +11775,9 @@ async function cmdActionsScript(input, flags) {
12198
11775
  }
12199
11776
 
12200
11777
  // src/plugin/ingest.ts
12201
- import { existsSync as existsSync21 } from "fs";
12202
- import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
12203
- import { basename as basename10, join as join25, resolve as resolve14 } from "path";
11778
+ import { existsSync as existsSync20 } from "fs";
11779
+ import { copyFile, mkdir as mkdir8, readFile as readFile14, stat as stat3 } from "fs/promises";
11780
+ import { basename as basename8, join as join23, resolve as resolve14 } from "path";
12204
11781
  import { inflateRawSync } from "zlib";
12205
11782
  import {
12206
11783
  cursorFromPlaywrightTrace,
@@ -12281,14 +11858,14 @@ async function copyRecording(src, dest, ext) {
12281
11858
  }
12282
11859
  }
12283
11860
  async function readTraceFile(path, offsetMs) {
12284
- const kind = traceKindOf(basename10(path));
11861
+ const kind = traceKindOf(basename8(path));
12285
11862
  if (!kind) {
12286
11863
  throw new UsageError(
12287
11864
  `${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
12288
11865
  );
12289
11866
  }
12290
11867
  if (kind === "zip") {
12291
- const bytes = new Uint8Array(await readFile16(path));
11868
+ const bytes = new Uint8Array(await readFile14(path));
12292
11869
  const entry = playwrightTraceEntry(zipEntries(bytes));
12293
11870
  if (!entry) {
12294
11871
  throw new UsageError(
@@ -12301,8 +11878,8 @@ async function readTraceFile(path, offsetMs) {
12301
11878
  );
12302
11879
  return cursorFromPlaywrightTrace(text2, { offsetMs });
12303
11880
  }
12304
- const text = await readFile16(path, "utf8");
12305
- return cursorFromTrace(basename10(path), text, { offsetMs });
11881
+ const text = await readFile14(path, "utf8");
11882
+ return cursorFromTrace(basename8(path), text, { offsetMs });
12306
11883
  }
12307
11884
  function parseViewport(v) {
12308
11885
  if (!v) return null;
@@ -12318,7 +11895,7 @@ async function cmdIngest(argv) {
12318
11895
  "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]"
12319
11896
  );
12320
11897
  const videoPath = resolve14(source);
12321
- if (!existsSync21(videoPath)) throw new UsageError(`${source}: no such file`);
11898
+ if (!existsSync20(videoPath)) throw new UsageError(`${source}: no such file`);
12322
11899
  const r = createReporter(flags.json === true);
12323
11900
  const outDir = resolve14(strFlag(flags, "out") ?? "take");
12324
11901
  const offsetMs = numFlag(flags, "offset", 0);
@@ -12333,7 +11910,7 @@ async function cmdIngest(argv) {
12333
11910
  "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
12334
11911
  );
12335
11912
  const backdrop = footage ? null : await takeBackdrop(flags, r);
12336
- r.log(`probing ${basename10(videoPath)}\u2026`);
11913
+ r.log(`probing ${basename8(videoPath)}\u2026`);
12337
11914
  const probed = await probeVideoFile(videoPath);
12338
11915
  if (!(probed.durationMs > 0)) {
12339
11916
  throw new UsageError(
@@ -12345,15 +11922,15 @@ async function cmdIngest(argv) {
12345
11922
  trace = await readTraceFile(resolve14(cursorPath), offsetMs);
12346
11923
  for (const n of trace.notes) r.log(`note: ${n}`);
12347
11924
  }
12348
- if (existsSync21(join25(outDir, "meta.json"))) {
11925
+ if (existsSync20(join23(outDir, "meta.json"))) {
12349
11926
  const { prevDoc, kept } = await prepareReRecord(outDir);
12350
11927
  r.log(
12351
11928
  `note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
12352
11929
  );
12353
11930
  }
12354
- await mkdir10(outDir, { recursive: true });
11931
+ await mkdir8(outDir, { recursive: true });
12355
11932
  const paths = await ensureTakeDir(outDir);
12356
- const recording = join25(outDir, `recording.${probed.ext}`);
11933
+ const recording = join23(outDir, `recording.${probed.ext}`);
12357
11934
  r.log("copying the recording\u2026");
12358
11935
  const remuxed = await copyRecording(videoPath, recording, probed.ext);
12359
11936
  const viewport = viewportFlag ?? trace?.viewport ?? {
@@ -12361,7 +11938,7 @@ async function cmdIngest(argv) {
12361
11938
  height: probed.height
12362
11939
  };
12363
11940
  const events = trace?.events ?? [];
12364
- const fileStat = await stat4(videoPath);
11941
+ const fileStat = await stat3(videoPath);
12365
11942
  const meta = {
12366
11943
  dpr: 1,
12367
11944
  zoom: 1,
@@ -12382,13 +11959,13 @@ async function cmdIngest(argv) {
12382
11959
  const clicks = events.filter((e) => e.type === "down").length;
12383
11960
  const seconds = (probed.durationMs / 1e3).toFixed(1);
12384
11961
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
12385
- const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename10(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
11962
+ const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename8(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
12386
11963
  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)`;
12387
11964
  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`;
12388
11965
  r.done(
12389
11966
  {
12390
11967
  out: outDir,
12391
- recording: basename10(recording),
11968
+ recording: basename8(recording),
12392
11969
  durationMs: probed.durationMs,
12393
11970
  width: probed.width,
12394
11971
  height: probed.height,
@@ -12397,7 +11974,7 @@ async function cmdIngest(argv) {
12397
11974
  hasAudio: probed.hasAudio,
12398
11975
  remuxed,
12399
11976
  cursor: trace ? {
12400
- file: basename10(cursorPath),
11977
+ file: basename8(cursorPath),
12401
11978
  kind: trace.kind,
12402
11979
  events: events.length,
12403
11980
  clicks,
@@ -12409,7 +11986,7 @@ async function cmdIngest(argv) {
12409
11986
  backdrop: plan.backdrop ?? null,
12410
11987
  as: footage ? "footage" : "take"
12411
11988
  },
12412
- `Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
11989
+ `Ingested ${basename8(videoPath)} \u2192 ${outDir}/${basename8(recording)} (${facts})
12413
11990
  ${opened}
12414
11991
  ${cursorLine}
12415
11992
  ${next}`
@@ -12421,19 +11998,19 @@ async function cmdIngest(argv) {
12421
11998
  import { spawnSync as spawnSync4 } from "child_process";
12422
11999
  import {
12423
12000
  cpSync,
12424
- existsSync as existsSync23,
12001
+ existsSync as existsSync22,
12425
12002
  mkdirSync as mkdirSync6,
12426
12003
  readFileSync as readFileSync10,
12427
12004
  rmSync as rmSync3,
12428
12005
  writeFileSync as writeFileSync6
12429
12006
  } from "fs";
12430
12007
  import { homedir as homedir2 } from "os";
12431
- import { join as join27 } from "path";
12008
+ import { join as join25 } from "path";
12432
12009
  import { chromium as chromium2 } from "playwright";
12433
12010
 
12434
12011
  // src/plugin/machine.ts
12435
- import { existsSync as existsSync22, readFileSync as readFileSync9, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
12436
- import { dirname as dirname9, join as join26 } from "path";
12012
+ import { existsSync as existsSync21, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
12013
+ import { dirname as dirname9, join as join24 } from "path";
12437
12014
  var AGENT_NAMES = [
12438
12015
  "claude",
12439
12016
  "cursor",
@@ -12453,17 +12030,17 @@ var HOME_DIRS = {
12453
12030
  copilot: [".copilot"]
12454
12031
  };
12455
12032
  function detectAgents(root, opts = {}) {
12456
- const exists = opts.exists ?? existsSync22;
12033
+ const exists = opts.exists ?? existsSync21;
12457
12034
  const out = [];
12458
12035
  for (const agent of AGENT_NAMES) {
12459
12036
  for (const rel of PROJECT_DIRS[agent]) {
12460
- const dir = join26(root, rel);
12037
+ const dir = join24(root, rel);
12461
12038
  if (exists(dir)) out.push({ agent, dir, scope: "project" });
12462
12039
  }
12463
- if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join26(root, ".github", "copilot-instructions.md"))) {
12040
+ if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join24(root, ".github", "copilot-instructions.md"))) {
12464
12041
  out.push({
12465
12042
  agent,
12466
- dir: join26(root, ".github", "copilot"),
12043
+ dir: join24(root, ".github", "copilot"),
12467
12044
  scope: "project"
12468
12045
  });
12469
12046
  }
@@ -12471,7 +12048,7 @@ function detectAgents(root, opts = {}) {
12471
12048
  if (opts.global && opts.home) {
12472
12049
  for (const agent of AGENT_NAMES) {
12473
12050
  for (const rel of HOME_DIRS[agent]) {
12474
- const dir = join26(opts.home, rel);
12051
+ const dir = join24(opts.home, rel);
12475
12052
  if (exists(dir)) out.push({ agent, dir, scope: "global" });
12476
12053
  }
12477
12054
  }
@@ -12489,26 +12066,26 @@ function agentsFromFlag(value, root) {
12489
12066
  return {
12490
12067
  homes: wanted.map((agent) => ({
12491
12068
  agent,
12492
- dir: join26(root, PROJECT_DIRS[agent][0]),
12069
+ dir: join24(root, PROJECT_DIRS[agent][0]),
12493
12070
  scope: "project"
12494
12071
  }))
12495
12072
  };
12496
12073
  }
12497
12074
  function skillsDirFor(home) {
12498
- return join26(home.dir, "skills");
12075
+ return join24(home.dir, "skills");
12499
12076
  }
12500
12077
  function fallbackSkillsHome(root) {
12501
- return { agent: "codex", dir: join26(root, ".agents"), scope: "project" };
12078
+ return { agent: "codex", dir: join24(root, ".agents"), scope: "project" };
12502
12079
  }
12503
- function installedSkills(dir, exists = existsSync22, list = (p) => {
12080
+ function installedSkills(dir, exists = existsSync21, list = (p) => {
12504
12081
  try {
12505
- return readdirSync6(p);
12082
+ return readdirSync5(p);
12506
12083
  } catch {
12507
12084
  return [];
12508
12085
  }
12509
12086
  }) {
12510
12087
  if (!exists(dir)) return [];
12511
- return list(dir).filter((name) => exists(join26(dir, name, "SKILL.md"))).sort();
12088
+ return list(dir).filter((name) => exists(join24(dir, name, "SKILL.md"))).sort();
12512
12089
  }
12513
12090
  var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
12514
12091
  var RULES_END = "<!-- vos:end -->";
@@ -12541,10 +12118,10 @@ ${RULES_BLOCK}
12541
12118
  }
12542
12119
  return { text, changed: text !== current };
12543
12120
  }
12544
- function rulesFileFor(root, exists = existsSync22) {
12545
- const agents = join26(root, "AGENTS.md");
12121
+ function rulesFileFor(root, exists = existsSync21) {
12122
+ const agents = join24(root, "AGENTS.md");
12546
12123
  if (exists(agents)) return agents;
12547
- const claude = join26(root, "CLAUDE.md");
12124
+ const claude = join24(root, "CLAUDE.md");
12548
12125
  if (exists(claude)) return claude;
12549
12126
  return agents;
12550
12127
  }
@@ -12553,14 +12130,14 @@ function chromeCandidates(platform, env) {
12553
12130
  return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
12554
12131
  if (platform === "win32")
12555
12132
  return [
12556
- join26(
12133
+ join24(
12557
12134
  env["PROGRAMFILES"] ?? "C:\\Program Files",
12558
12135
  "Google",
12559
12136
  "Chrome",
12560
12137
  "Application",
12561
12138
  "chrome.exe"
12562
12139
  ),
12563
- join26(
12140
+ join24(
12564
12141
  env["LOCALAPPDATA"] ?? "",
12565
12142
  "Google",
12566
12143
  "Chrome",
@@ -12647,13 +12224,13 @@ function doctorReport(facts) {
12647
12224
  function bundledSkillsDir(from) {
12648
12225
  let dir = dirname9(from);
12649
12226
  for (let i = 0; i < 6; i++) {
12650
- const pkg = join26(dir, "package.json");
12651
- if (existsSync22(pkg)) {
12227
+ const pkg = join24(dir, "package.json");
12228
+ if (existsSync21(pkg)) {
12652
12229
  try {
12653
12230
  const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
12654
12231
  if (name === "@vosjs/cli") {
12655
- const skills = join26(dir, "skills");
12656
- return existsSync22(skills) && statSync7(skills).isDirectory() ? skills : null;
12232
+ const skills = join24(dir, "skills");
12233
+ return existsSync21(skills) && statSync7(skills).isDirectory() ? skills : null;
12657
12234
  }
12658
12235
  } catch {
12659
12236
  }
@@ -12684,7 +12261,7 @@ function npx() {
12684
12261
  function bundledChromium() {
12685
12262
  try {
12686
12263
  const p = chromium2.executablePath();
12687
- return p && existsSync23(p) ? p : null;
12264
+ return p && existsSync22(p) ? p : null;
12688
12265
  } catch {
12689
12266
  return null;
12690
12267
  }
@@ -12693,7 +12270,7 @@ function probe2() {
12693
12270
  return probeBrowser({
12694
12271
  platform: process.platform,
12695
12272
  env: process.env,
12696
- exists: existsSync23,
12273
+ exists: existsSync22,
12697
12274
  bundled: bundledChromium
12698
12275
  });
12699
12276
  }
@@ -12797,7 +12374,7 @@ async function cmdSetup(argv) {
12797
12374
  const dest = skillsDirFor(home);
12798
12375
  mkdirSync6(dest, { recursive: true });
12799
12376
  for (const name of installedSkills(bundled)) {
12800
- cpSync(join27(bundled, name), join27(dest, name), { recursive: true });
12377
+ cpSync(join25(bundled, name), join25(dest, name), { recursive: true });
12801
12378
  }
12802
12379
  }
12803
12380
  skillsVia = "bundled";
@@ -12832,7 +12409,7 @@ async function cmdSetup(argv) {
12832
12409
  let rules = null;
12833
12410
  if (!noRules) {
12834
12411
  const file = rulesFileFor(root);
12835
- const existing = existsSync23(file) ? readFileSync10(file, "utf8") : null;
12412
+ const existing = existsSync22(file) ? readFileSync10(file, "utf8") : null;
12836
12413
  const { text, changed } = applyRulesBlock(existing);
12837
12414
  if (changed) writeFileSync6(file, text);
12838
12415
  rules = { file, changed };
@@ -12946,7 +12523,7 @@ async function cmdWhoami(argv) {
12946
12523
  function cmdLogout(argv) {
12947
12524
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
12948
12525
  const r = reporterFor(flags);
12949
- const had = existsSync23(CREDENTIALS_PATH);
12526
+ const had = existsSync22(CREDENTIALS_PATH);
12950
12527
  rmSync3(CREDENTIALS_PATH, { force: true });
12951
12528
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
12952
12529
  r.done(
@@ -13300,7 +12877,7 @@ lint-gated, so a bad override fails like a bad doc.json):
13300
12877
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
13301
12878
  `;
13302
12879
  async function loadActions(file) {
13303
- const raw = JSON.parse(await readFile17(file, "utf8"));
12880
+ const raw = JSON.parse(await readFile15(file, "utf8"));
13304
12881
  const errors = validateActions(raw);
13305
12882
  if (errors.length)
13306
12883
  throw new UsageError(`invalid actions file:
@@ -13363,7 +12940,7 @@ function takeStorageState(flags) {
13363
12940
  const raw = strFlag(flags, "storage-state");
13364
12941
  if (!raw) return void 0;
13365
12942
  const path = resolve15(raw);
13366
- if (!existsSync24(path))
12943
+ if (!existsSync23(path))
13367
12944
  throw new UsageError(`--storage-state: no such file: ${path}`);
13368
12945
  return path;
13369
12946
  }
@@ -13376,7 +12953,7 @@ function takeSessionName(flags, storageState) {
13376
12953
  throw new UsageError(
13377
12954
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
13378
12955
  );
13379
- if (!existsSync24(sessionDir(name)))
12956
+ if (!existsSync23(sessionDir(name)))
13380
12957
  throw new UsageError(
13381
12958
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
13382
12959
  );
@@ -13568,7 +13145,7 @@ async function cmdRecord(argv) {
13568
13145
  const what = s.selector ? ` ${s.selector}` : "";
13569
13146
  return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
13570
13147
  });
13571
- const failed2 = rec.skipped.length > 0 || rec.navTimeout || maskMisses(rec).length > 0;
13148
+ const failed = rec.skipped.length > 0 || rec.navTimeout || maskMisses(rec).length > 0;
13572
13149
  const carried = [
13573
13150
  ...strFlag(flags, "storage-state") ? [`--storage-state ${strFlag(flags, "storage-state")}`] : [],
13574
13151
  ...browserArgs.map((a) => `--browser-arg=${a}`),
@@ -13580,7 +13157,7 @@ async function cmdRecord(argv) {
13580
13157
  r.done(
13581
13158
  {
13582
13159
  dryRun: true,
13583
- ok: !failed2,
13160
+ ok: !failed,
13584
13161
  seconds: +((Date.now() - started) / 1e3).toFixed(1),
13585
13162
  steps,
13586
13163
  skipped: rec.skipped,
@@ -13588,24 +13165,24 @@ async function cmdRecord(argv) {
13588
13165
  exposures: rec.exposures,
13589
13166
  masks: rec.masks
13590
13167
  },
13591
- `${failed2 ? "REHEARSAL FAILED" : "Rehearsal passed"}: ${steps.length - rec.skipped.length}/${steps.length} steps resolved in ${((Date.now() - started) / 1e3).toFixed(1)}s${rec.navTimeout ? " (the first load never reached networkidle)" : ""}
13168
+ `${failed ? "REHEARSAL FAILED" : "Rehearsal passed"}: ${steps.length - rec.skipped.length}/${steps.length} steps resolved in ${((Date.now() - started) / 1e3).toFixed(1)}s${rec.navTimeout ? " (the first load never reached networkidle)" : ""}
13592
13169
  ${lines.join("\n")}
13593
- ${maskMisses(rec).length ? `MASK hid nothing: ${maskMisses(rec).join(", ")} (the selector reached no element). ` : ""}${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${nextOut}${carried ? ` ${carried}` : ""} --strict`}${exposureNote(rec)}`
13170
+ ${maskMisses(rec).length ? `MASK hid nothing: ${maskMisses(rec).join(", ")} (the selector reached no element). ` : ""}${failed ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${nextOut}${carried ? ` ${carried}` : ""} --strict`}${exposureNote(rec)}`
13594
13171
  );
13595
- return failed2 ? EXIT_USAGE : EXIT_OK;
13172
+ return failed ? EXIT_USAGE : EXIT_OK;
13596
13173
  } finally {
13597
13174
  await browser2.close();
13598
13175
  }
13599
13176
  }
13600
13177
  const paths = takePaths(outDir);
13601
13178
  const prepare = async () => {
13602
- if (existsSync24(join28(outDir, "meta.json"))) {
13179
+ if (existsSync23(join26(outDir, "meta.json"))) {
13603
13180
  const { prevDoc, kept } = await prepareReRecord(outDir);
13604
13181
  r.log(
13605
13182
  `note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
13606
13183
  );
13607
13184
  }
13608
- await mkdir11(outDir, { recursive: true });
13185
+ await mkdir9(outDir, { recursive: true });
13609
13186
  await ensureTakeDir(outDir);
13610
13187
  };
13611
13188
  const browser = await launchBrowser(browserArgs);
@@ -13664,7 +13241,7 @@ ${lines.join("\n")}
13664
13241
  },
13665
13242
  strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
13666
13243
  ${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen \xB7 ${rec.dead.pct}% dead${deadWarns(rec.dead) ? " \u26A0 " + deadLine(rec.dead) : ""}${skippedNote}
13667
- Next: edit ${join28(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
13244
+ Next: edit ${join26(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
13668
13245
  );
13669
13246
  return strictFail ? EXIT_USAGE : EXIT_OK;
13670
13247
  } finally {
@@ -13690,7 +13267,7 @@ async function cmdCreate2(argv) {
13690
13267
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
13691
13268
  const outDir = resolve15(strFlag(flags, "out") ?? "take");
13692
13269
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
13693
- const out = positionals[0] ?? join28(outDir, `out.${format}`);
13270
+ const out = positionals[0] ?? join26(outDir, `out.${format}`);
13694
13271
  const parallel = numFlag(flags, "parallel", 1);
13695
13272
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
13696
13273
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -13703,13 +13280,13 @@ async function cmdCreate2(argv) {
13703
13280
  const headers = takeHeaders(multi);
13704
13281
  const paths = takePaths(outDir);
13705
13282
  const prepare = async () => {
13706
- if (existsSync24(join28(outDir, "meta.json"))) {
13283
+ if (existsSync23(join26(outDir, "meta.json"))) {
13707
13284
  const { prevDoc, kept } = await prepareReRecord(outDir);
13708
13285
  r.log(
13709
13286
  `note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
13710
13287
  );
13711
13288
  }
13712
- await mkdir11(outDir, { recursive: true });
13289
+ await mkdir9(outDir, { recursive: true });
13713
13290
  await ensureTakeDir(outDir);
13714
13291
  };
13715
13292
  const browser = await launchBrowser(browserArgs);
@@ -13810,24 +13387,24 @@ async function cmdPlan(argv) {
13810
13387
  );
13811
13388
  const r = createReporter(flags.json === true);
13812
13389
  if (flags.fresh === true) {
13813
- await rm5(join28(dir, "doc.json"), { force: true });
13390
+ await rm3(join26(dir, "doc.json"), { force: true });
13814
13391
  r.log("note: --fresh discarded the existing doc.json");
13815
13392
  }
13816
13393
  let reuse;
13817
13394
  if (flags.reuse === true) {
13818
- const from = resolve15(strFlag(flags, "from") ?? join28(dir, PREV_DOC_NAME));
13819
- if (!existsSync24(from)) {
13395
+ const from = resolve15(strFlag(flags, "from") ?? join26(dir, PREV_DOC_NAME));
13396
+ if (!existsSync23(from)) {
13820
13397
  throw new UsageError(
13821
13398
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
13822
13399
  );
13823
13400
  }
13824
13401
  reuse = {
13825
13402
  from,
13826
- doc: JSON.parse(await readFile17(from, "utf8"))
13403
+ doc: JSON.parse(await readFile15(from, "utf8"))
13827
13404
  };
13828
13405
  }
13829
13406
  const style = await resolveStyleRef(flags);
13830
- const hasDoc = existsSync24(join28(dir, "doc.json"));
13407
+ const hasDoc = existsSync23(join26(dir, "doc.json"));
13831
13408
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
13832
13409
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
13833
13410
  const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
@@ -13885,7 +13462,7 @@ async function cmdPlan(argv) {
13885
13462
  ...s.motion ? { motion: s.motion } : {},
13886
13463
  ...s.reuse ? { reuse: s.reuse } : {}
13887
13464
  },
13888
- `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join28(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
13465
+ `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join26(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
13889
13466
  );
13890
13467
  return EXIT_OK;
13891
13468
  }
@@ -14008,7 +13585,7 @@ async function cmdRender(argv) {
14008
13585
  );
14009
13586
  const r = createReporter(flags.json === true);
14010
13587
  const format = videoFormat(strFlag(flags, "format"), positionals[1]);
14011
- const out = positionals[1] ?? join28(dir, `out.${format}`);
13588
+ const out = positionals[1] ?? join26(dir, `out.${format}`);
14012
13589
  const parallel = numFlag(flags, "parallel", 1);
14013
13590
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
14014
13591
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -14255,15 +13832,15 @@ async function resolveStyleRef(flags) {
14255
13832
  }
14256
13833
  async function resolveDocRef(ref, flags, what) {
14257
13834
  const styleRef = ref;
14258
- const file = existsSync24(styleRef) ? resolve15(
13835
+ const file = existsSync23(styleRef) ? resolve15(
14259
13836
  styleRef,
14260
- existsSync24(join28(styleRef, "doc.json")) ? "doc.json" : ""
13837
+ existsSync23(join26(styleRef, "doc.json")) ? "doc.json" : ""
14261
13838
  ) : null;
14262
- if (file && existsSync24(file)) {
13839
+ if (file && existsSync23(file)) {
14263
13840
  return {
14264
13841
  from: file,
14265
13842
  doc: migrateHostedDoc5(
14266
- JSON.parse(await readFile17(file, "utf8"))
13843
+ JSON.parse(await readFile15(file, "utf8"))
14267
13844
  )
14268
13845
  };
14269
13846
  }
@@ -14315,7 +13892,7 @@ async function cmdDigest(argv) {
14315
13892
  const take = await loadTake(dir);
14316
13893
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
14317
13894
  const transcriptPath = strFlag(flags, "transcript");
14318
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile17(transcriptPath, "utf8"))) : null;
13895
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile15(transcriptPath, "utf8"))) : null;
14319
13896
  const style = await resolveStyleRef(flags);
14320
13897
  const noFrames = flags["no-frames"] === true;
14321
13898
  let browser = null;
@@ -14346,7 +13923,7 @@ async function cmdDigest(argv) {
14346
13923
  kinds,
14347
13924
  frames: d.images.full,
14348
13925
  crops: d.images.crop,
14349
- sheet: d.images.sheet ? join28(result.outDir, d.images.sheet) : null,
13926
+ sheet: d.images.sheet ? join26(result.outDir, d.images.sheet) : null,
14350
13927
  scenes: kinds.scene ?? 0,
14351
13928
  sourceDuration: d.take.sourceDuration,
14352
13929
  outputDuration: d.take.outputDuration,
@@ -14374,7 +13951,7 @@ async function cmdOpen(argv) {
14374
13951
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
14375
13952
  const r = createReporter(flags.json === true);
14376
13953
  const take = await loadTake(dir);
14377
- if (!existsSync24(take.paths.recording)) {
13954
+ if (!existsSync23(take.paths.recording)) {
14378
13955
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
14379
13956
  }
14380
13957
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
@@ -14415,12 +13992,12 @@ async function cmdJudge(argv) {
14415
13992
  "vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
14416
13993
  );
14417
13994
  const r = createReporter(flags.json === true);
14418
- const kitFile = target.endsWith("kit.json") ? target : join28(target, "kit.json");
14419
- if (!existsSync24(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
14420
- if (!existsSync24(against))
13995
+ const kitFile = target.endsWith("kit.json") ? target : join26(target, "kit.json");
13996
+ if (!existsSync23(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
13997
+ if (!existsSync23(against))
14421
13998
  throw new UsageError(`${against}: no reference manifest`);
14422
13999
  const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
14423
- const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
14000
+ const verdicts = JSON.parse(await readFile15(result.verdictFile, "utf8")).verdicts;
14424
14001
  const rate = winRate(verdicts);
14425
14002
  const lines = result.sheets.map(
14426
14003
  (s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
@@ -14460,7 +14037,7 @@ async function cmdCallout(argv) {
14460
14037
  `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`
14461
14038
  );
14462
14039
  const codeFile = strFlag(flags, "code-file");
14463
- const code = codeFile ? await readFile17(codeFile, "utf8") : strFlag(flags, "code");
14040
+ const code = codeFile ? await readFile15(codeFile, "utf8") : strFlag(flags, "code");
14464
14041
  const words2 = {
14465
14042
  kicker: strFlag(flags, "kicker"),
14466
14043
  title: strFlag(flags, "title"),
@@ -14539,7 +14116,7 @@ async function cmdValidate(argv) {
14539
14116
  const target = positionals[0];
14540
14117
  if (!target) throw new UsageError("vos validate <actions.json|take>");
14541
14118
  const r = createReporter(flags.json === true);
14542
- const kitTarget = target.endsWith("kit.json") ? target : existsSync24(join28(target, "kit.json")) && !isTakeDir(target) ? join28(target, "kit.json") : null;
14119
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync23(join26(target, "kit.json")) && !isTakeDir(target) ? join26(target, "kit.json") : null;
14543
14120
  if (kitTarget) {
14544
14121
  const picture = flags.picture === true;
14545
14122
  const verdict = await validateKit(kitTarget, { picture });
@@ -14571,9 +14148,9 @@ async function cmdValidate(argv) {
14571
14148
  r.done({ valid: true, target }, `${target}: valid actions file`);
14572
14149
  return EXIT_OK;
14573
14150
  }
14574
- if (!isTakeDir(target) && existsSync24(join28(target, "config.json"))) {
14151
+ if (!isTakeDir(target) && existsSync23(join26(target, "config.json"))) {
14575
14152
  const parsed = JSON.parse(
14576
- await readFile17(join28(target, "config.json"), "utf8")
14153
+ await readFile15(join26(target, "config.json"), "utf8")
14577
14154
  );
14578
14155
  const pre = preflightConfig(parsed);
14579
14156
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -14614,7 +14191,7 @@ async function cmdValidate(argv) {
14614
14191
  const take = await loadTake(target);
14615
14192
  const problems = [];
14616
14193
  const warnings = [];
14617
- if (!existsSync24(take.paths.recording)) {
14194
+ if (!existsSync23(take.paths.recording)) {
14618
14195
  problems.push("missing recording (re-run record)");
14619
14196
  } else {
14620
14197
  const mismatch = containerMismatch(
@@ -14966,7 +14543,7 @@ async function cmdSession(argv) {
14966
14543
  if (!arg) throw new UsageError(usage);
14967
14544
  const bad = sessionNameVerdict(arg);
14968
14545
  if (bad) throw new UsageError(bad);
14969
- if (!existsSync24(sessionDir(arg)))
14546
+ if (!existsSync23(sessionDir(arg)))
14970
14547
  throw new UsageError(`no session "${arg}" (vos session list)`);
14971
14548
  const summary3 = await summarizeSession(arg);
14972
14549
  const url = strFlag(flags, "url");
@@ -15047,10 +14624,6 @@ async function cmdSession(argv) {
15047
14624
 
15048
14625
  export {
15049
14626
  validateActions,
15050
- RECORDING_NAME,
15051
- takePaths,
15052
- loadTake,
15053
- pullMedia,
15054
14627
  startTakeServer,
15055
14628
  waitForPageDone,
15056
14629
  renderAnimation,
@@ -15070,4 +14643,4 @@ export {
15070
14643
  verbHelp,
15071
14644
  run
15072
14645
  };
15073
- //# sourceMappingURL=chunk-67L7FPJR.js.map
14646
+ //# sourceMappingURL=chunk-NE5MHC3D.js.map