@vosjs/cli 0.54.2 → 0.55.1

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.
@@ -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 existsSync23 } from "fs";
103
- import { join as join27, resolve as resolve15 } from "path";
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 existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
9239
- import { dirname as dirname8, join as join23, resolve as resolve11 } from "path";
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 existsSync17,
9245
+ existsSync as existsSync18,
9246
9246
  mkdirSync as mkdirSync3,
9247
- readFileSync as readFileSync5,
9248
- readdirSync as readdirSync4,
9249
- statSync as statSync5
9247
+ readFileSync as readFileSync6,
9248
+ readdirSync as readdirSync5,
9249
+ statSync as statSync6
9250
9250
  } from "fs";
9251
- import { dirname as dirname6, join as join21, relative as relative2, resolve as resolve9 } from "path";
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 (existsSync17(join21(dir, "hyperframes.json"))) return "hyperframes";
9256
- const pkg = join21(dir, "package.json");
9257
- if (existsSync17(pkg)) {
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(readFileSync5(pkg, "utf8"));
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 (!existsSync17(source)) return null;
9272
- if (statSync5(source).isFile()) return /\.html?$/i.test(source) ? source : null;
9273
- const index = join21(source, "index.html");
9274
- return existsSync17(index) ? index : null;
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) => existsSync17(d) ? readdirSync4(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join21(d, f)) : [];
9278
- for (const d of [join21(dir, "renders"), join21(dir, "out"), dir]) {
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
- const text = (node.textContent || '').replace(/\\s+/g, ' ').trim()
9395
- const el = node.parentElement
9396
- if (!text || !el || ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TITLE'].includes(el.tagName)) continue
9397
- if (seen.has(el)) continue
9398
- seen.add(el)
9399
- const cs = getComputedStyle(el)
9400
- const r = el.getBoundingClientRect()
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: cs.fontFamily.split(',')[0].trim().replace(/^["']|["']$/g, ''),
9411
- size: Math.round(parseFloat(cs.fontSize) * sy), weight: Number(cs.fontWeight) || 400,
9412
- style: cs.fontStyle, color: cs.color, letterSpacing: Number.isFinite(ls) ? Math.round(ls * sy * 100) / 100 : 0,
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 = existsSync17(abs) && statSync5(abs).isDirectory() ? abs : dirname6(abs);
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) break;
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
- "a Remotion project: its words and colours live in its source (src/), which the agent reads; the inventory records the render and its stills"
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 = join21(opts.outDir, "ref");
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 = join21(refDir, `src-${t.toFixed(2)}.png`);
9974
+ const file = join22(refDir, `src-${t.toFixed(2)}.png`);
9589
9975
  const ok = spawnSync2("ffmpeg", [
9590
9976
  "-v",
9591
9977
  "error",
@@ -9638,8 +10024,8 @@ async function readInventory(source, opts) {
9638
10024
 
9639
10025
  // src/plugin/portScaffold.ts
9640
10026
  import { spawnSync as spawnSync3 } from "child_process";
9641
- import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
9642
- import { basename as basename7, dirname as dirname7, join as join22, resolve as resolve10 } from "path";
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";
9643
10029
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
9644
10030
  var DESIGN_HEIGHT = 1080;
9645
10031
  function toHex(css) {
@@ -9728,6 +10114,7 @@ function planScaffold(inv, opts) {
9728
10114
  id: key,
9729
10115
  type: "text",
9730
10116
  content: { $data: key },
10117
+ ...t.split ? { split: { type: t.split } } : {},
9731
10118
  position: t.box ? { x: fmtPct(t.box.x / inv.width), y: fmtPct(t.box.y / inv.height) } : "center",
9732
10119
  font: {
9733
10120
  family,
@@ -9802,6 +10189,7 @@ function planScaffold(inv, opts) {
9802
10189
  data.scenes = inv.scenes.map((s) => ({
9803
10190
  name: s.name,
9804
10191
  at: s.start,
10192
+ ...s.ground ? { ground: colorOf(s.ground) } : {},
9805
10193
  until: s.duration == null ? null : Math.round((s.start + s.duration) * 1e3) / 1e3
9806
10194
  }));
9807
10195
  data.sceneElements = sceneElements;
@@ -9848,8 +10236,8 @@ function planScaffold(inv, opts) {
9848
10236
  const copies = [];
9849
10237
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
9850
10238
  const from = resolve10(opts.sourceDir, m.src);
9851
- const name = basename7(from);
9852
- if (!existsSync18(from)) {
10239
+ const name = basename8(from);
10240
+ if (!existsSync19(from)) {
9853
10241
  report.push(
9854
10242
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
9855
10243
  );
@@ -9977,7 +10365,9 @@ export default {
9977
10365
  tl.addLabel(s.name, s.at)
9978
10366
  // Each scene's words show in its window; the motion replaces this.
9979
10367
  for (const id of ctx.data.sceneElements[s.name] || []) {
9980
- const p = ctx.elements.get(id).props
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
9981
10371
  if (s.at > 0) {
9982
10372
  tl.set(p, { opacity: 0 }, 0)
9983
10373
  tl.set(p, { opacity: 1 }, s.at)
@@ -9991,20 +10381,25 @@ ${sceneTodos}
9991
10381
  },
9992
10382
 
9993
10383
  onFrame(ctx, content) {
9994
- ctx.scene.background.set(ctx.data.bg)${painterFrame}
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}
9995
10390
  },
9996
10391
  }
9997
10392
  `;
9998
10393
  }
9999
10394
  function reportText(inv, r) {
10000
10395
  const lines = [
10001
- `# Port report: ${inv.source}`,
10396
+ `# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
10002
10397
  "",
10003
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}\`.`,
10004
10399
  "",
10005
10400
  "## Placed",
10006
10401
  "",
10007
- `- ${r.elements} words as bound text elements, ${Object.keys(inv.palette).length} palette colours from the source's custom properties, ${r.params} knobs.`,
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.`,
10008
10403
  `- ${inv.scenes.length} scenes as labels${inv.scenes.length ? "" : " (none declared: name them as you translate)"}.`,
10009
10404
  ...r.scores.length ? [
10010
10405
  `- the score as a doc.json track (${r.scores.join(", ")}, copied beside the program; vos push uploads it).`
@@ -10043,20 +10438,20 @@ function reportText(inv, r) {
10043
10438
  "",
10044
10439
  "## Reference",
10045
10440
  "",
10046
- inv.render ? `The source's render: ${inv.render}. Stills in port/ref/. Check every scene: \`npx vos compare . --against ${inv.render}\`.` : "No render of the source was found: render it with its own tool and pass it to `vos compare --against`.",
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`.",
10047
10442
  ""
10048
10443
  ];
10049
10444
  return lines.join("\n");
10050
10445
  }
10051
10446
  function writeScaffold(plan, outDir) {
10052
10447
  mkdirSync4(outDir, { recursive: true });
10053
- writeFileSync4(join22(outDir, "program.mjs"), plan.program);
10448
+ writeFileSync4(join23(outDir, "program.mjs"), plan.program);
10054
10449
  writeFileSync4(
10055
- join22(outDir, "doc.json"),
10450
+ join23(outDir, "doc.json"),
10056
10451
  JSON.stringify(plan.doc, null, 2) + "\n"
10057
10452
  );
10058
- writeFileSync4(join22(dirname7(outDir), "REPORT.md"), plan.report);
10059
- for (const [from, name] of plan.copies) copyFileSync(from, join22(outDir, name));
10453
+ writeFileSync4(join23(dirname7(outDir), "REPORT.md"), plan.report);
10454
+ for (const [from, name] of plan.copies) copyFileSync(from, join23(outDir, name));
10060
10455
  }
10061
10456
  function probeAudioDuration(file) {
10062
10457
  const res = spawnSync3(
@@ -10090,7 +10485,7 @@ async function inventory(argv) {
10090
10485
  outDir,
10091
10486
  log: (l) => r.log(`note: ${l}`)
10092
10487
  });
10093
- const file = join23(outDir, "inventory.json");
10488
+ const file = join24(outDir, "inventory.json");
10094
10489
  writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
10095
10490
  const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
10096
10491
  r.log(
@@ -10122,32 +10517,32 @@ function summary(inv) {
10122
10517
  async function scaffold(argv) {
10123
10518
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
10124
10519
  const r = createReporter(flags.json === true);
10125
- const from = resolve11(strFlag(flags, "from") ?? join23("port", "inventory.json"));
10126
- if (!existsSync19(from))
10520
+ const from = resolve11(strFlag(flags, "from") ?? join24("port", "inventory.json"));
10521
+ if (!existsSync20(from))
10127
10522
  throw new UsageError(
10128
10523
  `no inventory at ${from}: run npx vos port inventory <source> first`
10129
10524
  );
10130
- const inv = JSON.parse(readFileSync6(from, "utf8"));
10525
+ const inv = JSON.parse(readFileSync7(from, "utf8"));
10131
10526
  const outDir = resolve11(
10132
- strFlag(flags, "out") ?? join23(dirname8(from), "program")
10527
+ strFlag(flags, "out") ?? join24(dirname8(from), "program")
10133
10528
  );
10134
10529
  const plan = planScaffold(inv, {
10135
10530
  sourceDir: scaffoldSourceDir(inv),
10136
10531
  probeDuration: probeAudioDuration
10137
10532
  });
10138
10533
  writeScaffold(plan, outDir);
10139
- const programFile = join23(outDir, "program.mjs");
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
- join23(outDir, "config.json"),
10540
+ join24(outDir, "config.json"),
10146
10541
  JSON.stringify(built.config, null, 2) + "\n"
10147
10542
  );
10148
10543
  r.done(
10149
- { program: outDir, report: join23(dirname8(outDir), "REPORT.md") },
10150
- `Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join23(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
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
  }
@@ -10157,12 +10552,12 @@ function scaffoldSourceDir(inv) {
10157
10552
  }
10158
10553
 
10159
10554
  // src/plugin/recipe.ts
10160
- import { readFileSync as readFileSync7 } from "fs";
10161
- import { basename as basename8 } from "path";
10555
+ import { readFileSync as readFileSync8 } from "fs";
10556
+ import { basename as basename9 } from "path";
10162
10557
  var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set(["json", "help"]);
10163
10558
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
10164
10559
  function recipeFilename(file) {
10165
- const name = basename8(file);
10560
+ const name = basename9(file);
10166
10561
  if (!/\.md$/i.test(name)) {
10167
10562
  throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
10168
10563
  }
@@ -10177,7 +10572,7 @@ async function cmdPush2(argv) {
10177
10572
  throw new UsageError(USAGE);
10178
10573
  }
10179
10574
  const filename = recipeFilename(file);
10180
- const body = readFileSync7(file);
10575
+ const body = readFileSync8(file);
10181
10576
  const r = createReporter(flags.json === true);
10182
10577
  const origin = platformOrigin({
10183
10578
  origin: strFlag(flags, "origin"),
@@ -11248,9 +11643,9 @@ async function cmdActionsScript(input, flags) {
11248
11643
  }
11249
11644
 
11250
11645
  // src/plugin/ingest.ts
11251
- import { existsSync as existsSync20 } from "fs";
11646
+ import { existsSync as existsSync21 } from "fs";
11252
11647
  import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
11253
- import { basename as basename9, join as join24, resolve as resolve14 } from "path";
11648
+ import { basename as basename10, join as join25, resolve as resolve14 } from "path";
11254
11649
  import { inflateRawSync } from "zlib";
11255
11650
  import {
11256
11651
  cursorFromPlaywrightTrace,
@@ -11331,7 +11726,7 @@ async function copyRecording(src, dest, ext) {
11331
11726
  }
11332
11727
  }
11333
11728
  async function readTraceFile(path, offsetMs) {
11334
- const kind = traceKindOf(basename9(path));
11729
+ const kind = traceKindOf(basename10(path));
11335
11730
  if (!kind) {
11336
11731
  throw new UsageError(
11337
11732
  `${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
@@ -11352,7 +11747,7 @@ async function readTraceFile(path, offsetMs) {
11352
11747
  return cursorFromPlaywrightTrace(text2, { offsetMs });
11353
11748
  }
11354
11749
  const text = await readFile16(path, "utf8");
11355
- return cursorFromTrace(basename9(path), text, { offsetMs });
11750
+ return cursorFromTrace(basename10(path), text, { offsetMs });
11356
11751
  }
11357
11752
  function parseViewport(v) {
11358
11753
  if (!v) return null;
@@ -11368,7 +11763,7 @@ async function cmdIngest(argv) {
11368
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]"
11369
11764
  );
11370
11765
  const videoPath = resolve14(source);
11371
- if (!existsSync20(videoPath)) throw new UsageError(`${source}: no such file`);
11766
+ if (!existsSync21(videoPath)) throw new UsageError(`${source}: no such file`);
11372
11767
  const r = createReporter(flags.json === true);
11373
11768
  const outDir = resolve14(strFlag(flags, "out") ?? "take");
11374
11769
  const offsetMs = numFlag(flags, "offset", 0);
@@ -11383,7 +11778,7 @@ async function cmdIngest(argv) {
11383
11778
  "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
11384
11779
  );
11385
11780
  const backdrop = footage ? null : await takeBackdrop(flags, r);
11386
- r.log(`probing ${basename9(videoPath)}\u2026`);
11781
+ r.log(`probing ${basename10(videoPath)}\u2026`);
11387
11782
  const probed = await probeVideoFile(videoPath);
11388
11783
  if (!(probed.durationMs > 0)) {
11389
11784
  throw new UsageError(
@@ -11395,7 +11790,7 @@ async function cmdIngest(argv) {
11395
11790
  trace = await readTraceFile(resolve14(cursorPath), offsetMs);
11396
11791
  for (const n of trace.notes) r.log(`note: ${n}`);
11397
11792
  }
11398
- if (existsSync20(join24(outDir, "meta.json"))) {
11793
+ if (existsSync21(join25(outDir, "meta.json"))) {
11399
11794
  const { prevDoc, kept } = await prepareReRecord(outDir);
11400
11795
  r.log(
11401
11796
  `note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
@@ -11403,7 +11798,7 @@ async function cmdIngest(argv) {
11403
11798
  }
11404
11799
  await mkdir10(outDir, { recursive: true });
11405
11800
  const paths = await ensureTakeDir(outDir);
11406
- const recording = join24(outDir, `recording.${probed.ext}`);
11801
+ const recording = join25(outDir, `recording.${probed.ext}`);
11407
11802
  r.log("copying the recording\u2026");
11408
11803
  const remuxed = await copyRecording(videoPath, recording, probed.ext);
11409
11804
  const viewport = viewportFlag ?? trace?.viewport ?? {
@@ -11432,13 +11827,13 @@ async function cmdIngest(argv) {
11432
11827
  const clicks = events.filter((e) => e.type === "down").length;
11433
11828
  const seconds = (probed.durationMs / 1e3).toFixed(1);
11434
11829
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
11435
- const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename9(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
11830
+ const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename10(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
11436
11831
  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)`;
11437
11832
  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`;
11438
11833
  r.done(
11439
11834
  {
11440
11835
  out: outDir,
11441
- recording: basename9(recording),
11836
+ recording: basename10(recording),
11442
11837
  durationMs: probed.durationMs,
11443
11838
  width: probed.width,
11444
11839
  height: probed.height,
@@ -11447,7 +11842,7 @@ async function cmdIngest(argv) {
11447
11842
  hasAudio: probed.hasAudio,
11448
11843
  remuxed,
11449
11844
  cursor: trace ? {
11450
- file: basename9(cursorPath),
11845
+ file: basename10(cursorPath),
11451
11846
  kind: trace.kind,
11452
11847
  events: events.length,
11453
11848
  clicks,
@@ -11459,7 +11854,7 @@ async function cmdIngest(argv) {
11459
11854
  backdrop: plan.backdrop ?? null,
11460
11855
  as: footage ? "footage" : "take"
11461
11856
  },
11462
- `Ingested ${basename9(videoPath)} \u2192 ${outDir}/${basename9(recording)} (${facts})
11857
+ `Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
11463
11858
  ${opened}
11464
11859
  ${cursorLine}
11465
11860
  ${next}`
@@ -11471,19 +11866,19 @@ async function cmdIngest(argv) {
11471
11866
  import { spawnSync as spawnSync4 } from "child_process";
11472
11867
  import {
11473
11868
  cpSync,
11474
- existsSync as existsSync22,
11869
+ existsSync as existsSync23,
11475
11870
  mkdirSync as mkdirSync6,
11476
- readFileSync as readFileSync9,
11871
+ readFileSync as readFileSync10,
11477
11872
  rmSync as rmSync3,
11478
11873
  writeFileSync as writeFileSync6
11479
11874
  } from "fs";
11480
11875
  import { homedir as homedir2 } from "os";
11481
- import { join as join26 } from "path";
11876
+ import { join as join27 } from "path";
11482
11877
  import { chromium as chromium2 } from "playwright";
11483
11878
 
11484
11879
  // src/plugin/machine.ts
11485
- import { existsSync as existsSync21, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
11486
- import { dirname as dirname9, join as join25 } from "path";
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";
11487
11882
  var AGENT_NAMES = [
11488
11883
  "claude",
11489
11884
  "cursor",
@@ -11503,17 +11898,17 @@ var HOME_DIRS = {
11503
11898
  copilot: [".copilot"]
11504
11899
  };
11505
11900
  function detectAgents(root, opts = {}) {
11506
- const exists = opts.exists ?? existsSync21;
11901
+ const exists = opts.exists ?? existsSync22;
11507
11902
  const out = [];
11508
11903
  for (const agent of AGENT_NAMES) {
11509
11904
  for (const rel of PROJECT_DIRS[agent]) {
11510
- const dir = join25(root, rel);
11905
+ const dir = join26(root, rel);
11511
11906
  if (exists(dir)) out.push({ agent, dir, scope: "project" });
11512
11907
  }
11513
- if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join25(root, ".github", "copilot-instructions.md"))) {
11908
+ if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join26(root, ".github", "copilot-instructions.md"))) {
11514
11909
  out.push({
11515
11910
  agent,
11516
- dir: join25(root, ".github", "copilot"),
11911
+ dir: join26(root, ".github", "copilot"),
11517
11912
  scope: "project"
11518
11913
  });
11519
11914
  }
@@ -11521,7 +11916,7 @@ function detectAgents(root, opts = {}) {
11521
11916
  if (opts.global && opts.home) {
11522
11917
  for (const agent of AGENT_NAMES) {
11523
11918
  for (const rel of HOME_DIRS[agent]) {
11524
- const dir = join25(opts.home, rel);
11919
+ const dir = join26(opts.home, rel);
11525
11920
  if (exists(dir)) out.push({ agent, dir, scope: "global" });
11526
11921
  }
11527
11922
  }
@@ -11539,26 +11934,26 @@ function agentsFromFlag(value, root) {
11539
11934
  return {
11540
11935
  homes: wanted.map((agent) => ({
11541
11936
  agent,
11542
- dir: join25(root, PROJECT_DIRS[agent][0]),
11937
+ dir: join26(root, PROJECT_DIRS[agent][0]),
11543
11938
  scope: "project"
11544
11939
  }))
11545
11940
  };
11546
11941
  }
11547
11942
  function skillsDirFor(home) {
11548
- return join25(home.dir, "skills");
11943
+ return join26(home.dir, "skills");
11549
11944
  }
11550
11945
  function fallbackSkillsHome(root) {
11551
- return { agent: "codex", dir: join25(root, ".agents"), scope: "project" };
11946
+ return { agent: "codex", dir: join26(root, ".agents"), scope: "project" };
11552
11947
  }
11553
- function installedSkills(dir, exists = existsSync21, list = (p) => {
11948
+ function installedSkills(dir, exists = existsSync22, list = (p) => {
11554
11949
  try {
11555
- return readdirSync5(p);
11950
+ return readdirSync6(p);
11556
11951
  } catch {
11557
11952
  return [];
11558
11953
  }
11559
11954
  }) {
11560
11955
  if (!exists(dir)) return [];
11561
- return list(dir).filter((name) => exists(join25(dir, name, "SKILL.md"))).sort();
11956
+ return list(dir).filter((name) => exists(join26(dir, name, "SKILL.md"))).sort();
11562
11957
  }
11563
11958
  var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
11564
11959
  var RULES_END = "<!-- vos:end -->";
@@ -11590,10 +11985,10 @@ ${RULES_BLOCK}
11590
11985
  }
11591
11986
  return { text, changed: text !== current };
11592
11987
  }
11593
- function rulesFileFor(root, exists = existsSync21) {
11594
- const agents = join25(root, "AGENTS.md");
11988
+ function rulesFileFor(root, exists = existsSync22) {
11989
+ const agents = join26(root, "AGENTS.md");
11595
11990
  if (exists(agents)) return agents;
11596
- const claude = join25(root, "CLAUDE.md");
11991
+ const claude = join26(root, "CLAUDE.md");
11597
11992
  if (exists(claude)) return claude;
11598
11993
  return agents;
11599
11994
  }
@@ -11602,14 +11997,14 @@ function chromeCandidates(platform, env) {
11602
11997
  return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
11603
11998
  if (platform === "win32")
11604
11999
  return [
11605
- join25(
12000
+ join26(
11606
12001
  env["PROGRAMFILES"] ?? "C:\\Program Files",
11607
12002
  "Google",
11608
12003
  "Chrome",
11609
12004
  "Application",
11610
12005
  "chrome.exe"
11611
12006
  ),
11612
- join25(
12007
+ join26(
11613
12008
  env["LOCALAPPDATA"] ?? "",
11614
12009
  "Google",
11615
12010
  "Chrome",
@@ -11696,13 +12091,13 @@ function doctorReport(facts) {
11696
12091
  function bundledSkillsDir(from) {
11697
12092
  let dir = dirname9(from);
11698
12093
  for (let i = 0; i < 6; i++) {
11699
- const pkg = join25(dir, "package.json");
11700
- if (existsSync21(pkg)) {
12094
+ const pkg = join26(dir, "package.json");
12095
+ if (existsSync22(pkg)) {
11701
12096
  try {
11702
- const name = JSON.parse(readFileSync8(pkg, "utf8")).name;
12097
+ const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
11703
12098
  if (name === "@vosjs/cli") {
11704
- const skills = join25(dir, "skills");
11705
- return existsSync21(skills) && statSync6(skills).isDirectory() ? skills : null;
12099
+ const skills = join26(dir, "skills");
12100
+ return existsSync22(skills) && statSync7(skills).isDirectory() ? skills : null;
11706
12101
  }
11707
12102
  } catch {
11708
12103
  }
@@ -11733,7 +12128,7 @@ function npx() {
11733
12128
  function bundledChromium() {
11734
12129
  try {
11735
12130
  const p = chromium2.executablePath();
11736
- return p && existsSync22(p) ? p : null;
12131
+ return p && existsSync23(p) ? p : null;
11737
12132
  } catch {
11738
12133
  return null;
11739
12134
  }
@@ -11742,7 +12137,7 @@ function probe2() {
11742
12137
  return probeBrowser({
11743
12138
  platform: process.platform,
11744
12139
  env: process.env,
11745
- exists: existsSync22,
12140
+ exists: existsSync23,
11746
12141
  bundled: bundledChromium
11747
12142
  });
11748
12143
  }
@@ -11846,7 +12241,7 @@ async function cmdSetup(argv) {
11846
12241
  const dest = skillsDirFor(home);
11847
12242
  mkdirSync6(dest, { recursive: true });
11848
12243
  for (const name of installedSkills(bundled)) {
11849
- cpSync(join26(bundled, name), join26(dest, name), { recursive: true });
12244
+ cpSync(join27(bundled, name), join27(dest, name), { recursive: true });
11850
12245
  }
11851
12246
  }
11852
12247
  skillsVia = "bundled";
@@ -11881,7 +12276,7 @@ async function cmdSetup(argv) {
11881
12276
  let rules = null;
11882
12277
  if (!noRules) {
11883
12278
  const file = rulesFileFor(root);
11884
- const existing = existsSync22(file) ? readFileSync9(file, "utf8") : null;
12279
+ const existing = existsSync23(file) ? readFileSync10(file, "utf8") : null;
11885
12280
  const { text, changed } = applyRulesBlock(existing);
11886
12281
  if (changed) writeFileSync6(file, text);
11887
12282
  rules = { file, changed };
@@ -11995,7 +12390,7 @@ async function cmdWhoami(argv) {
11995
12390
  function cmdLogout(argv) {
11996
12391
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
11997
12392
  const r = reporterFor(flags);
11998
- const had = existsSync22(CREDENTIALS_PATH);
12393
+ const had = existsSync23(CREDENTIALS_PATH);
11999
12394
  rmSync3(CREDENTIALS_PATH, { force: true });
12000
12395
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
12001
12396
  r.done(
@@ -12058,7 +12453,7 @@ Take pipeline
12058
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]
12059
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
12060
12455
  vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
12061
- a finished piece (HyperFrames, Remotion's render, 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
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
12062
12457
  vos port scaffold [--from port/inventory.json] [--out port/program] [--json]
12063
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
12064
12459
  vos build <program.mjs> [--out config.json] [--json]
@@ -12399,7 +12794,7 @@ function takeStorageState(flags) {
12399
12794
  const raw = strFlag(flags, "storage-state");
12400
12795
  if (!raw) return void 0;
12401
12796
  const path = resolve15(raw);
12402
- if (!existsSync23(path))
12797
+ if (!existsSync24(path))
12403
12798
  throw new UsageError(`--storage-state: no such file: ${path}`);
12404
12799
  return path;
12405
12800
  }
@@ -12412,7 +12807,7 @@ function takeSessionName(flags, storageState) {
12412
12807
  throw new UsageError(
12413
12808
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
12414
12809
  );
12415
- if (!existsSync23(sessionDir(name)))
12810
+ if (!existsSync24(sessionDir(name)))
12416
12811
  throw new UsageError(
12417
12812
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
12418
12813
  );
@@ -12635,7 +13030,7 @@ ${lines.join("\n")}
12635
13030
  }
12636
13031
  const paths = takePaths(outDir);
12637
13032
  const prepare = async () => {
12638
- if (existsSync23(join27(outDir, "meta.json"))) {
13033
+ if (existsSync24(join28(outDir, "meta.json"))) {
12639
13034
  const { prevDoc, kept } = await prepareReRecord(outDir);
12640
13035
  r.log(
12641
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" : "")
@@ -12700,7 +13095,7 @@ ${lines.join("\n")}
12700
13095
  },
12701
13096
  strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
12702
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}
12703
- Next: edit ${join27(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
13098
+ Next: edit ${join28(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
12704
13099
  );
12705
13100
  return strictFail ? EXIT_USAGE : EXIT_OK;
12706
13101
  } finally {
@@ -12726,7 +13121,7 @@ async function cmdCreate2(argv) {
12726
13121
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
12727
13122
  const outDir = resolve15(strFlag(flags, "out") ?? "take");
12728
13123
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
12729
- const out = positionals[0] ?? join27(outDir, `out.${format}`);
13124
+ const out = positionals[0] ?? join28(outDir, `out.${format}`);
12730
13125
  const parallel = numFlag(flags, "parallel", 1);
12731
13126
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
12732
13127
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -12739,7 +13134,7 @@ async function cmdCreate2(argv) {
12739
13134
  const headers = takeHeaders(multi);
12740
13135
  const paths = takePaths(outDir);
12741
13136
  const prepare = async () => {
12742
- if (existsSync23(join27(outDir, "meta.json"))) {
13137
+ if (existsSync24(join28(outDir, "meta.json"))) {
12743
13138
  const { prevDoc, kept } = await prepareReRecord(outDir);
12744
13139
  r.log(
12745
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" : "")
@@ -12846,13 +13241,13 @@ async function cmdPlan(argv) {
12846
13241
  );
12847
13242
  const r = createReporter(flags.json === true);
12848
13243
  if (flags.fresh === true) {
12849
- await rm5(join27(dir, "doc.json"), { force: true });
13244
+ await rm5(join28(dir, "doc.json"), { force: true });
12850
13245
  r.log("note: --fresh discarded the existing doc.json");
12851
13246
  }
12852
13247
  let reuse;
12853
13248
  if (flags.reuse === true) {
12854
- const from = resolve15(strFlag(flags, "from") ?? join27(dir, PREV_DOC_NAME));
12855
- if (!existsSync23(from)) {
13249
+ const from = resolve15(strFlag(flags, "from") ?? join28(dir, PREV_DOC_NAME));
13250
+ if (!existsSync24(from)) {
12856
13251
  throw new UsageError(
12857
13252
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
12858
13253
  );
@@ -12863,7 +13258,7 @@ async function cmdPlan(argv) {
12863
13258
  };
12864
13259
  }
12865
13260
  const style = await resolveStyleRef(flags);
12866
- const hasDoc = existsSync23(join27(dir, "doc.json"));
13261
+ const hasDoc = existsSync24(join28(dir, "doc.json"));
12867
13262
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
12868
13263
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
12869
13264
  const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
@@ -12921,7 +13316,7 @@ async function cmdPlan(argv) {
12921
13316
  ...s.motion ? { motion: s.motion } : {},
12922
13317
  ...s.reuse ? { reuse: s.reuse } : {}
12923
13318
  },
12924
- `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join27(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
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}`
12925
13320
  );
12926
13321
  return EXIT_OK;
12927
13322
  }
@@ -13044,7 +13439,7 @@ async function cmdRender(argv) {
13044
13439
  );
13045
13440
  const r = createReporter(flags.json === true);
13046
13441
  const format = videoFormat(strFlag(flags, "format"), positionals[1]);
13047
- const out = positionals[1] ?? join27(dir, `out.${format}`);
13442
+ const out = positionals[1] ?? join28(dir, `out.${format}`);
13048
13443
  const parallel = numFlag(flags, "parallel", 1);
13049
13444
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
13050
13445
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -13291,11 +13686,11 @@ async function resolveStyleRef(flags) {
13291
13686
  }
13292
13687
  async function resolveDocRef(ref, flags, what) {
13293
13688
  const styleRef = ref;
13294
- const file = existsSync23(styleRef) ? resolve15(
13689
+ const file = existsSync24(styleRef) ? resolve15(
13295
13690
  styleRef,
13296
- existsSync23(join27(styleRef, "doc.json")) ? "doc.json" : ""
13691
+ existsSync24(join28(styleRef, "doc.json")) ? "doc.json" : ""
13297
13692
  ) : null;
13298
- if (file && existsSync23(file)) {
13693
+ if (file && existsSync24(file)) {
13299
13694
  return {
13300
13695
  from: file,
13301
13696
  doc: migrateHostedDoc5(
@@ -13382,7 +13777,7 @@ async function cmdDigest(argv) {
13382
13777
  kinds,
13383
13778
  frames: d.images.full,
13384
13779
  crops: d.images.crop,
13385
- sheet: d.images.sheet ? join27(result.outDir, d.images.sheet) : null,
13780
+ sheet: d.images.sheet ? join28(result.outDir, d.images.sheet) : null,
13386
13781
  scenes: kinds.scene ?? 0,
13387
13782
  sourceDuration: d.take.sourceDuration,
13388
13783
  outputDuration: d.take.outputDuration,
@@ -13410,7 +13805,7 @@ async function cmdOpen(argv) {
13410
13805
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
13411
13806
  const r = createReporter(flags.json === true);
13412
13807
  const take = await loadTake(dir);
13413
- if (!existsSync23(take.paths.recording)) {
13808
+ if (!existsSync24(take.paths.recording)) {
13414
13809
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
13415
13810
  }
13416
13811
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
@@ -13451,9 +13846,9 @@ async function cmdJudge(argv) {
13451
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)"
13452
13847
  );
13453
13848
  const r = createReporter(flags.json === true);
13454
- const kitFile = target.endsWith("kit.json") ? target : join27(target, "kit.json");
13455
- if (!existsSync23(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
13456
- if (!existsSync23(against))
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))
13457
13852
  throw new UsageError(`${against}: no reference manifest`);
13458
13853
  const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
13459
13854
  const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
@@ -13575,7 +13970,7 @@ async function cmdValidate(argv) {
13575
13970
  const target = positionals[0];
13576
13971
  if (!target) throw new UsageError("vos validate <actions.json|take>");
13577
13972
  const r = createReporter(flags.json === true);
13578
- const kitTarget = target.endsWith("kit.json") ? target : existsSync23(join27(target, "kit.json")) && !isTakeDir(target) ? join27(target, "kit.json") : null;
13973
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync24(join28(target, "kit.json")) && !isTakeDir(target) ? join28(target, "kit.json") : null;
13579
13974
  if (kitTarget) {
13580
13975
  const picture = flags.picture === true;
13581
13976
  const verdict = await validateKit(kitTarget, { picture });
@@ -13607,9 +14002,9 @@ async function cmdValidate(argv) {
13607
14002
  r.done({ valid: true, target }, `${target}: valid actions file`);
13608
14003
  return EXIT_OK;
13609
14004
  }
13610
- if (!isTakeDir(target) && existsSync23(join27(target, "config.json"))) {
14005
+ if (!isTakeDir(target) && existsSync24(join28(target, "config.json"))) {
13611
14006
  const parsed = JSON.parse(
13612
- await readFile17(join27(target, "config.json"), "utf8")
14007
+ await readFile17(join28(target, "config.json"), "utf8")
13613
14008
  );
13614
14009
  const pre = preflightConfig(parsed);
13615
14010
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -13650,7 +14045,7 @@ async function cmdValidate(argv) {
13650
14045
  const take = await loadTake(target);
13651
14046
  const problems = [];
13652
14047
  const warnings = [];
13653
- if (!existsSync23(take.paths.recording)) {
14048
+ if (!existsSync24(take.paths.recording)) {
13654
14049
  problems.push("missing recording (re-run record)");
13655
14050
  } else {
13656
14051
  const mismatch = containerMismatch(
@@ -13998,7 +14393,7 @@ async function cmdSession(argv) {
13998
14393
  if (!arg) throw new UsageError(usage);
13999
14394
  const bad = sessionNameVerdict(arg);
14000
14395
  if (bad) throw new UsageError(bad);
14001
- if (!existsSync23(sessionDir(arg)))
14396
+ if (!existsSync24(sessionDir(arg)))
14002
14397
  throw new UsageError(`no session "${arg}" (vos session list)`);
14003
14398
  const summary3 = await summarizeSession(arg);
14004
14399
  const url = strFlag(flags, "url");
@@ -14102,4 +14497,4 @@ export {
14102
14497
  verbHelp,
14103
14498
  run
14104
14499
  };
14105
- //# sourceMappingURL=chunk-MTOCJ2LC.js.map
14500
+ //# sourceMappingURL=chunk-FQA7LFXG.js.map