@vosjs/cli 0.51.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/{chunk-GCWE75OA.js → chunk-3OIYR5VL.js} +27 -2
- package/dist/chunk-3OIYR5VL.js.map +1 -0
- package/dist/{chunk-JVINOGJG.js → chunk-F2QCWV4U.js} +676 -763
- package/dist/chunk-F2QCWV4U.js.map +1 -0
- package/dist/chunk-KSEU3YK5.js +776 -0
- package/dist/chunk-KSEU3YK5.js.map +1 -0
- package/dist/cli.js +175 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -12
- package/dist/{run-AV3X2454.js → run-ASYY2YTV.js} +3 -3
- package/package.json +4 -4
- package/skills/VERSION +1 -1
- package/skills/product-video/SKILL.md +6 -1
- package/skills/vos-footage/SKILL.md +7 -5
- package/skills/vos-migrate/SKILL.md +34 -38
- package/dist/chunk-FACYY7VV.js +0 -308
- package/dist/chunk-FACYY7VV.js.map +0 -1
- package/dist/chunk-GCWE75OA.js.map +0 -1
- package/dist/chunk-JVINOGJG.js.map +0 -1
- /package/dist/{run-AV3X2454.js.map → run-ASYY2YTV.js.map} +0 -0
|
@@ -1,18 +1,93 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BrowserUnavailableError,
|
|
4
|
+
CREDENTIALS_PATH,
|
|
5
|
+
MEDIA_HEAD_BYTES,
|
|
6
|
+
SYNC_STATE_NAME,
|
|
7
|
+
TAKE_MEDIA_EXTENSIONS,
|
|
8
|
+
UsageError,
|
|
9
|
+
apiError,
|
|
10
|
+
apiJson,
|
|
11
|
+
clientId,
|
|
12
|
+
containerMismatch,
|
|
13
|
+
deriveSlug,
|
|
4
14
|
directoryKind,
|
|
15
|
+
extensionFor,
|
|
16
|
+
formatChanges,
|
|
17
|
+
hasFlag,
|
|
5
18
|
launchBrowser,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
nameForType,
|
|
20
|
+
numFlag2 as numFlag,
|
|
21
|
+
parseArgs2 as parseArgs,
|
|
22
|
+
parseVosId,
|
|
23
|
+
platformOrigin,
|
|
24
|
+
readSyncState,
|
|
25
|
+
requireCredential,
|
|
26
|
+
resolveCredential,
|
|
27
|
+
resolveMediaType,
|
|
28
|
+
sniffMediaType,
|
|
29
|
+
strFlag,
|
|
30
|
+
videoFormat,
|
|
31
|
+
writeCredential,
|
|
32
|
+
writeSyncState
|
|
33
|
+
} from "./chunk-KSEU3YK5.js";
|
|
9
34
|
import {
|
|
10
35
|
deadLine,
|
|
11
36
|
deadWarns
|
|
12
37
|
} from "./chunk-6XPFWQKP.js";
|
|
13
38
|
|
|
39
|
+
// src/plugin/landed.ts
|
|
40
|
+
var absolute = (origin, url) => typeof url === "string" && url ? /^https?:/.test(url) ? url : `${origin}${url.startsWith("/") ? "" : "/"}${url}` : null;
|
|
41
|
+
async function waitForLanded(opts) {
|
|
42
|
+
const origin = opts.origin.replace(/\/+$/, "");
|
|
43
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
44
|
+
const now = opts.now ?? (() => Date.now());
|
|
45
|
+
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
46
|
+
const intervalMs = opts.intervalMs ?? 4e3;
|
|
47
|
+
const started = now();
|
|
48
|
+
let still = false;
|
|
49
|
+
if (opts.versionId) {
|
|
50
|
+
opts.log?.("waiting for the still to render\u2026");
|
|
51
|
+
for (; ; ) {
|
|
52
|
+
const res = await fetch(
|
|
53
|
+
`${origin}/api/vos/${opts.vosId}/versions/${opts.versionId}/thumbnail`,
|
|
54
|
+
{ headers: { authorization: `Bearer ${opts.key}` } }
|
|
55
|
+
);
|
|
56
|
+
await res.arrayBuffer().catch(() => void 0);
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
still = true;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (now() - started >= timeoutMs) break;
|
|
62
|
+
await sleep(intervalMs);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const meta = await apiJson(origin, `/api/vos/${opts.vosId}`, {
|
|
66
|
+
key: opts.key
|
|
67
|
+
});
|
|
68
|
+
const vos = meta.body.vos ?? {};
|
|
69
|
+
const urls = vos.contentUrls ?? {};
|
|
70
|
+
return {
|
|
71
|
+
still,
|
|
72
|
+
watchUrl: `${origin}/vos/${opts.vosId}`,
|
|
73
|
+
studioUrl: `${origin}/studio?vos=${opts.vosId}`,
|
|
74
|
+
thumbnailUrl: still ? absolute(origin, urls.thumbnail) : null,
|
|
75
|
+
previewUrl: still ? absolute(origin, urls.preview) : null,
|
|
76
|
+
waitedMs: now() - started
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function landedLines(l) {
|
|
80
|
+
if (!l.still) {
|
|
81
|
+
return ` still: not rendered yet (the fleet renders it after the push; open the watch page in a minute)`;
|
|
82
|
+
}
|
|
83
|
+
return [
|
|
84
|
+
l.thumbnailUrl ? ` still: ${l.thumbnailUrl}` : null,
|
|
85
|
+
l.previewUrl ? ` preview: ${l.previewUrl}` : null
|
|
86
|
+
].filter(Boolean).join("\n");
|
|
87
|
+
}
|
|
88
|
+
|
|
14
89
|
// src/plugin/run.ts
|
|
15
|
-
import { mkdir as mkdir11, readFile as
|
|
90
|
+
import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
|
|
16
91
|
import { existsSync as existsSync20 } from "fs";
|
|
17
92
|
import { join as join22, resolve as resolve10 } from "path";
|
|
18
93
|
import {
|
|
@@ -21,63 +96,6 @@ import {
|
|
|
21
96
|
migrateHostedDoc as migrateHostedDoc5
|
|
22
97
|
} from "@vosjs/studio-core";
|
|
23
98
|
|
|
24
|
-
// src/plugin/args.ts
|
|
25
|
-
var UsageError = class extends Error {
|
|
26
|
-
};
|
|
27
|
-
function parseArgs(argv, booleanFlags, multiFlags = /* @__PURE__ */ new Set()) {
|
|
28
|
-
const positionals = [];
|
|
29
|
-
const flags = {};
|
|
30
|
-
const multi = {};
|
|
31
|
-
const put = (name, value) => {
|
|
32
|
-
if (multiFlags.has(name)) (multi[name] ??= []).push(value);
|
|
33
|
-
else flags[name] = value;
|
|
34
|
-
};
|
|
35
|
-
for (let i = 0; i < argv.length; i++) {
|
|
36
|
-
const arg = argv[i];
|
|
37
|
-
if (arg === "--") {
|
|
38
|
-
positionals.push(...argv.slice(i + 1));
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
if (arg.startsWith("--")) {
|
|
42
|
-
const eq = arg.indexOf("=");
|
|
43
|
-
if (eq !== -1) {
|
|
44
|
-
put(arg.slice(2, eq), arg.slice(eq + 1));
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const name = arg.slice(2);
|
|
48
|
-
if (booleanFlags.has(name)) {
|
|
49
|
-
flags[name] = true;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (i + 1 >= argv.length || argv[i + 1].startsWith("--")) {
|
|
53
|
-
throw new UsageError(`--${name} expects a value`);
|
|
54
|
-
}
|
|
55
|
-
put(name, argv[i + 1]);
|
|
56
|
-
i++;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
positionals.push(arg);
|
|
60
|
-
}
|
|
61
|
-
for (const name of Object.keys(flags)) noteGivenFlag(name);
|
|
62
|
-
for (const name of Object.keys(multi)) noteGivenFlag(name);
|
|
63
|
-
return { positionals, flags: trackFlags(flags), multi: trackFlags(multi) };
|
|
64
|
-
}
|
|
65
|
-
function numFlag(flags, name, fallback) {
|
|
66
|
-
const v = flags[name];
|
|
67
|
-
if (typeof v !== "string") return fallback;
|
|
68
|
-
const n = Number(v);
|
|
69
|
-
if (!Number.isFinite(n))
|
|
70
|
-
throw new UsageError(`--${name} expects a number, got "${v}"`);
|
|
71
|
-
return n;
|
|
72
|
-
}
|
|
73
|
-
function strFlag(flags, name) {
|
|
74
|
-
const v = flags[name];
|
|
75
|
-
return typeof v === "string" ? v : void 0;
|
|
76
|
-
}
|
|
77
|
-
function hasFlag(flags, name) {
|
|
78
|
-
return Object.prototype.hasOwnProperty.call(flags, name);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
99
|
// src/plugin/output.ts
|
|
82
100
|
var EXIT_OK = 0;
|
|
83
101
|
var EXIT_ERROR = 1;
|
|
@@ -1634,111 +1652,14 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
|
|
|
1634
1652
|
}
|
|
1635
1653
|
}
|
|
1636
1654
|
|
|
1637
|
-
// src/plugin/container.ts
|
|
1638
|
-
var OCTET_STREAM = "application/octet-stream";
|
|
1639
|
-
var TYPES_BY_EXTENSION = {
|
|
1640
|
-
svg: "image/svg+xml",
|
|
1641
|
-
png: "image/png",
|
|
1642
|
-
jpg: "image/jpeg",
|
|
1643
|
-
jpeg: "image/jpeg",
|
|
1644
|
-
webp: "image/webp",
|
|
1645
|
-
gif: "image/gif",
|
|
1646
|
-
avif: "image/avif",
|
|
1647
|
-
webm: "video/webm",
|
|
1648
|
-
mp4: "video/mp4",
|
|
1649
|
-
mov: "video/quicktime",
|
|
1650
|
-
mp3: "audio/mpeg",
|
|
1651
|
-
wav: "audio/wav",
|
|
1652
|
-
ogg: "audio/ogg",
|
|
1653
|
-
m4a: "audio/mp4"
|
|
1654
|
-
};
|
|
1655
|
-
var EXTENSION_BY_TYPE = {
|
|
1656
|
-
"image/jpeg": ".jpg",
|
|
1657
|
-
"video/mp4": ".mp4",
|
|
1658
|
-
"audio/mp4": ".m4a"
|
|
1659
|
-
};
|
|
1660
|
-
var TAKE_MEDIA_EXTENSIONS = [
|
|
1661
|
-
".webm",
|
|
1662
|
-
".mp4",
|
|
1663
|
-
".mov",
|
|
1664
|
-
".m4a",
|
|
1665
|
-
".mp3",
|
|
1666
|
-
".ogg",
|
|
1667
|
-
".wav"
|
|
1668
|
-
];
|
|
1669
|
-
var MEDIA_HEAD_BYTES = 16;
|
|
1670
|
-
var ISO_BRANDS = {
|
|
1671
|
-
"qt ": "video/quicktime",
|
|
1672
|
-
"M4A ": "audio/mp4",
|
|
1673
|
-
"M4B ": "audio/mp4",
|
|
1674
|
-
avif: "image/avif",
|
|
1675
|
-
avis: "image/avif"
|
|
1676
|
-
};
|
|
1677
|
-
var RIFF_FORMS = {
|
|
1678
|
-
WEBP: "image/webp",
|
|
1679
|
-
WAVE: "audio/wav",
|
|
1680
|
-
"AVI ": "video/x-msvideo"
|
|
1681
|
-
};
|
|
1682
|
-
function sniffMediaType(head) {
|
|
1683
|
-
const ascii = (at2, len) => head.length >= at2 + len ? String.fromCharCode(...head.subarray(at2, at2 + len)) : "";
|
|
1684
|
-
const startsWith = (...bytes) => bytes.every((b, i) => head[i] === b);
|
|
1685
|
-
if (startsWith(26, 69, 223, 163)) return "video/webm";
|
|
1686
|
-
if (ascii(4, 4) === "ftyp") return ISO_BRANDS[ascii(8, 4)] ?? "video/mp4";
|
|
1687
|
-
if (ascii(0, 4) === "RIFF") return RIFF_FORMS[ascii(8, 4)] ?? null;
|
|
1688
|
-
if (startsWith(137, 80, 78, 71, 13, 10, 26, 10))
|
|
1689
|
-
return "image/png";
|
|
1690
|
-
if (startsWith(255, 216, 255)) return "image/jpeg";
|
|
1691
|
-
if (ascii(0, 4) === "GIF8") return "image/gif";
|
|
1692
|
-
if (ascii(0, 4) === "OggS") return "audio/ogg";
|
|
1693
|
-
if (ascii(0, 3) === "ID3") return "audio/mpeg";
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
1696
|
-
function bareType(value) {
|
|
1697
|
-
const type = (value ?? "").split(";")[0].trim().toLowerCase();
|
|
1698
|
-
return !type || type === OCTET_STREAM || type === "binary/octet-stream" ? "" : type;
|
|
1699
|
-
}
|
|
1700
|
-
function mediaContentType(file) {
|
|
1701
|
-
const ext = (/\.([a-z0-9]+)$/i.exec(file)?.[1] ?? "").toLowerCase();
|
|
1702
|
-
return TYPES_BY_EXTENSION[ext] ?? OCTET_STREAM;
|
|
1703
|
-
}
|
|
1704
|
-
function extensionFor(contentType) {
|
|
1705
|
-
const type = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
1706
|
-
if (EXTENSION_BY_TYPE[type]) return EXTENSION_BY_TYPE[type];
|
|
1707
|
-
const ext = Object.keys(TYPES_BY_EXTENSION).find(
|
|
1708
|
-
(e) => TYPES_BY_EXTENSION[e] === type
|
|
1709
|
-
);
|
|
1710
|
-
return ext ? `.${ext}` : "";
|
|
1711
|
-
}
|
|
1712
|
-
function resolveMediaType(claims) {
|
|
1713
|
-
const sniffed = claims.head?.length ? sniffMediaType(claims.head) : null;
|
|
1714
|
-
if (sniffed) return { type: sniffed, from: "bytes" };
|
|
1715
|
-
const declared = bareType(claims.declared);
|
|
1716
|
-
if (declared) return { type: declared, from: "declared" };
|
|
1717
|
-
const named = mediaContentType(claims.filename ?? "");
|
|
1718
|
-
if (named !== OCTET_STREAM) return { type: named, from: "filename" };
|
|
1719
|
-
return { type: OCTET_STREAM, from: "unknown" };
|
|
1720
|
-
}
|
|
1721
|
-
function nameForType(filename, type) {
|
|
1722
|
-
const ext = extensionFor(type);
|
|
1723
|
-
if (!ext) return filename;
|
|
1724
|
-
const current = /\.[a-z0-9]+$/i.exec(filename)?.[0] ?? "";
|
|
1725
|
-
if (current && mediaContentType(`x${current}`) === type) return filename;
|
|
1726
|
-
return `${filename.slice(0, filename.length - current.length)}${ext}`;
|
|
1727
|
-
}
|
|
1728
|
-
function containerMismatch(filename, head) {
|
|
1729
|
-
const actual = sniffMediaType(head);
|
|
1730
|
-
if (!actual) return null;
|
|
1731
|
-
const claimed = mediaContentType(filename);
|
|
1732
|
-
if (claimed === actual) return null;
|
|
1733
|
-
return `${filename} is ${actual}, not the ${claimed} its name claims \u2014 rename it to ${nameForType(filename, actual)}`;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
1655
|
// src/plugin/media.ts
|
|
1737
1656
|
import { isKeyedOverlay } from "@vosjs/studio-core";
|
|
1657
|
+
import { createHash } from "crypto";
|
|
1738
1658
|
import { createWriteStream, existsSync as existsSync2 } from "fs";
|
|
1739
1659
|
import {
|
|
1740
1660
|
mkdir as mkdir2,
|
|
1741
1661
|
open,
|
|
1662
|
+
readFile as readFile2,
|
|
1742
1663
|
readdir,
|
|
1743
1664
|
rename as rename2,
|
|
1744
1665
|
rm as rm2,
|
|
@@ -1847,6 +1768,150 @@ async function loadTake(dir) {
|
|
|
1847
1768
|
return { paths, cursor, meta, actions, doc };
|
|
1848
1769
|
}
|
|
1849
1770
|
|
|
1771
|
+
// src/plugin/uploadAsset.ts
|
|
1772
|
+
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
1773
|
+
var PART_ATTEMPTS = 3;
|
|
1774
|
+
function planParts(totalBytes, partBytes) {
|
|
1775
|
+
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
1776
|
+
const parts = [];
|
|
1777
|
+
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
1778
|
+
parts.push({
|
|
1779
|
+
partNumber: parts.length + 1,
|
|
1780
|
+
start,
|
|
1781
|
+
end: Math.min(start + partBytes, totalBytes)
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
return parts;
|
|
1785
|
+
}
|
|
1786
|
+
function failed(what, r) {
|
|
1787
|
+
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
1788
|
+
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
1789
|
+
}
|
|
1790
|
+
function asAsset(body) {
|
|
1791
|
+
return {
|
|
1792
|
+
id: String(body.id),
|
|
1793
|
+
url: String(body.url),
|
|
1794
|
+
size: typeof body.size === "number" ? body.size : 0,
|
|
1795
|
+
reused: body.reused === true
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
function transient(status) {
|
|
1799
|
+
return status >= 500 || status === 429;
|
|
1800
|
+
}
|
|
1801
|
+
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1802
|
+
async function uploadAsset(target, bytes, opts) {
|
|
1803
|
+
if (bytes.length > SINGLE_SHOT_MAX_BYTES && opts.folderId) {
|
|
1804
|
+
throw new Error(
|
|
1805
|
+
`${opts.filename} is over ${SINGLE_SHOT_MAX_BYTES / 1024 / 1024} MB, and a file that large cannot be filed into a project on upload yet. Push it without --folder and reference it from a document, which keeps it`
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
1809
|
+
}
|
|
1810
|
+
async function uploadWhole(target, bytes, opts) {
|
|
1811
|
+
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
1812
|
+
method: "POST",
|
|
1813
|
+
key: target.key,
|
|
1814
|
+
headers: {
|
|
1815
|
+
"Content-Type": opts.contentType,
|
|
1816
|
+
"Content-Length": String(bytes.length),
|
|
1817
|
+
"X-Filename": opts.filename,
|
|
1818
|
+
"X-Content-Hash": opts.contentHash,
|
|
1819
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {},
|
|
1820
|
+
...opts.folderId ? { "X-Folder-Id": opts.folderId } : {}
|
|
1821
|
+
},
|
|
1822
|
+
raw: bytes
|
|
1823
|
+
});
|
|
1824
|
+
if (res.status !== 201 && res.status !== 200)
|
|
1825
|
+
throw failed("upload failed", res);
|
|
1826
|
+
return asAsset(res.body);
|
|
1827
|
+
}
|
|
1828
|
+
async function uploadInParts(target, bytes, opts) {
|
|
1829
|
+
const begun = await apiJson(
|
|
1830
|
+
target.origin,
|
|
1831
|
+
"/api/assets/recording/multipart",
|
|
1832
|
+
{
|
|
1833
|
+
method: "POST",
|
|
1834
|
+
key: target.key,
|
|
1835
|
+
body: {
|
|
1836
|
+
filename: opts.filename,
|
|
1837
|
+
contentType: opts.contentType,
|
|
1838
|
+
size: bytes.length,
|
|
1839
|
+
contentHash: opts.contentHash,
|
|
1840
|
+
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
);
|
|
1844
|
+
if (begun.status !== 201 && begun.status !== 200) {
|
|
1845
|
+
throw failed("upload failed", begun);
|
|
1846
|
+
}
|
|
1847
|
+
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
1848
|
+
return asAsset(begun.body);
|
|
1849
|
+
}
|
|
1850
|
+
const uploadId = String(begun.body.id);
|
|
1851
|
+
const partBytes = Number(begun.body.partBytes);
|
|
1852
|
+
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
1853
|
+
throw new Error("upload failed: the platform returned no part size");
|
|
1854
|
+
}
|
|
1855
|
+
const parts = planParts(bytes.length, partBytes);
|
|
1856
|
+
const etags = [];
|
|
1857
|
+
try {
|
|
1858
|
+
for (const part of parts) {
|
|
1859
|
+
const chunk2 = bytes.subarray(part.start, part.end);
|
|
1860
|
+
let sent = null;
|
|
1861
|
+
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
1862
|
+
try {
|
|
1863
|
+
sent = await apiJson(
|
|
1864
|
+
target.origin,
|
|
1865
|
+
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
1866
|
+
{
|
|
1867
|
+
method: "PUT",
|
|
1868
|
+
key: target.key,
|
|
1869
|
+
headers: {
|
|
1870
|
+
"Content-Type": "application/octet-stream",
|
|
1871
|
+
"Content-Length": String(chunk2.length)
|
|
1872
|
+
},
|
|
1873
|
+
raw: chunk2
|
|
1874
|
+
}
|
|
1875
|
+
);
|
|
1876
|
+
} catch (networkError) {
|
|
1877
|
+
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
1878
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (sent.status === 200) break;
|
|
1882
|
+
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
1883
|
+
throw failed(`part ${part.partNumber} failed`, sent);
|
|
1884
|
+
}
|
|
1885
|
+
await wait(500 * 2 ** (attempt - 1));
|
|
1886
|
+
}
|
|
1887
|
+
if (!sent || sent.status !== 200) {
|
|
1888
|
+
throw new Error(`part ${part.partNumber} failed`);
|
|
1889
|
+
}
|
|
1890
|
+
etags.push({
|
|
1891
|
+
partNumber: Number(sent.body.partNumber),
|
|
1892
|
+
etag: String(sent.body.etag)
|
|
1893
|
+
});
|
|
1894
|
+
opts.onPart?.(etags.length, parts.length);
|
|
1895
|
+
}
|
|
1896
|
+
const sealed = await apiJson(
|
|
1897
|
+
target.origin,
|
|
1898
|
+
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
1899
|
+
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
1900
|
+
);
|
|
1901
|
+
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
1902
|
+
throw failed("upload failed", sealed);
|
|
1903
|
+
}
|
|
1904
|
+
return asAsset(sealed.body);
|
|
1905
|
+
} catch (err) {
|
|
1906
|
+
await apiJson(
|
|
1907
|
+
target.origin,
|
|
1908
|
+
`/api/assets/recording/multipart/${uploadId}`,
|
|
1909
|
+
{ method: "DELETE", key: target.key }
|
|
1910
|
+
).catch(() => void 0);
|
|
1911
|
+
throw err;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1850
1915
|
// src/plugin/media.ts
|
|
1851
1916
|
var SOURCE_MEDIA = [
|
|
1852
1917
|
{ key: "videoKey", stem: "recording", fallbackExt: ".webm" },
|
|
@@ -1915,8 +1980,58 @@ function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
|
1915
1980
|
bg.key = next;
|
|
1916
1981
|
}
|
|
1917
1982
|
});
|
|
1983
|
+
out.push(...docAudioRefs(doc, keep));
|
|
1918
1984
|
return out;
|
|
1919
1985
|
}
|
|
1986
|
+
function docAudioRefs(doc, keep = isTakeRelativeKey) {
|
|
1987
|
+
const out = [];
|
|
1988
|
+
for (const clip of doc.audio ?? []) {
|
|
1989
|
+
if (!keep(clip.key)) continue;
|
|
1990
|
+
out.push({
|
|
1991
|
+
where: `audio ${clip.id}`,
|
|
1992
|
+
key: clip.key,
|
|
1993
|
+
set: (next) => {
|
|
1994
|
+
clip.key = next;
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
return out;
|
|
1999
|
+
}
|
|
2000
|
+
async function uploadDocRefs(refs, dir, target, log) {
|
|
2001
|
+
for (const ref of refs) {
|
|
2002
|
+
const file = join2(dir, takeRelativeFile(ref.key));
|
|
2003
|
+
if (!existsSync2(file)) {
|
|
2004
|
+
log(` ${ref.where}: ${ref.key} is not beside the document \u2014 left as is`);
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
const media = await readFile2(file);
|
|
2008
|
+
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
2009
|
+
const { type } = resolveMediaType({
|
|
2010
|
+
head: media.subarray(0, MEDIA_HEAD_BYTES),
|
|
2011
|
+
filename: file
|
|
2012
|
+
});
|
|
2013
|
+
const name = nameForType(basename2(file), type);
|
|
2014
|
+
if (name !== basename2(file)) {
|
|
2015
|
+
log(` ${ref.where}: ${basename2(file)} holds ${type} \u2014 sent as ${name}`);
|
|
2016
|
+
}
|
|
2017
|
+
let put;
|
|
2018
|
+
try {
|
|
2019
|
+
put = await uploadAsset(target, new Uint8Array(media), {
|
|
2020
|
+
filename: name,
|
|
2021
|
+
contentType: type,
|
|
2022
|
+
contentHash: mediaHash
|
|
2023
|
+
});
|
|
2024
|
+
} catch (e) {
|
|
2025
|
+
throw new Error(
|
|
2026
|
+
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
ref.set(put.url);
|
|
2030
|
+
log(
|
|
2031
|
+
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
1920
2035
|
function assetIdOf(url) {
|
|
1921
2036
|
if (!url) return null;
|
|
1922
2037
|
const m = /\/api\/assets\/([A-Za-z0-9_-]+)\/file/.exec(url);
|
|
@@ -2105,7 +2220,7 @@ function mediaProbeLints(results) {
|
|
|
2105
2220
|
}
|
|
2106
2221
|
|
|
2107
2222
|
// src/plugin/framesTake.ts
|
|
2108
|
-
import { mkdir as mkdir4, readFile as
|
|
2223
|
+
import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
2109
2224
|
import { existsSync as existsSync5 } from "fs";
|
|
2110
2225
|
import { join as join5, resolve as resolve2 } from "path";
|
|
2111
2226
|
import { compileVosConfig } from "@vosjs/core";
|
|
@@ -2246,7 +2361,7 @@ async function waitForPageDone(page, label, onProgress, timeoutMs) {
|
|
|
2246
2361
|
}
|
|
2247
2362
|
|
|
2248
2363
|
// src/plugin/renderAnimation.ts
|
|
2249
|
-
import { readFile as
|
|
2364
|
+
import { readFile as readFile3, rm as rm3 } from "fs/promises";
|
|
2250
2365
|
import { tweenRuntimeCode } from "@vosjs/tween/bundle";
|
|
2251
2366
|
import { elementsBundleCode } from "@vosjs/elements/bundle";
|
|
2252
2367
|
import { concatEncodedVideo, planChunks } from "@vosjs/render-core";
|
|
@@ -2541,7 +2656,7 @@ async function renderAnimation(browser, opts) {
|
|
|
2541
2656
|
)
|
|
2542
2657
|
);
|
|
2543
2658
|
const files = await Promise.all(
|
|
2544
|
-
outNames.map((name) =>
|
|
2659
|
+
outNames.map((name) => readFile3(`${opts.workDir}/${name}`))
|
|
2545
2660
|
);
|
|
2546
2661
|
await Promise.all(
|
|
2547
2662
|
outNames.map((name) => rm3(`${opts.workDir}/${name}`, { force: true }))
|
|
@@ -2799,7 +2914,7 @@ function applyAndValidate(doc, o) {
|
|
|
2799
2914
|
}
|
|
2800
2915
|
|
|
2801
2916
|
// src/plugin/digestTake.ts
|
|
2802
|
-
import { mkdir as mkdir3, readFile as
|
|
2917
|
+
import { mkdir as mkdir3, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
|
|
2803
2918
|
import { existsSync as existsSync4 } from "fs";
|
|
2804
2919
|
import { join as join4, resolve } from "path";
|
|
2805
2920
|
import {
|
|
@@ -3199,7 +3314,7 @@ async function discoverTranscript(dir) {
|
|
|
3199
3314
|
const file = join4(dir, "transcript.json");
|
|
3200
3315
|
if (!existsSync4(file)) return null;
|
|
3201
3316
|
try {
|
|
3202
|
-
return parseTranscript(JSON.parse(await
|
|
3317
|
+
return parseTranscript(JSON.parse(await readFile4(file, "utf8")));
|
|
3203
3318
|
} catch {
|
|
3204
3319
|
return null;
|
|
3205
3320
|
}
|
|
@@ -3207,7 +3322,7 @@ async function discoverTranscript(dir) {
|
|
|
3207
3322
|
async function readPriorDigest(outDir) {
|
|
3208
3323
|
try {
|
|
3209
3324
|
const raw = JSON.parse(
|
|
3210
|
-
await
|
|
3325
|
+
await readFile4(join4(outDir, "digest.json"), "utf8")
|
|
3211
3326
|
);
|
|
3212
3327
|
return raw.digestVersion === DIGEST_VERSION && Array.isArray(raw.moments) ? raw : null;
|
|
3213
3328
|
} catch {
|
|
@@ -3216,7 +3331,7 @@ async function readPriorDigest(outDir) {
|
|
|
3216
3331
|
}
|
|
3217
3332
|
async function pngSize(file) {
|
|
3218
3333
|
try {
|
|
3219
|
-
const buf = await
|
|
3334
|
+
const buf = await readFile4(file);
|
|
3220
3335
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return null;
|
|
3221
3336
|
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
3222
3337
|
} catch {
|
|
@@ -3456,7 +3571,7 @@ async function momentsFor(dir, doc) {
|
|
|
3456
3571
|
const file = join5(dir, DIGEST_DIR, "digest.json");
|
|
3457
3572
|
if (existsSync5(file)) {
|
|
3458
3573
|
try {
|
|
3459
|
-
const d = JSON.parse(await
|
|
3574
|
+
const d = JSON.parse(await readFile5(file, "utf8"));
|
|
3460
3575
|
if (Array.isArray(d.moments)) return d.moments;
|
|
3461
3576
|
} catch {
|
|
3462
3577
|
}
|
|
@@ -3496,9 +3611,9 @@ async function writeIndexJson(result) {
|
|
|
3496
3611
|
// src/plugin/deliver.ts
|
|
3497
3612
|
import { mkdir as mkdir5, mkdtemp, rename as rename5, rm as rm4, stat as stat2, writeFile as writeFile7 } from "fs/promises";
|
|
3498
3613
|
import { tmpdir } from "os";
|
|
3499
|
-
import { join as
|
|
3500
|
-
import { existsSync as
|
|
3501
|
-
import { readFile as
|
|
3614
|
+
import { join as join7, relative, resolve as resolve4 } from "path";
|
|
3615
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3616
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
3502
3617
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
3503
3618
|
import {
|
|
3504
3619
|
CHANNEL_SPECS_VERIFIED,
|
|
@@ -4477,9 +4592,9 @@ function isLightHexGround(hex2) {
|
|
|
4477
4592
|
}
|
|
4478
4593
|
|
|
4479
4594
|
// src/plugin/posterDoc.ts
|
|
4480
|
-
import { existsSync as
|
|
4481
|
-
import { readFile as
|
|
4482
|
-
import { isAbsolute, join as
|
|
4595
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4596
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
4597
|
+
import { isAbsolute, join as join6, resolve as resolve3 } from "path";
|
|
4483
4598
|
import {
|
|
4484
4599
|
computeCardLayout,
|
|
4485
4600
|
docStillTime as docStillTime2,
|
|
@@ -4487,215 +4602,6 @@ import {
|
|
|
4487
4602
|
overlayRect,
|
|
4488
4603
|
resolveOverlayStyle
|
|
4489
4604
|
} from "@vosjs/studio-core";
|
|
4490
|
-
|
|
4491
|
-
// src/plugin/platform.ts
|
|
4492
|
-
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4493
|
-
import { homedir } from "os";
|
|
4494
|
-
import { join as join6 } from "path";
|
|
4495
|
-
|
|
4496
|
-
// src/plugin/hostAgent.ts
|
|
4497
|
-
import { existsSync as existsSync6 } from "fs";
|
|
4498
|
-
var SLUG = /^[a-z0-9][a-z0-9.-]{0,39}$/;
|
|
4499
|
-
var MARKERS = [
|
|
4500
|
-
["cursor", (e) => !!e.CURSOR_TRACE_ID],
|
|
4501
|
-
[
|
|
4502
|
-
"cursor-cli",
|
|
4503
|
-
(e) => !!e.CURSOR_AGENT || e.CURSOR_EXTENSION_HOST_ROLE === "agent-exec"
|
|
4504
|
-
],
|
|
4505
|
-
["gemini-cli", (e) => !!e.GEMINI_CLI],
|
|
4506
|
-
["codex", (e) => !!(e.CODEX_SANDBOX || e.CODEX_CI || e.CODEX_THREAD_ID)],
|
|
4507
|
-
["antigravity", (e) => !!e.ANTIGRAVITY_AGENT],
|
|
4508
|
-
["augment-cli", (e) => !!e.AUGMENT_AGENT],
|
|
4509
|
-
["opencode", (e) => !!e.OPENCODE_CLIENT],
|
|
4510
|
-
[
|
|
4511
|
-
"claude-code",
|
|
4512
|
-
(e) => !!(e.CLAUDECODE || e.CLAUDE_CODE) && !e.CLAUDE_CODE_IS_COWORK
|
|
4513
|
-
],
|
|
4514
|
-
["claude-cowork", (e) => !!(e.CLAUDECODE || e.CLAUDE_CODE)],
|
|
4515
|
-
["replit", (e) => !!e.REPL_ID],
|
|
4516
|
-
[
|
|
4517
|
-
"github-copilot",
|
|
4518
|
-
(e) => !!(e.COPILOT_MODEL || e.COPILOT_ALLOW_ALL || e.COPILOT_GITHUB_TOKEN)
|
|
4519
|
-
]
|
|
4520
|
-
];
|
|
4521
|
-
var DEVIN_LOCAL_PATH = "/opt/.devin";
|
|
4522
|
-
function slugFromAiAgent(value) {
|
|
4523
|
-
const head = value.trim().toLowerCase().split("_")[0] ?? "";
|
|
4524
|
-
if (head === "github-copilot-cli") return "github-copilot";
|
|
4525
|
-
return SLUG.test(head) ? head : null;
|
|
4526
|
-
}
|
|
4527
|
-
function detectHostAgent(env = process.env, exists = existsSync6) {
|
|
4528
|
-
if (env.AI_AGENT) {
|
|
4529
|
-
const slug = slugFromAiAgent(env.AI_AGENT);
|
|
4530
|
-
if (slug) return slug;
|
|
4531
|
-
}
|
|
4532
|
-
for (const [slug, test] of MARKERS) if (test(env)) return slug;
|
|
4533
|
-
try {
|
|
4534
|
-
if (exists(DEVIN_LOCAL_PATH)) return "devin";
|
|
4535
|
-
} catch {
|
|
4536
|
-
}
|
|
4537
|
-
return null;
|
|
4538
|
-
}
|
|
4539
|
-
|
|
4540
|
-
// src/plugin/platform.ts
|
|
4541
|
-
function platformOrigin(flags = {}) {
|
|
4542
|
-
const legacyEnv = process.env.VOS_API_BASE?.trim();
|
|
4543
|
-
const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
|
|
4544
|
-
if (!flags.origin && !flags.api && !process.env.VOS_ORIGIN && legacyEnv) {
|
|
4545
|
-
process.stderr.write(
|
|
4546
|
-
"note: VOS_API_BASE is deprecated \u2014 set VOS_ORIGIN (an origin, no /api suffix)\n"
|
|
4547
|
-
);
|
|
4548
|
-
}
|
|
4549
|
-
return raw.replace(/\/+$/, "").replace(/\/api$/, "");
|
|
4550
|
-
}
|
|
4551
|
-
function parseVosId(input) {
|
|
4552
|
-
if (!/^https?:\/\//.test(input)) {
|
|
4553
|
-
if (/^[A-Za-z0-9_-]+$/.test(input)) return input;
|
|
4554
|
-
throw new UsageError(`"${input}" is not a vos id or URL`);
|
|
4555
|
-
}
|
|
4556
|
-
let url;
|
|
4557
|
-
try {
|
|
4558
|
-
url = new URL(input);
|
|
4559
|
-
} catch {
|
|
4560
|
-
throw new UsageError(`"${input}" is not a valid URL`);
|
|
4561
|
-
}
|
|
4562
|
-
const query = url.searchParams.get("vos");
|
|
4563
|
-
if (query) return query;
|
|
4564
|
-
const path = url.pathname.match(/\/vos\/([A-Za-z0-9_-]+)/);
|
|
4565
|
-
if (path) return path[1];
|
|
4566
|
-
throw new UsageError(
|
|
4567
|
-
`could not find a vos id in ${input} \u2014 expected /vos/{id} or ?vos={id}`
|
|
4568
|
-
);
|
|
4569
|
-
}
|
|
4570
|
-
function deriveSlug(title) {
|
|
4571
|
-
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
|
|
4572
|
-
}
|
|
4573
|
-
var CREDENTIALS_PATH = join6(homedir(), ".config", "vos", "credentials");
|
|
4574
|
-
function resolveCredential(explicit) {
|
|
4575
|
-
const flag = explicit?.trim();
|
|
4576
|
-
if (flag) return flag;
|
|
4577
|
-
const env = process.env.VOS_API_KEY?.trim();
|
|
4578
|
-
if (env) return env;
|
|
4579
|
-
try {
|
|
4580
|
-
const first = readFileSync2(CREDENTIALS_PATH, "utf8").split("\n")[0].trim();
|
|
4581
|
-
return first || null;
|
|
4582
|
-
} catch {
|
|
4583
|
-
return null;
|
|
4584
|
-
}
|
|
4585
|
-
}
|
|
4586
|
-
function requireCredential(explicit) {
|
|
4587
|
-
const key = resolveCredential(explicit);
|
|
4588
|
-
if (!key) {
|
|
4589
|
-
throw new Error(
|
|
4590
|
-
"no credential found \u2014 pass --key, set VOS_API_KEY, or run `vos login` (mint a content key at https://vos.so/app/api; a vos_rg_ remix grant works too)"
|
|
4591
|
-
);
|
|
4592
|
-
}
|
|
4593
|
-
return key;
|
|
4594
|
-
}
|
|
4595
|
-
function writeCredential(key) {
|
|
4596
|
-
mkdirSync(join6(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
|
|
4597
|
-
writeFileSync(CREDENTIALS_PATH, `${key.trim()}
|
|
4598
|
-
`, { mode: 384 });
|
|
4599
|
-
return CREDENTIALS_PATH;
|
|
4600
|
-
}
|
|
4601
|
-
function clientId(env = process.env, exists) {
|
|
4602
|
-
const explicit = env.VOS_CLIENT?.trim();
|
|
4603
|
-
if (explicit && explicit.length <= 60 && !/[\r\n]/.test(explicit))
|
|
4604
|
-
return explicit;
|
|
4605
|
-
const host = detectHostAgent(env, exists);
|
|
4606
|
-
return host ? `${host} vos-cli` : "vos-cli";
|
|
4607
|
-
}
|
|
4608
|
-
async function apiJson(origin, path, init = {}) {
|
|
4609
|
-
const headers = {
|
|
4610
|
-
accept: "application/json",
|
|
4611
|
-
...init.headers
|
|
4612
|
-
};
|
|
4613
|
-
if (init.key) headers.authorization = `Bearer ${init.key}`;
|
|
4614
|
-
if (init.body !== void 0) headers["content-type"] = "application/json";
|
|
4615
|
-
const reqBody = init.raw !== void 0 ? init.raw : init.body === void 0 ? void 0 : JSON.stringify(init.body);
|
|
4616
|
-
const res = await fetch(`${origin}${path}`, {
|
|
4617
|
-
method: init.method ?? "GET",
|
|
4618
|
-
headers,
|
|
4619
|
-
body: reqBody
|
|
4620
|
-
});
|
|
4621
|
-
let body = {};
|
|
4622
|
-
try {
|
|
4623
|
-
body = await res.json();
|
|
4624
|
-
} catch {
|
|
4625
|
-
}
|
|
4626
|
-
return { status: res.status, body };
|
|
4627
|
-
}
|
|
4628
|
-
function apiError(what, r) {
|
|
4629
|
-
const detail = typeof r.body.error === "string" ? r.body.error : r.body.details !== void 0 ? JSON.stringify(r.body.details) : "";
|
|
4630
|
-
const hint = r.status === 401 ? " (run `vos login`, or set VOS_API_KEY \u2014 mint a key at https://vos.so/app/api)" : "";
|
|
4631
|
-
return `${what} \u2192 ${r.status}${detail ? `: ${detail}` : ""}${hint}`;
|
|
4632
|
-
}
|
|
4633
|
-
var SYNC_STATE_NAME = "vos.json";
|
|
4634
|
-
function readJsonFile(file) {
|
|
4635
|
-
try {
|
|
4636
|
-
return JSON.parse(readFileSync2(file, "utf8"));
|
|
4637
|
-
} catch {
|
|
4638
|
-
return null;
|
|
4639
|
-
}
|
|
4640
|
-
}
|
|
4641
|
-
function readSyncState(dir) {
|
|
4642
|
-
const own = readJsonFile(join6(dir, SYNC_STATE_NAME));
|
|
4643
|
-
if (own && typeof own.vosId === "string") {
|
|
4644
|
-
return {
|
|
4645
|
-
vosId: own.vosId,
|
|
4646
|
-
versionId: typeof own.versionId === "string" ? own.versionId : null,
|
|
4647
|
-
...typeof own.pushedAt === "string" ? { pushedAt: own.pushedAt } : {},
|
|
4648
|
-
...typeof own.title === "string" ? { title: own.title } : {},
|
|
4649
|
-
...typeof own.slug === "string" ? { slug: own.slug } : {},
|
|
4650
|
-
...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
|
|
4651
|
-
};
|
|
4652
|
-
}
|
|
4653
|
-
const push = readJsonFile(join6(dir, "push.json"));
|
|
4654
|
-
if (push && typeof push.vosId === "string") {
|
|
4655
|
-
return {
|
|
4656
|
-
vosId: push.vosId,
|
|
4657
|
-
versionId: typeof push.versionId === "string" ? push.versionId : null,
|
|
4658
|
-
...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
|
|
4659
|
-
};
|
|
4660
|
-
}
|
|
4661
|
-
const meta = readJsonFile(join6(dir, "meta.json"));
|
|
4662
|
-
if (meta && typeof meta.id === "string") {
|
|
4663
|
-
return {
|
|
4664
|
-
vosId: meta.id,
|
|
4665
|
-
versionId: typeof meta.currentVersionId === "string" ? meta.currentVersionId : null,
|
|
4666
|
-
...typeof meta.title === "string" ? { title: meta.title } : {},
|
|
4667
|
-
...typeof meta.slug === "string" ? { slug: meta.slug } : {},
|
|
4668
|
-
...typeof meta.remixOfId === "string" ? { remixOfId: meta.remixOfId } : {}
|
|
4669
|
-
};
|
|
4670
|
-
}
|
|
4671
|
-
return null;
|
|
4672
|
-
}
|
|
4673
|
-
function writeSyncState(dir, patch) {
|
|
4674
|
-
const merged = {
|
|
4675
|
-
...readSyncState(dir) ?? { versionId: null },
|
|
4676
|
-
...patch
|
|
4677
|
-
};
|
|
4678
|
-
writeFileSync(
|
|
4679
|
-
join6(dir, SYNC_STATE_NAME),
|
|
4680
|
-
`${JSON.stringify(merged, null, 2)}
|
|
4681
|
-
`
|
|
4682
|
-
);
|
|
4683
|
-
return merged;
|
|
4684
|
-
}
|
|
4685
|
-
function formatChanges(changes) {
|
|
4686
|
-
const lines = [];
|
|
4687
|
-
for (const ch of changes) {
|
|
4688
|
-
const label = ch.label ? ` \xB7 ${ch.label}` : "";
|
|
4689
|
-
const who = ch.origin === "studio" ? "human" : ch.origin ?? "unknown";
|
|
4690
|
-
lines.push(
|
|
4691
|
-
`v${String(ch.versionNumber ?? "?")} (${who}${label}): ${ch.summary ?? ""}`
|
|
4692
|
-
);
|
|
4693
|
-
if (ch.note) lines.push(` note: ${ch.note}`);
|
|
4694
|
-
}
|
|
4695
|
-
return lines;
|
|
4696
|
-
}
|
|
4697
|
-
|
|
4698
|
-
// src/plugin/posterDoc.ts
|
|
4699
4605
|
var POSTER_CLASSES = [
|
|
4700
4606
|
"landscape",
|
|
4701
4607
|
"square",
|
|
@@ -4715,16 +4621,16 @@ var roleFor = (cls) => `poster-${cls}`;
|
|
|
4715
4621
|
async function readPosterAt(cls, path, takeDir, from) {
|
|
4716
4622
|
let file = path;
|
|
4717
4623
|
let docDir = path;
|
|
4718
|
-
if (
|
|
4719
|
-
file =
|
|
4720
|
-
} else if (!/\.json$/i.test(path) || !
|
|
4624
|
+
if (existsSync6(join6(path, "doc.json"))) {
|
|
4625
|
+
file = join6(path, "doc.json");
|
|
4626
|
+
} else if (!/\.json$/i.test(path) || !existsSync6(path)) {
|
|
4721
4627
|
return null;
|
|
4722
4628
|
} else {
|
|
4723
4629
|
docDir = resolve3(path, "..");
|
|
4724
4630
|
}
|
|
4725
4631
|
let raw;
|
|
4726
4632
|
try {
|
|
4727
|
-
raw = JSON.parse(await
|
|
4633
|
+
raw = JSON.parse(await readFile6(file, "utf8"));
|
|
4728
4634
|
} catch {
|
|
4729
4635
|
return null;
|
|
4730
4636
|
}
|
|
@@ -4734,7 +4640,7 @@ async function readPosterAt(cls, path, takeDir, from) {
|
|
|
4734
4640
|
const doc = migrateHostedDoc(
|
|
4735
4641
|
raw
|
|
4736
4642
|
);
|
|
4737
|
-
const ownFootage =
|
|
4643
|
+
const ownFootage = existsSync6(join6(docDir, "meta.json")) && existsSync6(join6(docDir, source.videoKey));
|
|
4738
4644
|
const sync = readSyncState(docDir);
|
|
4739
4645
|
return {
|
|
4740
4646
|
cls,
|
|
@@ -4757,10 +4663,10 @@ async function findPosterDocs(takeDir, launchRoles) {
|
|
|
4757
4663
|
candidates.push({ path: at2(named.trim()), from: `LAUNCH.md ${role}` });
|
|
4758
4664
|
}
|
|
4759
4665
|
candidates.push({
|
|
4760
|
-
path:
|
|
4666
|
+
path: join6(takeDir, "poster", cls),
|
|
4761
4667
|
from: `poster/${cls}`
|
|
4762
4668
|
});
|
|
4763
|
-
candidates.push({ path:
|
|
4669
|
+
candidates.push({ path: join6(takeDir, "poster"), from: "poster" });
|
|
4764
4670
|
for (const c of candidates) {
|
|
4765
4671
|
const ref = await readPosterAt(cls, c.path, takeDir, c.from);
|
|
4766
4672
|
if (ref) {
|
|
@@ -4856,19 +4762,19 @@ function resolveChannels(raw) {
|
|
|
4856
4762
|
return out;
|
|
4857
4763
|
}
|
|
4858
4764
|
async function readBrandBesideTake(dir, explicit) {
|
|
4859
|
-
const candidates = explicit ? [explicit] : [
|
|
4765
|
+
const candidates = explicit ? [explicit] : [join7(dir, "BRAND.md"), join7(dir, "..", "BRAND.md")];
|
|
4860
4766
|
for (const file of candidates) {
|
|
4861
|
-
if (!
|
|
4862
|
-
const roles = parseFrontmatter(await
|
|
4767
|
+
if (!existsSync7(file)) continue;
|
|
4768
|
+
const roles = parseFrontmatter(await readFile7(file, "utf8"));
|
|
4863
4769
|
return { file, roles };
|
|
4864
4770
|
}
|
|
4865
4771
|
return null;
|
|
4866
4772
|
}
|
|
4867
4773
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4868
|
-
const candidates = explicit ? [explicit] : [
|
|
4774
|
+
const candidates = explicit ? [explicit] : [join7(dir, "LAUNCH.md"), join7(dir, "..", "LAUNCH.md")];
|
|
4869
4775
|
for (const file of candidates) {
|
|
4870
|
-
if (!
|
|
4871
|
-
return { file, roles: parseFrontmatter(await
|
|
4776
|
+
if (!existsSync7(file)) continue;
|
|
4777
|
+
return { file, roles: parseFrontmatter(await readFile7(file, "utf8")) };
|
|
4872
4778
|
}
|
|
4873
4779
|
return null;
|
|
4874
4780
|
}
|
|
@@ -4932,7 +4838,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4932
4838
|
const meta = doc.source.meta;
|
|
4933
4839
|
const vw = meta.captureWidth ?? meta.width;
|
|
4934
4840
|
const vh = meta.captureHeight ?? meta.height;
|
|
4935
|
-
const probeDir = await mkdtemp(
|
|
4841
|
+
const probeDir = await mkdtemp(join7(tmpdir(), "vos-moments-"));
|
|
4936
4842
|
try {
|
|
4937
4843
|
const probe2 = await framesTake(browser, dir, {
|
|
4938
4844
|
times: candidates.map((c) => c.time),
|
|
@@ -4946,7 +4852,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4946
4852
|
});
|
|
4947
4853
|
const byTime = /* @__PURE__ */ new Map();
|
|
4948
4854
|
for (const frame of probe2.frames) {
|
|
4949
|
-
const img = decodePng(new Uint8Array(await
|
|
4855
|
+
const img = decodePng(new Uint8Array(await readFile7(frame.file)));
|
|
4950
4856
|
if (!img) continue;
|
|
4951
4857
|
const whole = { x: 0, y: 0, w: img.w, h: img.h };
|
|
4952
4858
|
byTime.set(+frame.time.toFixed(3), {
|
|
@@ -5019,7 +4925,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5019
4925
|
const doc = take.doc;
|
|
5020
4926
|
const duration = docOutputDuration2(doc);
|
|
5021
4927
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
5022
|
-
const outDir = resolve4(opts.outDir ??
|
|
4928
|
+
const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
|
|
5023
4929
|
await mkdir5(outDir, { recursive: true });
|
|
5024
4930
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
5025
4931
|
const assets = [];
|
|
@@ -5080,7 +4986,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5080
4986
|
opts.onPhase?.(
|
|
5081
4987
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
5082
4988
|
);
|
|
5083
|
-
const shotDir = await mkdtemp(
|
|
4989
|
+
const shotDir = await mkdtemp(join7(tmpdir(), "vos-poster-"));
|
|
5084
4990
|
try {
|
|
5085
4991
|
const captured = await framesTake(browser, ref.takeDir, {
|
|
5086
4992
|
times: [time],
|
|
@@ -5090,7 +4996,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5090
4996
|
doc: ref.doc,
|
|
5091
4997
|
overrides: opts.overrides
|
|
5092
4998
|
});
|
|
5093
|
-
const to =
|
|
4999
|
+
const to = join7(outDir, `${d.id}.png`);
|
|
5094
5000
|
await rename5(captured.frames[0].file, to);
|
|
5095
5001
|
const bytes = (await stat2(to)).size;
|
|
5096
5002
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -5162,7 +5068,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5162
5068
|
}
|
|
5163
5069
|
}
|
|
5164
5070
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
5165
|
-
const outFile =
|
|
5071
|
+
const outFile = join7(outDir, `${d.id}.${d.format}`);
|
|
5166
5072
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
5167
5073
|
1e7,
|
|
5168
5074
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -5209,7 +5115,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5209
5115
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
5210
5116
|
const frame = captured.frames[i];
|
|
5211
5117
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
5212
|
-
const to =
|
|
5118
|
+
const to = join7(outDir, name);
|
|
5213
5119
|
await rename5(frame.file, to);
|
|
5214
5120
|
const bytes = (await stat2(to)).size;
|
|
5215
5121
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -5247,15 +5153,15 @@ async function deliverTake(browser, dir, opts) {
|
|
|
5247
5153
|
skipped,
|
|
5248
5154
|
assets
|
|
5249
5155
|
};
|
|
5250
|
-
const kitFile =
|
|
5156
|
+
const kitFile = join7(outDir, "kit.json");
|
|
5251
5157
|
await writeFile7(kitFile, JSON.stringify(kit, null, 2));
|
|
5252
5158
|
return { kit, kitFile, outDir };
|
|
5253
5159
|
}
|
|
5254
5160
|
|
|
5255
5161
|
// src/plugin/markAsset.ts
|
|
5256
|
-
import { mkdir as mkdir6, readFile as
|
|
5257
|
-
import { existsSync as
|
|
5258
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5162
|
+
import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
|
|
5163
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5164
|
+
import { isAbsolute as isAbsolute2, join as join8, resolve as resolve5 } from "path";
|
|
5259
5165
|
function markExtension(url) {
|
|
5260
5166
|
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
5261
5167
|
if (!m) return null;
|
|
@@ -5337,8 +5243,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5337
5243
|
bytes = new Uint8Array(await res.arrayBuffer());
|
|
5338
5244
|
} else {
|
|
5339
5245
|
const file = isAbsolute2(src) ? src : resolve5(takeDir, src);
|
|
5340
|
-
if (!
|
|
5341
|
-
bytes = new Uint8Array(await
|
|
5246
|
+
if (!existsSync8(file)) throw new Error("no such file");
|
|
5247
|
+
bytes = new Uint8Array(await readFile8(file));
|
|
5342
5248
|
}
|
|
5343
5249
|
} catch (e) {
|
|
5344
5250
|
out.notes.push(
|
|
@@ -5353,8 +5259,8 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
5353
5259
|
);
|
|
5354
5260
|
return null;
|
|
5355
5261
|
}
|
|
5356
|
-
await mkdir6(
|
|
5357
|
-
await writeFile8(
|
|
5262
|
+
await mkdir6(join8(takeDir, "brand"), { recursive: true });
|
|
5263
|
+
await writeFile8(join8(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
5358
5264
|
return { key: `/brand/${name}.${ext}`, aspect };
|
|
5359
5265
|
};
|
|
5360
5266
|
out.light = await one("logoUrl", "mark");
|
|
@@ -5390,9 +5296,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
5390
5296
|
}
|
|
5391
5297
|
|
|
5392
5298
|
// src/plugin/judge.ts
|
|
5393
|
-
import { existsSync as
|
|
5394
|
-
import { mkdir as mkdir7, readFile as
|
|
5395
|
-
import { dirname as dirname2, isAbsolute as isAbsolute3, join as
|
|
5299
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5300
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
5301
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join9, resolve as resolve6 } from "path";
|
|
5396
5302
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
5397
5303
|
|
|
5398
5304
|
// src/plugin/shotBake.ts
|
|
@@ -5563,28 +5469,28 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
5563
5469
|
return { w, h, data: out };
|
|
5564
5470
|
}
|
|
5565
5471
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
5566
|
-
const kit = JSON.parse(await
|
|
5472
|
+
const kit = JSON.parse(await readFile9(kitPath, "utf8"));
|
|
5567
5473
|
const manifest = JSON.parse(
|
|
5568
|
-
await
|
|
5474
|
+
await readFile9(manifestPath, "utf8")
|
|
5569
5475
|
);
|
|
5570
5476
|
const manifestDir = dirname2(resolve6(manifestPath));
|
|
5571
5477
|
const kitDir = dirname2(resolve6(kitPath));
|
|
5572
|
-
const out = resolve6(outDir ??
|
|
5478
|
+
const out = resolve6(outDir ?? join9(kitDir, "judge"));
|
|
5573
5479
|
await mkdir7(out, { recursive: true });
|
|
5574
5480
|
const sheets = [];
|
|
5575
5481
|
const skipped = [];
|
|
5576
5482
|
const refCache = /* @__PURE__ */ new Map();
|
|
5577
5483
|
const loadRef = async (file) => {
|
|
5578
5484
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
5579
|
-
const p =
|
|
5580
|
-
const img =
|
|
5485
|
+
const p = join9(manifestDir, file);
|
|
5486
|
+
const img = existsSync9(p) ? decodePng(new Uint8Array(await readFile9(p))) : null;
|
|
5581
5487
|
refCache.set(file, img);
|
|
5582
5488
|
return img;
|
|
5583
5489
|
};
|
|
5584
5490
|
for (const a of kit.assets) {
|
|
5585
5491
|
if (!/\.png$/i.test(a.path)) continue;
|
|
5586
|
-
const file = isAbsolute3(a.path) &&
|
|
5587
|
-
const img =
|
|
5492
|
+
const file = isAbsolute3(a.path) && existsSync9(a.path) ? a.path : join9(kitDir, a.path.split("/").pop() ?? a.path);
|
|
5493
|
+
const img = existsSync9(file) ? decodePng(new Uint8Array(await readFile9(file))) : null;
|
|
5588
5494
|
if (!img) {
|
|
5589
5495
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
5590
5496
|
continue;
|
|
@@ -5615,8 +5521,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5615
5521
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
5616
5522
|
const nameA = `${id}--A.png`;
|
|
5617
5523
|
const nameB = `${id}--B.png`;
|
|
5618
|
-
await writeFile9(
|
|
5619
|
-
await writeFile9(
|
|
5524
|
+
await writeFile9(join9(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5525
|
+
await writeFile9(join9(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
5620
5526
|
const rubric = [
|
|
5621
5527
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
5622
5528
|
"",
|
|
@@ -5636,7 +5542,7 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5636
5542
|
"In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
|
|
5637
5543
|
""
|
|
5638
5544
|
].join("\n");
|
|
5639
|
-
await writeFile9(
|
|
5545
|
+
await writeFile9(join9(out, `${id}.md`), rubric);
|
|
5640
5546
|
sheets.push({
|
|
5641
5547
|
asset: id,
|
|
5642
5548
|
reference: ref.id,
|
|
@@ -5645,8 +5551,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
5645
5551
|
rubric: `${id}.md`
|
|
5646
5552
|
});
|
|
5647
5553
|
}
|
|
5648
|
-
const verdictFile =
|
|
5649
|
-
if (!
|
|
5554
|
+
const verdictFile = join9(out, "judge.json");
|
|
5555
|
+
if (!existsSync9(verdictFile)) {
|
|
5650
5556
|
await writeFile9(
|
|
5651
5557
|
verdictFile,
|
|
5652
5558
|
JSON.stringify(
|
|
@@ -5698,9 +5604,9 @@ function catalogFamily(family) {
|
|
|
5698
5604
|
}
|
|
5699
5605
|
|
|
5700
5606
|
// src/plugin/callout.ts
|
|
5701
|
-
import { existsSync as
|
|
5702
|
-
import { readFile as
|
|
5703
|
-
import { join as
|
|
5607
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5608
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5609
|
+
import { join as join10 } from "path";
|
|
5704
5610
|
import { lerpArray, sample } from "@vosjs/timeline";
|
|
5705
5611
|
import {
|
|
5706
5612
|
CALLOUT_DEFAULT_BODY_PX,
|
|
@@ -5897,13 +5803,13 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
5897
5803
|
const out = /* @__PURE__ */ new Map();
|
|
5898
5804
|
if (!times.length) return out;
|
|
5899
5805
|
const bare = { ...doc, overlays: [] };
|
|
5900
|
-
const outDir =
|
|
5806
|
+
const outDir = join10(dir, ".ground-check");
|
|
5901
5807
|
const res = await framesTake2(browser, dir, { times, outDir, doc: bare });
|
|
5902
5808
|
for (const f of res.frames) {
|
|
5903
|
-
if (!
|
|
5809
|
+
if (!existsSync10(f.file)) continue;
|
|
5904
5810
|
out.set(
|
|
5905
5811
|
+f.time.toFixed(3),
|
|
5906
|
-
decodePng(new Uint8Array(await
|
|
5812
|
+
decodePng(new Uint8Array(await readFile10(f.file)))
|
|
5907
5813
|
);
|
|
5908
5814
|
}
|
|
5909
5815
|
return out;
|
|
@@ -5912,7 +5818,7 @@ async function footageFrames(browser, dir, doc, times, framesTake2) {
|
|
|
5912
5818
|
// src/plugin/kitPicture.ts
|
|
5913
5819
|
import { execFile } from "child_process";
|
|
5914
5820
|
import { promisify } from "util";
|
|
5915
|
-
import { readFile as
|
|
5821
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5916
5822
|
var execFileP = promisify(execFile);
|
|
5917
5823
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
5918
5824
|
var BLANK_INK = 0.12;
|
|
@@ -6188,7 +6094,7 @@ async function pictureChecks(assets) {
|
|
|
6188
6094
|
let ffmpeg = null;
|
|
6189
6095
|
for (const a of assets) {
|
|
6190
6096
|
if (/\.png$/i.test(a.file)) {
|
|
6191
|
-
const img = decodePng(new Uint8Array(await
|
|
6097
|
+
const img = decodePng(new Uint8Array(await readFile11(a.file)));
|
|
6192
6098
|
if (!img) {
|
|
6193
6099
|
findings.push({
|
|
6194
6100
|
code: "unreadable",
|
|
@@ -6281,9 +6187,9 @@ function formatFinding(f) {
|
|
|
6281
6187
|
}
|
|
6282
6188
|
|
|
6283
6189
|
// src/plugin/validateKit.ts
|
|
6284
|
-
import { existsSync as
|
|
6285
|
-
import { readFile as
|
|
6286
|
-
import { dirname as dirname3, isAbsolute as isAbsolute4, join as
|
|
6190
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6191
|
+
import { readFile as readFile12, stat as stat3 } from "fs/promises";
|
|
6192
|
+
import { dirname as dirname3, isAbsolute as isAbsolute4, join as join11 } from "path";
|
|
6287
6193
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
6288
6194
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
6289
6195
|
function pngDimensions(bytes) {
|
|
@@ -6302,7 +6208,7 @@ async function probeVideo(path) {
|
|
|
6302
6208
|
const MB = await import("mediabunny");
|
|
6303
6209
|
const input = new MB.Input({
|
|
6304
6210
|
formats: MB.ALL_FORMATS,
|
|
6305
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
6211
|
+
source: new MB.BufferSource(new Uint8Array(await readFile12(path)))
|
|
6306
6212
|
});
|
|
6307
6213
|
try {
|
|
6308
6214
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -6323,7 +6229,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6323
6229
|
const measured = [];
|
|
6324
6230
|
let kit;
|
|
6325
6231
|
try {
|
|
6326
|
-
kit = JSON.parse(await
|
|
6232
|
+
kit = JSON.parse(await readFile12(kitPath, "utf8"));
|
|
6327
6233
|
} catch (e) {
|
|
6328
6234
|
return {
|
|
6329
6235
|
valid: false,
|
|
@@ -6342,8 +6248,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6342
6248
|
}
|
|
6343
6249
|
const base = dirname3(kitPath);
|
|
6344
6250
|
const resolvePath = (p) => {
|
|
6345
|
-
if (isAbsolute4(p) &&
|
|
6346
|
-
const beside =
|
|
6251
|
+
if (isAbsolute4(p) && existsSync11(p)) return p;
|
|
6252
|
+
const beside = join11(base, p.split("/").pop() ?? p);
|
|
6347
6253
|
return beside;
|
|
6348
6254
|
};
|
|
6349
6255
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -6369,7 +6275,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6369
6275
|
let h = null;
|
|
6370
6276
|
let seconds = null;
|
|
6371
6277
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
6372
|
-
const head = Buffer.from(await
|
|
6278
|
+
const head = Buffer.from(await readFile12(file));
|
|
6373
6279
|
const kind = sniffImage(head);
|
|
6374
6280
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
6375
6281
|
problems.push(
|
|
@@ -6478,8 +6384,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
6478
6384
|
}
|
|
6479
6385
|
|
|
6480
6386
|
// src/plugin/recorder.ts
|
|
6481
|
-
import { writeFileSync
|
|
6482
|
-
import { join as
|
|
6387
|
+
import { writeFileSync } from "fs";
|
|
6388
|
+
import { join as join12 } from "path";
|
|
6483
6389
|
import { recordTake as recordTakeCore } from "@vosjs/render-core/record";
|
|
6484
6390
|
async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
6485
6391
|
const dry = opts.dryRun === true;
|
|
@@ -6489,7 +6395,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
6489
6395
|
actions,
|
|
6490
6396
|
{
|
|
6491
6397
|
frame: (file, bytes) => {
|
|
6492
|
-
|
|
6398
|
+
writeFileSync(join12(paths.framesDir, file), bytes);
|
|
6493
6399
|
}
|
|
6494
6400
|
},
|
|
6495
6401
|
log,
|
|
@@ -6541,11 +6447,11 @@ import {
|
|
|
6541
6447
|
|
|
6542
6448
|
// src/plugin/session.ts
|
|
6543
6449
|
import { spawn } from "child_process";
|
|
6544
|
-
import { existsSync as
|
|
6545
|
-
import { homedir
|
|
6546
|
-
import { join as
|
|
6450
|
+
import { existsSync as existsSync12, mkdirSync, readdirSync as readdirSync2, rmSync, statSync as statSync3 } from "fs";
|
|
6451
|
+
import { homedir } from "os";
|
|
6452
|
+
import { join as join13 } from "path";
|
|
6547
6453
|
import { chromium } from "playwright";
|
|
6548
|
-
var SESSIONS_DIR =
|
|
6454
|
+
var SESSIONS_DIR = join13(homedir(), ".config", "vos", "sessions");
|
|
6549
6455
|
var NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
6550
6456
|
function sessionNameVerdict(name) {
|
|
6551
6457
|
if (!name) return "a session needs a name: --name <app>";
|
|
@@ -6554,20 +6460,20 @@ function sessionNameVerdict(name) {
|
|
|
6554
6460
|
return null;
|
|
6555
6461
|
}
|
|
6556
6462
|
function sessionDir(name) {
|
|
6557
|
-
return
|
|
6463
|
+
return join13(SESSIONS_DIR, name);
|
|
6558
6464
|
}
|
|
6559
6465
|
function systemChromePath() {
|
|
6560
6466
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6561
6467
|
if (explicit) return explicit;
|
|
6562
6468
|
const candidates = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : process.platform === "win32" ? [
|
|
6563
|
-
|
|
6469
|
+
join13(
|
|
6564
6470
|
process.env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
6565
6471
|
"Google",
|
|
6566
6472
|
"Chrome",
|
|
6567
6473
|
"Application",
|
|
6568
6474
|
"chrome.exe"
|
|
6569
6475
|
),
|
|
6570
|
-
|
|
6476
|
+
join13(
|
|
6571
6477
|
process.env["LOCALAPPDATA"] ?? "",
|
|
6572
6478
|
"Google",
|
|
6573
6479
|
"Chrome",
|
|
@@ -6579,7 +6485,7 @@ function systemChromePath() {
|
|
|
6579
6485
|
"/usr/bin/google-chrome-stable",
|
|
6580
6486
|
"/opt/google/chrome/chrome"
|
|
6581
6487
|
];
|
|
6582
|
-
const found = candidates.find((c) =>
|
|
6488
|
+
const found = candidates.find((c) => existsSync12(c));
|
|
6583
6489
|
if (!found)
|
|
6584
6490
|
throw new Error(
|
|
6585
6491
|
"no Google Chrome found: a session needs the system Chrome (the bundled Chromium cannot read a profile Chrome wrote). Install Chrome, or set VOS_BROWSER_PATH"
|
|
@@ -6589,7 +6495,7 @@ function systemChromePath() {
|
|
|
6589
6495
|
async function openSession(name, url, opts = { log: () => {
|
|
6590
6496
|
} }) {
|
|
6591
6497
|
const dir = sessionDir(name);
|
|
6592
|
-
|
|
6498
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6593
6499
|
const exe = systemChromePath();
|
|
6594
6500
|
const args = [
|
|
6595
6501
|
`--user-data-dir=${dir}`,
|
|
@@ -6610,7 +6516,7 @@ async function openSession(name, url, opts = { log: () => {
|
|
|
6610
6516
|
}
|
|
6611
6517
|
async function withSession(name, fn, opts = {}) {
|
|
6612
6518
|
const dir = sessionDir(name);
|
|
6613
|
-
if (!
|
|
6519
|
+
if (!existsSync12(dir))
|
|
6614
6520
|
throw new Error(`no session "${name}" (vos session list)`);
|
|
6615
6521
|
const explicit = process.env.VOS_BROWSER_PATH;
|
|
6616
6522
|
const ctx = await chromium.launchPersistentContext(dir, {
|
|
@@ -6659,9 +6565,9 @@ function summaryLine(s) {
|
|
|
6659
6565
|
return `session "${s.name}" holds cookies for ${hosts}${more}; ${exp}`;
|
|
6660
6566
|
}
|
|
6661
6567
|
function listSessions() {
|
|
6662
|
-
if (!
|
|
6568
|
+
if (!existsSync12(SESSIONS_DIR)) return [];
|
|
6663
6569
|
return readdirSync2(SESSIONS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && NAME.test(d.name)).map((d) => {
|
|
6664
|
-
const st = statSync3(
|
|
6570
|
+
const st = statSync3(join13(SESSIONS_DIR, d.name));
|
|
6665
6571
|
return {
|
|
6666
6572
|
name: d.name,
|
|
6667
6573
|
ageDays: Math.floor(
|
|
@@ -6672,7 +6578,7 @@ function listSessions() {
|
|
|
6672
6578
|
}
|
|
6673
6579
|
function removeSession(name) {
|
|
6674
6580
|
const dir = sessionDir(name);
|
|
6675
|
-
if (!
|
|
6581
|
+
if (!existsSync12(dir)) return false;
|
|
6676
6582
|
rmSync(dir, { recursive: true, force: true });
|
|
6677
6583
|
return true;
|
|
6678
6584
|
}
|
|
@@ -6737,9 +6643,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6737
6643
|
}
|
|
6738
6644
|
|
|
6739
6645
|
// src/plugin/plan.ts
|
|
6740
|
-
import { existsSync as
|
|
6741
|
-
import { readFile as
|
|
6742
|
-
import { join as
|
|
6646
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6647
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6648
|
+
import { join as join14 } from "path";
|
|
6743
6649
|
import {
|
|
6744
6650
|
DEFAULT_FRAME_STYLE,
|
|
6745
6651
|
STYLE_FIELDS,
|
|
@@ -6961,10 +6867,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
6961
6867
|
// src/plugin/plan.ts
|
|
6962
6868
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
6963
6869
|
async function readDigestActivity(dir) {
|
|
6964
|
-
const file =
|
|
6965
|
-
if (!
|
|
6870
|
+
const file = join14(dir, "digest", "digest.json");
|
|
6871
|
+
if (!existsSync13(file)) return null;
|
|
6966
6872
|
try {
|
|
6967
|
-
const d = JSON.parse(await
|
|
6873
|
+
const d = JSON.parse(await readFile13(file, "utf8"));
|
|
6968
6874
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
6969
6875
|
} catch {
|
|
6970
6876
|
return null;
|
|
@@ -7025,7 +6931,7 @@ async function planTake(dir, opts = {}) {
|
|
|
7025
6931
|
const take = await loadTake(dir);
|
|
7026
6932
|
const { meta, cursor } = take;
|
|
7027
6933
|
const activity = await readDigestActivity(dir);
|
|
7028
|
-
const ingest = opts.backdrop ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) } : {};
|
|
6934
|
+
const ingest = opts.footage ? { footage: true } : opts.backdrop ? { frame: withBackdrop(DEFAULT_FRAME_STYLE, opts.backdrop) } : {};
|
|
7029
6935
|
let doc;
|
|
7030
6936
|
let fresh;
|
|
7031
6937
|
let layout;
|
|
@@ -7177,16 +7083,16 @@ async function planTake(dir, opts = {}) {
|
|
|
7177
7083
|
}
|
|
7178
7084
|
|
|
7179
7085
|
// src/plugin/sync.ts
|
|
7180
|
-
import { createHash } from "crypto";
|
|
7181
|
-
import { existsSync as
|
|
7182
|
-
import { readFile as
|
|
7086
|
+
import { createHash as createHash2 } from "crypto";
|
|
7087
|
+
import { existsSync as existsSync14, readFileSync as readFileSync2, readdirSync as readdirSync3 } from "fs";
|
|
7088
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
7183
7089
|
import { createInterface } from "readline/promises";
|
|
7184
|
-
import { basename as basename3, join as
|
|
7090
|
+
import { basename as basename3, join as join16 } from "path";
|
|
7185
7091
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
|
|
7186
7092
|
|
|
7187
7093
|
// src/plugin/folder.ts
|
|
7188
7094
|
import { mkdir as mkdir8, writeFile as writeFile10 } from "fs/promises";
|
|
7189
|
-
import { join as
|
|
7095
|
+
import { join as join15 } from "path";
|
|
7190
7096
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
7191
7097
|
import { migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
7192
7098
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -7362,18 +7268,18 @@ async function cmdPull(argv) {
|
|
|
7362
7268
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
7363
7269
|
const payload = res.body;
|
|
7364
7270
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
7365
|
-
await mkdir8(
|
|
7271
|
+
await mkdir8(join15(out, "recipes"), { recursive: true });
|
|
7366
7272
|
const lines = [];
|
|
7367
7273
|
const recipes = [];
|
|
7368
7274
|
for (const rec of payload.recipes) {
|
|
7369
|
-
const path =
|
|
7275
|
+
const path = join15(out, "recipes", safeName(rec.filename));
|
|
7370
7276
|
await writeFile10(path, rec.body ?? "");
|
|
7371
7277
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
7372
7278
|
}
|
|
7373
7279
|
for (const rec of payload.inheritedRecipes) {
|
|
7374
7280
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
7375
|
-
await mkdir8(
|
|
7376
|
-
const path =
|
|
7281
|
+
await mkdir8(join15(out, "recipes", "_inherited", from), { recursive: true });
|
|
7282
|
+
const path = join15(
|
|
7377
7283
|
out,
|
|
7378
7284
|
"recipes",
|
|
7379
7285
|
"_inherited",
|
|
@@ -7388,7 +7294,7 @@ async function cmdPull(argv) {
|
|
|
7388
7294
|
}
|
|
7389
7295
|
const members = [];
|
|
7390
7296
|
for (const v of payload.voses) {
|
|
7391
|
-
const dir =
|
|
7297
|
+
const dir = join15(out, "members", safeName(v.slug || v.title || v.id));
|
|
7392
7298
|
await mkdir8(dir, { recursive: true });
|
|
7393
7299
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
7394
7300
|
if (cfg.status !== 200) {
|
|
@@ -7396,7 +7302,7 @@ async function cmdPull(argv) {
|
|
|
7396
7302
|
continue;
|
|
7397
7303
|
}
|
|
7398
7304
|
await writeFile10(
|
|
7399
|
-
|
|
7305
|
+
join15(dir, "config.json"),
|
|
7400
7306
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7401
7307
|
);
|
|
7402
7308
|
let take = false;
|
|
@@ -7408,7 +7314,7 @@ async function cmdPull(argv) {
|
|
|
7408
7314
|
);
|
|
7409
7315
|
if (doc.status === 200) {
|
|
7410
7316
|
await writeFile10(
|
|
7411
|
-
|
|
7317
|
+
join15(dir, "doc.json"),
|
|
7412
7318
|
JSON.stringify(doc.body, null, 2)
|
|
7413
7319
|
);
|
|
7414
7320
|
take = true;
|
|
@@ -7478,144 +7384,6 @@ async function cmdFolder(argv) {
|
|
|
7478
7384
|
}
|
|
7479
7385
|
}
|
|
7480
7386
|
|
|
7481
|
-
// src/plugin/uploadAsset.ts
|
|
7482
|
-
var SINGLE_SHOT_MAX_BYTES = 32 * 1024 * 1024;
|
|
7483
|
-
var PART_ATTEMPTS = 3;
|
|
7484
|
-
function planParts(totalBytes, partBytes) {
|
|
7485
|
-
if (totalBytes <= 0 || partBytes <= 0) return [];
|
|
7486
|
-
const parts = [];
|
|
7487
|
-
for (let start = 0; start < totalBytes; start += partBytes) {
|
|
7488
|
-
parts.push({
|
|
7489
|
-
partNumber: parts.length + 1,
|
|
7490
|
-
start,
|
|
7491
|
-
end: Math.min(start + partBytes, totalBytes)
|
|
7492
|
-
});
|
|
7493
|
-
}
|
|
7494
|
-
return parts;
|
|
7495
|
-
}
|
|
7496
|
-
function failed(what, r) {
|
|
7497
|
-
const detail = typeof r.body.error === "string" ? r.body.error : "";
|
|
7498
|
-
return new Error(`${what} (${r.status})${detail ? `: ${detail}` : ""}`);
|
|
7499
|
-
}
|
|
7500
|
-
function asAsset(body) {
|
|
7501
|
-
return {
|
|
7502
|
-
id: String(body.id),
|
|
7503
|
-
url: String(body.url),
|
|
7504
|
-
size: typeof body.size === "number" ? body.size : 0,
|
|
7505
|
-
reused: body.reused === true
|
|
7506
|
-
};
|
|
7507
|
-
}
|
|
7508
|
-
function transient(status) {
|
|
7509
|
-
return status >= 500 || status === 429;
|
|
7510
|
-
}
|
|
7511
|
-
var wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7512
|
-
async function uploadAsset(target, bytes, opts) {
|
|
7513
|
-
return bytes.length > SINGLE_SHOT_MAX_BYTES ? uploadInParts(target, bytes, opts) : uploadWhole(target, bytes, opts);
|
|
7514
|
-
}
|
|
7515
|
-
async function uploadWhole(target, bytes, opts) {
|
|
7516
|
-
const res = await apiJson(target.origin, "/api/assets/recording", {
|
|
7517
|
-
method: "POST",
|
|
7518
|
-
key: target.key,
|
|
7519
|
-
headers: {
|
|
7520
|
-
"Content-Type": opts.contentType,
|
|
7521
|
-
"Content-Length": String(bytes.length),
|
|
7522
|
-
"X-Filename": opts.filename,
|
|
7523
|
-
"X-Content-Hash": opts.contentHash,
|
|
7524
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { "X-Content-Duration": opts.durationSeconds.toFixed(3) } : {}
|
|
7525
|
-
},
|
|
7526
|
-
raw: bytes
|
|
7527
|
-
});
|
|
7528
|
-
if (res.status !== 201 && res.status !== 200)
|
|
7529
|
-
throw failed("upload failed", res);
|
|
7530
|
-
return asAsset(res.body);
|
|
7531
|
-
}
|
|
7532
|
-
async function uploadInParts(target, bytes, opts) {
|
|
7533
|
-
const begun = await apiJson(
|
|
7534
|
-
target.origin,
|
|
7535
|
-
"/api/assets/recording/multipart",
|
|
7536
|
-
{
|
|
7537
|
-
method: "POST",
|
|
7538
|
-
key: target.key,
|
|
7539
|
-
body: {
|
|
7540
|
-
filename: opts.filename,
|
|
7541
|
-
contentType: opts.contentType,
|
|
7542
|
-
size: bytes.length,
|
|
7543
|
-
contentHash: opts.contentHash,
|
|
7544
|
-
...opts.durationSeconds && opts.durationSeconds > 0 ? { durationSeconds: opts.durationSeconds } : {}
|
|
7545
|
-
}
|
|
7546
|
-
}
|
|
7547
|
-
);
|
|
7548
|
-
if (begun.status !== 201 && begun.status !== 200) {
|
|
7549
|
-
throw failed("upload failed", begun);
|
|
7550
|
-
}
|
|
7551
|
-
if (begun.body.reused === true || typeof begun.body.url === "string") {
|
|
7552
|
-
return asAsset(begun.body);
|
|
7553
|
-
}
|
|
7554
|
-
const uploadId = String(begun.body.id);
|
|
7555
|
-
const partBytes = Number(begun.body.partBytes);
|
|
7556
|
-
if (!Number.isFinite(partBytes) || partBytes <= 0) {
|
|
7557
|
-
throw new Error("upload failed: the platform returned no part size");
|
|
7558
|
-
}
|
|
7559
|
-
const parts = planParts(bytes.length, partBytes);
|
|
7560
|
-
const etags = [];
|
|
7561
|
-
try {
|
|
7562
|
-
for (const part of parts) {
|
|
7563
|
-
const chunk2 = bytes.subarray(part.start, part.end);
|
|
7564
|
-
let sent = null;
|
|
7565
|
-
for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
|
|
7566
|
-
try {
|
|
7567
|
-
sent = await apiJson(
|
|
7568
|
-
target.origin,
|
|
7569
|
-
`/api/assets/recording/multipart/${uploadId}/parts/${part.partNumber}`,
|
|
7570
|
-
{
|
|
7571
|
-
method: "PUT",
|
|
7572
|
-
key: target.key,
|
|
7573
|
-
headers: {
|
|
7574
|
-
"Content-Type": "application/octet-stream",
|
|
7575
|
-
"Content-Length": String(chunk2.length)
|
|
7576
|
-
},
|
|
7577
|
-
raw: chunk2
|
|
7578
|
-
}
|
|
7579
|
-
);
|
|
7580
|
-
} catch (networkError) {
|
|
7581
|
-
if (attempt === PART_ATTEMPTS) throw networkError;
|
|
7582
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
7583
|
-
continue;
|
|
7584
|
-
}
|
|
7585
|
-
if (sent.status === 200) break;
|
|
7586
|
-
if (!transient(sent.status) || attempt === PART_ATTEMPTS) {
|
|
7587
|
-
throw failed(`part ${part.partNumber} failed`, sent);
|
|
7588
|
-
}
|
|
7589
|
-
await wait(500 * 2 ** (attempt - 1));
|
|
7590
|
-
}
|
|
7591
|
-
if (!sent || sent.status !== 200) {
|
|
7592
|
-
throw new Error(`part ${part.partNumber} failed`);
|
|
7593
|
-
}
|
|
7594
|
-
etags.push({
|
|
7595
|
-
partNumber: Number(sent.body.partNumber),
|
|
7596
|
-
etag: String(sent.body.etag)
|
|
7597
|
-
});
|
|
7598
|
-
opts.onPart?.(etags.length, parts.length);
|
|
7599
|
-
}
|
|
7600
|
-
const sealed = await apiJson(
|
|
7601
|
-
target.origin,
|
|
7602
|
-
`/api/assets/recording/multipart/${uploadId}/complete`,
|
|
7603
|
-
{ method: "POST", key: target.key, body: { parts: etags } }
|
|
7604
|
-
);
|
|
7605
|
-
if (sealed.status !== 201 && sealed.status !== 200) {
|
|
7606
|
-
throw failed("upload failed", sealed);
|
|
7607
|
-
}
|
|
7608
|
-
return asAsset(sealed.body);
|
|
7609
|
-
} catch (err) {
|
|
7610
|
-
await apiJson(
|
|
7611
|
-
target.origin,
|
|
7612
|
-
`/api/assets/recording/multipart/${uploadId}`,
|
|
7613
|
-
{ method: "DELETE", key: target.key }
|
|
7614
|
-
).catch(() => void 0);
|
|
7615
|
-
throw err;
|
|
7616
|
-
}
|
|
7617
|
-
}
|
|
7618
|
-
|
|
7619
7387
|
// src/plugin/sync.ts
|
|
7620
7388
|
function apiContext(flags) {
|
|
7621
7389
|
return {
|
|
@@ -7682,8 +7450,8 @@ function storageStateInDir(dir) {
|
|
|
7682
7450
|
if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
|
|
7683
7451
|
continue;
|
|
7684
7452
|
try {
|
|
7685
|
-
const v = JSON.parse(
|
|
7686
|
-
if (looksLikeStorageState(v)) return
|
|
7453
|
+
const v = JSON.parse(readFileSync2(join16(dir, f), "utf8"));
|
|
7454
|
+
if (looksLikeStorageState(v)) return join16(dir, f);
|
|
7687
7455
|
} catch {
|
|
7688
7456
|
}
|
|
7689
7457
|
}
|
|
@@ -7704,7 +7472,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7704
7472
|
throw new Error(`doc.json fails lint:
|
|
7705
7473
|
${lint.problems.join("\n ")}`);
|
|
7706
7474
|
}
|
|
7707
|
-
if (!
|
|
7475
|
+
if (!existsSync14(take.paths.recording)) {
|
|
7708
7476
|
throw new Error(
|
|
7709
7477
|
"no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
|
|
7710
7478
|
);
|
|
@@ -7753,8 +7521,8 @@ async function pushTake(dir, flags, r) {
|
|
|
7753
7521
|
throw new Error("push cancelled");
|
|
7754
7522
|
}
|
|
7755
7523
|
}
|
|
7756
|
-
const bytes = await
|
|
7757
|
-
const hash =
|
|
7524
|
+
const bytes = await readFile14(take.paths.recording);
|
|
7525
|
+
const hash = createHash2("sha256").update(bytes).digest("hex");
|
|
7758
7526
|
const { type: contentType } = resolveMediaType({
|
|
7759
7527
|
head: bytes.subarray(0, MEDIA_HEAD_BYTES),
|
|
7760
7528
|
filename: take.paths.recordingName
|
|
@@ -7795,39 +7563,7 @@ async function pushTake(dir, flags, r) {
|
|
|
7795
7563
|
r.log(" mic track is local-only \u2014 dropped from the push");
|
|
7796
7564
|
delete docForPush.source.micKey;
|
|
7797
7565
|
}
|
|
7798
|
-
|
|
7799
|
-
const file = join17(dir, takeRelativeFile(ref.key));
|
|
7800
|
-
if (!existsSync15(file)) {
|
|
7801
|
-
r.log(` ${ref.where}: ${ref.key} is not in the take \u2014 left as is`);
|
|
7802
|
-
continue;
|
|
7803
|
-
}
|
|
7804
|
-
const media = await readFile13(file);
|
|
7805
|
-
const mediaHash = createHash("sha256").update(media).digest("hex");
|
|
7806
|
-
const { type } = resolveMediaType({
|
|
7807
|
-
head: media.subarray(0, MEDIA_HEAD_BYTES),
|
|
7808
|
-
filename: file
|
|
7809
|
-
});
|
|
7810
|
-
const name = nameForType(basename3(file), type);
|
|
7811
|
-
if (name !== basename3(file)) {
|
|
7812
|
-
r.log(` ${ref.where}: ${basename3(file)} holds ${type} \u2014 sent as ${name}`);
|
|
7813
|
-
}
|
|
7814
|
-
let put;
|
|
7815
|
-
try {
|
|
7816
|
-
put = await uploadAsset(ctx, new Uint8Array(media), {
|
|
7817
|
-
filename: name,
|
|
7818
|
-
contentType: type,
|
|
7819
|
-
contentHash: mediaHash
|
|
7820
|
-
});
|
|
7821
|
-
} catch (e) {
|
|
7822
|
-
throw new Error(
|
|
7823
|
-
`${ref.where} upload failed: ${e instanceof Error ? e.message : String(e)}`
|
|
7824
|
-
);
|
|
7825
|
-
}
|
|
7826
|
-
ref.set(put.url);
|
|
7827
|
-
r.log(
|
|
7828
|
-
` ${ref.where}: ${ref.key} \u2192 asset ${put.id}${put.reused ? " (reused)" : ""}`
|
|
7829
|
-
);
|
|
7830
|
-
}
|
|
7566
|
+
await uploadDocRefs(docMediaRefs(docForPush), dir, ctx, (l) => r.log(l));
|
|
7831
7567
|
const lowered = lowerToComposition3(docForPush);
|
|
7832
7568
|
const config = { ...lowered.config, data: lowered.data };
|
|
7833
7569
|
if (!state) {
|
|
@@ -7941,9 +7677,9 @@ async function pullTake(dir, flags, r) {
|
|
|
7941
7677
|
};
|
|
7942
7678
|
}
|
|
7943
7679
|
let media2;
|
|
7944
|
-
if (flags.media &&
|
|
7680
|
+
if (flags.media && existsSync14(join16(dir, "doc.json"))) {
|
|
7945
7681
|
const local = JSON.parse(
|
|
7946
|
-
await
|
|
7682
|
+
await readFile14(join16(dir, "doc.json"), "utf8")
|
|
7947
7683
|
);
|
|
7948
7684
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7949
7685
|
}
|
|
@@ -8004,10 +7740,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8004
7740
|
const hostedDoc = migrateHostedDoc3(docRes.json);
|
|
8005
7741
|
const doc = hostedDoc;
|
|
8006
7742
|
const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
|
|
8007
|
-
if (
|
|
7743
|
+
if (existsSync14(localRecording)) {
|
|
8008
7744
|
doc.source.videoKey = basename3(localRecording);
|
|
8009
7745
|
}
|
|
8010
|
-
await writeJson(
|
|
7746
|
+
await writeJson(join16(dir, "doc.json"), doc, true);
|
|
8011
7747
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
8012
7748
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
8013
7749
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -8026,10 +7762,10 @@ async function pullTake(dir, flags, r) {
|
|
|
8026
7762
|
}
|
|
8027
7763
|
|
|
8028
7764
|
// src/plugin/program.ts
|
|
8029
|
-
import { existsSync as
|
|
8030
|
-
import { mkdir as mkdir9, readFile as
|
|
7765
|
+
import { existsSync as existsSync15 } from "fs";
|
|
7766
|
+
import { mkdir as mkdir9, readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
|
|
8031
7767
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
8032
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
7768
|
+
import { basename as basename4, dirname as dirname4, join as join17 } from "path";
|
|
8033
7769
|
import {
|
|
8034
7770
|
CURRENT_CONFIG_VERSION,
|
|
8035
7771
|
migrateConfig,
|
|
@@ -8169,7 +7905,8 @@ var BOOLEAN_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
8169
7905
|
"check",
|
|
8170
7906
|
"yes",
|
|
8171
7907
|
"claimable",
|
|
8172
|
-
"no-browser"
|
|
7908
|
+
"no-browser",
|
|
7909
|
+
"wait"
|
|
8173
7910
|
]);
|
|
8174
7911
|
var MULTI_FLAGS = /* @__PURE__ */ new Set(["override"]);
|
|
8175
7912
|
function preflightConfig(parsed) {
|
|
@@ -8234,8 +7971,8 @@ function preflightConfig(parsed) {
|
|
|
8234
7971
|
}
|
|
8235
7972
|
function resolveConfigPath(target) {
|
|
8236
7973
|
if (target.endsWith(".json")) return target;
|
|
8237
|
-
const inDir =
|
|
8238
|
-
if (
|
|
7974
|
+
const inDir = join17(target, "config.json");
|
|
7975
|
+
if (existsSync15(inDir)) return inDir;
|
|
8239
7976
|
throw new UsageError(
|
|
8240
7977
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
8241
7978
|
);
|
|
@@ -8263,7 +8000,7 @@ async function cmdFetch(argv) {
|
|
|
8263
8000
|
const out = strFlag(flags, "out") ?? slug;
|
|
8264
8001
|
await mkdir9(out, { recursive: true });
|
|
8265
8002
|
await writeFile11(
|
|
8266
|
-
|
|
8003
|
+
join17(out, "config.json"),
|
|
8267
8004
|
JSON.stringify(cfg.body.config, null, 2)
|
|
8268
8005
|
);
|
|
8269
8006
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -8288,12 +8025,12 @@ async function cmdFetch(argv) {
|
|
|
8288
8025
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8289
8026
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
8290
8027
|
if (own) {
|
|
8291
|
-
await writeFile11(
|
|
8028
|
+
await writeFile11(join17(out, "config.json"), JSON.stringify(own, null, 2));
|
|
8292
8029
|
}
|
|
8293
8030
|
} else if (doc.status === 200) {
|
|
8294
8031
|
take = true;
|
|
8295
8032
|
const hosted = migrateHostedDoc4(doc.body);
|
|
8296
|
-
await writeFile11(
|
|
8033
|
+
await writeFile11(join17(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
8297
8034
|
if (flags.media === true) {
|
|
8298
8035
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
8299
8036
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -8406,7 +8143,7 @@ async function cmdPushProgram(argv) {
|
|
|
8406
8143
|
api: strFlag(flags, "api")
|
|
8407
8144
|
});
|
|
8408
8145
|
const dir = dirname4(source);
|
|
8409
|
-
const parsed = JSON.parse(await
|
|
8146
|
+
const parsed = JSON.parse(await readFile15(source, "utf8"));
|
|
8410
8147
|
const pre = preflightConfig(parsed);
|
|
8411
8148
|
if (!pre.ok || !pre.config) {
|
|
8412
8149
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -8435,7 +8172,25 @@ async function cmdPushProgram(argv) {
|
|
|
8435
8172
|
}
|
|
8436
8173
|
const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
|
|
8437
8174
|
const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
|
|
8438
|
-
|
|
8175
|
+
let claimDoc = programDoc;
|
|
8176
|
+
if (programDoc) {
|
|
8177
|
+
const sound = soundOf(programDoc);
|
|
8178
|
+
const local = docAudioRefs(sound);
|
|
8179
|
+
if (local.length) {
|
|
8180
|
+
const dropped = new Set(local.map((ref) => ref.key));
|
|
8181
|
+
claimDoc = {
|
|
8182
|
+
...programDoc,
|
|
8183
|
+
audio: (sound.audio ?? []).filter((a) => !dropped.has(a.key))
|
|
8184
|
+
};
|
|
8185
|
+
r.log(
|
|
8186
|
+
`warning a claimable push carries no files, so ${local.length} local sound${local.length === 1 ? "" : "s"} (${[...dropped].join(", ")}) ${local.length === 1 ? "was" : "were"} left out. After the claim, push again with a key to add ${local.length === 1 ? "it" : "them"}`
|
|
8187
|
+
);
|
|
8188
|
+
}
|
|
8189
|
+
}
|
|
8190
|
+
const body = {
|
|
8191
|
+
title: title2,
|
|
8192
|
+
config: storedProgramConfig(config, claimDoc)
|
|
8193
|
+
};
|
|
8439
8194
|
const slug = strFlag(flags, "slug");
|
|
8440
8195
|
if (slug) body.slug = slug;
|
|
8441
8196
|
const res = await apiJson(origin, "/api/claim", { method: "POST", body });
|
|
@@ -8455,6 +8210,15 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8455
8210
|
return EXIT_OK;
|
|
8456
8211
|
}
|
|
8457
8212
|
const key = requireCredential(strFlag(flags, "key"));
|
|
8213
|
+
void flags.yes;
|
|
8214
|
+
if (programDoc) {
|
|
8215
|
+
await uploadDocRefs(
|
|
8216
|
+
docAudioRefs(soundOf(programDoc)),
|
|
8217
|
+
dir,
|
|
8218
|
+
{ origin, key },
|
|
8219
|
+
(l) => r.log(l)
|
|
8220
|
+
);
|
|
8221
|
+
}
|
|
8458
8222
|
const overrides = [
|
|
8459
8223
|
...multi.override ?? [],
|
|
8460
8224
|
...strFlag(flags, "overrides")?.split(",") ?? []
|
|
@@ -8524,6 +8288,13 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8524
8288
|
}
|
|
8525
8289
|
const watchUrl2 = `${origin}/vos/${vosId}`;
|
|
8526
8290
|
const studioUrl2 = `${origin}/studio?vos=${vosId}`;
|
|
8291
|
+
const landed2 = flags.wait === true ? await waitForLanded({
|
|
8292
|
+
origin,
|
|
8293
|
+
key,
|
|
8294
|
+
vosId,
|
|
8295
|
+
versionId: typeof version.id === "string" ? version.id : null,
|
|
8296
|
+
log: (l) => r.log(l)
|
|
8297
|
+
}) : null;
|
|
8527
8298
|
r.done(
|
|
8528
8299
|
{
|
|
8529
8300
|
id: vosId,
|
|
@@ -8531,11 +8302,17 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8531
8302
|
versionNumber: version.versionNumber ?? null,
|
|
8532
8303
|
base: base ?? null,
|
|
8533
8304
|
watchUrl: watchUrl2,
|
|
8534
|
-
studioUrl: studioUrl2
|
|
8305
|
+
studioUrl: studioUrl2,
|
|
8306
|
+
...landed2 ? {
|
|
8307
|
+
still: landed2.still,
|
|
8308
|
+
thumbnailUrl: landed2.thumbnailUrl,
|
|
8309
|
+
previewUrl: landed2.previewUrl
|
|
8310
|
+
} : {}
|
|
8535
8311
|
},
|
|
8536
8312
|
`Pushed version ${String(version.versionNumber ?? "?")} of ${vosId}
|
|
8537
8313
|
watch: ${watchUrl2}
|
|
8538
|
-
studio: ${studioUrl2}`
|
|
8314
|
+
studio: ${studioUrl2}` + (landed2 ? `
|
|
8315
|
+
${landedLines(landed2)}` : "")
|
|
8539
8316
|
);
|
|
8540
8317
|
return EXIT_OK;
|
|
8541
8318
|
}
|
|
@@ -8571,20 +8348,35 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
|
|
|
8571
8348
|
});
|
|
8572
8349
|
const watchUrl = `${origin}/vos/${created.id}`;
|
|
8573
8350
|
const studioUrl = `${origin}/studio?vos=${created.id}`;
|
|
8351
|
+
const landed = flags.wait === true ? await waitForLanded({
|
|
8352
|
+
origin,
|
|
8353
|
+
key,
|
|
8354
|
+
vosId: created.id,
|
|
8355
|
+
versionId: created.currentVersionId,
|
|
8356
|
+
log: (l) => r.log(l)
|
|
8357
|
+
}) : null;
|
|
8574
8358
|
r.done(
|
|
8575
8359
|
{
|
|
8576
8360
|
id: created.id,
|
|
8577
8361
|
slug: created.slug,
|
|
8578
8362
|
title,
|
|
8579
8363
|
visibility: "private",
|
|
8364
|
+
folder: folderRef ?? null,
|
|
8580
8365
|
remixOfId: remixOfId ?? null,
|
|
8581
8366
|
currentVersionId: created.currentVersionId,
|
|
8582
8367
|
watchUrl,
|
|
8583
|
-
studioUrl
|
|
8368
|
+
studioUrl,
|
|
8369
|
+
...landed ? {
|
|
8370
|
+
still: landed.still,
|
|
8371
|
+
thumbnailUrl: landed.thumbnailUrl,
|
|
8372
|
+
previewUrl: landed.previewUrl
|
|
8373
|
+
} : {}
|
|
8584
8374
|
},
|
|
8585
8375
|
`Created private vos ${created.id} (${title})
|
|
8586
8376
|
watch: ${watchUrl}
|
|
8587
8377
|
studio: ${studioUrl}
|
|
8378
|
+
` + (landed ? `${landedLines(landed)}
|
|
8379
|
+
` : "") + ` shelf: ${folderRef ? `in ${folderRef}` : `unfiled, at the root of your shelf (file it: vos folder move ${created.id} --to <slug>)`}
|
|
8588
8380
|
Iterate with: vos push ${source} --vos ${created.id}`
|
|
8589
8381
|
);
|
|
8590
8382
|
return EXIT_OK;
|
|
@@ -8656,10 +8448,10 @@ async function cmdPullProgram(argv) {
|
|
|
8656
8448
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
8657
8449
|
if (cfg.status !== 200)
|
|
8658
8450
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
8659
|
-
const configPath =
|
|
8451
|
+
const configPath = join17(dir, "config.json");
|
|
8660
8452
|
let backedUp = false;
|
|
8661
|
-
if (
|
|
8662
|
-
await writeFile11(
|
|
8453
|
+
if (existsSync15(configPath)) {
|
|
8454
|
+
await writeFile11(join17(dir, "config.backup.json"), await readFile15(configPath));
|
|
8663
8455
|
backedUp = true;
|
|
8664
8456
|
}
|
|
8665
8457
|
let headConfig = cfg.body.config;
|
|
@@ -8692,7 +8484,7 @@ async function cmdPullProgram(argv) {
|
|
|
8692
8484
|
protected: protectedIds,
|
|
8693
8485
|
changes,
|
|
8694
8486
|
out: configPath,
|
|
8695
|
-
backup: backedUp ?
|
|
8487
|
+
backup: backedUp ? join17(dir, "config.backup.json") : null
|
|
8696
8488
|
},
|
|
8697
8489
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
8698
8490
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -8763,15 +8555,18 @@ async function storeKey(key, origin, r) {
|
|
|
8763
8555
|
function isTakeDir(target) {
|
|
8764
8556
|
return directoryKind(target) === "take";
|
|
8765
8557
|
}
|
|
8558
|
+
function soundOf(doc) {
|
|
8559
|
+
return { audio: Array.isArray(doc.audio) ? doc.audio : [] };
|
|
8560
|
+
}
|
|
8766
8561
|
function storedProgramConfig(config, programDoc) {
|
|
8767
8562
|
if (!programDoc) return config;
|
|
8768
8563
|
const doc = { audio: [], ...programDoc };
|
|
8769
8564
|
return lowerProgramDoc(doc).config;
|
|
8770
8565
|
}
|
|
8771
8566
|
async function readProgramDoc(dir, config) {
|
|
8772
|
-
const docPath =
|
|
8773
|
-
if (!
|
|
8774
|
-
const parsed = JSON.parse(await
|
|
8567
|
+
const docPath = join17(dir, "doc.json");
|
|
8568
|
+
if (!existsSync15(docPath)) return null;
|
|
8569
|
+
const parsed = JSON.parse(await readFile15(docPath, "utf8"));
|
|
8775
8570
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
8776
8571
|
return null;
|
|
8777
8572
|
const raw = parsed;
|
|
@@ -8782,14 +8577,15 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
8782
8577
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
8783
8578
|
const { config, ...rest } = program;
|
|
8784
8579
|
const onDisk = { ...hosted, program: rest };
|
|
8785
|
-
await writeFile11(
|
|
8580
|
+
await writeFile11(join17(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
8786
8581
|
return {
|
|
8787
8582
|
config: config && typeof config === "object" ? config : null
|
|
8788
8583
|
};
|
|
8789
8584
|
}
|
|
8790
8585
|
|
|
8791
8586
|
// src/plugin/asset.ts
|
|
8792
|
-
import {
|
|
8587
|
+
import { createHash as createHash3 } from "crypto";
|
|
8588
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
8793
8589
|
import { basename as basename5, extname as extname2 } from "path";
|
|
8794
8590
|
var BOOLEAN_FLAGS3 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8795
8591
|
var MIME_BY_EXT = {
|
|
@@ -8840,10 +8636,38 @@ async function cmdPush(argv) {
|
|
|
8840
8636
|
const folderRef = strFlag(flags, "folder");
|
|
8841
8637
|
const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
|
|
8842
8638
|
const uploaded = [];
|
|
8639
|
+
let unfiledMedia = 0;
|
|
8843
8640
|
for (const file of positionals) {
|
|
8844
8641
|
const name = basename5(file);
|
|
8642
|
+
const body = readFileSync3(file);
|
|
8643
|
+
const media = resolveMediaType({
|
|
8644
|
+
head: body.subarray(0, MEDIA_HEAD_BYTES),
|
|
8645
|
+
filename: name
|
|
8646
|
+
});
|
|
8647
|
+
if (/^(audio|video)\//.test(media.type)) {
|
|
8648
|
+
const sent = nameForType(name, media.type);
|
|
8649
|
+
let put;
|
|
8650
|
+
try {
|
|
8651
|
+
put = await uploadAsset({ origin, key }, new Uint8Array(body), {
|
|
8652
|
+
filename: sent,
|
|
8653
|
+
contentType: media.type,
|
|
8654
|
+
contentHash: createHash3("sha256").update(body).digest("hex"),
|
|
8655
|
+
...folder ? { folderId: folder.id } : {}
|
|
8656
|
+
});
|
|
8657
|
+
} catch (e) {
|
|
8658
|
+
const landed = uploaded.length ? ` (${uploaded.length} of ${positionals.length} landed before it)` : "";
|
|
8659
|
+
throw new Error(
|
|
8660
|
+
`push ${name}${landed}: ${e instanceof Error ? e.message : String(e)}`
|
|
8661
|
+
);
|
|
8662
|
+
}
|
|
8663
|
+
if (!folder && !put.reused) unfiledMedia += 1;
|
|
8664
|
+
uploaded.push({ id: put.id, filename: sent });
|
|
8665
|
+
r.log(
|
|
8666
|
+
`uploaded ${sent} (${put.id})${put.reused ? " \u2014 already on your shelf" : ""} ${put.url}`
|
|
8667
|
+
);
|
|
8668
|
+
continue;
|
|
8669
|
+
}
|
|
8845
8670
|
const mime = MIME_BY_EXT[extname2(name).toLowerCase()] ?? "application/octet-stream";
|
|
8846
|
-
const body = readFileSync4(file);
|
|
8847
8671
|
const form = new FormData();
|
|
8848
8672
|
form.set("file", new Blob([body], { type: mime }), name);
|
|
8849
8673
|
if (folder) form.set("folderId", folder.id);
|
|
@@ -8877,7 +8701,8 @@ async function cmdPush(argv) {
|
|
|
8877
8701
|
count: uploaded.length,
|
|
8878
8702
|
folder: folder?.slug ?? null
|
|
8879
8703
|
},
|
|
8880
|
-
`pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}`
|
|
8704
|
+
`pushed ${uploaded.length} asset${uploaded.length === 1 ? "" : "s"}${folder ? ` into ${folder.slug}` : ""}` + (unfiledMedia ? `
|
|
8705
|
+
note: an unfiled sound or video that no document uses is removed after 7 days. Reference it from a doc (doc.audio key = its url) or push it with --folder <slug>` : "")
|
|
8881
8706
|
);
|
|
8882
8707
|
return EXIT_OK;
|
|
8883
8708
|
}
|
|
@@ -8896,10 +8721,69 @@ async function cmdAsset(argv) {
|
|
|
8896
8721
|
}
|
|
8897
8722
|
}
|
|
8898
8723
|
|
|
8724
|
+
// src/plugin/remove.ts
|
|
8725
|
+
import { existsSync as existsSync16, rmSync as rmSync2, statSync as statSync4 } from "fs";
|
|
8726
|
+
import { join as join18 } from "path";
|
|
8727
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
8728
|
+
var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help", "yes"]);
|
|
8729
|
+
async function cmdDelete(argv) {
|
|
8730
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
|
|
8731
|
+
const target = positionals[0];
|
|
8732
|
+
if (!target)
|
|
8733
|
+
throw new UsageError("vos delete <vosId|watch-url|dir> [--yes] [--json]");
|
|
8734
|
+
const r = createReporter(flags.json === true);
|
|
8735
|
+
const origin = platformOrigin({
|
|
8736
|
+
origin: strFlag(flags, "origin"),
|
|
8737
|
+
api: strFlag(flags, "api")
|
|
8738
|
+
}).replace(/\/+$/, "");
|
|
8739
|
+
const key = requireCredential(strFlag(flags, "key"));
|
|
8740
|
+
const dir = existsSync16(target) && statSync4(target).isDirectory() ? target : null;
|
|
8741
|
+
const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
|
|
8742
|
+
if (!vosId)
|
|
8743
|
+
throw new UsageError(
|
|
8744
|
+
`${target} tracks no vos (no ${SYNC_STATE_NAME}): pass the vos id or its watch URL`
|
|
8745
|
+
);
|
|
8746
|
+
const meta = await apiJson(origin, `/api/vos/${vosId}`, { key });
|
|
8747
|
+
if (meta.status !== 200) throw new Error(apiError(`read ${vosId}`, meta));
|
|
8748
|
+
const vos = meta.body.vos ?? {};
|
|
8749
|
+
const title = typeof vos.title === "string" ? vos.title : vosId;
|
|
8750
|
+
if (flags.yes !== true) {
|
|
8751
|
+
if (!process.stdin.isTTY)
|
|
8752
|
+
throw new UsageError(
|
|
8753
|
+
`deleting "${title}" (${vosId}) cannot be undone: re-run with --yes to confirm`
|
|
8754
|
+
);
|
|
8755
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
8756
|
+
const answer = await rl.question(
|
|
8757
|
+
`Delete "${title}" (${vosId}) and every version of it? This cannot be undone. [y/N] `
|
|
8758
|
+
);
|
|
8759
|
+
rl.close();
|
|
8760
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
8761
|
+
r.done({ id: vosId, deleted: false }, "kept \u2014 nothing was deleted");
|
|
8762
|
+
return EXIT_OK;
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8765
|
+
const res = await apiJson(origin, `/api/vos/${vosId}`, {
|
|
8766
|
+
method: "DELETE",
|
|
8767
|
+
key
|
|
8768
|
+
});
|
|
8769
|
+
if (res.status !== 200 && res.status !== 204)
|
|
8770
|
+
throw new Error(apiError(`delete ${vosId}`, res));
|
|
8771
|
+
let unlinked = false;
|
|
8772
|
+
if (dir) {
|
|
8773
|
+
rmSync2(join18(dir, SYNC_STATE_NAME), { force: true });
|
|
8774
|
+
unlinked = true;
|
|
8775
|
+
}
|
|
8776
|
+
r.done(
|
|
8777
|
+
{ id: vosId, title, deleted: true, unlinked },
|
|
8778
|
+
`deleted "${title}" (${vosId})${unlinked ? `; ${dir} no longer tracks it` : ""}`
|
|
8779
|
+
);
|
|
8780
|
+
return EXIT_OK;
|
|
8781
|
+
}
|
|
8782
|
+
|
|
8899
8783
|
// src/plugin/recipe.ts
|
|
8900
|
-
import { readFileSync as
|
|
8784
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
8901
8785
|
import { basename as basename6 } from "path";
|
|
8902
|
-
var
|
|
8786
|
+
var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
8903
8787
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
8904
8788
|
function recipeFilename(file) {
|
|
8905
8789
|
const name = basename6(file);
|
|
@@ -8909,7 +8793,7 @@ function recipeFilename(file) {
|
|
|
8909
8793
|
return name;
|
|
8910
8794
|
}
|
|
8911
8795
|
async function cmdPush2(argv) {
|
|
8912
|
-
const { positionals, flags } = parseArgs(argv,
|
|
8796
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
8913
8797
|
const [file] = positionals;
|
|
8914
8798
|
const folderRef = strFlag(flags, "folder");
|
|
8915
8799
|
const assetId = strFlag(flags, "asset");
|
|
@@ -8917,7 +8801,7 @@ async function cmdPush2(argv) {
|
|
|
8917
8801
|
throw new UsageError(USAGE);
|
|
8918
8802
|
}
|
|
8919
8803
|
const filename = recipeFilename(file);
|
|
8920
|
-
const body =
|
|
8804
|
+
const body = readFileSync4(file);
|
|
8921
8805
|
const r = createReporter(flags.json === true);
|
|
8922
8806
|
const origin = platformOrigin({
|
|
8923
8807
|
origin: strFlag(flags, "origin"),
|
|
@@ -9938,8 +9822,8 @@ async function cmdActions(argv) {
|
|
|
9938
9822
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
9939
9823
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
9940
9824
|
}
|
|
9941
|
-
const { readFile:
|
|
9942
|
-
const text = await
|
|
9825
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
9826
|
+
const text = await readFile18(resolve8(input), "utf8");
|
|
9943
9827
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
9944
9828
|
for (const p of problems) r.log(` ${p}`);
|
|
9945
9829
|
const result = convertAgentBrowser(records, {
|
|
@@ -9976,9 +9860,9 @@ function summary(result) {
|
|
|
9976
9860
|
}
|
|
9977
9861
|
async function cmdActionsScript(input, flags) {
|
|
9978
9862
|
const r = createReporter(flags.json === true);
|
|
9979
|
-
const { readFile:
|
|
9863
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
9980
9864
|
const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
|
|
9981
|
-
const raw = JSON.parse(await
|
|
9865
|
+
const raw = JSON.parse(await readFile18(resolve8(input), "utf8"));
|
|
9982
9866
|
const errors = validateActions(raw);
|
|
9983
9867
|
if (errors.length) throw new UsageError(`${input}:
|
|
9984
9868
|
${errors.join("\n ")}`);
|
|
@@ -9989,7 +9873,7 @@ async function cmdActionsScript(input, flags) {
|
|
|
9989
9873
|
|
|
9990
9874
|
// src/plugin/ingest.ts
|
|
9991
9875
|
import { existsSync as existsSync17 } from "fs";
|
|
9992
|
-
import { copyFile, mkdir as mkdir10, readFile as
|
|
9876
|
+
import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
|
|
9993
9877
|
import { basename as basename7, join as join19, resolve as resolve9 } from "path";
|
|
9994
9878
|
import { inflateRawSync } from "zlib";
|
|
9995
9879
|
import {
|
|
@@ -10010,7 +9894,7 @@ import {
|
|
|
10010
9894
|
Output,
|
|
10011
9895
|
WebMOutputFormat
|
|
10012
9896
|
} from "mediabunny";
|
|
10013
|
-
var
|
|
9897
|
+
var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
10014
9898
|
async function probeVideoFile(path) {
|
|
10015
9899
|
const input = new Input({
|
|
10016
9900
|
source: new FilePathSource(path),
|
|
@@ -10078,7 +9962,7 @@ async function readTraceFile(path, offsetMs) {
|
|
|
10078
9962
|
);
|
|
10079
9963
|
}
|
|
10080
9964
|
if (kind === "zip") {
|
|
10081
|
-
const bytes = new Uint8Array(await
|
|
9965
|
+
const bytes = new Uint8Array(await readFile16(path));
|
|
10082
9966
|
const entry = playwrightTraceEntry(zipEntries(bytes));
|
|
10083
9967
|
if (!entry) {
|
|
10084
9968
|
throw new UsageError(
|
|
@@ -10091,7 +9975,7 @@ async function readTraceFile(path, offsetMs) {
|
|
|
10091
9975
|
);
|
|
10092
9976
|
return cursorFromPlaywrightTrace(text2, { offsetMs });
|
|
10093
9977
|
}
|
|
10094
|
-
const text = await
|
|
9978
|
+
const text = await readFile16(path, "utf8");
|
|
10095
9979
|
return cursorFromTrace(basename7(path), text, { offsetMs });
|
|
10096
9980
|
}
|
|
10097
9981
|
function parseViewport(v) {
|
|
@@ -10101,11 +9985,11 @@ function parseViewport(v) {
|
|
|
10101
9985
|
return { width: Number(m[1]), height: Number(m[2]) };
|
|
10102
9986
|
}
|
|
10103
9987
|
async function cmdIngest(argv) {
|
|
10104
|
-
const { positionals, flags } = parseArgs(argv,
|
|
9988
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS6, MULTI_FLAGS2);
|
|
10105
9989
|
const source = positionals[0];
|
|
10106
9990
|
if (!source)
|
|
10107
9991
|
throw new UsageError(
|
|
10108
|
-
"vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
|
|
9992
|
+
"vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
|
|
10109
9993
|
);
|
|
10110
9994
|
const videoPath = resolve9(source);
|
|
10111
9995
|
if (!existsSync17(videoPath)) throw new UsageError(`${source}: no such file`);
|
|
@@ -10114,7 +9998,15 @@ async function cmdIngest(argv) {
|
|
|
10114
9998
|
const offsetMs = numFlag(flags, "offset", 0);
|
|
10115
9999
|
const viewportFlag = parseViewport(strFlag(flags, "viewport"));
|
|
10116
10000
|
const cursorPath = strFlag(flags, "cursor");
|
|
10117
|
-
const
|
|
10001
|
+
const asFlag = strFlag(flags, "as");
|
|
10002
|
+
if (asFlag !== void 0 && asFlag !== "footage" && asFlag !== "take")
|
|
10003
|
+
throw new UsageError("--as expects footage or take");
|
|
10004
|
+
const footage = asFlag ? asFlag === "footage" : !cursorPath;
|
|
10005
|
+
if (footage && strFlag(flags, "background") !== void 0)
|
|
10006
|
+
r.log(
|
|
10007
|
+
"note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
|
|
10008
|
+
);
|
|
10009
|
+
const backdrop = footage ? null : await takeBackdrop(flags, r);
|
|
10118
10010
|
r.log(`probing ${basename7(videoPath)}\u2026`);
|
|
10119
10011
|
const probed = await probeVideoFile(videoPath);
|
|
10120
10012
|
if (!(probed.durationMs > 0)) {
|
|
@@ -10160,12 +10052,13 @@ async function cmdIngest(argv) {
|
|
|
10160
10052
|
};
|
|
10161
10053
|
await writeJson(paths.meta, meta, true);
|
|
10162
10054
|
if (events.length) await writeJson(paths.cursor, events);
|
|
10163
|
-
const plan = await planTake(outDir, { backdrop });
|
|
10055
|
+
const plan = await planTake(outDir, { backdrop, footage });
|
|
10164
10056
|
const clicks = events.filter((e) => e.type === "down").length;
|
|
10165
10057
|
const seconds = (probed.durationMs / 1e3).toFixed(1);
|
|
10166
10058
|
const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
|
|
10167
10059
|
const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename7(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
|
|
10168
|
-
const
|
|
10060
|
+
const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
|
|
10061
|
+
const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
|
|
10169
10062
|
r.done(
|
|
10170
10063
|
{
|
|
10171
10064
|
out: outDir,
|
|
@@ -10187,9 +10080,11 @@ async function cmdIngest(argv) {
|
|
|
10187
10080
|
notes: trace.notes
|
|
10188
10081
|
} : null,
|
|
10189
10082
|
zooms: plan.zoomAuto,
|
|
10190
|
-
backdrop: plan.backdrop ?? null
|
|
10083
|
+
backdrop: plan.backdrop ?? null,
|
|
10084
|
+
as: footage ? "footage" : "take"
|
|
10191
10085
|
},
|
|
10192
10086
|
`Ingested ${basename7(videoPath)} \u2192 ${outDir}/${basename7(recording)} (${facts})
|
|
10087
|
+
${opened}
|
|
10193
10088
|
${cursorLine}
|
|
10194
10089
|
${next}`
|
|
10195
10090
|
);
|
|
@@ -10201,17 +10096,17 @@ import { spawnSync } from "child_process";
|
|
|
10201
10096
|
import {
|
|
10202
10097
|
cpSync,
|
|
10203
10098
|
existsSync as existsSync19,
|
|
10204
|
-
mkdirSync as
|
|
10205
|
-
readFileSync as
|
|
10206
|
-
rmSync as
|
|
10207
|
-
writeFileSync as
|
|
10099
|
+
mkdirSync as mkdirSync2,
|
|
10100
|
+
readFileSync as readFileSync6,
|
|
10101
|
+
rmSync as rmSync3,
|
|
10102
|
+
writeFileSync as writeFileSync2
|
|
10208
10103
|
} from "fs";
|
|
10209
|
-
import { homedir as
|
|
10104
|
+
import { homedir as homedir2 } from "os";
|
|
10210
10105
|
import { join as join21 } from "path";
|
|
10211
10106
|
import { chromium as chromium2 } from "playwright";
|
|
10212
10107
|
|
|
10213
10108
|
// src/plugin/machine.ts
|
|
10214
|
-
import { existsSync as existsSync18, readFileSync as
|
|
10109
|
+
import { existsSync as existsSync18, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
10215
10110
|
import { dirname as dirname5, join as join20 } from "path";
|
|
10216
10111
|
var AGENT_NAMES = [
|
|
10217
10112
|
"claude",
|
|
@@ -10428,10 +10323,10 @@ function bundledSkillsDir(from) {
|
|
|
10428
10323
|
const pkg = join20(dir, "package.json");
|
|
10429
10324
|
if (existsSync18(pkg)) {
|
|
10430
10325
|
try {
|
|
10431
|
-
const name = JSON.parse(
|
|
10326
|
+
const name = JSON.parse(readFileSync5(pkg, "utf8")).name;
|
|
10432
10327
|
if (name === "@vosjs/cli") {
|
|
10433
10328
|
const skills = join20(dir, "skills");
|
|
10434
|
-
return existsSync18(skills) &&
|
|
10329
|
+
return existsSync18(skills) && statSync5(skills).isDirectory() ? skills : null;
|
|
10435
10330
|
}
|
|
10436
10331
|
} catch {
|
|
10437
10332
|
}
|
|
@@ -10444,7 +10339,7 @@ function bundledSkillsDir(from) {
|
|
|
10444
10339
|
}
|
|
10445
10340
|
|
|
10446
10341
|
// src/plugin/machineCmd.ts
|
|
10447
|
-
var
|
|
10342
|
+
var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set([
|
|
10448
10343
|
"json",
|
|
10449
10344
|
"help",
|
|
10450
10345
|
"global",
|
|
@@ -10522,7 +10417,7 @@ async function doctorFacts(opts) {
|
|
|
10522
10417
|
};
|
|
10523
10418
|
}
|
|
10524
10419
|
async function cmdSetup(argv) {
|
|
10525
|
-
const { flags } = parseArgs(argv,
|
|
10420
|
+
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10526
10421
|
const r = reporterFor(flags);
|
|
10527
10422
|
const root = process.cwd();
|
|
10528
10423
|
const global = flags.global === true;
|
|
@@ -10537,7 +10432,7 @@ async function cmdSetup(argv) {
|
|
|
10537
10432
|
if ("error" in v) throw new UsageError(v.error);
|
|
10538
10433
|
homes = v.homes;
|
|
10539
10434
|
} else {
|
|
10540
|
-
homes = detectAgents(root, { home:
|
|
10435
|
+
homes = detectAgents(root, { home: homedir2(), global });
|
|
10541
10436
|
}
|
|
10542
10437
|
const agents = [...new Set(homes.map((h) => h.agent))];
|
|
10543
10438
|
r.event({ event: "agents", agents, global });
|
|
@@ -10573,7 +10468,7 @@ async function cmdSetup(argv) {
|
|
|
10573
10468
|
const targets = homes.length ? homes : [fallbackSkillsHome(root)];
|
|
10574
10469
|
for (const home of targets) {
|
|
10575
10470
|
const dest = skillsDirFor(home);
|
|
10576
|
-
|
|
10471
|
+
mkdirSync2(dest, { recursive: true });
|
|
10577
10472
|
for (const name of installedSkills(bundled)) {
|
|
10578
10473
|
cpSync(join21(bundled, name), join21(dest, name), { recursive: true });
|
|
10579
10474
|
}
|
|
@@ -10610,9 +10505,9 @@ async function cmdSetup(argv) {
|
|
|
10610
10505
|
let rules = null;
|
|
10611
10506
|
if (!noRules) {
|
|
10612
10507
|
const file = rulesFileFor(root);
|
|
10613
|
-
const existing = existsSync19(file) ?
|
|
10508
|
+
const existing = existsSync19(file) ? readFileSync6(file, "utf8") : null;
|
|
10614
10509
|
const { text, changed } = applyRulesBlock(existing);
|
|
10615
|
-
if (changed)
|
|
10510
|
+
if (changed) writeFileSync2(file, text);
|
|
10616
10511
|
rules = { file, changed };
|
|
10617
10512
|
r.log(
|
|
10618
10513
|
changed ? `rules: wrote the vosso block into ${file}` : `rules: ${file} already carries the block`
|
|
@@ -10641,10 +10536,10 @@ Next: ${report.next_step}`
|
|
|
10641
10536
|
return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
|
|
10642
10537
|
}
|
|
10643
10538
|
async function cmdDoctor(argv) {
|
|
10644
|
-
const { flags } = parseArgs(argv,
|
|
10539
|
+
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10645
10540
|
const r = reporterFor(flags);
|
|
10646
10541
|
const root = process.cwd();
|
|
10647
|
-
const homes = detectAgents(root, { home:
|
|
10542
|
+
const homes = detectAgents(root, { home: homedir2(), global: true });
|
|
10648
10543
|
const report = doctorReport(
|
|
10649
10544
|
await doctorFacts({
|
|
10650
10545
|
homes: homes.length ? homes : [fallbackSkillsHome(root)],
|
|
@@ -10660,7 +10555,7 @@ Next: ${report.next_step}`
|
|
|
10660
10555
|
return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
|
|
10661
10556
|
}
|
|
10662
10557
|
async function cmdWhoami(argv) {
|
|
10663
|
-
const { flags } = parseArgs(argv,
|
|
10558
|
+
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10664
10559
|
const r = reporterFor(flags);
|
|
10665
10560
|
const origin = platformOrigin({
|
|
10666
10561
|
origin: strFlag(flags, "origin"),
|
|
@@ -10722,10 +10617,10 @@ async function cmdWhoami(argv) {
|
|
|
10722
10617
|
return EXIT_OK;
|
|
10723
10618
|
}
|
|
10724
10619
|
function cmdLogout(argv) {
|
|
10725
|
-
const { flags } = parseArgs(argv,
|
|
10620
|
+
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10726
10621
|
const r = reporterFor(flags);
|
|
10727
10622
|
const had = existsSync19(CREDENTIALS_PATH);
|
|
10728
|
-
|
|
10623
|
+
rmSync3(CREDENTIALS_PATH, { force: true });
|
|
10729
10624
|
const envKey = Boolean(process.env.VOS_API_KEY?.trim());
|
|
10730
10625
|
r.done(
|
|
10731
10626
|
{
|
|
@@ -10740,9 +10635,10 @@ function cmdLogout(argv) {
|
|
|
10740
10635
|
}
|
|
10741
10636
|
|
|
10742
10637
|
// src/plugin/run.ts
|
|
10743
|
-
var
|
|
10638
|
+
var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set([
|
|
10744
10639
|
"json",
|
|
10745
10640
|
"help",
|
|
10641
|
+
"wait",
|
|
10746
10642
|
"picture",
|
|
10747
10643
|
"fresh",
|
|
10748
10644
|
"reuse",
|
|
@@ -10783,8 +10679,8 @@ Take pipeline
|
|
|
10783
10679
|
vos validate <actions.json|take|kit.json> [--picture] [--json]
|
|
10784
10680
|
vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
|
|
10785
10681
|
vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
|
|
10786
|
-
vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
|
|
10787
|
-
a take from a recording someone else made (a cloud agent's PR video, a Playwright run's video, a Loom export): the file becomes recording.<container>, meta.json from its own dimensions and duration, and a trace beside it becomes cursor.json so the planner has clicks to zoom on; without one, nothing is planned and the done event says so
|
|
10682
|
+
vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
|
|
10683
|
+
a take from a recording someone else made (a cloud agent's PR video, a Playwright run's video, a Loom export): the file becomes recording.<container>, meta.json from its own dimensions and duration, and a trace beside it becomes cursor.json so the planner has clicks to zoom on; without one, nothing is planned and the done event says so. Without a trace the file opens as FINISHED footage (a render from HyperFrames or Remotion, a film): no card, no browser bar, no drawn cursor, its own frame rate; --as take gives a screen demo the card
|
|
10788
10684
|
vos session open <url> --name <app> a plain Chrome window on a vos-owned profile; sign in, close it
|
|
10789
10685
|
vos session check <name> [--url <url>] headless: does the session still open the page? exit 0/4
|
|
10790
10686
|
vos session list | rm <name> what exists and how old; delete one
|
|
@@ -10820,7 +10716,7 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
|
|
|
10820
10716
|
vos push <config.json|take> [--vos id] [--title t] [--slug s] [--remix-of id]
|
|
10821
10717
|
[--desc d] [--tags a,b] [--folder <folderId|slug>]
|
|
10822
10718
|
[--note n] [--label l] [--base versionId] [--override <id>]... [--yes] [--json]
|
|
10823
|
-
[--claimable]
|
|
10719
|
+
[--claimable] [--wait]
|
|
10824
10720
|
a take DIRECTORY (doc.json) pushes recording + doc; a config.json
|
|
10825
10721
|
pushes the program. No --vos: create a PRIVATE vos; with --vos:
|
|
10826
10722
|
add a version against the tracked base \u2014 a stale push 409s WITH
|
|
@@ -10832,6 +10728,14 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
|
|
|
10832
10728
|
user and nowhere else; unclaimed work is deleted after 72h.
|
|
10833
10729
|
--override consents to touching
|
|
10834
10730
|
protected (human-edited) nodes, ONLY when the user asked.
|
|
10731
|
+
Without --folder a new vos lands unfiled, at the root of your
|
|
10732
|
+
shelf. A document's local sound (doc.json audio keys) uploads
|
|
10733
|
+
with the push. --wait stays until the version's still has
|
|
10734
|
+
rendered and prints absolute still and preview links.
|
|
10735
|
+
vos delete <vosId|watch-url|dir> [--yes] [--json]
|
|
10736
|
+
take a vos off vos.so, every version of it, for good. Asks on a
|
|
10737
|
+
terminal; headless it needs --yes. A dir that tracked it is
|
|
10738
|
+
unlinked (its vos.json removed)
|
|
10835
10739
|
vos pull [dir|take] [--vos <id>] [--since versionId] [--check] [--media] [--json]
|
|
10836
10740
|
what changed on vos.so since your base; syncs config.json (backup
|
|
10837
10741
|
kept) or the take's doc.json. ALWAYS pull before editing pushed work.
|
|
@@ -10853,7 +10757,10 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
|
|
|
10853
10757
|
vos asset push <file...> [--folder <folderId|slug>]
|
|
10854
10758
|
upload files onto your shelf (a key takes models .glb/.gltf,
|
|
10855
10759
|
recipes .md and images .png/.jpg/.webp/.gif \u2014 posters and
|
|
10856
|
-
store stills file into the release's project; 40 images/24h
|
|
10760
|
+
store stills file into the release's project; 40 images/24h \u2014
|
|
10761
|
+
and sound and video, which land in Sound > Uploads and print
|
|
10762
|
+
the url a doc.json audio key takes; an unfiled one that no
|
|
10763
|
+
document uses is removed after 7 days, so pass --folder)
|
|
10857
10764
|
vos recipe push <file.md> --folder <folderId|slug>
|
|
10858
10765
|
vos recipe push <file.md> --asset <assetId>
|
|
10859
10766
|
put a recipe (a plain .md) on the shelf: --folder creates it
|
|
@@ -11042,7 +10949,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
11042
10949
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
11043
10950
|
`;
|
|
11044
10951
|
async function loadActions(file) {
|
|
11045
|
-
const raw = JSON.parse(await
|
|
10952
|
+
const raw = JSON.parse(await readFile17(file, "utf8"));
|
|
11046
10953
|
const errors = validateActions(raw);
|
|
11047
10954
|
if (errors.length)
|
|
11048
10955
|
throw new UsageError(`invalid actions file:
|
|
@@ -11174,7 +11081,7 @@ function wallGate(flags, r, opts) {
|
|
|
11174
11081
|
async function cmdRecord(argv) {
|
|
11175
11082
|
const { positionals, flags, multi } = parseArgs(
|
|
11176
11083
|
argv,
|
|
11177
|
-
|
|
11084
|
+
BOOLEAN_FLAGS8,
|
|
11178
11085
|
MULTI_FLAGS2
|
|
11179
11086
|
);
|
|
11180
11087
|
const actionsPath = strFlag(flags, "actions") ?? positionals[0];
|
|
@@ -11416,7 +11323,7 @@ ${lines.join("\n")}
|
|
|
11416
11323
|
async function cmdCreate2(argv) {
|
|
11417
11324
|
const { positionals, flags, multi } = parseArgs(
|
|
11418
11325
|
argv,
|
|
11419
|
-
|
|
11326
|
+
BOOLEAN_FLAGS8,
|
|
11420
11327
|
MULTI_FLAGS2
|
|
11421
11328
|
);
|
|
11422
11329
|
const actionsPath = strFlag(flags, "actions");
|
|
@@ -11431,10 +11338,7 @@ async function cmdCreate2(argv) {
|
|
|
11431
11338
|
if (!url)
|
|
11432
11339
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
11433
11340
|
const outDir = resolve10(strFlag(flags, "out") ?? "take");
|
|
11434
|
-
const
|
|
11435
|
-
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11436
|
-
throw new UsageError("--format must be webm or mp4");
|
|
11437
|
-
const format = fmtRaw;
|
|
11341
|
+
const format = videoFormat(strFlag(flags, "format"), positionals[0]);
|
|
11438
11342
|
const out = positionals[0] ?? join22(outDir, `out.${format}`);
|
|
11439
11343
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11440
11344
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
@@ -11545,7 +11449,7 @@ async function cmdCreate2(argv) {
|
|
|
11545
11449
|
async function cmdPlan(argv) {
|
|
11546
11450
|
const { positionals, flags, multi } = parseArgs(
|
|
11547
11451
|
argv,
|
|
11548
|
-
|
|
11452
|
+
BOOLEAN_FLAGS8,
|
|
11549
11453
|
/* @__PURE__ */ new Set(["with"])
|
|
11550
11454
|
);
|
|
11551
11455
|
const dir = positionals[0];
|
|
@@ -11568,7 +11472,7 @@ async function cmdPlan(argv) {
|
|
|
11568
11472
|
}
|
|
11569
11473
|
reuse = {
|
|
11570
11474
|
from,
|
|
11571
|
-
doc: JSON.parse(await
|
|
11475
|
+
doc: JSON.parse(await readFile17(from, "utf8"))
|
|
11572
11476
|
};
|
|
11573
11477
|
}
|
|
11574
11478
|
const style = await resolveStyleRef(flags);
|
|
@@ -11743,7 +11647,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
|
|
|
11743
11647
|
async function cmdRender(argv) {
|
|
11744
11648
|
const { positionals, flags, multi } = parseArgs(
|
|
11745
11649
|
argv,
|
|
11746
|
-
|
|
11650
|
+
BOOLEAN_FLAGS8,
|
|
11747
11651
|
MULTI_FLAGS2
|
|
11748
11652
|
);
|
|
11749
11653
|
const dir = positionals[0];
|
|
@@ -11752,10 +11656,7 @@ async function cmdRender(argv) {
|
|
|
11752
11656
|
"vos render <take> [out] [--width] [--height] [--fps] [--format] [--parallel]"
|
|
11753
11657
|
);
|
|
11754
11658
|
const r = createReporter(flags.json === true);
|
|
11755
|
-
const
|
|
11756
|
-
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
11757
|
-
throw new UsageError("--format must be webm or mp4");
|
|
11758
|
-
const format = fmtRaw;
|
|
11659
|
+
const format = videoFormat(strFlag(flags, "format"), positionals[1]);
|
|
11759
11660
|
const out = positionals[1] ?? join22(dir, `out.${format}`);
|
|
11760
11661
|
const parallel = numFlag(flags, "parallel", 1);
|
|
11761
11662
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
@@ -11814,7 +11715,7 @@ async function cmdRender(argv) {
|
|
|
11814
11715
|
async function cmdFrames(argv) {
|
|
11815
11716
|
const { positionals, flags, multi } = parseArgs(
|
|
11816
11717
|
argv,
|
|
11817
|
-
|
|
11718
|
+
BOOLEAN_FLAGS8,
|
|
11818
11719
|
MULTI_FLAGS2
|
|
11819
11720
|
);
|
|
11820
11721
|
const dir = positionals[0];
|
|
@@ -11887,7 +11788,7 @@ async function cmdFrames(argv) {
|
|
|
11887
11788
|
async function cmdDeliver(argv) {
|
|
11888
11789
|
const { positionals, flags, multi } = parseArgs(
|
|
11889
11790
|
argv,
|
|
11890
|
-
|
|
11791
|
+
BOOLEAN_FLAGS8,
|
|
11891
11792
|
MULTI_FLAGS2
|
|
11892
11793
|
);
|
|
11893
11794
|
const dir = positionals[0];
|
|
@@ -12011,7 +11912,7 @@ async function resolveDocRef(ref, flags, what) {
|
|
|
12011
11912
|
return {
|
|
12012
11913
|
from: file,
|
|
12013
11914
|
doc: migrateHostedDoc5(
|
|
12014
|
-
JSON.parse(await
|
|
11915
|
+
JSON.parse(await readFile17(file, "utf8"))
|
|
12015
11916
|
)
|
|
12016
11917
|
};
|
|
12017
11918
|
}
|
|
@@ -12053,7 +11954,7 @@ async function officialTemplate(origin, ref) {
|
|
|
12053
11954
|
}
|
|
12054
11955
|
}
|
|
12055
11956
|
async function cmdDigest(argv) {
|
|
12056
|
-
const { positionals, flags } = parseArgs(argv,
|
|
11957
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12057
11958
|
const dir = positionals[0];
|
|
12058
11959
|
if (!dir)
|
|
12059
11960
|
throw new UsageError(
|
|
@@ -12063,7 +11964,7 @@ async function cmdDigest(argv) {
|
|
|
12063
11964
|
const take = await loadTake(dir);
|
|
12064
11965
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
12065
11966
|
const transcriptPath = strFlag(flags, "transcript");
|
|
12066
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
11967
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile17(transcriptPath, "utf8"))) : null;
|
|
12067
11968
|
const style = await resolveStyleRef(flags);
|
|
12068
11969
|
const noFrames = flags["no-frames"] === true;
|
|
12069
11970
|
let browser = null;
|
|
@@ -12117,7 +12018,7 @@ async function cmdDigest(argv) {
|
|
|
12117
12018
|
}
|
|
12118
12019
|
}
|
|
12119
12020
|
async function cmdOpen(argv) {
|
|
12120
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12021
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12121
12022
|
const dir = positionals[0];
|
|
12122
12023
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
12123
12024
|
const r = createReporter(flags.json === true);
|
|
@@ -12155,7 +12056,7 @@ async function cmdOpen(argv) {
|
|
|
12155
12056
|
return EXIT_OK;
|
|
12156
12057
|
}
|
|
12157
12058
|
async function cmdJudge(argv) {
|
|
12158
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12059
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12159
12060
|
const target = positionals[0];
|
|
12160
12061
|
const against = strFlag(flags, "against");
|
|
12161
12062
|
if (!target || !against)
|
|
@@ -12168,7 +12069,7 @@ async function cmdJudge(argv) {
|
|
|
12168
12069
|
if (!existsSync20(against))
|
|
12169
12070
|
throw new UsageError(`${against}: no reference manifest`);
|
|
12170
12071
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
12171
|
-
const verdicts = JSON.parse(await
|
|
12072
|
+
const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
|
|
12172
12073
|
const rate = winRate(verdicts);
|
|
12173
12074
|
const lines = result.sheets.map(
|
|
12174
12075
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -12184,7 +12085,7 @@ Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judg
|
|
|
12184
12085
|
return EXIT_OK;
|
|
12185
12086
|
}
|
|
12186
12087
|
async function cmdCallout(argv) {
|
|
12187
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12088
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12188
12089
|
const dir = positionals[0];
|
|
12189
12090
|
const shape = positionals[1];
|
|
12190
12091
|
if (!dir || !shape || !isCalloutShape(shape))
|
|
@@ -12208,7 +12109,7 @@ async function cmdCallout(argv) {
|
|
|
12208
12109
|
`no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
|
|
12209
12110
|
);
|
|
12210
12111
|
const codeFile = strFlag(flags, "code-file");
|
|
12211
|
-
const code = codeFile ? await
|
|
12112
|
+
const code = codeFile ? await readFile17(codeFile, "utf8") : strFlag(flags, "code");
|
|
12212
12113
|
const words = {
|
|
12213
12114
|
kicker: strFlag(flags, "kicker"),
|
|
12214
12115
|
title: strFlag(flags, "title"),
|
|
@@ -12283,7 +12184,7 @@ async function cmdCallout(argv) {
|
|
|
12283
12184
|
return EXIT_OK;
|
|
12284
12185
|
}
|
|
12285
12186
|
async function cmdValidate(argv) {
|
|
12286
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12187
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12287
12188
|
const target = positionals[0];
|
|
12288
12189
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
12289
12190
|
const r = createReporter(flags.json === true);
|
|
@@ -12321,7 +12222,7 @@ async function cmdValidate(argv) {
|
|
|
12321
12222
|
}
|
|
12322
12223
|
if (!isTakeDir(target) && existsSync20(join22(target, "config.json"))) {
|
|
12323
12224
|
const parsed = JSON.parse(
|
|
12324
|
-
await
|
|
12225
|
+
await readFile17(join22(target, "config.json"), "utf8")
|
|
12325
12226
|
);
|
|
12326
12227
|
const pre = preflightConfig(parsed);
|
|
12327
12228
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -12445,7 +12346,7 @@ async function cmdValidate(argv) {
|
|
|
12445
12346
|
async function cmdPush3(argv) {
|
|
12446
12347
|
const { positionals, flags, multi } = parseArgs(
|
|
12447
12348
|
argv,
|
|
12448
|
-
|
|
12349
|
+
BOOLEAN_FLAGS8,
|
|
12449
12350
|
MULTI_FLAGS2
|
|
12450
12351
|
);
|
|
12451
12352
|
const dir = positionals[0];
|
|
@@ -12483,16 +12384,33 @@ async function cmdPush3(argv) {
|
|
|
12483
12384
|
origin: strFlag(flags, "origin"),
|
|
12484
12385
|
api: strFlag(flags, "api")
|
|
12485
12386
|
}).replace(/\/+$/, "");
|
|
12387
|
+
const landed = flags.wait === true ? await waitForLanded({
|
|
12388
|
+
origin: pushedTo,
|
|
12389
|
+
key: requireCredential(strFlag(flags, "key")),
|
|
12390
|
+
vosId: result.vosId,
|
|
12391
|
+
versionId: result.versionId ?? null,
|
|
12392
|
+
log: (l) => r.log(l)
|
|
12393
|
+
}) : null;
|
|
12394
|
+
const filed = result.versionNumber === 1 ? `
|
|
12395
|
+
shelf: ${strFlag(flags, "folder") ? `in ${strFlag(flags, "folder")}` : `unfiled, at the root of your shelf (file it: vos folder move ${result.vosId} --to <slug>)`}` : "";
|
|
12486
12396
|
r.done(
|
|
12487
|
-
{
|
|
12397
|
+
{
|
|
12398
|
+
...result,
|
|
12399
|
+
...landed ? {
|
|
12400
|
+
still: landed.still,
|
|
12401
|
+
thumbnailUrl: landed.thumbnailUrl,
|
|
12402
|
+
previewUrl: landed.previewUrl
|
|
12403
|
+
} : {}
|
|
12404
|
+
},
|
|
12488
12405
|
`pushed v${result.versionNumber} \u2192 vos ${result.vosId}
|
|
12489
12406
|
review: ${pushedTo}/vos/${result.vosId}
|
|
12490
|
-
studio: ${pushedTo}/studio?vos=${result.vosId}`
|
|
12407
|
+
studio: ${pushedTo}/studio?vos=${result.vosId}` + filed + (landed ? `
|
|
12408
|
+
${landedLines(landed)}` : "")
|
|
12491
12409
|
);
|
|
12492
12410
|
return EXIT_OK;
|
|
12493
12411
|
}
|
|
12494
12412
|
async function cmdPull2(argv) {
|
|
12495
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12413
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8, MULTI_FLAGS2);
|
|
12496
12414
|
const dir = positionals[0] ?? ".";
|
|
12497
12415
|
const r = createReporter(flags.json === true);
|
|
12498
12416
|
const result = await pullTake(
|
|
@@ -12603,6 +12521,8 @@ async function run(argv) {
|
|
|
12603
12521
|
const target = rest.find((a) => !a.startsWith("-")) ?? ".";
|
|
12604
12522
|
return isTakeDir(target) ? await cmdPull2(rest) : await cmdPullProgram(rest);
|
|
12605
12523
|
}
|
|
12524
|
+
case "delete":
|
|
12525
|
+
return await cmdDelete(rest);
|
|
12606
12526
|
case "folder":
|
|
12607
12527
|
return await cmdFolder(rest);
|
|
12608
12528
|
case "asset":
|
|
@@ -12649,7 +12569,7 @@ async function run(argv) {
|
|
|
12649
12569
|
}
|
|
12650
12570
|
}
|
|
12651
12571
|
async function cmdSession(argv) {
|
|
12652
|
-
const { positionals, flags } = parseArgs(argv,
|
|
12572
|
+
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
|
|
12653
12573
|
const [sub, arg] = positionals;
|
|
12654
12574
|
const r = createReporter(flags.json === true);
|
|
12655
12575
|
const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
|
|
@@ -12776,13 +12696,6 @@ export {
|
|
|
12776
12696
|
digestTake,
|
|
12777
12697
|
parseTranscript,
|
|
12778
12698
|
renderTake,
|
|
12779
|
-
platformOrigin,
|
|
12780
|
-
parseVosId,
|
|
12781
|
-
resolveCredential,
|
|
12782
|
-
apiJson,
|
|
12783
|
-
apiError,
|
|
12784
|
-
readSyncState,
|
|
12785
|
-
writeSyncState,
|
|
12786
12699
|
recordTake,
|
|
12787
12700
|
encodeRecording,
|
|
12788
12701
|
planTake,
|
|
@@ -12796,4 +12709,4 @@ export {
|
|
|
12796
12709
|
verbHelp,
|
|
12797
12710
|
run
|
|
12798
12711
|
};
|
|
12799
|
-
//# sourceMappingURL=chunk-
|
|
12712
|
+
//# sourceMappingURL=chunk-F2QCWV4U.js.map
|