@vosjs/cli 0.54.1 → 0.55.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 +1 -1
- package/dist/{chunk-M7FLXLBN.js → chunk-FQA7LFXG.js} +532 -133
- package/dist/chunk-FQA7LFXG.js.map +1 -0
- package/dist/cli.js +5 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-SYDEWMK2.js → run-QREATZCN.js} +2 -2
- package/package.json +3 -3
- package/skills/VERSION +1 -1
- package/skills/vos-port/SKILL.md +7 -2
- package/dist/chunk-M7FLXLBN.js.map +0 -1
- /package/dist/{run-SYDEWMK2.js.map → run-QREATZCN.js.map} +0 -0
|
@@ -99,8 +99,8 @@ function landedLines(l) {
|
|
|
99
99
|
|
|
100
100
|
// src/plugin/run.ts
|
|
101
101
|
import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
|
|
102
|
-
import { existsSync as
|
|
103
|
-
import { join as
|
|
102
|
+
import { existsSync as existsSync24 } from "fs";
|
|
103
|
+
import { join as join28, resolve as resolve15 } from "path";
|
|
104
104
|
import {
|
|
105
105
|
END_CARD_FROM as END_CARD_FROM2,
|
|
106
106
|
docOutputDuration as docOutputDuration3,
|
|
@@ -9235,28 +9235,350 @@ async function cmdCompare(argv) {
|
|
|
9235
9235
|
}
|
|
9236
9236
|
|
|
9237
9237
|
// src/plugin/port.ts
|
|
9238
|
-
import { existsSync as
|
|
9239
|
-
import { dirname as dirname8, join as
|
|
9238
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
9239
|
+
import { dirname as dirname8, join as join24, resolve as resolve11 } from "path";
|
|
9240
9240
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
9241
9241
|
|
|
9242
9242
|
// src/plugin/portInventory.ts
|
|
9243
9243
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9244
9244
|
import {
|
|
9245
|
-
existsSync as
|
|
9245
|
+
existsSync as existsSync18,
|
|
9246
9246
|
mkdirSync as mkdirSync3,
|
|
9247
|
-
readFileSync as
|
|
9248
|
-
readdirSync as
|
|
9249
|
-
statSync as
|
|
9247
|
+
readFileSync as readFileSync6,
|
|
9248
|
+
readdirSync as readdirSync5,
|
|
9249
|
+
statSync as statSync6
|
|
9250
9250
|
} from "fs";
|
|
9251
|
-
import {
|
|
9251
|
+
import { dirname as dirname6, join as join22, relative as relative2, resolve as resolve9 } from "path";
|
|
9252
9252
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
9253
9253
|
import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
|
|
9254
|
+
|
|
9255
|
+
// src/plugin/portRemotion.ts
|
|
9256
|
+
import { existsSync as existsSync17, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
9257
|
+
import { createServer as createServer2 } from "http";
|
|
9258
|
+
import { createRequire } from "module";
|
|
9259
|
+
import { basename as basename7, extname as extname3, join as join21 } from "path";
|
|
9260
|
+
function themeColors(sources) {
|
|
9261
|
+
const out = {};
|
|
9262
|
+
const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
|
|
9263
|
+
for (const src of sources)
|
|
9264
|
+
for (const m of src.matchAll(re)) {
|
|
9265
|
+
const key = m[1].toLowerCase().replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
9266
|
+
if (!(key in out)) out[key] = m[2].toLowerCase();
|
|
9267
|
+
}
|
|
9268
|
+
return out;
|
|
9269
|
+
}
|
|
9270
|
+
function remotionScenes(seqs, fps) {
|
|
9271
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9272
|
+
const live = seqs.filter((s) => s.durationInFrames > 0);
|
|
9273
|
+
const overlaps2 = (a, b) => a.from < b.from + b.durationInFrames && b.from < a.from + a.durationInFrames;
|
|
9274
|
+
return live.filter(
|
|
9275
|
+
(s) => !live.some(
|
|
9276
|
+
(o) => o !== s && o.durationInFrames > s.durationInFrames && overlaps2(o, s)
|
|
9277
|
+
)
|
|
9278
|
+
).sort((a, b) => a.from - b.from).map((s, i) => {
|
|
9279
|
+
const base = s.name ?? `scene${i + 1}`;
|
|
9280
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
9281
|
+
seen.set(base, n);
|
|
9282
|
+
return {
|
|
9283
|
+
name: n > 1 ? `${base}${n}` : base,
|
|
9284
|
+
start: Math.round(s.from / fps * 1e3) / 1e3,
|
|
9285
|
+
duration: Math.round(s.durationInFrames / fps * 1e3) / 1e3
|
|
9286
|
+
};
|
|
9287
|
+
});
|
|
9288
|
+
}
|
|
9289
|
+
function mergeSceneTexts(perScene) {
|
|
9290
|
+
const sig = (t) => `${t.text}|${t.font.family}|${t.font.size}|${t.box ? `${Math.round(t.box.x / 8)},${Math.round(t.box.y / 8)}` : "-"}`;
|
|
9291
|
+
const counts = /* @__PURE__ */ new Map();
|
|
9292
|
+
for (const s of perScene)
|
|
9293
|
+
for (const k of new Set(s.texts.map(sig)))
|
|
9294
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
9295
|
+
const global = perScene.length >= 3 ? Math.ceil(perScene.length / 2) : Infinity;
|
|
9296
|
+
const out = [];
|
|
9297
|
+
const placed = /* @__PURE__ */ new Set();
|
|
9298
|
+
const textPlaced = /* @__PURE__ */ new Set();
|
|
9299
|
+
for (const s of perScene)
|
|
9300
|
+
for (const t of s.texts) {
|
|
9301
|
+
const k = sig(t);
|
|
9302
|
+
const shared = (counts.get(k) ?? 0) >= global;
|
|
9303
|
+
const id = shared ? k : `${s.scene}|${k}`;
|
|
9304
|
+
if (placed.has(id)) continue;
|
|
9305
|
+
const shape = `${t.text.replace(/\d/g, "0")}|${t.font.family}|${t.box ? Math.round(t.box.y / 8) : "-"}`;
|
|
9306
|
+
if (/\d/.test(t.text) && textPlaced.has(shape)) continue;
|
|
9307
|
+
placed.add(id);
|
|
9308
|
+
textPlaced.add(shape);
|
|
9309
|
+
out.push({
|
|
9310
|
+
...t,
|
|
9311
|
+
id: `${t.id}-${out.length}`,
|
|
9312
|
+
scene: shared ? null : s.scene
|
|
9313
|
+
});
|
|
9314
|
+
}
|
|
9315
|
+
return out;
|
|
9316
|
+
}
|
|
9317
|
+
function sourceTexts(dir) {
|
|
9318
|
+
const out = [];
|
|
9319
|
+
const walk = (d, depth) => {
|
|
9320
|
+
if (depth > 4 || out.length > 200) return;
|
|
9321
|
+
for (const name of readdirSync4(d)) {
|
|
9322
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
9323
|
+
const p = join21(d, name);
|
|
9324
|
+
if (statSync5(p).isDirectory()) walk(p, depth + 1);
|
|
9325
|
+
else if (/\.(tsx?|jsx?|mjs)$/.test(name))
|
|
9326
|
+
out.push(readFileSync5(p, "utf8"));
|
|
9327
|
+
}
|
|
9328
|
+
};
|
|
9329
|
+
const src = join21(dir, "src");
|
|
9330
|
+
walk(existsSync17(src) ? src : dir, 0);
|
|
9331
|
+
return out;
|
|
9332
|
+
}
|
|
9333
|
+
function entryPoint(dir) {
|
|
9334
|
+
for (const p of [
|
|
9335
|
+
"src/index.ts",
|
|
9336
|
+
"src/index.tsx",
|
|
9337
|
+
"src/index.js",
|
|
9338
|
+
"src/index.jsx",
|
|
9339
|
+
"remotion/index.ts",
|
|
9340
|
+
"remotion/index.tsx"
|
|
9341
|
+
])
|
|
9342
|
+
if (existsSync17(join21(dir, p))) return join21(dir, p);
|
|
9343
|
+
return null;
|
|
9344
|
+
}
|
|
9345
|
+
var TYPES = {
|
|
9346
|
+
".html": "text/html",
|
|
9347
|
+
".js": "text/javascript",
|
|
9348
|
+
".css": "text/css",
|
|
9349
|
+
".json": "application/json",
|
|
9350
|
+
".woff2": "font/woff2",
|
|
9351
|
+
".woff": "font/woff",
|
|
9352
|
+
".ttf": "font/ttf",
|
|
9353
|
+
".png": "image/png",
|
|
9354
|
+
".jpg": "image/jpeg",
|
|
9355
|
+
".svg": "image/svg+xml",
|
|
9356
|
+
".wav": "audio/wav",
|
|
9357
|
+
".mp3": "audio/mpeg",
|
|
9358
|
+
".ogg": "audio/ogg",
|
|
9359
|
+
".mp4": "video/mp4",
|
|
9360
|
+
".webm": "video/webm"
|
|
9361
|
+
};
|
|
9362
|
+
var INIT = `(() => {
|
|
9363
|
+
window.remotion_puppeteerTimeout = 30000
|
|
9364
|
+
window.remotion_isMainTab = true
|
|
9365
|
+
window.remotion_mediaCacheSizeInBytes = 100000000
|
|
9366
|
+
window.remotion_initialMemoryAvailable = 1000000000
|
|
9367
|
+
window.remotion_sampleRate = 48000
|
|
9368
|
+
window.process = window.process || {}
|
|
9369
|
+
window.process.env = window.process.env || {}
|
|
9370
|
+
window.process.env.NODE_ENV = 'production'
|
|
9371
|
+
window.remotion_envVariables = '{}'
|
|
9372
|
+
window.remotion_inputProps = '{}'
|
|
9373
|
+
window.remotion_initialFrame = 0
|
|
9374
|
+
window.remotion_attempt = 1
|
|
9375
|
+
window.remotion_audioEnabled = false
|
|
9376
|
+
window.remotion_videoEnabled = false
|
|
9377
|
+
window.remotion_logLevel = 'error'
|
|
9378
|
+
})()`;
|
|
9379
|
+
var READ_TREE = `(() => {
|
|
9380
|
+
let fiber = null
|
|
9381
|
+
for (const el of document.querySelectorAll('body, body *')) {
|
|
9382
|
+
const k = Object.keys(el).find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactContainer$'))
|
|
9383
|
+
if (k) { fiber = el[k]; break }
|
|
9384
|
+
}
|
|
9385
|
+
while (fiber && fiber.return) fiber = fiber.return
|
|
9386
|
+
const nameOf = (t) => t && (t.displayName || t.name || (t.render && (t.render.displayName || t.render.name))) || null
|
|
9387
|
+
const internal = /^(Sequence|AbsoluteFill|Freeze|Loop|Series|Fragment)$|Provider|Context|Forwarding|^Inner/
|
|
9388
|
+
const seqs = []
|
|
9389
|
+
const audio = []
|
|
9390
|
+
const walk = (f, offset) => {
|
|
9391
|
+
for (let x = f; x; x = x.sibling) {
|
|
9392
|
+
const name = nameOf(x.elementType)
|
|
9393
|
+
const p = x.memoizedProps || {}
|
|
9394
|
+
let next = offset
|
|
9395
|
+
if (name === 'Sequence' && typeof p.from === 'number') {
|
|
9396
|
+
next = offset + p.from
|
|
9397
|
+
if (typeof p.durationInFrames === 'number' && isFinite(p.durationInFrames)) {
|
|
9398
|
+
const kids = Array.isArray(p.children) ? p.children : [p.children]
|
|
9399
|
+
const held = kids.map((k) => k && nameOf(k.type)).find((n) => n && !internal.test(n))
|
|
9400
|
+
seqs.push({ from: next, durationInFrames: p.durationInFrames, name: p.name || held || null })
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
if (name && /Audio$/.test(name) && typeof p.src === 'string')
|
|
9404
|
+
audio.push({ src: p.src, from: next, volume: typeof p.volume === 'number' ? p.volume : null, startFrom: typeof p.startFrom === 'number' ? p.startFrom : (typeof p.trimBefore === 'number' ? p.trimBefore : null) })
|
|
9405
|
+
if (x.child) walk(x.child, next)
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
walk(fiber, 0)
|
|
9409
|
+
// Nested sequences repeat their parent's window at the same offset: keep the outermost.
|
|
9410
|
+
const seen = new Set()
|
|
9411
|
+
return { seqs: seqs.filter((s) => { const k = s.from + ':' + s.durationInFrames; if (seen.has(k)) return false; seen.add(k); return true }), audio }
|
|
9412
|
+
})()`;
|
|
9413
|
+
async function readRemotion(dir, log) {
|
|
9414
|
+
const entry = entryPoint(dir);
|
|
9415
|
+
let bundler;
|
|
9416
|
+
try {
|
|
9417
|
+
bundler = createRequire(join21(dir, "package.json"))("@remotion/bundler");
|
|
9418
|
+
} catch {
|
|
9419
|
+
log?.(
|
|
9420
|
+
"a Remotion project whose dependencies are not installed: run `npm install` in it so vos can read its compositions (words, scenes, faces); reading only the render"
|
|
9421
|
+
);
|
|
9422
|
+
return null;
|
|
9423
|
+
}
|
|
9424
|
+
if (!entry) {
|
|
9425
|
+
log?.(
|
|
9426
|
+
"a Remotion project with no src/index entry (registerRoot): reading only the render"
|
|
9427
|
+
);
|
|
9428
|
+
return null;
|
|
9429
|
+
}
|
|
9430
|
+
const out = await bundler.bundle({ entryPoint: entry, onProgress: () => {
|
|
9431
|
+
} });
|
|
9432
|
+
const server = createServer2((rq, rs) => {
|
|
9433
|
+
const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
|
|
9434
|
+
let f = join21(out, path);
|
|
9435
|
+
if (!f.startsWith(out) || !existsSync17(f) || statSync5(f).isDirectory())
|
|
9436
|
+
f = join21(out, "index.html");
|
|
9437
|
+
rs.writeHead(200, {
|
|
9438
|
+
"content-type": TYPES[extname3(f)] ?? "application/octet-stream"
|
|
9439
|
+
});
|
|
9440
|
+
rs.end(readFileSync5(f));
|
|
9441
|
+
}).listen(0, "127.0.0.1");
|
|
9442
|
+
await new Promise((r) => server.once("listening", r));
|
|
9443
|
+
const port = server.address().port;
|
|
9444
|
+
const browser = await launchBrowser();
|
|
9445
|
+
try {
|
|
9446
|
+
const ctx = await browser.newContext({
|
|
9447
|
+
viewport: { width: 1920, height: 1080 }
|
|
9448
|
+
});
|
|
9449
|
+
const p = await ctx.newPage();
|
|
9450
|
+
await p.addInitScript(INIT);
|
|
9451
|
+
await p.goto(`http://127.0.0.1:${port}/index.html`);
|
|
9452
|
+
await p.waitForFunction(
|
|
9453
|
+
'typeof window.getStaticCompositions === "function"',
|
|
9454
|
+
null,
|
|
9455
|
+
{ timeout: 3e4 }
|
|
9456
|
+
);
|
|
9457
|
+
await p.evaluate("window.remotion_setBundleMode({ type: 'evaluation' })");
|
|
9458
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9459
|
+
timeout: 3e4
|
|
9460
|
+
});
|
|
9461
|
+
const comps = await p.evaluate("window.getStaticCompositions()");
|
|
9462
|
+
if (!comps.length) {
|
|
9463
|
+
log?.(
|
|
9464
|
+
"the Remotion project registers no composition: reading only the render"
|
|
9465
|
+
);
|
|
9466
|
+
return null;
|
|
9467
|
+
}
|
|
9468
|
+
const c = comps[0];
|
|
9469
|
+
if (comps.length > 1)
|
|
9470
|
+
log?.(`${comps.length} compositions; reading the first, ${c.id}`);
|
|
9471
|
+
await p.setViewportSize({ width: c.width, height: c.height });
|
|
9472
|
+
await p.evaluate(
|
|
9473
|
+
`window.remotion_setBundleMode(${JSON.stringify({
|
|
9474
|
+
type: "composition",
|
|
9475
|
+
compositionName: c.id,
|
|
9476
|
+
serializedResolvedPropsWithSchema: JSON.stringify(c.defaultProps ?? {}),
|
|
9477
|
+
compositionDurationInFrames: c.durationInFrames,
|
|
9478
|
+
compositionFps: c.fps,
|
|
9479
|
+
compositionHeight: c.height,
|
|
9480
|
+
compositionWidth: c.width,
|
|
9481
|
+
compositionDefaultCodec: null,
|
|
9482
|
+
compositionDefaultOutName: null,
|
|
9483
|
+
compositionDefaultVideoImageFormat: null,
|
|
9484
|
+
compositionDefaultPixelFormat: null,
|
|
9485
|
+
compositionDefaultProResProfile: null,
|
|
9486
|
+
compositionDefaultSampleRate: null
|
|
9487
|
+
})})`
|
|
9488
|
+
);
|
|
9489
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9490
|
+
timeout: 3e4
|
|
9491
|
+
});
|
|
9492
|
+
const seek = async (frame) => {
|
|
9493
|
+
await p.evaluate(
|
|
9494
|
+
`window.remotion_setFrame(${frame}, ${JSON.stringify(c.id)}, 1)`
|
|
9495
|
+
);
|
|
9496
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9497
|
+
timeout: 3e4
|
|
9498
|
+
});
|
|
9499
|
+
await p.evaluate("document.fonts && document.fonts.ready");
|
|
9500
|
+
};
|
|
9501
|
+
await seek(0);
|
|
9502
|
+
const tree = await p.evaluate(READ_TREE);
|
|
9503
|
+
let scenes = remotionScenes(tree.seqs, c.fps);
|
|
9504
|
+
const total = c.durationInFrames / c.fps;
|
|
9505
|
+
if (!scenes.length) scenes = [{ name: c.id, start: 0, duration: total }];
|
|
9506
|
+
const perScene = [];
|
|
9507
|
+
let first = null;
|
|
9508
|
+
const colors = /* @__PURE__ */ new Set();
|
|
9509
|
+
const gaps = /* @__PURE__ */ new Set();
|
|
9510
|
+
for (const s of scenes) {
|
|
9511
|
+
const frame = Math.min(
|
|
9512
|
+
c.durationInFrames - 1,
|
|
9513
|
+
Math.round((s.start + s.duration * 0.9) * c.fps)
|
|
9514
|
+
);
|
|
9515
|
+
await seek(frame);
|
|
9516
|
+
const read = await p.evaluate(READ_PAGE);
|
|
9517
|
+
const unboxed = read.texts.filter((t) => !t.box);
|
|
9518
|
+
if (unboxed.length) {
|
|
9519
|
+
await seek(Math.round((s.start + s.duration * 0.5) * c.fps));
|
|
9520
|
+
const mid = await p.evaluate(READ_PAGE);
|
|
9521
|
+
for (const t of unboxed) {
|
|
9522
|
+
const m = mid.texts.find(
|
|
9523
|
+
(x) => x.box && x.text === t.text && x.font.family === t.font.family
|
|
9524
|
+
);
|
|
9525
|
+
if (m) t.box = m.box;
|
|
9526
|
+
}
|
|
9527
|
+
}
|
|
9528
|
+
s.ground = await p.evaluate(READ_GROUND);
|
|
9529
|
+
first ??= read;
|
|
9530
|
+
for (const x of read.colors ?? []) colors.add(x);
|
|
9531
|
+
for (const g of read.gaps ?? []) gaps.add(g);
|
|
9532
|
+
perScene.push({ scene: s.name, texts: read.texts ?? [] });
|
|
9533
|
+
}
|
|
9534
|
+
const publicDir = join21(dir, "public");
|
|
9535
|
+
const media = tree.audio.map((a) => {
|
|
9536
|
+
const name = basename7(a.src.split("?")[0]);
|
|
9537
|
+
const local = existsSync17(join21(publicDir, name)) ? `public/${name}` : a.src;
|
|
9538
|
+
return {
|
|
9539
|
+
kind: "audio",
|
|
9540
|
+
src: local,
|
|
9541
|
+
start: Math.round(a.from / c.fps * 1e3) / 1e3,
|
|
9542
|
+
duration: null,
|
|
9543
|
+
volume: a.volume,
|
|
9544
|
+
mediaStart: a.startFrom != null ? Math.round(a.startFrom / c.fps * 1e3) / 1e3 : null
|
|
9545
|
+
};
|
|
9546
|
+
});
|
|
9547
|
+
return {
|
|
9548
|
+
width: c.width,
|
|
9549
|
+
height: c.height,
|
|
9550
|
+
fps: c.fps,
|
|
9551
|
+
duration: Math.round(total * 1e3) / 1e3,
|
|
9552
|
+
texts: mergeSceneTexts(perScene),
|
|
9553
|
+
palette: themeColors(sourceTexts(dir)),
|
|
9554
|
+
colors: [...colors],
|
|
9555
|
+
gaps: [...gaps],
|
|
9556
|
+
media,
|
|
9557
|
+
canvases: first?.canvases ?? [],
|
|
9558
|
+
scenes,
|
|
9559
|
+
sceneEls: [],
|
|
9560
|
+
variables: c.defaultProps ?? {},
|
|
9561
|
+
scriptText: "",
|
|
9562
|
+
composition: c.id
|
|
9563
|
+
};
|
|
9564
|
+
} catch (e) {
|
|
9565
|
+
log?.(
|
|
9566
|
+
`the Remotion bundle did not mount (${e instanceof Error ? e.message.split("\n")[0] : String(e)}): reading only the render`
|
|
9567
|
+
);
|
|
9568
|
+
return null;
|
|
9569
|
+
} finally {
|
|
9570
|
+
await browser.close();
|
|
9571
|
+
server.close();
|
|
9572
|
+
}
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
// src/plugin/portInventory.ts
|
|
9254
9576
|
function detectEngine(dir) {
|
|
9255
|
-
if (
|
|
9256
|
-
const pkg =
|
|
9257
|
-
if (
|
|
9577
|
+
if (existsSync18(join22(dir, "hyperframes.json"))) return "hyperframes";
|
|
9578
|
+
const pkg = join22(dir, "package.json");
|
|
9579
|
+
if (existsSync18(pkg)) {
|
|
9258
9580
|
try {
|
|
9259
|
-
const p = JSON.parse(
|
|
9581
|
+
const p = JSON.parse(readFileSync6(pkg, "utf8"));
|
|
9260
9582
|
const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
|
|
9261
9583
|
if ("remotion" in deps) return "remotion";
|
|
9262
9584
|
if ("hyperframes" in deps) return "hyperframes";
|
|
@@ -9268,14 +9590,14 @@ function detectEngine(dir) {
|
|
|
9268
9590
|
return "page";
|
|
9269
9591
|
}
|
|
9270
9592
|
function sourcePage(source) {
|
|
9271
|
-
if (!
|
|
9272
|
-
if (
|
|
9273
|
-
const index =
|
|
9274
|
-
return
|
|
9593
|
+
if (!existsSync18(source)) return null;
|
|
9594
|
+
if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
|
|
9595
|
+
const index = join22(source, "index.html");
|
|
9596
|
+
return existsSync18(index) ? index : null;
|
|
9275
9597
|
}
|
|
9276
9598
|
function findRender(dir) {
|
|
9277
|
-
const videos = (d) =>
|
|
9278
|
-
for (const d of [
|
|
9599
|
+
const videos = (d) => existsSync18(d) ? readdirSync5(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join22(d, f)) : [];
|
|
9600
|
+
for (const d of [join22(dir, "renders"), join22(dir, "out"), dir]) {
|
|
9279
9601
|
const found = videos(d);
|
|
9280
9602
|
if (found.length) return found[0];
|
|
9281
9603
|
}
|
|
@@ -9385,31 +9707,68 @@ var READ_PAGE = `(() => {
|
|
|
9385
9707
|
const sx = rootRect.width ? width / rootRect.width : 1
|
|
9386
9708
|
const sy = rootRect.height ? height / rootRect.height : 1
|
|
9387
9709
|
|
|
9710
|
+
// The faces that actually render: a family loaded only italic, or only at
|
|
9711
|
+
// one weight, draws that face whatever the CSS asked for.
|
|
9712
|
+
const loaded = {}
|
|
9713
|
+
if (document.fonts) for (const f of document.fonts) {
|
|
9714
|
+
if (f.status !== 'loaded') continue
|
|
9715
|
+
const fam = f.family.replace(/^["']|["']$/g, '')
|
|
9716
|
+
const e = loaded[fam] || (loaded[fam] = { styles: new Set(), weights: new Set() })
|
|
9717
|
+
e.styles.add(f.style)
|
|
9718
|
+
const w = Number(f.weight)
|
|
9719
|
+
if (Number.isFinite(w)) e.weights.add(w)
|
|
9720
|
+
}
|
|
9721
|
+
const renderedFace = (fam, style, weight) => {
|
|
9722
|
+
const e = loaded[fam]
|
|
9723
|
+
if (!e) return { style, weight }
|
|
9724
|
+
const st = e.styles.has(style) ? style : style === 'normal' && e.styles.has('italic') ? 'italic' : style
|
|
9725
|
+
const ws = [...e.weights]
|
|
9726
|
+
const wt = !ws.length || ws.includes(weight) ? weight : ws.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a))
|
|
9727
|
+
return { style: st, weight: wt }
|
|
9728
|
+
}
|
|
9388
9729
|
const texts = []
|
|
9389
9730
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
9390
9731
|
const seen = new Set()
|
|
9391
9732
|
let n = 0
|
|
9392
9733
|
while (walker.nextNode()) {
|
|
9393
9734
|
const node = walker.currentNode
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
const
|
|
9400
|
-
|
|
9735
|
+
let el = node.parentElement
|
|
9736
|
+
if (!el || !(node.textContent || '').trim() || el.closest('script, style, noscript, title')) continue
|
|
9737
|
+
// A word animated letter by letter is one span per glyph: read the word,
|
|
9738
|
+
// once, as a split word, never as its letters.
|
|
9739
|
+
let split = null
|
|
9740
|
+
const glyphs = (a) => a && a.children.length >= 3 && [...a.childNodes].every((c) => c.nodeType === 1 ? (c.textContent || '').trim().length <= 2 : !(c.textContent || '').trim())
|
|
9741
|
+
for (let a = el.parentElement, up = 0; a && up < 3; a = a.parentElement, up++) {
|
|
9742
|
+
if (glyphs(a)) { el = a; split = 'chars'; break }
|
|
9743
|
+
}
|
|
9744
|
+
// A line with a styled word inside it ("TYPE <span>that</span> MOVES")
|
|
9745
|
+
// is read run by run, each with its own box, or the runs after the
|
|
9746
|
+
// first are lost.
|
|
9747
|
+
const mixed = !split && [...el.children].some((c) => (c.textContent || '').trim())
|
|
9748
|
+
const unit = mixed ? node : el
|
|
9749
|
+
if (seen.has(unit)) continue
|
|
9750
|
+
seen.add(unit)
|
|
9751
|
+
const text = ((mixed ? node.textContent : el.textContent) || '').replace(/\\s+/g, ' ').trim()
|
|
9752
|
+
if (!text) continue
|
|
9753
|
+
// A split word wears its letters' face, never its container's.
|
|
9754
|
+
const cs = getComputedStyle(split ? node.parentElement : el)
|
|
9755
|
+
let r
|
|
9756
|
+
if (mixed) { const range = document.createRange(); range.selectNodeContents(node); r = range.getBoundingClientRect() }
|
|
9757
|
+
else r = el.getBoundingClientRect()
|
|
9401
9758
|
const box = r.width && r.height ? {
|
|
9402
9759
|
x: Math.round((r.left - rootRect.left) * sx), y: Math.round((r.top - rootRect.top) * sy),
|
|
9403
9760
|
width: Math.round(r.width * sx), height: Math.round(r.height * sy) } : null
|
|
9404
9761
|
const ls = parseFloat(cs.letterSpacing)
|
|
9405
|
-
el.setAttribute('data-vos-inv', String(texts.length))
|
|
9762
|
+
if (!mixed) el.setAttribute('data-vos-inv', String(texts.length))
|
|
9406
9763
|
const sceneEl = el.closest('.scene, [data-scene], section[id]')
|
|
9764
|
+
const fam = cs.fontFamily.split(',')[0].trim().replace(/^["']|["']$/g, '')
|
|
9765
|
+
const face = renderedFace(fam, cs.fontStyle, Number(cs.fontWeight) || 400)
|
|
9407
9766
|
texts.push({
|
|
9408
|
-
id: el.id || (el.className && typeof el.className === 'string' ? el.className.split(' ')[0] : '') || el.tagName.toLowerCase() + '-' + (n++),
|
|
9409
|
-
text, tag: el.tagName.toLowerCase(), box, scene: sceneEl && sceneEl.id ? sceneEl.id : null,
|
|
9410
|
-
font: { family:
|
|
9411
|
-
size: Math.round(parseFloat(cs.fontSize) * sy), weight:
|
|
9412
|
-
style:
|
|
9767
|
+
id: (mixed ? '' : el.id || (el.className && typeof el.className === 'string' ? el.className.split(' ')[0] : '')) || el.tagName.toLowerCase() + '-' + (n++),
|
|
9768
|
+
text, tag: el.tagName.toLowerCase(), box, scene: sceneEl && sceneEl.id ? sceneEl.id : null, split,
|
|
9769
|
+
font: { family: fam,
|
|
9770
|
+
size: Math.round(parseFloat(cs.fontSize) * sy), weight: face.weight,
|
|
9771
|
+
style: face.style, color: cs.color, letterSpacing: Number.isFinite(ls) ? Math.round(ls * sy * 100) / 100 : 0,
|
|
9413
9772
|
transform: cs.textTransform },
|
|
9414
9773
|
})
|
|
9415
9774
|
}
|
|
@@ -9463,6 +9822,24 @@ var READ_PAGE = `(() => {
|
|
|
9463
9822
|
texts, palette, colors: [...colors], gaps: [...gaps], media, canvases, scenes, sceneEls, variables, scriptText,
|
|
9464
9823
|
}
|
|
9465
9824
|
})()`;
|
|
9825
|
+
var READ_GROUND = `(() => {
|
|
9826
|
+
const W = innerWidth, H = innerHeight
|
|
9827
|
+
const counts = {}
|
|
9828
|
+
for (const [px, py] of [[0.5, 0.5], [0.06, 0.06], [0.94, 0.94], [0.06, 0.94], [0.94, 0.06]]) {
|
|
9829
|
+
for (const el of document.elementsFromPoint(px * W, py * H)) {
|
|
9830
|
+
const cs = getComputedStyle(el)
|
|
9831
|
+
const bg = cs.backgroundColor
|
|
9832
|
+
if (!bg || bg === 'transparent' || /rgba\\([^)]*,\\s*0\\)$/.test(bg)) continue
|
|
9833
|
+
const r = el.getBoundingClientRect()
|
|
9834
|
+
if (r.width < W * 0.9 || r.height < H * 0.9 || Number(cs.opacity) < 0.99) continue
|
|
9835
|
+
counts[bg] = (counts[bg] || 0) + 1
|
|
9836
|
+
break
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
let best = null
|
|
9840
|
+
for (const k in counts) if (!best || counts[k] > counts[best]) best = k
|
|
9841
|
+
return best
|
|
9842
|
+
})()`;
|
|
9466
9843
|
var SEEK_AND_READ = `((args) => {
|
|
9467
9844
|
const tls = window.__timelines ? Object.values(window.__timelines) : []
|
|
9468
9845
|
if (!tls.length) return null
|
|
@@ -9482,7 +9859,7 @@ var SEEK_AND_READ = `((args) => {
|
|
|
9482
9859
|
})`;
|
|
9483
9860
|
async function readInventory(source, opts) {
|
|
9484
9861
|
const abs = resolve9(source);
|
|
9485
|
-
const dir =
|
|
9862
|
+
const dir = existsSync18(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
9486
9863
|
const engine = detectEngine(dir);
|
|
9487
9864
|
const page = sourcePage(abs);
|
|
9488
9865
|
const render = opts.render === void 0 ? findRender(dir) : opts.render;
|
|
@@ -9527,12 +9904,15 @@ async function readInventory(source, opts) {
|
|
|
9527
9904
|
if (scripted.length > (read.scenes?.length ?? 0)) read.scenes = scripted;
|
|
9528
9905
|
for (const s of read.scenes) {
|
|
9529
9906
|
const indices = read.texts.map((t2, i) => t2.scene === s.name ? i : -1).filter((i) => i >= 0);
|
|
9530
|
-
if (!indices.length) continue;
|
|
9531
9907
|
const t = s.start + (s.duration ?? 1) * 0.75;
|
|
9532
9908
|
const boxes = await p.evaluate(
|
|
9533
9909
|
`${SEEK_AND_READ}(${JSON.stringify({ t, indices, width: read.width, height: read.height })})`
|
|
9534
9910
|
);
|
|
9535
|
-
if (!boxes)
|
|
9911
|
+
if (!boxes)
|
|
9912
|
+
break;
|
|
9913
|
+
s.ground = await p.evaluate(
|
|
9914
|
+
READ_GROUND
|
|
9915
|
+
);
|
|
9536
9916
|
for (const [i, box] of Object.entries(boxes)) {
|
|
9537
9917
|
read.texts[Number(i)].box = box;
|
|
9538
9918
|
settled++;
|
|
@@ -9543,9 +9923,15 @@ async function readInventory(source, opts) {
|
|
|
9543
9923
|
await browser.close();
|
|
9544
9924
|
}
|
|
9545
9925
|
} else if (engine === "remotion") {
|
|
9546
|
-
opts.log
|
|
9547
|
-
|
|
9548
|
-
|
|
9926
|
+
const r = await readRemotion(dir, opts.log);
|
|
9927
|
+
if (r) {
|
|
9928
|
+
read = r;
|
|
9929
|
+
fps = r.fps ?? fps;
|
|
9930
|
+
duration = r.duration ?? duration;
|
|
9931
|
+
opts.log?.(
|
|
9932
|
+
`read the Remotion composition ${String(r.composition)} in its own bundle: ${r.scenes.length} sequences as scenes, the words where they settle in each`
|
|
9933
|
+
);
|
|
9934
|
+
}
|
|
9549
9935
|
}
|
|
9550
9936
|
if (settled)
|
|
9551
9937
|
opts.log?.(
|
|
@@ -9576,7 +9962,7 @@ async function readInventory(source, opts) {
|
|
|
9576
9962
|
if (render && duration) {
|
|
9577
9963
|
const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
|
|
9578
9964
|
if (has) {
|
|
9579
|
-
const refDir =
|
|
9965
|
+
const refDir = join22(opts.outDir, "ref");
|
|
9580
9966
|
mkdirSync3(refDir, { recursive: true });
|
|
9581
9967
|
const times = read.scenes.length >= 3 ? read.scenes.map(
|
|
9582
9968
|
(s) => s.start + (s.duration ?? 1) / 2
|
|
@@ -9585,7 +9971,7 @@ async function readInventory(source, opts) {
|
|
|
9585
9971
|
(_, i) => i + 0.5
|
|
9586
9972
|
);
|
|
9587
9973
|
for (const t of times.filter((t2) => t2 < duration)) {
|
|
9588
|
-
const file =
|
|
9974
|
+
const file = join22(refDir, `src-${t.toFixed(2)}.png`);
|
|
9589
9975
|
const ok = spawnSync2("ffmpeg", [
|
|
9590
9976
|
"-v",
|
|
9591
9977
|
"error",
|
|
@@ -9614,7 +10000,8 @@ async function readInventory(source, opts) {
|
|
|
9614
10000
|
}
|
|
9615
10001
|
const scriptStrings = scriptStringsOf(read.scriptText);
|
|
9616
10002
|
return {
|
|
9617
|
-
source: relative2(process.cwd(), abs) ||
|
|
10003
|
+
source: relative2(process.cwd(), abs) || ".",
|
|
10004
|
+
root: abs.endsWith(".html") ? dirname6(abs) : abs,
|
|
9618
10005
|
engine,
|
|
9619
10006
|
width: read.width,
|
|
9620
10007
|
height: read.height,
|
|
@@ -9637,8 +10024,8 @@ async function readInventory(source, opts) {
|
|
|
9637
10024
|
|
|
9638
10025
|
// src/plugin/portScaffold.ts
|
|
9639
10026
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9640
|
-
import { copyFileSync, existsSync as
|
|
9641
|
-
import { basename as basename8, dirname as dirname7, join as
|
|
10027
|
+
import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
10028
|
+
import { basename as basename8, dirname as dirname7, join as join23, resolve as resolve10 } from "path";
|
|
9642
10029
|
import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
|
|
9643
10030
|
var DESIGN_HEIGHT = 1080;
|
|
9644
10031
|
function toHex(css) {
|
|
@@ -9727,6 +10114,7 @@ function planScaffold(inv, opts) {
|
|
|
9727
10114
|
id: key,
|
|
9728
10115
|
type: "text",
|
|
9729
10116
|
content: { $data: key },
|
|
10117
|
+
...t.split ? { split: { type: t.split } } : {},
|
|
9730
10118
|
position: t.box ? { x: fmtPct(t.box.x / inv.width), y: fmtPct(t.box.y / inv.height) } : "center",
|
|
9731
10119
|
font: {
|
|
9732
10120
|
family,
|
|
@@ -9801,6 +10189,7 @@ function planScaffold(inv, opts) {
|
|
|
9801
10189
|
data.scenes = inv.scenes.map((s) => ({
|
|
9802
10190
|
name: s.name,
|
|
9803
10191
|
at: s.start,
|
|
10192
|
+
...s.ground ? { ground: colorOf(s.ground) } : {},
|
|
9804
10193
|
until: s.duration == null ? null : Math.round((s.start + s.duration) * 1e3) / 1e3
|
|
9805
10194
|
}));
|
|
9806
10195
|
data.sceneElements = sceneElements;
|
|
@@ -9848,7 +10237,7 @@ function planScaffold(inv, opts) {
|
|
|
9848
10237
|
inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
|
|
9849
10238
|
const from = resolve10(opts.sourceDir, m.src);
|
|
9850
10239
|
const name = basename8(from);
|
|
9851
|
-
if (!
|
|
10240
|
+
if (!existsSync19(from)) {
|
|
9852
10241
|
report.push(
|
|
9853
10242
|
`- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
|
|
9854
10243
|
);
|
|
@@ -9891,6 +10280,7 @@ function planScaffold(inv, opts) {
|
|
|
9891
10280
|
elements: elements.length,
|
|
9892
10281
|
params: params.length,
|
|
9893
10282
|
painter,
|
|
10283
|
+
scores: audio.map((a) => String(a.key)),
|
|
9894
10284
|
extra: report
|
|
9895
10285
|
})
|
|
9896
10286
|
};
|
|
@@ -9975,7 +10365,9 @@ export default {
|
|
|
9975
10365
|
tl.addLabel(s.name, s.at)
|
|
9976
10366
|
// Each scene's words show in its window; the motion replaces this.
|
|
9977
10367
|
for (const id of ctx.data.sceneElements[s.name] || []) {
|
|
9978
|
-
|
|
10368
|
+
// A split word's units carry their own opacity: hide the units.
|
|
10369
|
+
const el = ctx.elements.get(id)
|
|
10370
|
+
const p = el.segments && el.segments.length ? el.segments : el.props
|
|
9979
10371
|
if (s.at > 0) {
|
|
9980
10372
|
tl.set(p, { opacity: 0 }, 0)
|
|
9981
10373
|
tl.set(p, { opacity: 1 }, s.at)
|
|
@@ -9989,22 +10381,29 @@ ${sceneTodos}
|
|
|
9989
10381
|
},
|
|
9990
10382
|
|
|
9991
10383
|
onFrame(ctx, content) {
|
|
9992
|
-
|
|
10384
|
+
// Each scene's ground, by its palette key; the frame's own colour between them.
|
|
10385
|
+
let ground = ctx.data.bg
|
|
10386
|
+
for (const s of ctx.data.scenes)
|
|
10387
|
+
if (s.ground && ctx.time >= s.at && (s.until == null || ctx.time < s.until))
|
|
10388
|
+
ground = ctx.data[s.ground] ?? ground
|
|
10389
|
+
ctx.scene.background.set(ground)${painterFrame}
|
|
9993
10390
|
},
|
|
9994
10391
|
}
|
|
9995
10392
|
`;
|
|
9996
10393
|
}
|
|
9997
10394
|
function reportText(inv, r) {
|
|
9998
10395
|
const lines = [
|
|
9999
|
-
`# Port report: ${inv.source}`,
|
|
10396
|
+
`# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
|
|
10000
10397
|
"",
|
|
10001
10398
|
`${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
|
|
10002
10399
|
"",
|
|
10003
10400
|
"## Placed",
|
|
10004
10401
|
"",
|
|
10005
|
-
`- ${r.elements} words as bound text elements, ${Object.keys(inv.palette).length} palette colours
|
|
10402
|
+
`- ${r.elements} words as bound text elements, ${Object.keys(inv.palette).length} palette colours the source names (its custom properties or colour constants), ${r.params} knobs.`,
|
|
10006
10403
|
`- ${inv.scenes.length} scenes as labels${inv.scenes.length ? "" : " (none declared: name them as you translate)"}.`,
|
|
10007
|
-
...
|
|
10404
|
+
...r.scores.length ? [
|
|
10405
|
+
`- the score as a doc.json track (${r.scores.join(", ")}, copied beside the program; vos push uploads it).`
|
|
10406
|
+
] : [],
|
|
10008
10407
|
"",
|
|
10009
10408
|
"## Faces",
|
|
10010
10409
|
"",
|
|
@@ -10039,20 +10438,20 @@ function reportText(inv, r) {
|
|
|
10039
10438
|
"",
|
|
10040
10439
|
"## Reference",
|
|
10041
10440
|
"",
|
|
10042
|
-
inv.render ? `The source's render: ${inv.render}. Stills in port/ref/. Check every scene: \`npx vos compare
|
|
10441
|
+
inv.render ? `The source's render: ${inv.render}. Stills in port/ref/. Check every scene, from where the inventory ran: \`npx vos compare port/program --against ${inv.render}\`.` : "No render of the source was found: render it with its own tool and pass it to `vos compare --against`.",
|
|
10043
10442
|
""
|
|
10044
10443
|
];
|
|
10045
10444
|
return lines.join("\n");
|
|
10046
10445
|
}
|
|
10047
10446
|
function writeScaffold(plan, outDir) {
|
|
10048
10447
|
mkdirSync4(outDir, { recursive: true });
|
|
10049
|
-
writeFileSync4(
|
|
10448
|
+
writeFileSync4(join23(outDir, "program.mjs"), plan.program);
|
|
10050
10449
|
writeFileSync4(
|
|
10051
|
-
|
|
10450
|
+
join23(outDir, "doc.json"),
|
|
10052
10451
|
JSON.stringify(plan.doc, null, 2) + "\n"
|
|
10053
10452
|
);
|
|
10054
|
-
writeFileSync4(
|
|
10055
|
-
for (const [from, name] of plan.copies) copyFileSync(from,
|
|
10453
|
+
writeFileSync4(join23(dirname7(outDir), "REPORT.md"), plan.report);
|
|
10454
|
+
for (const [from, name] of plan.copies) copyFileSync(from, join23(outDir, name));
|
|
10056
10455
|
}
|
|
10057
10456
|
function probeAudioDuration(file) {
|
|
10058
10457
|
const res = spawnSync3(
|
|
@@ -10086,7 +10485,7 @@ async function inventory(argv) {
|
|
|
10086
10485
|
outDir,
|
|
10087
10486
|
log: (l) => r.log(`note: ${l}`)
|
|
10088
10487
|
});
|
|
10089
|
-
const file =
|
|
10488
|
+
const file = join24(outDir, "inventory.json");
|
|
10090
10489
|
writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
|
|
10091
10490
|
const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
|
|
10092
10491
|
r.log(
|
|
@@ -10118,42 +10517,42 @@ function summary(inv) {
|
|
|
10118
10517
|
async function scaffold(argv) {
|
|
10119
10518
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10120
10519
|
const r = createReporter(flags.json === true);
|
|
10121
|
-
const from = resolve11(strFlag(flags, "from") ??
|
|
10122
|
-
if (!
|
|
10520
|
+
const from = resolve11(strFlag(flags, "from") ?? join24("port", "inventory.json"));
|
|
10521
|
+
if (!existsSync20(from))
|
|
10123
10522
|
throw new UsageError(
|
|
10124
10523
|
`no inventory at ${from}: run npx vos port inventory <source> first`
|
|
10125
10524
|
);
|
|
10126
|
-
const inv = JSON.parse(
|
|
10525
|
+
const inv = JSON.parse(readFileSync7(from, "utf8"));
|
|
10127
10526
|
const outDir = resolve11(
|
|
10128
|
-
strFlag(flags, "out") ??
|
|
10527
|
+
strFlag(flags, "out") ?? join24(dirname8(from), "program")
|
|
10129
10528
|
);
|
|
10130
10529
|
const plan = planScaffold(inv, {
|
|
10131
|
-
sourceDir:
|
|
10132
|
-
resolve11(
|
|
10133
|
-
inv.source.endsWith(".html") ? inv.source : join23(inv.source, "index.html")
|
|
10134
|
-
)
|
|
10135
|
-
),
|
|
10530
|
+
sourceDir: scaffoldSourceDir(inv),
|
|
10136
10531
|
probeDuration: probeAudioDuration
|
|
10137
10532
|
});
|
|
10138
10533
|
writeScaffold(plan, outDir);
|
|
10139
|
-
const programFile =
|
|
10534
|
+
const programFile = join24(outDir, "program.mjs");
|
|
10140
10535
|
const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
|
|
10141
10536
|
const built = buildProgram(mod.default, "program.mjs", plan.program);
|
|
10142
10537
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
10143
10538
|
if (built.problems.length) return EXIT_ERROR;
|
|
10144
10539
|
writeFileSync5(
|
|
10145
|
-
|
|
10540
|
+
join24(outDir, "config.json"),
|
|
10146
10541
|
JSON.stringify(built.config, null, 2) + "\n"
|
|
10147
10542
|
);
|
|
10148
10543
|
r.done(
|
|
10149
|
-
{ program: outDir, report:
|
|
10150
|
-
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${
|
|
10544
|
+
{ program: outDir, report: join24(dirname8(outDir), "REPORT.md") },
|
|
10545
|
+
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join24(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
|
|
10151
10546
|
);
|
|
10152
10547
|
return EXIT_OK;
|
|
10153
10548
|
}
|
|
10549
|
+
function scaffoldSourceDir(inv) {
|
|
10550
|
+
if (inv.root) return inv.root;
|
|
10551
|
+
return inv.source.endsWith(".html") ? dirname8(resolve11(inv.source)) : resolve11(inv.source);
|
|
10552
|
+
}
|
|
10154
10553
|
|
|
10155
10554
|
// src/plugin/recipe.ts
|
|
10156
|
-
import { readFileSync as
|
|
10555
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
10157
10556
|
import { basename as basename9 } from "path";
|
|
10158
10557
|
var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
10159
10558
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
@@ -10173,7 +10572,7 @@ async function cmdPush2(argv) {
|
|
|
10173
10572
|
throw new UsageError(USAGE);
|
|
10174
10573
|
}
|
|
10175
10574
|
const filename = recipeFilename(file);
|
|
10176
|
-
const body =
|
|
10575
|
+
const body = readFileSync8(file);
|
|
10177
10576
|
const r = createReporter(flags.json === true);
|
|
10178
10577
|
const origin = platformOrigin({
|
|
10179
10578
|
origin: strFlag(flags, "origin"),
|
|
@@ -11244,9 +11643,9 @@ async function cmdActionsScript(input, flags) {
|
|
|
11244
11643
|
}
|
|
11245
11644
|
|
|
11246
11645
|
// src/plugin/ingest.ts
|
|
11247
|
-
import { existsSync as
|
|
11646
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11248
11647
|
import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
|
|
11249
|
-
import { basename as basename10, join as
|
|
11648
|
+
import { basename as basename10, join as join25, resolve as resolve14 } from "path";
|
|
11250
11649
|
import { inflateRawSync } from "zlib";
|
|
11251
11650
|
import {
|
|
11252
11651
|
cursorFromPlaywrightTrace,
|
|
@@ -11364,7 +11763,7 @@ async function cmdIngest(argv) {
|
|
|
11364
11763
|
"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]"
|
|
11365
11764
|
);
|
|
11366
11765
|
const videoPath = resolve14(source);
|
|
11367
|
-
if (!
|
|
11766
|
+
if (!existsSync21(videoPath)) throw new UsageError(`${source}: no such file`);
|
|
11368
11767
|
const r = createReporter(flags.json === true);
|
|
11369
11768
|
const outDir = resolve14(strFlag(flags, "out") ?? "take");
|
|
11370
11769
|
const offsetMs = numFlag(flags, "offset", 0);
|
|
@@ -11391,7 +11790,7 @@ async function cmdIngest(argv) {
|
|
|
11391
11790
|
trace = await readTraceFile(resolve14(cursorPath), offsetMs);
|
|
11392
11791
|
for (const n of trace.notes) r.log(`note: ${n}`);
|
|
11393
11792
|
}
|
|
11394
|
-
if (
|
|
11793
|
+
if (existsSync21(join25(outDir, "meta.json"))) {
|
|
11395
11794
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11396
11795
|
r.log(
|
|
11397
11796
|
`note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
|
|
@@ -11399,7 +11798,7 @@ async function cmdIngest(argv) {
|
|
|
11399
11798
|
}
|
|
11400
11799
|
await mkdir10(outDir, { recursive: true });
|
|
11401
11800
|
const paths = await ensureTakeDir(outDir);
|
|
11402
|
-
const recording =
|
|
11801
|
+
const recording = join25(outDir, `recording.${probed.ext}`);
|
|
11403
11802
|
r.log("copying the recording\u2026");
|
|
11404
11803
|
const remuxed = await copyRecording(videoPath, recording, probed.ext);
|
|
11405
11804
|
const viewport = viewportFlag ?? trace?.viewport ?? {
|
|
@@ -11467,19 +11866,19 @@ async function cmdIngest(argv) {
|
|
|
11467
11866
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11468
11867
|
import {
|
|
11469
11868
|
cpSync,
|
|
11470
|
-
existsSync as
|
|
11869
|
+
existsSync as existsSync23,
|
|
11471
11870
|
mkdirSync as mkdirSync6,
|
|
11472
|
-
readFileSync as
|
|
11871
|
+
readFileSync as readFileSync10,
|
|
11473
11872
|
rmSync as rmSync3,
|
|
11474
11873
|
writeFileSync as writeFileSync6
|
|
11475
11874
|
} from "fs";
|
|
11476
11875
|
import { homedir as homedir2 } from "os";
|
|
11477
|
-
import { join as
|
|
11876
|
+
import { join as join27 } from "path";
|
|
11478
11877
|
import { chromium as chromium2 } from "playwright";
|
|
11479
11878
|
|
|
11480
11879
|
// src/plugin/machine.ts
|
|
11481
|
-
import { existsSync as
|
|
11482
|
-
import { dirname as dirname9, join as
|
|
11880
|
+
import { existsSync as existsSync22, readFileSync as readFileSync9, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
11881
|
+
import { dirname as dirname9, join as join26 } from "path";
|
|
11483
11882
|
var AGENT_NAMES = [
|
|
11484
11883
|
"claude",
|
|
11485
11884
|
"cursor",
|
|
@@ -11499,17 +11898,17 @@ var HOME_DIRS = {
|
|
|
11499
11898
|
copilot: [".copilot"]
|
|
11500
11899
|
};
|
|
11501
11900
|
function detectAgents(root, opts = {}) {
|
|
11502
|
-
const exists = opts.exists ??
|
|
11901
|
+
const exists = opts.exists ?? existsSync22;
|
|
11503
11902
|
const out = [];
|
|
11504
11903
|
for (const agent of AGENT_NAMES) {
|
|
11505
11904
|
for (const rel of PROJECT_DIRS[agent]) {
|
|
11506
|
-
const dir =
|
|
11905
|
+
const dir = join26(root, rel);
|
|
11507
11906
|
if (exists(dir)) out.push({ agent, dir, scope: "project" });
|
|
11508
11907
|
}
|
|
11509
|
-
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(
|
|
11908
|
+
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join26(root, ".github", "copilot-instructions.md"))) {
|
|
11510
11909
|
out.push({
|
|
11511
11910
|
agent,
|
|
11512
|
-
dir:
|
|
11911
|
+
dir: join26(root, ".github", "copilot"),
|
|
11513
11912
|
scope: "project"
|
|
11514
11913
|
});
|
|
11515
11914
|
}
|
|
@@ -11517,7 +11916,7 @@ function detectAgents(root, opts = {}) {
|
|
|
11517
11916
|
if (opts.global && opts.home) {
|
|
11518
11917
|
for (const agent of AGENT_NAMES) {
|
|
11519
11918
|
for (const rel of HOME_DIRS[agent]) {
|
|
11520
|
-
const dir =
|
|
11919
|
+
const dir = join26(opts.home, rel);
|
|
11521
11920
|
if (exists(dir)) out.push({ agent, dir, scope: "global" });
|
|
11522
11921
|
}
|
|
11523
11922
|
}
|
|
@@ -11535,26 +11934,26 @@ function agentsFromFlag(value, root) {
|
|
|
11535
11934
|
return {
|
|
11536
11935
|
homes: wanted.map((agent) => ({
|
|
11537
11936
|
agent,
|
|
11538
|
-
dir:
|
|
11937
|
+
dir: join26(root, PROJECT_DIRS[agent][0]),
|
|
11539
11938
|
scope: "project"
|
|
11540
11939
|
}))
|
|
11541
11940
|
};
|
|
11542
11941
|
}
|
|
11543
11942
|
function skillsDirFor(home) {
|
|
11544
|
-
return
|
|
11943
|
+
return join26(home.dir, "skills");
|
|
11545
11944
|
}
|
|
11546
11945
|
function fallbackSkillsHome(root) {
|
|
11547
|
-
return { agent: "codex", dir:
|
|
11946
|
+
return { agent: "codex", dir: join26(root, ".agents"), scope: "project" };
|
|
11548
11947
|
}
|
|
11549
|
-
function installedSkills(dir, exists =
|
|
11948
|
+
function installedSkills(dir, exists = existsSync22, list = (p) => {
|
|
11550
11949
|
try {
|
|
11551
|
-
return
|
|
11950
|
+
return readdirSync6(p);
|
|
11552
11951
|
} catch {
|
|
11553
11952
|
return [];
|
|
11554
11953
|
}
|
|
11555
11954
|
}) {
|
|
11556
11955
|
if (!exists(dir)) return [];
|
|
11557
|
-
return list(dir).filter((name) => exists(
|
|
11956
|
+
return list(dir).filter((name) => exists(join26(dir, name, "SKILL.md"))).sort();
|
|
11558
11957
|
}
|
|
11559
11958
|
var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
|
|
11560
11959
|
var RULES_END = "<!-- vos:end -->";
|
|
@@ -11586,10 +11985,10 @@ ${RULES_BLOCK}
|
|
|
11586
11985
|
}
|
|
11587
11986
|
return { text, changed: text !== current };
|
|
11588
11987
|
}
|
|
11589
|
-
function rulesFileFor(root, exists =
|
|
11590
|
-
const agents =
|
|
11988
|
+
function rulesFileFor(root, exists = existsSync22) {
|
|
11989
|
+
const agents = join26(root, "AGENTS.md");
|
|
11591
11990
|
if (exists(agents)) return agents;
|
|
11592
|
-
const claude =
|
|
11991
|
+
const claude = join26(root, "CLAUDE.md");
|
|
11593
11992
|
if (exists(claude)) return claude;
|
|
11594
11993
|
return agents;
|
|
11595
11994
|
}
|
|
@@ -11598,14 +11997,14 @@ function chromeCandidates(platform, env) {
|
|
|
11598
11997
|
return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
11599
11998
|
if (platform === "win32")
|
|
11600
11999
|
return [
|
|
11601
|
-
|
|
12000
|
+
join26(
|
|
11602
12001
|
env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
11603
12002
|
"Google",
|
|
11604
12003
|
"Chrome",
|
|
11605
12004
|
"Application",
|
|
11606
12005
|
"chrome.exe"
|
|
11607
12006
|
),
|
|
11608
|
-
|
|
12007
|
+
join26(
|
|
11609
12008
|
env["LOCALAPPDATA"] ?? "",
|
|
11610
12009
|
"Google",
|
|
11611
12010
|
"Chrome",
|
|
@@ -11692,13 +12091,13 @@ function doctorReport(facts) {
|
|
|
11692
12091
|
function bundledSkillsDir(from) {
|
|
11693
12092
|
let dir = dirname9(from);
|
|
11694
12093
|
for (let i = 0; i < 6; i++) {
|
|
11695
|
-
const pkg =
|
|
11696
|
-
if (
|
|
12094
|
+
const pkg = join26(dir, "package.json");
|
|
12095
|
+
if (existsSync22(pkg)) {
|
|
11697
12096
|
try {
|
|
11698
|
-
const name = JSON.parse(
|
|
12097
|
+
const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
|
|
11699
12098
|
if (name === "@vosjs/cli") {
|
|
11700
|
-
const skills =
|
|
11701
|
-
return
|
|
12099
|
+
const skills = join26(dir, "skills");
|
|
12100
|
+
return existsSync22(skills) && statSync7(skills).isDirectory() ? skills : null;
|
|
11702
12101
|
}
|
|
11703
12102
|
} catch {
|
|
11704
12103
|
}
|
|
@@ -11729,7 +12128,7 @@ function npx() {
|
|
|
11729
12128
|
function bundledChromium() {
|
|
11730
12129
|
try {
|
|
11731
12130
|
const p = chromium2.executablePath();
|
|
11732
|
-
return p &&
|
|
12131
|
+
return p && existsSync23(p) ? p : null;
|
|
11733
12132
|
} catch {
|
|
11734
12133
|
return null;
|
|
11735
12134
|
}
|
|
@@ -11738,7 +12137,7 @@ function probe2() {
|
|
|
11738
12137
|
return probeBrowser({
|
|
11739
12138
|
platform: process.platform,
|
|
11740
12139
|
env: process.env,
|
|
11741
|
-
exists:
|
|
12140
|
+
exists: existsSync23,
|
|
11742
12141
|
bundled: bundledChromium
|
|
11743
12142
|
});
|
|
11744
12143
|
}
|
|
@@ -11842,7 +12241,7 @@ async function cmdSetup(argv) {
|
|
|
11842
12241
|
const dest = skillsDirFor(home);
|
|
11843
12242
|
mkdirSync6(dest, { recursive: true });
|
|
11844
12243
|
for (const name of installedSkills(bundled)) {
|
|
11845
|
-
cpSync(
|
|
12244
|
+
cpSync(join27(bundled, name), join27(dest, name), { recursive: true });
|
|
11846
12245
|
}
|
|
11847
12246
|
}
|
|
11848
12247
|
skillsVia = "bundled";
|
|
@@ -11877,7 +12276,7 @@ async function cmdSetup(argv) {
|
|
|
11877
12276
|
let rules = null;
|
|
11878
12277
|
if (!noRules) {
|
|
11879
12278
|
const file = rulesFileFor(root);
|
|
11880
|
-
const existing =
|
|
12279
|
+
const existing = existsSync23(file) ? readFileSync10(file, "utf8") : null;
|
|
11881
12280
|
const { text, changed } = applyRulesBlock(existing);
|
|
11882
12281
|
if (changed) writeFileSync6(file, text);
|
|
11883
12282
|
rules = { file, changed };
|
|
@@ -11991,7 +12390,7 @@ async function cmdWhoami(argv) {
|
|
|
11991
12390
|
function cmdLogout(argv) {
|
|
11992
12391
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
|
|
11993
12392
|
const r = reporterFor(flags);
|
|
11994
|
-
const had =
|
|
12393
|
+
const had = existsSync23(CREDENTIALS_PATH);
|
|
11995
12394
|
rmSync3(CREDENTIALS_PATH, { force: true });
|
|
11996
12395
|
const envKey = Boolean(process.env.VOS_API_KEY?.trim());
|
|
11997
12396
|
r.done(
|
|
@@ -12054,7 +12453,7 @@ Take pipeline
|
|
|
12054
12453
|
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]
|
|
12055
12454
|
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
|
|
12056
12455
|
vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
|
|
12057
|
-
a finished piece (HyperFrames, Remotion
|
|
12456
|
+
a finished piece (a HyperFrames composition, a Remotion project bundled with its own bundler, a hand-rolled page) read as a browser renders it: words, palette, faces (catalog or missing), media, scenes, gaps \u2192 port/inventory.json, with stills from its render
|
|
12058
12457
|
vos port scaffold [--from port/inventory.json] [--out port/program] [--json]
|
|
12059
12458
|
a data-first program from it: every word a bound element, every colour a key, knobs, a label and a TODO per scene, the score as a doc.json track, port/REPORT.md
|
|
12060
12459
|
vos build <program.mjs> [--out config.json] [--json]
|
|
@@ -12395,7 +12794,7 @@ function takeStorageState(flags) {
|
|
|
12395
12794
|
const raw = strFlag(flags, "storage-state");
|
|
12396
12795
|
if (!raw) return void 0;
|
|
12397
12796
|
const path = resolve15(raw);
|
|
12398
|
-
if (!
|
|
12797
|
+
if (!existsSync24(path))
|
|
12399
12798
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
12400
12799
|
return path;
|
|
12401
12800
|
}
|
|
@@ -12408,7 +12807,7 @@ function takeSessionName(flags, storageState) {
|
|
|
12408
12807
|
throw new UsageError(
|
|
12409
12808
|
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
12410
12809
|
);
|
|
12411
|
-
if (!
|
|
12810
|
+
if (!existsSync24(sessionDir(name)))
|
|
12412
12811
|
throw new UsageError(
|
|
12413
12812
|
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
12414
12813
|
);
|
|
@@ -12631,7 +13030,7 @@ ${lines.join("\n")}
|
|
|
12631
13030
|
}
|
|
12632
13031
|
const paths = takePaths(outDir);
|
|
12633
13032
|
const prepare = async () => {
|
|
12634
|
-
if (
|
|
13033
|
+
if (existsSync24(join28(outDir, "meta.json"))) {
|
|
12635
13034
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12636
13035
|
r.log(
|
|
12637
13036
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
@@ -12696,7 +13095,7 @@ ${lines.join("\n")}
|
|
|
12696
13095
|
},
|
|
12697
13096
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
12698
13097
|
${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen \xB7 ${rec.dead.pct}% dead${deadWarns(rec.dead) ? " \u26A0 " + deadLine(rec.dead) : ""}${skippedNote}
|
|
12699
|
-
Next: edit ${
|
|
13098
|
+
Next: edit ${join28(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
12700
13099
|
);
|
|
12701
13100
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
12702
13101
|
} finally {
|
|
@@ -12722,7 +13121,7 @@ async function cmdCreate2(argv) {
|
|
|
12722
13121
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
12723
13122
|
const outDir = resolve15(strFlag(flags, "out") ?? "take");
|
|
12724
13123
|
const format = videoFormat(strFlag(flags, "format"), positionals[0]);
|
|
12725
|
-
const out = positionals[0] ??
|
|
13124
|
+
const out = positionals[0] ?? join28(outDir, `out.${format}`);
|
|
12726
13125
|
const parallel = numFlag(flags, "parallel", 1);
|
|
12727
13126
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
12728
13127
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -12735,7 +13134,7 @@ async function cmdCreate2(argv) {
|
|
|
12735
13134
|
const headers = takeHeaders(multi);
|
|
12736
13135
|
const paths = takePaths(outDir);
|
|
12737
13136
|
const prepare = async () => {
|
|
12738
|
-
if (
|
|
13137
|
+
if (existsSync24(join28(outDir, "meta.json"))) {
|
|
12739
13138
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12740
13139
|
r.log(
|
|
12741
13140
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
@@ -12842,13 +13241,13 @@ async function cmdPlan(argv) {
|
|
|
12842
13241
|
);
|
|
12843
13242
|
const r = createReporter(flags.json === true);
|
|
12844
13243
|
if (flags.fresh === true) {
|
|
12845
|
-
await rm5(
|
|
13244
|
+
await rm5(join28(dir, "doc.json"), { force: true });
|
|
12846
13245
|
r.log("note: --fresh discarded the existing doc.json");
|
|
12847
13246
|
}
|
|
12848
13247
|
let reuse;
|
|
12849
13248
|
if (flags.reuse === true) {
|
|
12850
|
-
const from = resolve15(strFlag(flags, "from") ??
|
|
12851
|
-
if (!
|
|
13249
|
+
const from = resolve15(strFlag(flags, "from") ?? join28(dir, PREV_DOC_NAME));
|
|
13250
|
+
if (!existsSync24(from)) {
|
|
12852
13251
|
throw new UsageError(
|
|
12853
13252
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
12854
13253
|
);
|
|
@@ -12859,7 +13258,7 @@ async function cmdPlan(argv) {
|
|
|
12859
13258
|
};
|
|
12860
13259
|
}
|
|
12861
13260
|
const style = await resolveStyleRef(flags);
|
|
12862
|
-
const hasDoc =
|
|
13261
|
+
const hasDoc = existsSync24(join28(dir, "doc.json"));
|
|
12863
13262
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
12864
13263
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
12865
13264
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -12917,7 +13316,7 @@ async function cmdPlan(argv) {
|
|
|
12917
13316
|
...s.motion ? { motion: s.motion } : {},
|
|
12918
13317
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
12919
13318
|
},
|
|
12920
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
13319
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join28(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
|
|
12921
13320
|
);
|
|
12922
13321
|
return EXIT_OK;
|
|
12923
13322
|
}
|
|
@@ -13040,7 +13439,7 @@ async function cmdRender(argv) {
|
|
|
13040
13439
|
);
|
|
13041
13440
|
const r = createReporter(flags.json === true);
|
|
13042
13441
|
const format = videoFormat(strFlag(flags, "format"), positionals[1]);
|
|
13043
|
-
const out = positionals[1] ??
|
|
13442
|
+
const out = positionals[1] ?? join28(dir, `out.${format}`);
|
|
13044
13443
|
const parallel = numFlag(flags, "parallel", 1);
|
|
13045
13444
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
13046
13445
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -13287,11 +13686,11 @@ async function resolveStyleRef(flags) {
|
|
|
13287
13686
|
}
|
|
13288
13687
|
async function resolveDocRef(ref, flags, what) {
|
|
13289
13688
|
const styleRef = ref;
|
|
13290
|
-
const file =
|
|
13689
|
+
const file = existsSync24(styleRef) ? resolve15(
|
|
13291
13690
|
styleRef,
|
|
13292
|
-
|
|
13691
|
+
existsSync24(join28(styleRef, "doc.json")) ? "doc.json" : ""
|
|
13293
13692
|
) : null;
|
|
13294
|
-
if (file &&
|
|
13693
|
+
if (file && existsSync24(file)) {
|
|
13295
13694
|
return {
|
|
13296
13695
|
from: file,
|
|
13297
13696
|
doc: migrateHostedDoc5(
|
|
@@ -13378,7 +13777,7 @@ async function cmdDigest(argv) {
|
|
|
13378
13777
|
kinds,
|
|
13379
13778
|
frames: d.images.full,
|
|
13380
13779
|
crops: d.images.crop,
|
|
13381
|
-
sheet: d.images.sheet ?
|
|
13780
|
+
sheet: d.images.sheet ? join28(result.outDir, d.images.sheet) : null,
|
|
13382
13781
|
scenes: kinds.scene ?? 0,
|
|
13383
13782
|
sourceDuration: d.take.sourceDuration,
|
|
13384
13783
|
outputDuration: d.take.outputDuration,
|
|
@@ -13406,7 +13805,7 @@ async function cmdOpen(argv) {
|
|
|
13406
13805
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
13407
13806
|
const r = createReporter(flags.json === true);
|
|
13408
13807
|
const take = await loadTake(dir);
|
|
13409
|
-
if (!
|
|
13808
|
+
if (!existsSync24(take.paths.recording)) {
|
|
13410
13809
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
13411
13810
|
}
|
|
13412
13811
|
const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
|
|
@@ -13447,9 +13846,9 @@ async function cmdJudge(argv) {
|
|
|
13447
13846
|
"vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
|
|
13448
13847
|
);
|
|
13449
13848
|
const r = createReporter(flags.json === true);
|
|
13450
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
13451
|
-
if (!
|
|
13452
|
-
if (!
|
|
13849
|
+
const kitFile = target.endsWith("kit.json") ? target : join28(target, "kit.json");
|
|
13850
|
+
if (!existsSync24(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
13851
|
+
if (!existsSync24(against))
|
|
13453
13852
|
throw new UsageError(`${against}: no reference manifest`);
|
|
13454
13853
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
13455
13854
|
const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
|
|
@@ -13571,7 +13970,7 @@ async function cmdValidate(argv) {
|
|
|
13571
13970
|
const target = positionals[0];
|
|
13572
13971
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
13573
13972
|
const r = createReporter(flags.json === true);
|
|
13574
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
13973
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync24(join28(target, "kit.json")) && !isTakeDir(target) ? join28(target, "kit.json") : null;
|
|
13575
13974
|
if (kitTarget) {
|
|
13576
13975
|
const picture = flags.picture === true;
|
|
13577
13976
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -13603,9 +14002,9 @@ async function cmdValidate(argv) {
|
|
|
13603
14002
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
13604
14003
|
return EXIT_OK;
|
|
13605
14004
|
}
|
|
13606
|
-
if (!isTakeDir(target) &&
|
|
14005
|
+
if (!isTakeDir(target) && existsSync24(join28(target, "config.json"))) {
|
|
13607
14006
|
const parsed = JSON.parse(
|
|
13608
|
-
await readFile17(
|
|
14007
|
+
await readFile17(join28(target, "config.json"), "utf8")
|
|
13609
14008
|
);
|
|
13610
14009
|
const pre = preflightConfig(parsed);
|
|
13611
14010
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -13646,7 +14045,7 @@ async function cmdValidate(argv) {
|
|
|
13646
14045
|
const take = await loadTake(target);
|
|
13647
14046
|
const problems = [];
|
|
13648
14047
|
const warnings = [];
|
|
13649
|
-
if (!
|
|
14048
|
+
if (!existsSync24(take.paths.recording)) {
|
|
13650
14049
|
problems.push("missing recording (re-run record)");
|
|
13651
14050
|
} else {
|
|
13652
14051
|
const mismatch = containerMismatch(
|
|
@@ -13994,7 +14393,7 @@ async function cmdSession(argv) {
|
|
|
13994
14393
|
if (!arg) throw new UsageError(usage);
|
|
13995
14394
|
const bad = sessionNameVerdict(arg);
|
|
13996
14395
|
if (bad) throw new UsageError(bad);
|
|
13997
|
-
if (!
|
|
14396
|
+
if (!existsSync24(sessionDir(arg)))
|
|
13998
14397
|
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
13999
14398
|
const summary3 = await summarizeSession(arg);
|
|
14000
14399
|
const url = strFlag(flags, "url");
|
|
@@ -14098,4 +14497,4 @@ export {
|
|
|
14098
14497
|
verbHelp,
|
|
14099
14498
|
run
|
|
14100
14499
|
};
|
|
14101
|
-
//# sourceMappingURL=chunk-
|
|
14500
|
+
//# sourceMappingURL=chunk-FQA7LFXG.js.map
|