@vosjs/cli 0.69.1 → 0.71.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.
@@ -131,9 +131,9 @@ function landedLines(l) {
131
131
  }
132
132
 
133
133
  // src/plugin/run.ts
134
- import { mkdir as mkdir8, readFile as readFile12, rm as rm2 } from "fs/promises";
135
- import { existsSync as existsSync22 } from "fs";
136
- import { join as join24, resolve as resolve13 } from "path";
134
+ import { mkdir as mkdir8, readFile as readFile13, rm as rm2 } from "fs/promises";
135
+ import { existsSync as existsSync23 } from "fs";
136
+ import { join as join25, resolve as resolve13 } from "path";
137
137
  import {
138
138
  END_CARD_FROM as END_CARD_FROM2,
139
139
  docOutputDuration as docOutputDuration3,
@@ -2710,15 +2710,21 @@ import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "f
2710
2710
  import { existsSync as existsSync2 } from "fs";
2711
2711
  import { join as join2, resolve } from "path";
2712
2712
  import {
2713
+ CHANGE_GRID,
2714
+ CHANGE_LEAD,
2715
+ CHANGE_OFFSETS,
2713
2716
  DIGEST_CROP_MAX,
2714
2717
  DIGEST_FULL_MAX,
2715
2718
  DIGEST_VERSION,
2716
2719
  MOTION_DELTA,
2720
+ ambientDrift,
2717
2721
  buildDigest,
2722
+ changedRegion,
2718
2723
  cropBox,
2719
2724
  frameGeometry,
2720
2725
  momentsFromDoc,
2721
2726
  outputDurationOf,
2727
+ pickChange,
2722
2728
  planForDigest,
2723
2729
  sceneChanges
2724
2730
  } from "@vosjs/studio-core";
@@ -2762,7 +2768,40 @@ async function binsInPage(opts) {
2762
2768
  prev = luma;
2763
2769
  w.__progress = (i + 1) / n;
2764
2770
  }
2765
- w.__done = { ok: true, bins, width: size.width, height: size.height };
2771
+ const g = document.createElement("canvas");
2772
+ g.width = opts.grid.w;
2773
+ g.height = opts.grid.h;
2774
+ const gctx = g.getContext("2d", { willReadFrequently: true });
2775
+ const gridAt = async (t) => {
2776
+ const el = await getFrame(Math.max(0, Math.min(opts.durationS, t)));
2777
+ gctx.drawImage(el, 0, 0, opts.grid.w, opts.grid.h);
2778
+ const d = gctx.getImageData(0, 0, opts.grid.w, opts.grid.h).data;
2779
+ const out = new Array(opts.grid.w * opts.grid.h);
2780
+ for (let p = 0; p < out.length; p++) {
2781
+ const o = p * 4;
2782
+ out[p] = (d[o] * 299 + d[o + 1] * 587 + d[o + 2] * 114) / 1e3;
2783
+ }
2784
+ return out;
2785
+ };
2786
+ const changes = [];
2787
+ for (const seed of opts.seeds) {
2788
+ const samples = [];
2789
+ if (seed.t - opts.lead >= 0)
2790
+ samples.push({ dt: -opts.lead, luma: await gridAt(seed.t - opts.lead) });
2791
+ samples.push({ dt: 0, luma: await gridAt(seed.t) });
2792
+ for (const dt of opts.offsets) {
2793
+ if (seed.t + dt > opts.durationS) break;
2794
+ samples.push({ dt, luma: await gridAt(seed.t + dt) });
2795
+ }
2796
+ changes.push({ id: seed.id, samples });
2797
+ }
2798
+ w.__done = {
2799
+ ok: true,
2800
+ bins,
2801
+ width: size.width,
2802
+ height: size.height,
2803
+ changes
2804
+ };
2766
2805
  } catch (e) {
2767
2806
  w.__error = String(e instanceof Error && e.stack || e);
2768
2807
  }
@@ -2985,23 +3024,66 @@ async function digestTake(browser, dir, opts = {}) {
2985
3024
  });
2986
3025
  await page.goto(`${server.base}/digest.html`);
2987
3026
  await page.waitForFunction("window.__pageReady__ === true");
2988
- void page.evaluate(binsInPage, { source, durationS: durS, delta: MOTION_DELTA }).catch(() => {
3027
+ const seeds = momentsFromDoc(doc, plan, {}).filter(
3028
+ (m) => m.at !== null && (m.kind === "click" || m.kind === "typing")
3029
+ ).map((m) => ({
3030
+ id: m.id,
3031
+ t: m.kind === "click" ? m.at : m.source.out
3032
+ }));
3033
+ void page.evaluate(binsInPage, {
3034
+ source,
3035
+ durationS: durS,
3036
+ delta: MOTION_DELTA,
3037
+ seeds,
3038
+ grid: { ...CHANGE_GRID },
3039
+ offsets: [...CHANGE_OFFSETS],
3040
+ lead: CHANGE_LEAD
3041
+ }).catch(() => {
2989
3042
  });
2990
3043
  const b = await waitForPageDone(
2991
3044
  page,
2992
3045
  "digest bins",
2993
3046
  () => {
2994
3047
  },
2995
- 6e4 + durS * 400
3048
+ 6e4 + durS * 400 + seeds.length * 2e3
2996
3049
  );
2997
3050
  bins = b.bins;
2998
3051
  plan = planForDigest(doc, bins);
2999
3052
  frameW = b.width;
3000
3053
  frameH = b.height;
3054
+ const sampled = b.changes ?? [];
3055
+ const changes = seeds.flatMap((seed) => {
3056
+ const row = sampled.find((c) => c.id === seed.id);
3057
+ const at2 = row?.samples.find((smp) => smp.dt === 0);
3058
+ const earlier = row?.samples.find((smp) => smp.dt < 0);
3059
+ const after = row?.samples.filter((smp) => smp.dt > 0) ?? [];
3060
+ if (!row || !at2 || !after.length) return [];
3061
+ const before = { ...CHANGE_GRID, luma: at2.luma };
3062
+ const rate = earlier ? ambientDrift(
3063
+ { ...CHANGE_GRID, luma: earlier.luma },
3064
+ before,
3065
+ -earlier.dt
3066
+ ) : null;
3067
+ const w = pickChange(
3068
+ { id: seed.id, at: seed.t },
3069
+ after.map((smp) => ({
3070
+ dt: smp.dt,
3071
+ region: changedRegion(
3072
+ before,
3073
+ { ...CHANGE_GRID, luma: smp.luma },
3074
+ MOTION_DELTA,
3075
+ 1,
3076
+ rate ? { rate, dt: smp.dt } : null
3077
+ )
3078
+ }))
3079
+ );
3080
+ return w ? [w] : [];
3081
+ });
3001
3082
  moments = momentsFromDoc(doc, plan, {
3002
3083
  bins,
3003
3084
  scenes: sceneChanges(bins),
3004
- transcript
3085
+ transcript,
3086
+ changes
3005
3087
  });
3006
3088
  const geo = frameGeometry(doc, frameW, frameH);
3007
3089
  const shots = moments.filter((m) => m.at !== null).map((m) => {
@@ -5341,8 +5423,12 @@ import {
5341
5423
  } from "@vosjs/studio-core";
5342
5424
 
5343
5425
  // src/plugin/reuse.ts
5344
- import { docFreezes } from "@vosjs/studio-core";
5426
+ import { docFreezes, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
5345
5427
  var MIN_SPAN = 0.15;
5428
+ var AFTER_STEP = 1;
5429
+ var LAYER_AFTER_STEP = 5;
5430
+ var WINDOW_SLACK = 0.05;
5431
+ var LAYER_DRIFT = 0.5;
5346
5432
  function matchStep(old, newSteps) {
5347
5433
  const usable = (s) => !!s && s.skipped !== true;
5348
5434
  if (old.id) {
@@ -5358,6 +5444,19 @@ function matchStep(old, newSteps) {
5358
5444
  );
5359
5445
  return old.selector !== void 0 && bySignature.length === 1 ? bySignature[0] : usable(atIndex) && old.selector === void 0 && atIndex.do === old.do ? atIndex : null;
5360
5446
  }
5447
+ function stepByRef(steps, ref) {
5448
+ return typeof ref === "string" ? steps.find((s) => s.id === ref) : steps.find((s) => s.step === ref);
5449
+ }
5450
+ var stepName = (s) => String(s.id ?? s.step);
5451
+ function stepUnder(t, steps, after = AFTER_STEP) {
5452
+ const gestures = steps.filter((s) => s.do !== "wait" && !s.skipped);
5453
+ const holding = gestures.find(
5454
+ (s) => s.tStart - WINDOW_SLACK <= t && t <= s.tEnd + WINDOW_SLACK
5455
+ );
5456
+ if (holding) return holding;
5457
+ const before = gestures.filter((s) => s.tEnd <= t && t - s.tEnd <= after).sort((a, b) => b.tEnd - a.tEnd);
5458
+ return before[0] ?? null;
5459
+ }
5361
5460
  function buildStepMap(oldSteps, newSteps, oldDuration, newDuration) {
5362
5461
  const points = [
5363
5462
  { from: 0, to: 0 },
@@ -5401,34 +5500,57 @@ function buildStepMap(oldSteps, newSteps, oldDuration, newDuration) {
5401
5500
  return { map, unmatched, matched };
5402
5501
  }
5403
5502
  function resolveAnchor2(anchor, newSteps) {
5404
- const step = typeof anchor.step === "string" ? newSteps.find((s) => s.id === anchor.step) : newSteps.find((s) => s.step === anchor.step);
5503
+ const step = stepByRef(newSteps, anchor.step);
5405
5504
  if (!step || step.skipped) return null;
5406
5505
  const base = anchor.at === "end" ? step.tEnd : step.tStart;
5407
5506
  return base + (anchor.offset ?? 0);
5408
5507
  }
5409
- function retimeSpans(kind, spans, stepMap, newSteps, newDuration, report) {
5508
+ function framed(span) {
5509
+ const s = span;
5510
+ if (typeof s.level === "number" && typeof s.cx === "number") {
5511
+ return ` (it framed ${s.cx.toFixed(2)}, ${(s.cy ?? 0).toFixed(2)} at ${s.level.toFixed(1)}\xD7)`;
5512
+ }
5513
+ if (typeof s.rate === "number") return ` (${s.rate}\xD7 speed)`;
5514
+ if (typeof s.rx === "number" || typeof s.ry === "number") {
5515
+ return ` (lean ${s.rx ?? 0}\xB0, ${s.ry ?? 0}\xB0)`;
5516
+ }
5517
+ if (typeof s.seconds === "number") return ` (a ${s.seconds} s hold)`;
5518
+ return "";
5519
+ }
5520
+ function placeMoment(t, anchor, ctx) {
5521
+ if (anchor) {
5522
+ const at2 = resolveAnchor2(anchor, ctx.newSteps);
5523
+ if (at2 === null) return { gone: String(anchor.step) };
5524
+ ctx.report.anchored++;
5525
+ return { at: at2, via: "anchor" };
5526
+ }
5527
+ const under = stepUnder(t, ctx.oldSteps);
5528
+ if (under) {
5529
+ const hit = matchStep(under, ctx.newSteps);
5530
+ if (!hit) return { gone: under };
5531
+ ctx.report.anchored++;
5532
+ return { at: hit.tStart + (t - under.tStart), via: "step" };
5533
+ }
5534
+ ctx.report.mapped++;
5535
+ return { at: ctx.stepMap.map(t), via: "map" };
5536
+ }
5537
+ var goneWords = (gone) => typeof gone === "string" ? `its anchored step (${gone}) is missing or skipped in the new recording` : `it sat on step ${stepName(gone)} (${gone.do}${gone.selector ? ` ${gone.selector}` : ""}), which is missing or skipped in the new recording`;
5538
+ function retimeSpans(kind, spans, ctx, opts = { overlap: "trim" }) {
5410
5539
  const out = [];
5411
5540
  for (const span of spans) {
5412
5541
  const length = span.out - span.in;
5413
- let nextIn = null;
5414
- if (span.anchor) {
5415
- nextIn = resolveAnchor2(span.anchor, newSteps);
5416
- if (nextIn === null) {
5417
- report.flagged.push(
5418
- `${kind} ${span.id}: its anchored step (${String(span.anchor.step)}) is missing or skipped in the new recording \u2014 fell back to the step map`
5419
- );
5420
- } else {
5421
- report.anchored++;
5422
- }
5423
- }
5424
- if (nextIn === null) {
5425
- nextIn = stepMap.map(span.in);
5426
- report.mapped++;
5542
+ const placed = placeMoment(span.in, span.anchor, ctx);
5543
+ if ("gone" in placed) {
5544
+ ctx.report.flagged.push(
5545
+ `${kind} ${span.id}: ${goneWords(placed.gone)} \u2014 dropped; re-aim it on the new footage${framed(span)}`
5546
+ );
5547
+ continue;
5427
5548
  }
5428
- const nextOut = Math.min(nextIn + length, newDuration);
5429
- const clampedIn = Math.max(0, Math.min(nextIn, newDuration));
5549
+ const nextIn = placed.at;
5550
+ const nextOut = Math.min(nextIn + length, ctx.newDuration);
5551
+ const clampedIn = Math.max(0, Math.min(nextIn, ctx.newDuration));
5430
5552
  if (nextOut - clampedIn < MIN_SPAN) {
5431
- report.flagged.push(
5553
+ ctx.report.flagged.push(
5432
5554
  `${kind} ${span.id}: lands outside the new recording (${clampedIn.toFixed(2)}s) \u2014 dropped`
5433
5555
  );
5434
5556
  continue;
@@ -5436,18 +5558,19 @@ function retimeSpans(kind, spans, stepMap, newSteps, newDuration, report) {
5436
5558
  out.push({ ...span, in: +clampedIn.toFixed(3), out: +nextOut.toFixed(3) });
5437
5559
  }
5438
5560
  out.sort((a, b) => a.in - b.in);
5561
+ if (opts.overlap === "keep") return out;
5439
5562
  const kept = [];
5440
5563
  for (const span of out) {
5441
5564
  const prev = kept.at(-1);
5442
5565
  if (prev && span.in < prev.out) {
5443
5566
  const trimmed = { ...span, in: +prev.out.toFixed(3) };
5444
5567
  if (trimmed.out - trimmed.in < MIN_SPAN) {
5445
- report.flagged.push(
5568
+ ctx.report.flagged.push(
5446
5569
  `${kind} ${span.id}: fully overlapped ${prev.id} after re-timing \u2014 dropped`
5447
5570
  );
5448
5571
  continue;
5449
5572
  }
5450
- report.flagged.push(
5573
+ ctx.report.flagged.push(
5451
5574
  `${kind} ${span.id}: overlapped ${prev.id} after re-timing \u2014 trimmed to ${trimmed.in.toFixed(2)}s`
5452
5575
  );
5453
5576
  kept.push(trimmed);
@@ -5466,9 +5589,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
5466
5589
  for (const miss of stepMap.unmatched) {
5467
5590
  if (miss.do === "wait") continue;
5468
5591
  report.flagged.push(
5469
- `step ${miss.id ?? miss.step} (${miss.do}${miss.selector ? ` ${miss.selector}` : ""}) has no match in the new recording \u2014 times near it interpolate`
5592
+ `step ${stepName(miss)} (${miss.do}${miss.selector ? ` ${miss.selector}` : ""}) has no match in the new recording \u2014 times near it interpolate`
5470
5593
  );
5471
5594
  }
5595
+ const ctx = { oldSteps, newSteps, stepMap, newDuration, report };
5472
5596
  const untrimmed = prev.segments.length === 0 || prev.segments.length === 1 && prev.segments[0].in <= 0.05 && prev.segments[0].out >= oldDuration - 0.05;
5473
5597
  const segments = untrimmed ? [{ in: 0, out: +newDuration.toFixed(3) }] : prev.segments.map((s) => ({
5474
5598
  in: +stepMap.map(s.in).toFixed(3),
@@ -5482,49 +5606,34 @@ function retimeCut(prev, newSteps, newDurationMs) {
5482
5606
  const zoom = retimeSpans(
5483
5607
  "zoom",
5484
5608
  prev.zoom.filter((z) => z.source === "manual"),
5485
- stepMap,
5486
- newSteps,
5487
- newDuration,
5488
- report
5609
+ ctx
5489
5610
  );
5490
5611
  const speed = retimeSpans(
5491
5612
  "speed",
5492
5613
  (prev.speed ?? []).filter((s) => s.source !== "auto"),
5493
- stepMap,
5494
- newSteps,
5495
- newDuration,
5496
- report
5614
+ ctx
5497
5615
  );
5498
5616
  const tilt = retimeSpans(
5499
5617
  "tilt",
5500
5618
  (prev.tilt ?? []).filter((t) => t.source !== "auto"),
5501
- stepMap,
5502
- newSteps,
5503
- newDuration,
5504
- report
5619
+ ctx
5505
5620
  );
5506
5621
  const freeze = [];
5507
5622
  for (const f of docFreezes(prev)) {
5508
- let at2 = null;
5509
- if (f.anchor) {
5510
- at2 = resolveAnchor2(f.anchor, newSteps);
5511
- if (at2 === null) {
5512
- report.flagged.push(
5513
- `freeze ${f.id}: its anchored step (${String(f.anchor.step)}) is missing or skipped in the new recording \u2014 fell back to the step map`
5514
- );
5515
- } else report.anchored++;
5516
- }
5517
- if (at2 === null) {
5518
- at2 = stepMap.map(f.at);
5519
- report.mapped++;
5623
+ const placed = placeMoment(f.at, f.anchor, ctx);
5624
+ if ("gone" in placed) {
5625
+ report.flagged.push(
5626
+ `freeze ${f.id}: ${goneWords(placed.gone)} \u2014 dropped; re-place it on the new footage${framed(f)}`
5627
+ );
5628
+ continue;
5520
5629
  }
5521
- if (at2 < 0 || at2 > newDuration) {
5630
+ if (placed.at < 0 || placed.at > newDuration) {
5522
5631
  report.flagged.push(
5523
- `freeze ${f.id}: lands outside the new recording (${at2.toFixed(2)}s) \u2014 dropped`
5632
+ `freeze ${f.id}: lands outside the new recording (${placed.at.toFixed(2)}s) \u2014 dropped`
5524
5633
  );
5525
5634
  continue;
5526
5635
  }
5527
- freeze.push({ ...f, at: +at2.toFixed(3) });
5636
+ freeze.push({ ...f, at: +placed.at.toFixed(3) });
5528
5637
  }
5529
5638
  freeze.sort((a, b) => a.at - b.at);
5530
5639
  const rejected = [];
@@ -5533,15 +5642,97 @@ function retimeCut(prev, newSteps, newDurationMs) {
5533
5642
  ...retimeSpans(
5534
5643
  `rejected ${lane}`,
5535
5644
  (prev.rejected ?? []).filter((r) => r.lane === lane),
5536
- stepMap,
5537
- newSteps,
5538
- newDuration,
5539
- report
5645
+ ctx,
5646
+ { overlap: "keep" }
5540
5647
  )
5541
5648
  );
5542
5649
  }
5543
5650
  return { segments, zoom, speed, freeze, tilt, rejected, report };
5544
5651
  }
5652
+ function sourceToOutput(pieces, s) {
5653
+ let acc = 0;
5654
+ for (const p of pieces) {
5655
+ const rate = p.rate && p.rate > 0 ? p.rate : 1;
5656
+ const len = Math.max(0, p.out - p.in) / rate;
5657
+ if (s < p.in) return acc;
5658
+ if (s <= p.out) return acc + (s - p.in) / rate;
5659
+ acc += len;
5660
+ }
5661
+ return acc;
5662
+ }
5663
+ function outputToSource(pieces, t) {
5664
+ let acc = 0;
5665
+ for (const p of pieces) {
5666
+ const rate = p.rate && p.rate > 0 ? p.rate : 1;
5667
+ const len = Math.max(0, p.out - p.in) / rate;
5668
+ if (acc + len >= t) return p.in + Math.max(0, t - acc) * rate;
5669
+ acc += len;
5670
+ }
5671
+ return pieces.at(-1)?.out ?? t;
5672
+ }
5673
+ function retimeOverlays(prev, next, report) {
5674
+ const overlays = prev.overlays ?? [];
5675
+ if (!overlays.length) return [];
5676
+ const oldSteps = prev.source.meta.steps ?? [];
5677
+ const newSteps = next.source.meta.steps ?? [];
5678
+ const oldPieces = ratedSegments6(prev);
5679
+ const newPieces = ratedSegments6(next);
5680
+ const oldDuration = prev.source.meta.durationMs / 1e3;
5681
+ const newDuration = next.source.meta.durationMs / 1e3;
5682
+ const stepMap = buildStepMap(oldSteps, newSteps, oldDuration, newDuration);
5683
+ const ctx = { oldSteps, newSteps, stepMap, report };
5684
+ const follow = (o, oldRef, newRef) => {
5685
+ const offset = o.start - sourceToOutput(oldPieces, oldRef);
5686
+ const start = Math.max(0, sourceToOutput(newPieces, newRef) + offset);
5687
+ return { ...o, start: +start.toFixed(3) };
5688
+ };
5689
+ return overlays.map((o) => {
5690
+ const pin = o.pin;
5691
+ if (pin?.step !== void 0) {
5692
+ const was = stepByRef(oldSteps, pin.step);
5693
+ const now2 = was ? matchStep(was, newSteps) : stepByRef(newSteps, pin.step);
5694
+ if (!now2 || now2.skipped) {
5695
+ report.flagged.push(
5696
+ `overlay ${o.id}: pinned to step ${String(pin.step)}, which is missing or skipped in the new recording \u2014 kept at ${o.start.toFixed(2)}s; re-pin or re-place it`
5697
+ );
5698
+ return o;
5699
+ }
5700
+ report.anchored++;
5701
+ return follow(o, was?.tStart ?? now2.tStart, now2.tStart);
5702
+ }
5703
+ if (typeof pin?.press === "number") {
5704
+ const placed = placeMoment(pin.press, void 0, ctx);
5705
+ if ("gone" in placed) {
5706
+ report.flagged.push(
5707
+ `overlay ${o.id}: pinned to a press at ${pin.press.toFixed(2)}s, and ${goneWords(placed.gone)} \u2014 kept at ${o.start.toFixed(2)}s; re-pin or re-place it`
5708
+ );
5709
+ return o;
5710
+ }
5711
+ const moved = follow(o, pin.press, placed.at);
5712
+ return { ...moved, pin: { ...pin, press: +placed.at.toFixed(3) } };
5713
+ }
5714
+ const under = stepUnder(
5715
+ outputToSource(oldPieces, o.start),
5716
+ oldSteps,
5717
+ LAYER_AFTER_STEP
5718
+ );
5719
+ if (!under) return o;
5720
+ const now = matchStep(under, newSteps);
5721
+ if (!now) {
5722
+ report.flagged.push(
5723
+ `overlay ${o.id} (${o.start.toFixed(2)}s): it sat over step ${stepName(under)}, which is missing or skipped in the new recording \u2014 kept at its output time; re-place it`
5724
+ );
5725
+ return o;
5726
+ }
5727
+ const expected = follow(o, under.tStart, now.tStart).start;
5728
+ if (Math.abs(expected - o.start) > LAYER_DRIFT) {
5729
+ report.flagged.push(
5730
+ `overlay ${o.id} (${o.start.toFixed(2)}s): it sat over step ${stepName(under)}, which now plays at ${expected.toFixed(2)}s in the cut \u2014 kept at ${o.start.toFixed(2)}s by the output-time contract; pin it to follow (pin: { step: ${JSON.stringify(under.id ?? under.step)} }) or move it`
5731
+ );
5732
+ }
5733
+ return o;
5734
+ });
5735
+ }
5545
5736
 
5546
5737
  // src/plugin/plan.ts
5547
5738
  var overlaps = (a, b) => a.in < b.out && b.in < a.out;
@@ -5590,7 +5781,8 @@ function planOtherMedia(doc, activity) {
5590
5781
  height: m.meta.height,
5591
5782
  duration: m.meta.durationMs / 1e3,
5592
5783
  style: doc.zoomStyle,
5593
- params: doc.zoomParams
5784
+ params: doc.zoomParams,
5785
+ steps: m.meta.steps
5594
5786
  }).filter((z) => !manualZoom.some((x) => overlaps(z, x))).filter((z) => !isRejected("zoom", z, rejected)).map((z) => ({ ...z, id: `${z.id}-${m.id}`, media: m.id }))
5595
5787
  );
5596
5788
  const manualSpeed = (doc.speed ?? []).filter(
@@ -5600,7 +5792,8 @@ function planOtherMedia(doc, activity) {
5600
5792
  ...planAutoSpeed(m.cursor, {
5601
5793
  durationMs: m.meta.durationMs,
5602
5794
  params: doc.speedParams,
5603
- activity
5795
+ activity,
5796
+ steps: m.meta.steps
5604
5797
  }).filter((x) => !manualSpeed.some((y) => x.in < y.out && x.out > y.in)).filter((x) => !isRejected("speed", x, rejected)).map((x) => ({ ...x, id: `${x.id}-${m.id}`, media: m.id }))
5605
5798
  );
5606
5799
  }
@@ -5633,14 +5826,16 @@ async function planTake(dir, opts = {}) {
5633
5826
  height: doc.source.meta.height,
5634
5827
  duration: doc.source.meta.durationMs / 1e3,
5635
5828
  style: doc.zoomStyle,
5636
- params: doc.zoomParams
5829
+ params: doc.zoomParams,
5830
+ steps: doc.source.meta.steps
5637
5831
  }).filter((z) => !manualZoom.some((m) => overlaps(z, m))).filter((z) => !isRejected("zoom", z, doc.rejected));
5638
5832
  doc.zoom = [...manualZoom, ...autoZoom].sort((a, b) => a.in - b.in);
5639
5833
  const manualSpeed = rt.speed;
5640
5834
  const autoSpeed = planAutoSpeed(doc.source.cursor, {
5641
5835
  durationMs: doc.source.meta.durationMs,
5642
5836
  params: doc.speedParams,
5643
- activity
5837
+ activity,
5838
+ steps: doc.source.meta.steps
5644
5839
  }).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
5645
5840
  doc.speed = [...manualSpeed, ...autoSpeed].sort((a, b) => a.in - b.in);
5646
5841
  {
@@ -5655,7 +5850,9 @@ async function planTake(dir, opts = {}) {
5655
5850
  ].sort((a, b) => a.in - b.in);
5656
5851
  }
5657
5852
  if (rt.tilt.length) doc.tilt = rt.tilt;
5658
- if (prev.overlays?.length) doc.overlays = prev.overlays;
5853
+ if (prev.overlays?.length) {
5854
+ doc.overlays = retimeOverlays(prev, doc, rt.report);
5855
+ }
5659
5856
  if (prev.objects?.length) doc.objects = prev.objects;
5660
5857
  if (prev.audio.length) doc.audio = prev.audio;
5661
5858
  if (prev.camMotion?.length) doc.camMotion = prev.camMotion;
@@ -5686,7 +5883,8 @@ async function planTake(dir, opts = {}) {
5686
5883
  height: doc.source.meta.height,
5687
5884
  duration: doc.source.meta.durationMs / 1e3,
5688
5885
  style: doc.zoomStyle,
5689
- params: doc.zoomParams
5886
+ params: doc.zoomParams,
5887
+ steps: doc.source.meta.steps
5690
5888
  }).filter((z) => !manual.some((m) => overlaps(z, m))).filter((z) => !isRejected("zoom", z, doc.rejected));
5691
5889
  doc.zoom = [...manual, ...auto].sort((a, b) => a.in - b.in);
5692
5890
  const manualSpeed = (doc.speed ?? []).filter(
@@ -5695,7 +5893,8 @@ async function planTake(dir, opts = {}) {
5695
5893
  const autoSpeed = planAutoSpeed(doc.source.cursor, {
5696
5894
  durationMs: doc.source.meta.durationMs,
5697
5895
  params: doc.speedParams,
5698
- activity
5896
+ activity,
5897
+ steps: doc.source.meta.steps
5699
5898
  }).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
5700
5899
  doc.speed = [...manualSpeed, ...autoSpeed].sort((a, b) => a.in - b.in);
5701
5900
  {
@@ -5726,12 +5925,14 @@ async function planTake(dir, opts = {}) {
5726
5925
  height: doc.source.meta.height,
5727
5926
  duration: doc.source.meta.durationMs / 1e3,
5728
5927
  style: doc.zoomStyle,
5729
- params: doc.zoomParams
5928
+ params: doc.zoomParams,
5929
+ steps: doc.source.meta.steps
5730
5930
  });
5731
5931
  doc.speed = planAutoSpeed(doc.source.cursor, {
5732
5932
  durationMs: doc.source.meta.durationMs,
5733
5933
  params: doc.speedParams,
5734
- activity
5934
+ activity,
5935
+ steps: doc.source.meta.steps
5735
5936
  });
5736
5937
  fresh = true;
5737
5938
  }
@@ -5763,15 +5964,48 @@ async function planTake(dir, opts = {}) {
5763
5964
 
5764
5965
  // src/plugin/sync.ts
5765
5966
  import { createHash } from "crypto";
5766
- import { existsSync as existsSync10, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
5767
- import { readFile as readFile9 } from "fs/promises";
5967
+ import { existsSync as existsSync11, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
5968
+ import { readFile as readFile10 } from "fs/promises";
5768
5969
  import { createInterface } from "readline/promises";
5769
- import { basename, join as join12 } from "path";
5970
+ import { basename, join as join13 } from "path";
5770
5971
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
5771
5972
 
5772
- // src/plugin/folder.ts
5773
- import { mkdir as mkdir5, writeFile as writeFile7 } from "fs/promises";
5973
+ // src/plugin/takeScript.ts
5974
+ import { existsSync as existsSync10 } from "fs";
5975
+ import { readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
5976
+ import { readFileSync as readFileSync2 } from "fs";
5774
5977
  import { join as join11 } from "path";
5978
+ function scriptFromText(text) {
5979
+ try {
5980
+ const v = JSON.parse(text);
5981
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
5982
+ } catch {
5983
+ return null;
5984
+ }
5985
+ }
5986
+ function scriptToWrite(doc, hasLocal) {
5987
+ const script = doc.source.script;
5988
+ if (hasLocal || !script || typeof script !== "object") return null;
5989
+ return `${JSON.stringify(script, null, 2)}
5990
+ `;
5991
+ }
5992
+ function embedScript(doc, dir) {
5993
+ const path = join11(dir, "actions.json");
5994
+ if (!existsSync10(path)) return;
5995
+ const script = scriptFromText(readFileSync2(path, "utf8"));
5996
+ if (script) doc.source.script = script;
5997
+ }
5998
+ async function writeScriptBeside(dir, doc) {
5999
+ const path = join11(dir, "actions.json");
6000
+ const text = scriptToWrite(doc, existsSync10(path));
6001
+ if (text === null) return false;
6002
+ await writeFile7(path, text, "utf8");
6003
+ return true;
6004
+ }
6005
+
6006
+ // src/plugin/folder.ts
6007
+ import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
6008
+ import { join as join12 } from "path";
5775
6009
  import { recipeHints } from "@vosjs/shared/frontmatter";
5776
6010
  import { migrateHostedDoc } from "@vosjs/studio-core";
5777
6011
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
@@ -5947,25 +6181,25 @@ async function cmdPull(argv) {
5947
6181
  throw new Error(apiError(`pull folder ${folder.slug}`, res));
5948
6182
  const payload = res.body;
5949
6183
  const out = strFlag(flags, "out") ?? folder.slug;
5950
- await mkdir5(join11(out, "recipes"), { recursive: true });
6184
+ await mkdir5(join12(out, "recipes"), { recursive: true });
5951
6185
  const lines = [];
5952
6186
  const recipes = [];
5953
6187
  for (const rec of payload.recipes) {
5954
- const path = join11(out, "recipes", safeName(rec.filename));
5955
- await writeFile7(path, rec.body ?? "");
6188
+ const path = join12(out, "recipes", safeName(rec.filename));
6189
+ await writeFile8(path, rec.body ?? "");
5956
6190
  recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
5957
6191
  }
5958
6192
  for (const rec of payload.inheritedRecipes) {
5959
6193
  const from = safeName(rec.folderSlug ?? "inherited");
5960
- await mkdir5(join11(out, "recipes", "_inherited", from), { recursive: true });
5961
- const path = join11(
6194
+ await mkdir5(join12(out, "recipes", "_inherited", from), { recursive: true });
6195
+ const path = join12(
5962
6196
  out,
5963
6197
  "recipes",
5964
6198
  "_inherited",
5965
6199
  from,
5966
6200
  safeName(rec.filename)
5967
6201
  );
5968
- await writeFile7(path, rec.body ?? "");
6202
+ await writeFile8(path, rec.body ?? "");
5969
6203
  recipes.push({
5970
6204
  path,
5971
6205
  line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
@@ -5973,15 +6207,15 @@ async function cmdPull(argv) {
5973
6207
  }
5974
6208
  const members = [];
5975
6209
  for (const v of payload.voses) {
5976
- const dir = join11(out, "members", safeName(v.slug || v.title || v.id));
6210
+ const dir = join12(out, "members", safeName(v.slug || v.title || v.id));
5977
6211
  await mkdir5(dir, { recursive: true });
5978
6212
  const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
5979
6213
  if (cfg.status !== 200) {
5980
6214
  lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
5981
6215
  continue;
5982
6216
  }
5983
- await writeFile7(
5984
- join11(dir, "config.json"),
6217
+ await writeFile8(
6218
+ join12(dir, "config.json"),
5985
6219
  JSON.stringify(cfg.body.config, null, 2)
5986
6220
  );
5987
6221
  let take = false;
@@ -5992,8 +6226,8 @@ async function cmdPull(argv) {
5992
6226
  { key }
5993
6227
  );
5994
6228
  if (doc.status === 200) {
5995
- await writeFile7(
5996
- join11(dir, "doc.json"),
6229
+ await writeFile8(
6230
+ join12(dir, "doc.json"),
5997
6231
  JSON.stringify(doc.body, null, 2)
5998
6232
  );
5999
6233
  take = true;
@@ -6119,8 +6353,8 @@ function storageStateInDir(dir) {
6119
6353
  if (!f.endsWith(".json") || f === "doc.json" || f === "meta.json" || f === "actions.json" || f === "cursor.json" || f === "vos.json" || f === "doc.prev.json")
6120
6354
  continue;
6121
6355
  try {
6122
- const v = JSON.parse(readFileSync2(join12(dir, f), "utf8"));
6123
- if (looksLikeStorageState(v)) return join12(dir, f);
6356
+ const v = JSON.parse(readFileSync3(join13(dir, f), "utf8"));
6357
+ if (looksLikeStorageState(v)) return join13(dir, f);
6124
6358
  } catch {
6125
6359
  }
6126
6360
  }
@@ -6141,7 +6375,7 @@ async function pushTake(dir, flags, r) {
6141
6375
  throw new Error(`doc.json fails lint:
6142
6376
  ${lint.problems.join("\n ")}`);
6143
6377
  }
6144
- if (!existsSync10(take.paths.recording)) {
6378
+ if (!existsSync11(take.paths.recording)) {
6145
6379
  throw new Error(
6146
6380
  "no recording in this take \u2014 record it, or `vos pull --media` to bring it home"
6147
6381
  );
@@ -6190,7 +6424,7 @@ async function pushTake(dir, flags, r) {
6190
6424
  throw new Error("push cancelled");
6191
6425
  }
6192
6426
  }
6193
- const bytes = await readFile9(take.paths.recording);
6427
+ const bytes = await readFile10(take.paths.recording);
6194
6428
  const hash = createHash("sha256").update(bytes).digest("hex");
6195
6429
  const { type: contentType } = resolveMediaType({
6196
6430
  head: bytes.subarray(0, MEDIA_HEAD_BYTES),
@@ -6225,6 +6459,7 @@ async function pushTake(dir, flags, r) {
6225
6459
  if (upload.reused) r.log(" recording already hosted \u2014 reused");
6226
6460
  const docForPush = structuredClone(take.doc);
6227
6461
  docForPush.source.videoKey = assetUrl;
6462
+ embedScript(docForPush, dir);
6228
6463
  await uploadDocRefs(sourceSidecarRefs(docForPush), dir, ctx, (l) => r.log(l));
6229
6464
  for (const field of ["micKey", "camKey"]) {
6230
6465
  const key = docForPush.source[field];
@@ -6362,9 +6597,9 @@ async function pullTake(dir, flags, r) {
6362
6597
  };
6363
6598
  }
6364
6599
  let media2;
6365
- if (flags.media && existsSync10(join12(dir, "doc.json"))) {
6600
+ if (flags.media && existsSync11(join13(dir, "doc.json"))) {
6366
6601
  const local = JSON.parse(
6367
- await readFile9(join12(dir, "doc.json"), "utf8")
6602
+ await readFile10(join13(dir, "doc.json"), "utf8")
6368
6603
  );
6369
6604
  media2 = await pullMedia(ctx, dir, local, r.log);
6370
6605
  }
@@ -6425,11 +6660,14 @@ async function pullTake(dir, flags, r) {
6425
6660
  const hostedDoc = migrateHostedDoc2(docRes.json);
6426
6661
  const doc = hostedDoc;
6427
6662
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
6428
- if (existsSync10(localRecording)) {
6663
+ if (existsSync11(localRecording)) {
6429
6664
  doc.source.videoKey = basename(localRecording);
6430
6665
  }
6431
- await writeJson(join12(dir, "doc.json"), doc, true);
6666
+ await writeJson(join13(dir, "doc.json"), doc, true);
6432
6667
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
6668
+ if (flags.media && await writeScriptBeside(dir, doc)) {
6669
+ r.log(" actions.json written from the document");
6670
+ }
6433
6671
  const versions = await api(ctx, `/vos/${vosId}/versions`);
6434
6672
  const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
6435
6673
  writeSyncState(dir, {
@@ -6463,6 +6701,7 @@ async function pushTakeClaimable(dir, flags, r) {
6463
6701
  const state = readSyncState(dir);
6464
6702
  const hosted = state?.hostedMedia ?? {};
6465
6703
  const docForPush = structuredClone(take.doc);
6704
+ embedScript(docForPush, dir);
6466
6705
  const refs = [
6467
6706
  ...isTakeRelativeKey(docForPush.source.videoKey) ? [
6468
6707
  {
@@ -6484,8 +6723,8 @@ async function pushTakeClaimable(dir, flags, r) {
6484
6723
  `${ref.where} (${file}) is not a file this take fetched from vos.so, and a claimable push never uploads footage. Push the take with an account (vos login, then vos push ${dir}), or remix a public take fetched with \`vos fetch <url> --media\``
6485
6724
  );
6486
6725
  }
6487
- const path = join12(dir, file);
6488
- const now = existsSync10(path) ? await fileSha256(path) : null;
6726
+ const path = join13(dir, file);
6727
+ const now = existsSync11(path) ? await fileSha256(path) : null;
6489
6728
  if (now !== origin2.sha256) {
6490
6729
  throw new Error(
6491
6730
  `${ref.where} (${file}) changed since it was fetched, so it is no longer the hosted file. A claimable push names only the original; push with an account to upload the new one`
@@ -6519,10 +6758,10 @@ async function pushTakeClaimable(dir, flags, r) {
6519
6758
  }
6520
6759
 
6521
6760
  // src/plugin/program.ts
6522
- import { existsSync as existsSync12 } from "fs";
6523
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
6761
+ import { existsSync as existsSync13 } from "fs";
6762
+ import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
6524
6763
  import { createInterface as createInterface2 } from "readline/promises";
6525
- import { basename as basename3, dirname as dirname2, join as join13 } from "path";
6764
+ import { basename as basename3, dirname as dirname2, join as join14 } from "path";
6526
6765
  import {
6527
6766
  CURRENT_CONFIG_VERSION,
6528
6767
  migrateConfig,
@@ -6656,12 +6895,12 @@ async function handoffLogin(url, r, opts = {}) {
6656
6895
 
6657
6896
  // src/plugin/programFiles.ts
6658
6897
  import { basename as basename2, extname as extname2, relative as relative2 } from "path";
6659
- import { existsSync as existsSync11, realpathSync } from "fs";
6898
+ import { existsSync as existsSync12, realpathSync } from "fs";
6660
6899
  function isLocalName(value) {
6661
6900
  if (/^(https?:|data:|blob:|asset:|media:)/.test(value)) return false;
6662
6901
  if (value.startsWith("$")) return false;
6663
6902
  if (value.startsWith("<svg") || value.startsWith("<?xml")) return false;
6664
- if (value.startsWith("/")) return existsSync11(value);
6903
+ if (value.startsWith("/")) return existsSync12(value);
6665
6904
  return true;
6666
6905
  }
6667
6906
  var FILE_EXT = /\.(png|jpe?g|webp|gif|avif|svg|mp4|webm|mov|mp3|m4a|ogg|wav|flac|aac|glb|gltf|woff2?|ttf|otf|hdr|exr)$/i;
@@ -6836,8 +7075,8 @@ function preflightConfig(parsed) {
6836
7075
  }
6837
7076
  function resolveConfigPath(target) {
6838
7077
  if (target.endsWith(".json")) return target;
6839
- const inDir = join13(target, "config.json");
6840
- if (existsSync12(inDir)) return inDir;
7078
+ const inDir = join14(target, "config.json");
7079
+ if (existsSync13(inDir)) return inDir;
6841
7080
  throw new UsageError(
6842
7081
  `${target} has no config.json (and is not a take \u2014 no doc.json)`
6843
7082
  );
@@ -6864,8 +7103,8 @@ async function cmdFetch(argv) {
6864
7103
  const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
6865
7104
  const out = strFlag(flags, "out") ?? slug;
6866
7105
  await mkdir6(out, { recursive: true });
6867
- await writeFile8(
6868
- join13(out, "config.json"),
7106
+ await writeFile9(
7107
+ join14(out, "config.json"),
6869
7108
  JSON.stringify(cfg.body.config, null, 2)
6870
7109
  );
6871
7110
  const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
@@ -6890,30 +7129,33 @@ async function cmdFetch(argv) {
6890
7129
  const hosted = migrateHostedDoc3(doc.body);
6891
7130
  const { config: own } = await writeProgramDoc(out, hosted);
6892
7131
  if (own) {
6893
- await writeFile8(join13(out, "config.json"), JSON.stringify(own, null, 2));
7132
+ await writeFile9(join14(out, "config.json"), JSON.stringify(own, null, 2));
6894
7133
  }
6895
7134
  } else if (doc.status === 200) {
6896
7135
  take = true;
6897
7136
  const hosted = migrateHostedDoc3(doc.body);
6898
- await writeFile8(join13(out, "doc.json"), JSON.stringify(hosted, null, 2));
7137
+ await writeFile9(join14(out, "doc.json"), JSON.stringify(hosted, null, 2));
6899
7138
  if (flags.media === true) {
6900
7139
  const media = await pullMedia({ origin, key }, out, hosted, r.log);
6901
7140
  await recordHostedMedia(out, vosId, media);
7141
+ if (await writeScriptBeside(out, hosted)) {
7142
+ r.log(" actions.json written from the document");
7143
+ }
6902
7144
  mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
6903
7145
  }
6904
7146
  }
6905
7147
  }
6906
7148
  let assetsLine = "";
6907
7149
  if (!take) {
6908
- const configPath = join13(out, "config.json");
6909
- const written = JSON.parse(await readFile10(configPath, "utf8"));
7150
+ const configPath = join14(out, "config.json");
7151
+ const written = JSON.parse(await readFile11(configPath, "utf8"));
6910
7152
  const hostedCount = manifestRefs(written).filter(
6911
7153
  ({ ref }) => hostedAssetId(ref, [origin, "https://vos.so", "https://www.vos.so"])
6912
7154
  ).length;
6913
7155
  if (hostedCount && flags.media === true) {
6914
7156
  const pulled = await pullManifest({ origin, key }, out, written, r.log);
6915
7157
  if (pulled) {
6916
- await writeFile8(
7158
+ await writeFile9(
6917
7159
  configPath,
6918
7160
  JSON.stringify({ ...written, assets: pulled.assets }, null, 2)
6919
7161
  );
@@ -7033,7 +7275,7 @@ async function cmdPushProgram(argv) {
7033
7275
  api: strFlag(flags, "api")
7034
7276
  });
7035
7277
  const dir = dirname2(source);
7036
- const parsed = JSON.parse(await readFile10(source, "utf8"));
7278
+ const parsed = JSON.parse(await readFile11(source, "utf8"));
7037
7279
  const pre = preflightConfig(parsed);
7038
7280
  if (!pre.ok || !pre.config) {
7039
7281
  for (const issue of pre.issues) r.log(`error ${issue}`);
@@ -7381,10 +7623,10 @@ async function cmdPullProgram(argv) {
7381
7623
  const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
7382
7624
  if (cfg.status !== 200)
7383
7625
  throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
7384
- const configPath = join13(dir, "config.json");
7626
+ const configPath = join14(dir, "config.json");
7385
7627
  let backedUp = false;
7386
- if (existsSync12(configPath)) {
7387
- await writeFile8(join13(dir, "config.backup.json"), await readFile10(configPath));
7628
+ if (existsSync13(configPath)) {
7629
+ await writeFile9(join14(dir, "config.backup.json"), await readFile11(configPath));
7388
7630
  backedUp = true;
7389
7631
  }
7390
7632
  let headConfig = cfg.body.config;
@@ -7403,7 +7645,7 @@ async function cmdPullProgram(argv) {
7403
7645
  if (own) headConfig = own;
7404
7646
  }
7405
7647
  }
7406
- await writeFile8(configPath, JSON.stringify(headConfig, null, 2));
7648
+ await writeFile9(configPath, JSON.stringify(headConfig, null, 2));
7407
7649
  writeSyncState(dir, {
7408
7650
  vosId,
7409
7651
  versionId: typeof head.id === "string" ? head.id : since
@@ -7417,7 +7659,7 @@ async function cmdPullProgram(argv) {
7417
7659
  protected: protectedIds,
7418
7660
  changes,
7419
7661
  out: configPath,
7420
- backup: backedUp ? join13(dir, "config.backup.json") : null
7662
+ backup: backedUp ? join14(dir, "config.backup.json") : null
7421
7663
  },
7422
7664
  `Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
7423
7665
  Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
@@ -7525,9 +7767,9 @@ function storedProgramConfig(config, programDoc) {
7525
7767
  return lowerProgramDoc(doc).config;
7526
7768
  }
7527
7769
  async function readProgramDoc(dir, config) {
7528
- const docPath = join13(dir, "doc.json");
7529
- if (!existsSync12(docPath)) return null;
7530
- const parsed = JSON.parse(await readFile10(docPath, "utf8"));
7770
+ const docPath = join14(dir, "doc.json");
7771
+ if (!existsSync13(docPath)) return null;
7772
+ const parsed = JSON.parse(await readFile11(docPath, "utf8"));
7531
7773
  if (typeof parsed !== "object" || parsed === null || "source" in parsed)
7532
7774
  return null;
7533
7775
  const raw = parsed;
@@ -7535,23 +7777,23 @@ async function readProgramDoc(dir, config) {
7535
7777
  return { ...raw, program: { ...program, config } };
7536
7778
  }
7537
7779
  async function setProgramStill(dir, t) {
7538
- const docPath = join13(dir, "doc.json");
7780
+ const docPath = join14(dir, "doc.json");
7539
7781
  let doc = { program: {}, audio: [] };
7540
- if (existsSync12(docPath)) {
7541
- const parsed = JSON.parse(await readFile10(docPath, "utf8"));
7782
+ if (existsSync13(docPath)) {
7783
+ const parsed = JSON.parse(await readFile11(docPath, "utf8"));
7542
7784
  if (!parsed || typeof parsed !== "object" || "source" in parsed)
7543
7785
  throw new UsageError(
7544
7786
  `${docPath} is a take's document; a take's cover is its own "still" field`
7545
7787
  );
7546
7788
  doc = parsed;
7547
7789
  }
7548
- await writeFile8(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
7790
+ await writeFile9(docPath, JSON.stringify({ ...doc, still: t }, null, 2) + "\n");
7549
7791
  }
7550
7792
  async function writeProgramDoc(dir, hosted) {
7551
7793
  const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
7552
7794
  const { config, ...rest } = program;
7553
7795
  const onDisk = { ...hosted, program: rest };
7554
- await writeFile8(join13(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
7796
+ await writeFile9(join14(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
7555
7797
  return {
7556
7798
  config: config && typeof config === "object" ? config : null
7557
7799
  };
@@ -7559,7 +7801,7 @@ async function writeProgramDoc(dir, hosted) {
7559
7801
 
7560
7802
  // src/plugin/asset.ts
7561
7803
  import { createHash as createHash2 } from "crypto";
7562
- import { readFileSync as readFileSync3 } from "fs";
7804
+ import { readFileSync as readFileSync4 } from "fs";
7563
7805
  import { basename as basename4 } from "path";
7564
7806
 
7565
7807
  // src/plugin/assetWords.ts
@@ -7702,7 +7944,7 @@ async function cmdPush(argv) {
7702
7944
  const uploaded = [];
7703
7945
  for (const file of positionals) {
7704
7946
  const name = basename4(file);
7705
- const body = readFileSync3(file);
7947
+ const body = readFileSync4(file);
7706
7948
  const media = resolveMediaType({
7707
7949
  head: body.subarray(0, MEDIA_HEAD_BYTES),
7708
7950
  filename: name
@@ -7963,8 +8205,8 @@ async function cmdAsset(argv) {
7963
8205
  }
7964
8206
 
7965
8207
  // src/plugin/remove.ts
7966
- import { existsSync as existsSync13, rmSync as rmSync2, statSync as statSync3 } from "fs";
7967
- import { join as join14 } from "path";
8208
+ import { existsSync as existsSync14, rmSync as rmSync2, statSync as statSync3 } from "fs";
8209
+ import { join as join15 } from "path";
7968
8210
  import { createInterface as createInterface3 } from "readline/promises";
7969
8211
 
7970
8212
  // src/plugin/trash.ts
@@ -8143,7 +8385,7 @@ async function cmdDelete(argv) {
8143
8385
  api: strFlag(flags, "api")
8144
8386
  }).replace(/\/+$/, "");
8145
8387
  const key = requireCredential(strFlag(flags, "key"));
8146
- const dir = existsSync13(target) && statSync3(target).isDirectory() ? target : null;
8388
+ const dir = existsSync14(target) && statSync3(target).isDirectory() ? target : null;
8147
8389
  const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
8148
8390
  if (!vosId)
8149
8391
  throw new UsageError(
@@ -8186,7 +8428,7 @@ async function cmdDelete(argv) {
8186
8428
  const tracking = dir ?? trackingCwd(vosId);
8187
8429
  let unlinked = false;
8188
8430
  if (tracking) {
8189
- rmSync2(join14(tracking, SYNC_STATE_NAME), { force: true });
8431
+ rmSync2(join15(tracking, SYNC_STATE_NAME), { force: true });
8190
8432
  unlinked = true;
8191
8433
  }
8192
8434
  const trashed = res.body.trashed === true;
@@ -8211,13 +8453,13 @@ async function cmdDelete(argv) {
8211
8453
  }
8212
8454
  function trackingCwd(vosId) {
8213
8455
  const cwd = process.cwd();
8214
- if (!existsSync13(join14(cwd, SYNC_STATE_NAME))) return null;
8456
+ if (!existsSync14(join15(cwd, SYNC_STATE_NAME))) return null;
8215
8457
  return readSyncState(cwd)?.vosId === vosId ? cwd : null;
8216
8458
  }
8217
8459
 
8218
8460
  // src/plugin/buildProgram.ts
8219
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
8220
- import { basename as basename5, dirname as dirname3, join as join15, resolve as resolve5 } from "path";
8461
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
8462
+ import { basename as basename5, dirname as dirname3, join as join16, resolve as resolve5 } from "path";
8221
8463
  import { pathToFileURL } from "url";
8222
8464
  import { parse } from "acorn";
8223
8465
  import { fullAncestor } from "acorn-walk";
@@ -8418,7 +8660,7 @@ async function cmdBuild(argv) {
8418
8660
  throw new UsageError("vos build <program.mjs> [--out config.json] [--json]");
8419
8661
  const r = createReporter(flags.json === true);
8420
8662
  const file = resolve5(source);
8421
- const text = readFileSync4(file, "utf8");
8663
+ const text = readFileSync5(file, "utf8");
8422
8664
  const mod = await import(`${pathToFileURL(file).href}?t=${Date.now()}`);
8423
8665
  const program = mod.default ?? mod.program;
8424
8666
  if (!program || typeof program !== "object")
@@ -8435,7 +8677,7 @@ async function cmdBuild(argv) {
8435
8677
  return EXIT_ERROR;
8436
8678
  }
8437
8679
  const out = resolve5(
8438
- strFlag(flags, "out") ?? join15(dirname3(file), "config.json")
8680
+ strFlag(flags, "out") ?? join16(dirname3(file), "config.json")
8439
8681
  );
8440
8682
  writeFileSync2(out, JSON.stringify(built.config, null, 2) + "\n");
8441
8683
  const sidecar = out.replace(/\.json$/, "") + ".sources.json";
@@ -8452,8 +8694,8 @@ async function cmdBuild(argv) {
8452
8694
 
8453
8695
  // src/plugin/compare.ts
8454
8696
  import { spawnSync } from "child_process";
8455
- import { existsSync as existsSync14, mkdirSync as mkdirSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
8456
- import { dirname as dirname4, join as join16, resolve as resolve6 } from "path";
8697
+ import { existsSync as existsSync15, mkdirSync as mkdirSync2, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
8698
+ import { dirname as dirname4, join as join17, resolve as resolve6 } from "path";
8457
8699
  var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
8458
8700
  function sampleTimes(duration, every) {
8459
8701
  const step = every > 0 ? every : 1;
@@ -8520,7 +8762,7 @@ async function cmdCompare(argv) {
8520
8762
  const config = loaded.config;
8521
8763
  const duration = configDuration(config) ?? 5;
8522
8764
  const assets = await programAssets(config, {
8523
- baseDir: /^https?:\/\//.test(source) ? null : existsSync14(source) && statSync4(source).isDirectory() ? source : dirname4(source),
8765
+ baseDir: /^https?:\/\//.test(source) ? null : existsSync15(source) && statSync4(source).isDirectory() ? source : dirname4(source),
8524
8766
  origin: platformOrigin({}),
8525
8767
  key: resolveCredential(),
8526
8768
  log: (line) => r.log(line)
@@ -8543,9 +8785,9 @@ async function cmdCompare(argv) {
8543
8785
  try {
8544
8786
  for (const t of times) {
8545
8787
  const tag = t.toFixed(2);
8546
- const refPng = join16(outDir, `src-${tag}.png`);
8547
- const vosPng = join16(outDir, `vos-${tag}.png`);
8548
- const sheet = join16(outDir, `sheet-${tag}.png`);
8788
+ const refPng = join17(outDir, `src-${tag}.png`);
8789
+ const vosPng = join17(outDir, `vos-${tag}.png`);
8790
+ const sheet = join17(outDir, `sheet-${tag}.png`);
8549
8791
  const grab = ffmpeg([
8550
8792
  "-v",
8551
8793
  "error",
@@ -8602,7 +8844,7 @@ async function cmdCompare(argv) {
8602
8844
  } finally {
8603
8845
  await browser.close();
8604
8846
  }
8605
- const overview = join16(outDir, "overview.png");
8847
+ const overview = join17(outDir, "overview.png");
8606
8848
  if (scores.length) {
8607
8849
  const inputs = scores.flatMap((s) => ["-i", s.sheet]);
8608
8850
  const scaled = scores.map((_, i) => `[${i}:v]scale=iw/3:-2[s${i}]`).join(";");
@@ -8628,7 +8870,7 @@ async function cmdCompare(argv) {
8628
8870
  overview
8629
8871
  };
8630
8872
  writeFileSync3(
8631
- join16(outDir, "report.json"),
8873
+ join17(outDir, "report.json"),
8632
8874
  JSON.stringify(report, null, 2) + "\n"
8633
8875
  );
8634
8876
  r.done(
@@ -8639,28 +8881,28 @@ async function cmdCompare(argv) {
8639
8881
  }
8640
8882
 
8641
8883
  // src/plugin/port.ts
8642
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
8643
- import { dirname as dirname7, join as join20, resolve as resolve9 } from "path";
8884
+ import { existsSync as existsSync19, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
8885
+ import { dirname as dirname7, join as join21, resolve as resolve9 } from "path";
8644
8886
  import { pathToFileURL as pathToFileURL3 } from "url";
8645
8887
 
8646
8888
  // src/plugin/portInventory.ts
8647
8889
  import { spawnSync as spawnSync2 } from "child_process";
8648
8890
  import {
8649
- existsSync as existsSync16,
8891
+ existsSync as existsSync17,
8650
8892
  mkdirSync as mkdirSync3,
8651
- readFileSync as readFileSync6,
8893
+ readFileSync as readFileSync7,
8652
8894
  readdirSync as readdirSync4,
8653
8895
  statSync as statSync6
8654
8896
  } from "fs";
8655
- import { dirname as dirname5, join as join18, relative as relative3, resolve as resolve7 } from "path";
8897
+ import { dirname as dirname5, join as join19, relative as relative3, resolve as resolve7 } from "path";
8656
8898
  import { pathToFileURL as pathToFileURL2 } from "url";
8657
8899
  import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
8658
8900
 
8659
8901
  // src/plugin/portRemotion.ts
8660
- import { existsSync as existsSync15, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
8902
+ import { existsSync as existsSync16, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
8661
8903
  import { createServer as createServer2 } from "http";
8662
8904
  import { createRequire } from "module";
8663
- import { basename as basename6, extname as extname3, join as join17 } from "path";
8905
+ import { basename as basename6, extname as extname3, join as join18 } from "path";
8664
8906
  function themeColors(sources) {
8665
8907
  const out = {};
8666
8908
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -8726,14 +8968,14 @@ function sourceTexts(dir) {
8726
8968
  if (depth > 4 || out.length > 200) return;
8727
8969
  for (const name of readdirSync3(d)) {
8728
8970
  if (name === "node_modules" || name.startsWith(".")) continue;
8729
- const p = join17(d, name);
8971
+ const p = join18(d, name);
8730
8972
  if (statSync5(p).isDirectory()) walk(p, depth + 1);
8731
8973
  else if (/\.(tsx?|jsx?|mjs)$/.test(name))
8732
- out.push(readFileSync5(p, "utf8"));
8974
+ out.push(readFileSync6(p, "utf8"));
8733
8975
  }
8734
8976
  };
8735
- const src = join17(dir, "src");
8736
- walk(existsSync15(src) ? src : dir, 0);
8977
+ const src = join18(dir, "src");
8978
+ walk(existsSync16(src) ? src : dir, 0);
8737
8979
  return out;
8738
8980
  }
8739
8981
  function entryPoint(dir) {
@@ -8745,7 +8987,7 @@ function entryPoint(dir) {
8745
8987
  "remotion/index.ts",
8746
8988
  "remotion/index.tsx"
8747
8989
  ])
8748
- if (existsSync15(join17(dir, p))) return join17(dir, p);
8990
+ if (existsSync16(join18(dir, p))) return join18(dir, p);
8749
8991
  return null;
8750
8992
  }
8751
8993
  var TYPES = {
@@ -8820,7 +9062,7 @@ async function readRemotion(dir, log) {
8820
9062
  const entry = entryPoint(dir);
8821
9063
  let bundler;
8822
9064
  try {
8823
- bundler = createRequire(join17(dir, "package.json"))("@remotion/bundler");
9065
+ bundler = createRequire(join18(dir, "package.json"))("@remotion/bundler");
8824
9066
  } catch {
8825
9067
  log?.(
8826
9068
  "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"
@@ -8837,13 +9079,13 @@ async function readRemotion(dir, log) {
8837
9079
  } });
8838
9080
  const server = createServer2((rq, rs) => {
8839
9081
  const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
8840
- let f = join17(out, path);
8841
- if (!f.startsWith(out) || !existsSync15(f) || statSync5(f).isDirectory())
8842
- f = join17(out, "index.html");
9082
+ let f = join18(out, path);
9083
+ if (!f.startsWith(out) || !existsSync16(f) || statSync5(f).isDirectory())
9084
+ f = join18(out, "index.html");
8843
9085
  rs.writeHead(200, {
8844
9086
  "content-type": TYPES[extname3(f)] ?? "application/octet-stream"
8845
9087
  });
8846
- rs.end(readFileSync5(f));
9088
+ rs.end(readFileSync6(f));
8847
9089
  }).listen(0, "127.0.0.1");
8848
9090
  await new Promise((r) => server.once("listening", r));
8849
9091
  const port = server.address().port;
@@ -8938,10 +9180,10 @@ async function readRemotion(dir, log) {
8938
9180
  perScene.push({ scene: s.name, texts: read.texts ?? [] });
8939
9181
  }
8940
9182
  const sources = sourceTexts(dir);
8941
- const publicDir = join17(dir, "public");
9183
+ const publicDir = join18(dir, "public");
8942
9184
  const media = tree.audio.map((a) => {
8943
9185
  const name = basename6(a.src.split("?")[0]);
8944
- const local = existsSync15(join17(publicDir, name)) ? `public/${name}` : a.src;
9186
+ const local = existsSync16(join18(publicDir, name)) ? `public/${name}` : a.src;
8945
9187
  return {
8946
9188
  kind: "audio",
8947
9189
  src: local,
@@ -8982,11 +9224,11 @@ async function readRemotion(dir, log) {
8982
9224
 
8983
9225
  // src/plugin/portInventory.ts
8984
9226
  function detectEngine(dir) {
8985
- if (existsSync16(join18(dir, "hyperframes.json"))) return "hyperframes";
8986
- const pkg = join18(dir, "package.json");
8987
- if (existsSync16(pkg)) {
9227
+ if (existsSync17(join19(dir, "hyperframes.json"))) return "hyperframes";
9228
+ const pkg = join19(dir, "package.json");
9229
+ if (existsSync17(pkg)) {
8988
9230
  try {
8989
- const p = JSON.parse(readFileSync6(pkg, "utf8"));
9231
+ const p = JSON.parse(readFileSync7(pkg, "utf8"));
8990
9232
  const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
8991
9233
  if ("remotion" in deps) return "remotion";
8992
9234
  if ("hyperframes" in deps) return "hyperframes";
@@ -8998,14 +9240,14 @@ function detectEngine(dir) {
8998
9240
  return "page";
8999
9241
  }
9000
9242
  function sourcePage(source) {
9001
- if (!existsSync16(source)) return null;
9243
+ if (!existsSync17(source)) return null;
9002
9244
  if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
9003
- const index = join18(source, "index.html");
9004
- return existsSync16(index) ? index : null;
9245
+ const index = join19(source, "index.html");
9246
+ return existsSync17(index) ? index : null;
9005
9247
  }
9006
9248
  function findRender(dir) {
9007
- const videos = (d) => existsSync16(d) ? readdirSync4(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join18(d, f)) : [];
9008
- for (const d of [join18(dir, "renders"), join18(dir, "out"), dir]) {
9249
+ const videos = (d) => existsSync17(d) ? readdirSync4(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join19(d, f)) : [];
9250
+ for (const d of [join19(dir, "renders"), join19(dir, "out"), dir]) {
9009
9251
  const found = videos(d);
9010
9252
  if (found.length) return found[0];
9011
9253
  }
@@ -9276,7 +9518,7 @@ var SEEK_AND_READ = `((args) => {
9276
9518
  })`;
9277
9519
  async function readInventory(source, opts) {
9278
9520
  const abs = resolve7(source);
9279
- const dir = existsSync16(abs) && statSync6(abs).isDirectory() ? abs : dirname5(abs);
9521
+ const dir = existsSync17(abs) && statSync6(abs).isDirectory() ? abs : dirname5(abs);
9280
9522
  const engine = detectEngine(dir);
9281
9523
  const page = sourcePage(abs);
9282
9524
  const render = opts.render === void 0 ? findRender(dir) : opts.render;
@@ -9379,7 +9621,7 @@ async function readInventory(source, opts) {
9379
9621
  if (render && duration) {
9380
9622
  const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
9381
9623
  if (has) {
9382
- const refDir = join18(opts.outDir, "ref");
9624
+ const refDir = join19(opts.outDir, "ref");
9383
9625
  mkdirSync3(refDir, { recursive: true });
9384
9626
  const times = read.scenes.length >= 3 ? read.scenes.map(
9385
9627
  (s) => s.start + (s.duration ?? 1) / 2
@@ -9388,7 +9630,7 @@ async function readInventory(source, opts) {
9388
9630
  (_, i) => i + 0.5
9389
9631
  );
9390
9632
  for (const t of times.filter((t2) => t2 < duration)) {
9391
- const file = join18(refDir, `src-${t.toFixed(2)}.png`);
9633
+ const file = join19(refDir, `src-${t.toFixed(2)}.png`);
9392
9634
  const ok = spawnSync2("ffmpeg", [
9393
9635
  "-v",
9394
9636
  "error",
@@ -9442,8 +9684,8 @@ async function readInventory(source, opts) {
9442
9684
 
9443
9685
  // src/plugin/portScaffold.ts
9444
9686
  import { spawnSync as spawnSync3 } from "child_process";
9445
- import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
9446
- import { basename as basename7, dirname as dirname6, join as join19, resolve as resolve8 } from "path";
9687
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
9688
+ import { basename as basename7, dirname as dirname6, join as join20, resolve as resolve8 } from "path";
9447
9689
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
9448
9690
 
9449
9691
  // src/plugin/painterLib.ts
@@ -9854,7 +10096,7 @@ function planScaffold(inv, opts) {
9854
10096
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
9855
10097
  const from = resolve8(opts.sourceDir, m.src);
9856
10098
  const name = basename7(from);
9857
- if (!existsSync17(from)) {
10099
+ if (!existsSync18(from)) {
9858
10100
  report.push(
9859
10101
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
9860
10102
  );
@@ -10052,13 +10294,13 @@ function reportText(inv, r) {
10052
10294
  }
10053
10295
  function writeScaffold(plan, outDir) {
10054
10296
  mkdirSync4(outDir, { recursive: true });
10055
- writeFileSync4(join19(outDir, "program.mjs"), plan.program);
10297
+ writeFileSync4(join20(outDir, "program.mjs"), plan.program);
10056
10298
  writeFileSync4(
10057
- join19(outDir, "doc.json"),
10299
+ join20(outDir, "doc.json"),
10058
10300
  JSON.stringify(plan.doc, null, 2) + "\n"
10059
10301
  );
10060
- writeFileSync4(join19(dirname6(outDir), "REPORT.md"), plan.report);
10061
- for (const [from, name] of plan.copies) copyFileSync(from, join19(outDir, name));
10302
+ writeFileSync4(join20(dirname6(outDir), "REPORT.md"), plan.report);
10303
+ for (const [from, name] of plan.copies) copyFileSync(from, join20(outDir, name));
10062
10304
  }
10063
10305
  function probeAudioDuration(file) {
10064
10306
  const res = spawnSync3(
@@ -10092,7 +10334,7 @@ async function inventory(argv) {
10092
10334
  outDir,
10093
10335
  log: (l) => r.log(`note: ${l}`)
10094
10336
  });
10095
- const file = join20(outDir, "inventory.json");
10337
+ const file = join21(outDir, "inventory.json");
10096
10338
  writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
10097
10339
  const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
10098
10340
  r.log(
@@ -10124,32 +10366,32 @@ function summary(inv) {
10124
10366
  async function scaffold(argv) {
10125
10367
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS8);
10126
10368
  const r = createReporter(flags.json === true);
10127
- const from = resolve9(strFlag(flags, "from") ?? join20("port", "inventory.json"));
10128
- if (!existsSync18(from))
10369
+ const from = resolve9(strFlag(flags, "from") ?? join21("port", "inventory.json"));
10370
+ if (!existsSync19(from))
10129
10371
  throw new UsageError(
10130
10372
  `no inventory at ${from}: run npx vos port inventory <source> first`
10131
10373
  );
10132
- const inv = JSON.parse(readFileSync7(from, "utf8"));
10374
+ const inv = JSON.parse(readFileSync8(from, "utf8"));
10133
10375
  const outDir = resolve9(
10134
- strFlag(flags, "out") ?? join20(dirname7(from), "program")
10376
+ strFlag(flags, "out") ?? join21(dirname7(from), "program")
10135
10377
  );
10136
10378
  const plan = planScaffold(inv, {
10137
10379
  sourceDir: scaffoldSourceDir(inv),
10138
10380
  probeDuration: probeAudioDuration
10139
10381
  });
10140
10382
  writeScaffold(plan, outDir);
10141
- const programFile = join20(outDir, "program.mjs");
10383
+ const programFile = join21(outDir, "program.mjs");
10142
10384
  const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
10143
10385
  const built = buildProgram(mod.default, "program.mjs", plan.program);
10144
10386
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
10145
10387
  if (built.problems.length) return EXIT_ERROR;
10146
10388
  writeFileSync5(
10147
- join20(outDir, "config.json"),
10389
+ join21(outDir, "config.json"),
10148
10390
  JSON.stringify(built.config, null, 2) + "\n"
10149
10391
  );
10150
10392
  r.done(
10151
- { program: outDir, report: join20(dirname7(outDir), "REPORT.md") },
10152
- `Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join20(dirname7(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
10393
+ { program: outDir, report: join21(dirname7(outDir), "REPORT.md") },
10394
+ `Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join21(dirname7(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
10153
10395
  );
10154
10396
  return EXIT_OK;
10155
10397
  }
@@ -10160,7 +10402,7 @@ function scaffoldSourceDir(inv) {
10160
10402
 
10161
10403
  // src/plugin/recipe.ts
10162
10404
  import { createHash as createHash3 } from "crypto";
10163
- import { readFileSync as readFileSync8 } from "fs";
10405
+ import { readFileSync as readFileSync9 } from "fs";
10164
10406
  import { basename as basename8 } from "path";
10165
10407
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
10166
10408
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
@@ -10180,7 +10422,7 @@ async function cmdPush2(argv) {
10180
10422
  throw new UsageError(USAGE);
10181
10423
  }
10182
10424
  const filename = recipeFilename(file);
10183
- const body = readFileSync8(file);
10425
+ const body = readFileSync9(file);
10184
10426
  const r = createReporter(flags.json === true);
10185
10427
  const origin = platformOrigin({
10186
10428
  origin: strFlag(flags, "origin"),
@@ -10246,7 +10488,7 @@ async function cmdRecipe(argv) {
10246
10488
  }
10247
10489
 
10248
10490
  // src/plugin/brand.ts
10249
- import { writeFile as writeFile9 } from "fs/promises";
10491
+ import { writeFile as writeFile10 } from "fs/promises";
10250
10492
  import { resolve as resolve10 } from "path";
10251
10493
  import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
10252
10494
  import { lookKindForGround } from "@vosjs/studio-core";
@@ -10600,7 +10842,7 @@ async function cmdBrand(argv) {
10600
10842
  composition,
10601
10843
  llms?.claim ?? design?.description ?? null
10602
10844
  );
10603
- await writeFile9(out, md);
10845
+ await writeFile10(out, md);
10604
10846
  const k = composition.kit;
10605
10847
  r.done(
10606
10848
  {
@@ -10715,7 +10957,7 @@ async function recordHosted(input) {
10715
10957
  }
10716
10958
 
10717
10959
  // src/plugin/agentBrowser.ts
10718
- import { writeFile as writeFile10 } from "fs/promises";
10960
+ import { writeFile as writeFile11 } from "fs/promises";
10719
10961
  import { resolve as resolve11 } from "path";
10720
10962
  function parseAgentBrowserLog(text) {
10721
10963
  const records = [];
@@ -11195,8 +11437,8 @@ async function cmdActions(argv) {
11195
11437
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
11196
11438
  viewport = { width: Number(m[1]), height: Number(m[2]) };
11197
11439
  }
11198
- const { readFile: readFile13 } = await import("fs/promises");
11199
- const text = await readFile13(resolve11(input), "utf8");
11440
+ const { readFile: readFile14 } = await import("fs/promises");
11441
+ const text = await readFile14(resolve11(input), "utf8");
11200
11442
  const { records, problems } = parseAgentBrowserLog(text);
11201
11443
  for (const p of problems) r.log(` ${p}`);
11202
11444
  const result = convertAgentBrowser(records, {
@@ -11213,7 +11455,7 @@ async function cmdActions(argv) {
11213
11455
  );
11214
11456
  return EXIT_ERROR;
11215
11457
  }
11216
- await writeFile10(out, `${JSON.stringify(result.actions, null, 2)}
11458
+ await writeFile11(out, `${JSON.stringify(result.actions, null, 2)}
11217
11459
  `);
11218
11460
  r.done(
11219
11461
  { ok: true, out, url: result.actions.url ?? null, ...summary2(result) },
@@ -11233,9 +11475,9 @@ function summary2(result) {
11233
11475
  }
11234
11476
  async function cmdActionsScript(input, flags) {
11235
11477
  const r = createReporter(flags.json === true);
11236
- const { readFile: readFile13 } = await import("fs/promises");
11237
- const { shotList, formatShotList } = await import("./shotList-KH6FTX4L.js");
11238
- const raw = JSON.parse(await readFile13(resolve11(input), "utf8"));
11478
+ const { readFile: readFile14 } = await import("fs/promises");
11479
+ const { shotList, formatShotList } = await import("./shotList-MAQVLZBY.js");
11480
+ const raw = JSON.parse(await readFile14(resolve11(input), "utf8"));
11239
11481
  const errors = validateActions(raw);
11240
11482
  if (errors.length) throw new UsageError(`${input}:
11241
11483
  ${errors.join("\n ")}`);
@@ -11245,9 +11487,9 @@ async function cmdActionsScript(input, flags) {
11245
11487
  }
11246
11488
 
11247
11489
  // src/plugin/ingest.ts
11248
- import { existsSync as existsSync19 } from "fs";
11249
- import { copyFile, mkdir as mkdir7, readFile as readFile11, stat as stat2 } from "fs/promises";
11250
- import { basename as basename9, join as join21, resolve as resolve12 } from "path";
11490
+ import { existsSync as existsSync20 } from "fs";
11491
+ import { copyFile, mkdir as mkdir7, readFile as readFile12, stat as stat2 } from "fs/promises";
11492
+ import { basename as basename9, join as join22, resolve as resolve12 } from "path";
11251
11493
  import { inflateRawSync } from "zlib";
11252
11494
  import {
11253
11495
  cursorFromPlaywrightTrace,
@@ -11335,7 +11577,7 @@ async function readTraceFile(path, offsetMs) {
11335
11577
  );
11336
11578
  }
11337
11579
  if (kind === "zip") {
11338
- const bytes = new Uint8Array(await readFile11(path));
11580
+ const bytes = new Uint8Array(await readFile12(path));
11339
11581
  const entry = playwrightTraceEntry(zipEntries(bytes));
11340
11582
  if (!entry) {
11341
11583
  throw new UsageError(
@@ -11348,7 +11590,7 @@ async function readTraceFile(path, offsetMs) {
11348
11590
  );
11349
11591
  return cursorFromPlaywrightTrace(text2, { offsetMs });
11350
11592
  }
11351
- const text = await readFile11(path, "utf8");
11593
+ const text = await readFile12(path, "utf8");
11352
11594
  return cursorFromTrace(basename9(path), text, { offsetMs });
11353
11595
  }
11354
11596
  function parseViewport(v) {
@@ -11365,7 +11607,7 @@ async function cmdIngest(argv) {
11365
11607
  "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]"
11366
11608
  );
11367
11609
  const videoPath = resolve12(source);
11368
- if (!existsSync19(videoPath)) throw new UsageError(`${source}: no such file`);
11610
+ if (!existsSync20(videoPath)) throw new UsageError(`${source}: no such file`);
11369
11611
  const r = createReporter(flags.json === true);
11370
11612
  const outDir = resolve12(strFlag(flags, "out") ?? "take");
11371
11613
  const offsetMs = numFlag(flags, "offset", 0);
@@ -11392,7 +11634,7 @@ async function cmdIngest(argv) {
11392
11634
  trace = await readTraceFile(resolve12(cursorPath), offsetMs);
11393
11635
  for (const n of trace.notes) r.log(`note: ${n}`);
11394
11636
  }
11395
- if (existsSync19(join21(outDir, "meta.json"))) {
11637
+ if (existsSync20(join22(outDir, "meta.json"))) {
11396
11638
  const { prevDoc, kept } = await prepareReRecord(outDir);
11397
11639
  r.log(
11398
11640
  `note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
@@ -11400,7 +11642,7 @@ async function cmdIngest(argv) {
11400
11642
  }
11401
11643
  await mkdir7(outDir, { recursive: true });
11402
11644
  const paths = await ensureTakeDir(outDir);
11403
- const recording = join21(outDir, `recording.${probed.ext}`);
11645
+ const recording = join22(outDir, `recording.${probed.ext}`);
11404
11646
  r.log("copying the recording\u2026");
11405
11647
  const remuxed = await copyRecording(videoPath, recording, probed.ext);
11406
11648
  const viewport = viewportFlag ?? trace?.viewport ?? {
@@ -11468,19 +11710,19 @@ async function cmdIngest(argv) {
11468
11710
  import { spawnSync as spawnSync4 } from "child_process";
11469
11711
  import {
11470
11712
  cpSync,
11471
- existsSync as existsSync21,
11713
+ existsSync as existsSync22,
11472
11714
  mkdirSync as mkdirSync6,
11473
- readFileSync as readFileSync10,
11715
+ readFileSync as readFileSync11,
11474
11716
  rmSync as rmSync3,
11475
11717
  writeFileSync as writeFileSync6
11476
11718
  } from "fs";
11477
11719
  import { homedir as homedir2 } from "os";
11478
- import { join as join23 } from "path";
11720
+ import { join as join24 } from "path";
11479
11721
  import { chromium as chromium2 } from "playwright";
11480
11722
 
11481
11723
  // src/plugin/machine.ts
11482
- import { existsSync as existsSync20, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
11483
- import { dirname as dirname8, join as join22 } from "path";
11724
+ import { existsSync as existsSync21, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
11725
+ import { dirname as dirname8, join as join23 } from "path";
11484
11726
  var AGENT_NAMES = [
11485
11727
  "claude",
11486
11728
  "cursor",
@@ -11500,17 +11742,17 @@ var HOME_DIRS = {
11500
11742
  copilot: [".copilot"]
11501
11743
  };
11502
11744
  function detectAgents(root, opts = {}) {
11503
- const exists = opts.exists ?? existsSync20;
11745
+ const exists = opts.exists ?? existsSync21;
11504
11746
  const out = [];
11505
11747
  for (const agent of AGENT_NAMES) {
11506
11748
  for (const rel of PROJECT_DIRS[agent]) {
11507
- const dir = join22(root, rel);
11749
+ const dir = join23(root, rel);
11508
11750
  if (exists(dir)) out.push({ agent, dir, scope: "project" });
11509
11751
  }
11510
- if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join22(root, ".github", "copilot-instructions.md"))) {
11752
+ if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join23(root, ".github", "copilot-instructions.md"))) {
11511
11753
  out.push({
11512
11754
  agent,
11513
- dir: join22(root, ".github", "copilot"),
11755
+ dir: join23(root, ".github", "copilot"),
11514
11756
  scope: "project"
11515
11757
  });
11516
11758
  }
@@ -11518,7 +11760,7 @@ function detectAgents(root, opts = {}) {
11518
11760
  if (opts.global && opts.home) {
11519
11761
  for (const agent of AGENT_NAMES) {
11520
11762
  for (const rel of HOME_DIRS[agent]) {
11521
- const dir = join22(opts.home, rel);
11763
+ const dir = join23(opts.home, rel);
11522
11764
  if (exists(dir)) out.push({ agent, dir, scope: "global" });
11523
11765
  }
11524
11766
  }
@@ -11536,18 +11778,18 @@ function agentsFromFlag(value, root) {
11536
11778
  return {
11537
11779
  homes: wanted.map((agent) => ({
11538
11780
  agent,
11539
- dir: join22(root, PROJECT_DIRS[agent][0]),
11781
+ dir: join23(root, PROJECT_DIRS[agent][0]),
11540
11782
  scope: "project"
11541
11783
  }))
11542
11784
  };
11543
11785
  }
11544
11786
  function skillsDirFor(home) {
11545
- return join22(home.dir, "skills");
11787
+ return join23(home.dir, "skills");
11546
11788
  }
11547
11789
  function fallbackSkillsHome(root) {
11548
- return { agent: "codex", dir: join22(root, ".agents"), scope: "project" };
11790
+ return { agent: "codex", dir: join23(root, ".agents"), scope: "project" };
11549
11791
  }
11550
- function installedSkills(dir, exists = existsSync20, list = (p) => {
11792
+ function installedSkills(dir, exists = existsSync21, list = (p) => {
11551
11793
  try {
11552
11794
  return readdirSync5(p);
11553
11795
  } catch {
@@ -11555,7 +11797,7 @@ function installedSkills(dir, exists = existsSync20, list = (p) => {
11555
11797
  }
11556
11798
  }) {
11557
11799
  if (!exists(dir)) return [];
11558
- return list(dir).filter((name) => exists(join22(dir, name, "SKILL.md"))).sort();
11800
+ return list(dir).filter((name) => exists(join23(dir, name, "SKILL.md"))).sort();
11559
11801
  }
11560
11802
  var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
11561
11803
  var RULES_END = "<!-- vos:end -->";
@@ -11588,10 +11830,10 @@ ${RULES_BLOCK}
11588
11830
  }
11589
11831
  return { text, changed: text !== current };
11590
11832
  }
11591
- function rulesFileFor(root, exists = existsSync20) {
11592
- const agents = join22(root, "AGENTS.md");
11833
+ function rulesFileFor(root, exists = existsSync21) {
11834
+ const agents = join23(root, "AGENTS.md");
11593
11835
  if (exists(agents)) return agents;
11594
- const claude = join22(root, "CLAUDE.md");
11836
+ const claude = join23(root, "CLAUDE.md");
11595
11837
  if (exists(claude)) return claude;
11596
11838
  return agents;
11597
11839
  }
@@ -11600,14 +11842,14 @@ function chromeCandidates(platform, env) {
11600
11842
  return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
11601
11843
  if (platform === "win32")
11602
11844
  return [
11603
- join22(
11845
+ join23(
11604
11846
  env["PROGRAMFILES"] ?? "C:\\Program Files",
11605
11847
  "Google",
11606
11848
  "Chrome",
11607
11849
  "Application",
11608
11850
  "chrome.exe"
11609
11851
  ),
11610
- join22(
11852
+ join23(
11611
11853
  env["LOCALAPPDATA"] ?? "",
11612
11854
  "Google",
11613
11855
  "Chrome",
@@ -11694,13 +11936,13 @@ function doctorReport(facts) {
11694
11936
  function bundledSkillsDir(from) {
11695
11937
  let dir = dirname8(from);
11696
11938
  for (let i = 0; i < 6; i++) {
11697
- const pkg = join22(dir, "package.json");
11698
- if (existsSync20(pkg)) {
11939
+ const pkg = join23(dir, "package.json");
11940
+ if (existsSync21(pkg)) {
11699
11941
  try {
11700
- const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
11942
+ const name = JSON.parse(readFileSync10(pkg, "utf8")).name;
11701
11943
  if (name === "@vosjs/cli") {
11702
- const skills = join22(dir, "skills");
11703
- return existsSync20(skills) && statSync7(skills).isDirectory() ? skills : null;
11944
+ const skills = join23(dir, "skills");
11945
+ return existsSync21(skills) && statSync7(skills).isDirectory() ? skills : null;
11704
11946
  }
11705
11947
  } catch {
11706
11948
  }
@@ -11731,7 +11973,7 @@ function npx() {
11731
11973
  function bundledChromium() {
11732
11974
  try {
11733
11975
  const p = chromium2.executablePath();
11734
- return p && existsSync21(p) ? p : null;
11976
+ return p && existsSync22(p) ? p : null;
11735
11977
  } catch {
11736
11978
  return null;
11737
11979
  }
@@ -11740,7 +11982,7 @@ function probe2() {
11740
11982
  return probeBrowser({
11741
11983
  platform: process.platform,
11742
11984
  env: process.env,
11743
- exists: existsSync21,
11985
+ exists: existsSync22,
11744
11986
  bundled: bundledChromium
11745
11987
  });
11746
11988
  }
@@ -11844,7 +12086,7 @@ async function cmdSetup(argv) {
11844
12086
  const dest = skillsDirFor(home);
11845
12087
  mkdirSync6(dest, { recursive: true });
11846
12088
  for (const name of installedSkills(bundled)) {
11847
- cpSync(join23(bundled, name), join23(dest, name), { recursive: true });
12089
+ cpSync(join24(bundled, name), join24(dest, name), { recursive: true });
11848
12090
  }
11849
12091
  }
11850
12092
  skillsVia = "bundled";
@@ -11879,7 +12121,7 @@ async function cmdSetup(argv) {
11879
12121
  let rules = null;
11880
12122
  if (!noRules) {
11881
12123
  const file = rulesFileFor(root);
11882
- const existing = existsSync21(file) ? readFileSync10(file, "utf8") : null;
12124
+ const existing = existsSync22(file) ? readFileSync11(file, "utf8") : null;
11883
12125
  const { text, changed } = applyRulesBlock(existing);
11884
12126
  if (changed) writeFileSync6(file, text);
11885
12127
  rules = { file, changed };
@@ -11993,7 +12235,7 @@ async function cmdWhoami(argv) {
11993
12235
  function cmdLogout(argv) {
11994
12236
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
11995
12237
  const r = reporterFor(flags);
11996
- const had = existsSync21(CREDENTIALS_PATH);
12238
+ const had = existsSync22(CREDENTIALS_PATH);
11997
12239
  rmSync3(CREDENTIALS_PATH, { force: true });
11998
12240
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
11999
12241
  r.done(
@@ -12199,12 +12441,16 @@ The re-render loop (re-recording the same script after the product
12199
12441
  changed): record into the SAME take dir \u2014 footage/cursor/meta are
12200
12442
  replaced, the cut survives as doc.prev.json \u2014 then plan --reuse re-times
12201
12443
  it onto the new recording. meta.json carries steps (when each actions
12202
- step ran); a span may carry anchor {step, at, offset} tying it to a step
12203
- id/index for exact re-timing; unanchored human spans map piecewise
12204
- through matched step edges; autos re-plan; overlays/audio keep OUTPUT
12205
- times. The done event's reuse block NAMES whatever could not follow \u2014
12206
- fix by exception, never re-cut from scratch. Give steps ids in
12207
- actions.json so anchors survive script edits.
12444
+ step ran); a human span follows the step it was made on (its window, or
12445
+ the gesture just before it), keeping its offset exactly, or an explicit
12446
+ anchor {step, at, offset}; a span between steps maps piecewise through
12447
+ matched step edges; a span whose step vanished is dropped and named with
12448
+ what it framed; autos re-plan; a layer pinned to a step follows its new
12449
+ output time, every other layer and audio keep OUTPUT times, and a layer
12450
+ over a step that moved is named with the pin to write. The done event's
12451
+ reuse block NAMES whatever could not follow \u2014 fix by exception, never
12452
+ re-cut from scratch. Give steps ids in actions.json so re-timing survives
12453
+ script edits.
12208
12454
 
12209
12455
  record --strict exits 2 when any selector was skipped, the page never
12210
12456
  reached networkidle, or the take hit --max-duration (the done event lists
@@ -12328,7 +12574,7 @@ lint-gated, so a bad override fails like a bad doc.json):
12328
12574
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
12329
12575
  `;
12330
12576
  async function loadActions(file) {
12331
- const raw = JSON.parse(await readFile12(file, "utf8"));
12577
+ const raw = JSON.parse(await readFile13(file, "utf8"));
12332
12578
  const errors = validateActions(raw);
12333
12579
  if (errors.length)
12334
12580
  throw new UsageError(`invalid actions file:
@@ -12391,7 +12637,7 @@ function takeStorageState(flags) {
12391
12637
  const raw = strFlag(flags, "storage-state");
12392
12638
  if (!raw) return void 0;
12393
12639
  const path = resolve13(raw);
12394
- if (!existsSync22(path))
12640
+ if (!existsSync23(path))
12395
12641
  throw new UsageError(`--storage-state: no such file: ${path}`);
12396
12642
  return path;
12397
12643
  }
@@ -12404,7 +12650,7 @@ function takeSessionName(flags, storageState) {
12404
12650
  throw new UsageError(
12405
12651
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
12406
12652
  );
12407
- if (!existsSync22(sessionDir(name)))
12653
+ if (!existsSync23(sessionDir(name)))
12408
12654
  throw new UsageError(
12409
12655
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
12410
12656
  );
@@ -12627,7 +12873,7 @@ ${lines.join("\n")}
12627
12873
  }
12628
12874
  const paths = takePaths(outDir);
12629
12875
  const prepare = async () => {
12630
- if (existsSync22(join24(outDir, "meta.json"))) {
12876
+ if (existsSync23(join25(outDir, "meta.json"))) {
12631
12877
  const { prevDoc, kept } = await prepareReRecord(outDir);
12632
12878
  r.log(
12633
12879
  `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" : "")
@@ -12692,7 +12938,7 @@ ${lines.join("\n")}
12692
12938
  },
12693
12939
  strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
12694
12940
  ${(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}
12695
- Next: edit ${join24(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
12941
+ Next: edit ${join25(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
12696
12942
  );
12697
12943
  return strictFail ? EXIT_USAGE : EXIT_OK;
12698
12944
  } finally {
@@ -12718,7 +12964,7 @@ async function cmdCreate2(argv) {
12718
12964
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
12719
12965
  const outDir = resolve13(strFlag(flags, "out") ?? "take");
12720
12966
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
12721
- const out = positionals[0] ?? join24(outDir, `out.${format}`);
12967
+ const out = positionals[0] ?? join25(outDir, `out.${format}`);
12722
12968
  const parallel = numFlag(flags, "parallel", 1);
12723
12969
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
12724
12970
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -12731,7 +12977,7 @@ async function cmdCreate2(argv) {
12731
12977
  const headers = takeHeaders(multi);
12732
12978
  const paths = takePaths(outDir);
12733
12979
  const prepare = async () => {
12734
- if (existsSync22(join24(outDir, "meta.json"))) {
12980
+ if (existsSync23(join25(outDir, "meta.json"))) {
12735
12981
  const { prevDoc, kept } = await prepareReRecord(outDir);
12736
12982
  r.log(
12737
12983
  `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" : "")
@@ -12838,24 +13084,24 @@ async function cmdPlan(argv) {
12838
13084
  );
12839
13085
  const r = createReporter(flags.json === true);
12840
13086
  if (flags.fresh === true) {
12841
- await rm2(join24(dir, "doc.json"), { force: true });
13087
+ await rm2(join25(dir, "doc.json"), { force: true });
12842
13088
  r.log("note: --fresh discarded the existing doc.json");
12843
13089
  }
12844
13090
  let reuse;
12845
13091
  if (flags.reuse === true) {
12846
- const from = resolve13(strFlag(flags, "from") ?? join24(dir, PREV_DOC_NAME));
12847
- if (!existsSync22(from)) {
13092
+ const from = resolve13(strFlag(flags, "from") ?? join25(dir, PREV_DOC_NAME));
13093
+ if (!existsSync23(from)) {
12848
13094
  throw new UsageError(
12849
13095
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
12850
13096
  );
12851
13097
  }
12852
13098
  reuse = {
12853
13099
  from,
12854
- doc: JSON.parse(await readFile12(from, "utf8"))
13100
+ doc: JSON.parse(await readFile13(from, "utf8"))
12855
13101
  };
12856
13102
  }
12857
13103
  const style = await resolveStyleRef(flags);
12858
- const hasDoc = existsSync22(join24(dir, "doc.json"));
13104
+ const hasDoc = existsSync23(join25(dir, "doc.json"));
12859
13105
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
12860
13106
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
12861
13107
  const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
@@ -12913,7 +13159,7 @@ async function cmdPlan(argv) {
12913
13159
  ...s.motion ? { motion: s.motion } : {},
12914
13160
  ...s.reuse ? { reuse: s.reuse } : {}
12915
13161
  },
12916
- `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join24(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
13162
+ `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join25(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
12917
13163
  );
12918
13164
  return EXIT_OK;
12919
13165
  }
@@ -13036,7 +13282,7 @@ async function cmdRender(argv) {
13036
13282
  );
13037
13283
  const r = createReporter(flags.json === true);
13038
13284
  const format = videoFormat(strFlag(flags, "format"), positionals[1]);
13039
- const out = positionals[1] ?? join24(dir, `out.${format}`);
13285
+ const out = positionals[1] ?? join25(dir, `out.${format}`);
13040
13286
  const parallel = numFlag(flags, "parallel", 1);
13041
13287
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
13042
13288
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -13267,15 +13513,15 @@ async function resolveStyleRef(flags) {
13267
13513
  }
13268
13514
  async function resolveDocRef(ref, flags, what) {
13269
13515
  const styleRef = ref;
13270
- const file = existsSync22(styleRef) ? resolve13(
13516
+ const file = existsSync23(styleRef) ? resolve13(
13271
13517
  styleRef,
13272
- existsSync22(join24(styleRef, "doc.json")) ? "doc.json" : ""
13518
+ existsSync23(join25(styleRef, "doc.json")) ? "doc.json" : ""
13273
13519
  ) : null;
13274
- if (file && existsSync22(file)) {
13520
+ if (file && existsSync23(file)) {
13275
13521
  return {
13276
13522
  from: file,
13277
13523
  doc: migrateHostedDoc4(
13278
- JSON.parse(await readFile12(file, "utf8"))
13524
+ JSON.parse(await readFile13(file, "utf8"))
13279
13525
  )
13280
13526
  };
13281
13527
  }
@@ -13327,7 +13573,7 @@ async function cmdDigest(argv) {
13327
13573
  const take = await loadTake(dir);
13328
13574
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
13329
13575
  const transcriptPath = strFlag(flags, "transcript");
13330
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile12(transcriptPath, "utf8"))) : null;
13576
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile13(transcriptPath, "utf8"))) : null;
13331
13577
  const style = await resolveStyleRef(flags);
13332
13578
  const noFrames = flags["no-frames"] === true;
13333
13579
  let browser = null;
@@ -13358,7 +13604,7 @@ async function cmdDigest(argv) {
13358
13604
  kinds,
13359
13605
  frames: d.images.full,
13360
13606
  crops: d.images.crop,
13361
- sheet: d.images.sheet ? join24(result.outDir, d.images.sheet) : null,
13607
+ sheet: d.images.sheet ? join25(result.outDir, d.images.sheet) : null,
13362
13608
  scenes: kinds.scene ?? 0,
13363
13609
  sourceDuration: d.take.sourceDuration,
13364
13610
  outputDuration: d.take.outputDuration,
@@ -13386,7 +13632,7 @@ async function cmdOpen(argv) {
13386
13632
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
13387
13633
  const r = createReporter(flags.json === true);
13388
13634
  const take = await loadTake(dir);
13389
- if (!existsSync22(take.paths.recording)) {
13635
+ if (!existsSync23(take.paths.recording)) {
13390
13636
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
13391
13637
  }
13392
13638
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
@@ -13443,7 +13689,7 @@ async function cmdCallout(argv) {
13443
13689
  `no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
13444
13690
  );
13445
13691
  const codeFile = strFlag(flags, "code-file");
13446
- const code = codeFile ? await readFile12(codeFile, "utf8") : strFlag(flags, "code");
13692
+ const code = codeFile ? await readFile13(codeFile, "utf8") : strFlag(flags, "code");
13447
13693
  const words2 = {
13448
13694
  kicker: strFlag(flags, "kicker"),
13449
13695
  title: strFlag(flags, "title"),
@@ -13522,7 +13768,7 @@ async function cmdValidate(argv) {
13522
13768
  const target = positionals[0];
13523
13769
  if (!target) throw new UsageError("vos validate <actions.json|take>");
13524
13770
  const r = createReporter(flags.json === true);
13525
- const kitTarget = target.endsWith("kit.json") ? target : existsSync22(join24(target, "kit.json")) && !isTakeDir(target) ? join24(target, "kit.json") : null;
13771
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync23(join25(target, "kit.json")) && !isTakeDir(target) ? join25(target, "kit.json") : null;
13526
13772
  if (kitTarget) {
13527
13773
  const picture = flags.picture === true;
13528
13774
  const verdict = await validateKit(kitTarget, { picture });
@@ -13554,9 +13800,9 @@ async function cmdValidate(argv) {
13554
13800
  r.done({ valid: true, target }, `${target}: valid actions file`);
13555
13801
  return EXIT_OK;
13556
13802
  }
13557
- if (!isTakeDir(target) && existsSync22(join24(target, "config.json"))) {
13803
+ if (!isTakeDir(target) && existsSync23(join25(target, "config.json"))) {
13558
13804
  const parsed = JSON.parse(
13559
- await readFile12(join24(target, "config.json"), "utf8")
13805
+ await readFile13(join25(target, "config.json"), "utf8")
13560
13806
  );
13561
13807
  const pre = preflightConfig(parsed);
13562
13808
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -13597,7 +13843,7 @@ async function cmdValidate(argv) {
13597
13843
  const take = await loadTake(target);
13598
13844
  const problems = [];
13599
13845
  const warnings = [];
13600
- if (!existsSync22(take.paths.recording)) {
13846
+ if (!existsSync23(take.paths.recording)) {
13601
13847
  problems.push("missing recording (re-run record)");
13602
13848
  } else {
13603
13849
  const mismatch = containerMismatch(
@@ -13982,7 +14228,7 @@ async function cmdSession(argv) {
13982
14228
  if (!arg) throw new UsageError(usage);
13983
14229
  const bad = sessionNameVerdict(arg);
13984
14230
  if (bad) throw new UsageError(bad);
13985
- if (!existsSync22(sessionDir(arg)))
14231
+ if (!existsSync23(sessionDir(arg)))
13986
14232
  throw new UsageError(`no session "${arg}" (vos session list)`);
13987
14233
  const summary3 = await summarizeSession(arg);
13988
14234
  const url = strFlag(flags, "url");
@@ -14082,4 +14328,4 @@ export {
14082
14328
  verbHelp,
14083
14329
  run
14084
14330
  };
14085
- //# sourceMappingURL=chunk-NHJRD2XC.js.map
14331
+ //# sourceMappingURL=chunk-XB5XRE4Q.js.map