@vosjs/cli 0.16.0 → 0.17.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.
@@ -6,9 +6,9 @@ import {
6
6
  } from "./chunk-NHKHTIDR.js";
7
7
 
8
8
  // src/plugin/run.ts
9
- import { mkdir as mkdir8, readFile as readFile12, rm as rm4 } from "fs/promises";
10
- import { existsSync as existsSync12 } from "fs";
11
- import { join as join15, resolve as resolve7 } from "path";
9
+ import { mkdir as mkdir9, readFile as readFile13, rm as rm4 } from "fs/promises";
10
+ import { existsSync as existsSync13 } from "fs";
11
+ import { join as join16, resolve as resolve8 } from "path";
12
12
  import { totalDuration as totalDuration2 } from "@vosjs/timeline";
13
13
  import { migrateHostedDoc as migrateHostedDoc4, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
14
14
 
@@ -1279,6 +1279,11 @@ var MIME = {
1279
1279
  ".json": "application/json",
1280
1280
  ".jpg": "image/jpeg",
1281
1281
  ".png": "image/png",
1282
+ // A brand mark or an image overlay: an <img> refuses an SVG served as an
1283
+ // octet stream, so the kinds a doc can key are named.
1284
+ ".svg": "image/svg+xml",
1285
+ ".webp": "image/webp",
1286
+ ".gif": "image/gif",
1282
1287
  ".webm": "video/webm",
1283
1288
  ".mp4": "video/mp4",
1284
1289
  // Take-dir audio (doc.audio keys like "/music.mp3") — decode goes through
@@ -1347,10 +1352,10 @@ function startTakeServer(rootDir, pages) {
1347
1352
  res.writeHead(404).end();
1348
1353
  });
1349
1354
  return new Promise(
1350
- (resolve8) => server.listen(0, () => {
1355
+ (resolve9) => server.listen(0, () => {
1351
1356
  const addr = server.address();
1352
1357
  const port = typeof addr === "object" && addr ? addr.port : 0;
1353
- resolve8({ base: `http://localhost:${port}`, close: () => server.close() });
1358
+ resolve9({ base: `http://localhost:${port}`, close: () => server.close() });
1354
1359
  })
1355
1360
  );
1356
1361
  }
@@ -2613,12 +2618,12 @@ async function writeIndexJson(result) {
2613
2618
  }
2614
2619
 
2615
2620
  // src/plugin/deliver.ts
2616
- import { mkdir as mkdir4, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile6 } from "fs/promises";
2621
+ import { mkdir as mkdir5, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile7 } from "fs/promises";
2617
2622
  import { tmpdir } from "os";
2618
- import { join as join5, relative, resolve as resolve3 } from "path";
2623
+ import { join as join6, relative, resolve as resolve4 } from "path";
2619
2624
  import { totalDuration } from "@vosjs/timeline";
2620
- import { existsSync as existsSync5 } from "fs";
2621
- import { readFile as readFile5 } from "fs/promises";
2625
+ import { existsSync as existsSync6 } from "fs";
2626
+ import { readFile as readFile6 } from "fs/promises";
2622
2627
  import { parseFrontmatter } from "@vosjs/shared/frontmatter";
2623
2628
  import {
2624
2629
  CHANNEL_SPECS_VERIFIED,
@@ -4096,7 +4101,7 @@ function stageSplitCover(input) {
4096
4101
  const column = portrait || square ? 0.84 : 0.36;
4097
4102
  const left = portrait || square ? 0.08 : 0.07;
4098
4103
  const size = portrait ? 60 : square ? 68 : 84;
4099
- const word = (id, text, preset, family, px, y, color, extra, role) => {
4104
+ const word = (id, text, preset, family, px, y, color, extra, role, x0 = left) => {
4100
4105
  if (!text) return;
4101
4106
  const lines = text.split("\n");
4102
4107
  const longest = Math.max(...lines.map((l) => l.length));
@@ -4117,7 +4122,7 @@ function stageSplitCover(input) {
4117
4122
  align: "left",
4118
4123
  maxWidth: column,
4119
4124
  lineHeight: lh,
4120
- transform: { x: left + widest / 2, y, scale: 1, rotation: 0 },
4125
+ transform: { x: x0 + widest / 2, y, scale: 1, rotation: 0 },
4121
4126
  // Words on the ground carry no footage shadow: a drop shadow under a
4122
4127
  // headline on a plate is the old-web tell.
4123
4128
  shadow: 0,
@@ -4128,7 +4133,7 @@ function stageSplitCover(input) {
4128
4133
  ...extra
4129
4134
  });
4130
4135
  boxes.push({
4131
- x: left,
4136
+ x: x0,
4132
4137
  y: y - h / 2,
4133
4138
  w: widest,
4134
4139
  h,
@@ -4137,6 +4142,28 @@ function stageSplitCover(input) {
4137
4142
  label: text.length > 24 ? `${text.slice(0, 24)}\u2026` : text
4138
4143
  });
4139
4144
  };
4145
+ const markKey = str(v.logoKey, "");
4146
+ const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
4147
+ const wideMark = !!markKey && markAspect > 2.2;
4148
+ const lockup = (px, y) => {
4149
+ if (!markKey) return left;
4150
+ const hPx = wideMark ? px * 1.6 : px * 1.3;
4151
+ const w = Math.min(column, hPx * markAspect / designW);
4152
+ clips.push({
4153
+ id: "stage-mark",
4154
+ kind: "image",
4155
+ key: markKey,
4156
+ width: +w.toFixed(4),
4157
+ radius: 0,
4158
+ shadow: "none",
4159
+ transform: { x: left + w / 2, y, scale: 1, rotation: 0 },
4160
+ start: 0,
4161
+ duration: +input.outputSeconds.toFixed(3),
4162
+ enter: "none",
4163
+ exit: "none"
4164
+ });
4165
+ return left + w + px * 0.5 / designW;
4166
+ };
4140
4167
  const kickerY = portrait ? 0.1 : square ? 0.12 : 0.24;
4141
4168
  const titleY = portrait ? 0.24 : square ? 0.28 : 0.46;
4142
4169
  const brandY = portrait ? 0.94 : square ? 0.94 : 0.86;
@@ -4162,17 +4189,20 @@ function stageSplitCover(input) {
4162
4189
  { weight: 600, letterSpacing: -Math.round(size * 0.02) },
4163
4190
  "headline"
4164
4191
  );
4165
- word(
4166
- "stage-brand",
4167
- brand,
4168
- "label",
4169
- body,
4170
- 25,
4171
- brandY,
4172
- ink,
4173
- { weight: 700, letterSpacing: 1 },
4174
- "body"
4175
- );
4192
+ const brandX = lockup(25, brandY);
4193
+ if (!wideMark)
4194
+ word(
4195
+ "stage-brand",
4196
+ brand,
4197
+ "label",
4198
+ body,
4199
+ 25,
4200
+ brandY,
4201
+ ink,
4202
+ { weight: 700, letterSpacing: 1 },
4203
+ "body",
4204
+ brandX
4205
+ );
4176
4206
  set.push(`overlays=${JSON.stringify(clips)}`);
4177
4207
  return { set, text: boxes, shot };
4178
4208
  }
@@ -4252,7 +4282,32 @@ function stageTile(input) {
4252
4282
  exit: "none"
4253
4283
  };
4254
4284
  if (wordless) {
4255
- set.push("overlays=[]");
4285
+ const markKey = str(v.logoKey, "");
4286
+ const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
4287
+ if (markKey) {
4288
+ const hFrac = top * 0.5;
4289
+ const w = Math.min(
4290
+ 0.8,
4291
+ hFrac * input.size.h * markAspect / input.size.w
4292
+ );
4293
+ set.push(
4294
+ `overlays=${JSON.stringify([
4295
+ {
4296
+ id: "stage-mark",
4297
+ kind: "image",
4298
+ key: markKey,
4299
+ width: +w.toFixed(4),
4300
+ radius: 0,
4301
+ shadow: "none",
4302
+ transform: { x: left + w / 2, y: top / 2, scale: 1, rotation: 0 },
4303
+ start: 0,
4304
+ duration: +input.outputSeconds.toFixed(3),
4305
+ enter: "none",
4306
+ exit: "none"
4307
+ }
4308
+ ])}`
4309
+ );
4310
+ } else set.push("overlays=[]");
4256
4311
  return { set, text: [], shot };
4257
4312
  }
4258
4313
  set.push(`overlays=${JSON.stringify([clip])}`);
@@ -4270,6 +4325,116 @@ function stageTile(input) {
4270
4325
  return { set, text, shot };
4271
4326
  }
4272
4327
 
4328
+ // src/plugin/markAsset.ts
4329
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
4330
+ import { existsSync as existsSync5 } from "fs";
4331
+ import { isAbsolute, join as join5, resolve as resolve3 } from "path";
4332
+ function markExtension(url) {
4333
+ const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
4334
+ if (!m) return null;
4335
+ const e = m[1].toLowerCase();
4336
+ return e === "jpeg" ? "jpg" : e;
4337
+ }
4338
+ function imageAspect(bytes, ext) {
4339
+ if (ext === "svg") {
4340
+ const head = new TextDecoder().decode(bytes.subarray(0, 4096));
4341
+ const vb = /viewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(
4342
+ head
4343
+ );
4344
+ if (vb) return ratio(+vb[1], +vb[2]);
4345
+ const w = /<svg[^>]*\swidth\s*=\s*["']([\d.]+)/i.exec(head);
4346
+ const h = /<svg[^>]*\sheight\s*=\s*["']([\d.]+)/i.exec(head);
4347
+ if (w && h) return ratio(+w[1], +h[1]);
4348
+ return null;
4349
+ }
4350
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
4351
+ if (ext === "png") {
4352
+ if (bytes.length < 24 || bytes[0] !== 137 || bytes[1] !== 80) return null;
4353
+ return ratio(dv.getUint32(16), dv.getUint32(20));
4354
+ }
4355
+ if (ext === "webp") {
4356
+ if (bytes.length < 30) return null;
4357
+ const tag = String.fromCharCode(...bytes.subarray(12, 16));
4358
+ if (tag === "VP8X")
4359
+ return ratio(
4360
+ 1 + (dv.getUint32(24, true) & 16777215),
4361
+ 1 + (dv.getUint32(27, true) & 16777215)
4362
+ );
4363
+ if (tag === "VP8L") {
4364
+ const b = dv.getUint32(21, true);
4365
+ return ratio(1 + (b & 16383), 1 + (b >> 14 & 16383));
4366
+ }
4367
+ if (tag === "VP8 ")
4368
+ return ratio(
4369
+ dv.getUint16(26, true) & 16383,
4370
+ dv.getUint16(28, true) & 16383
4371
+ );
4372
+ return null;
4373
+ }
4374
+ if (ext === "jpg") {
4375
+ let i = 2;
4376
+ while (i + 9 < bytes.length) {
4377
+ if (bytes[i] !== 255) return null;
4378
+ const marker = bytes[i + 1];
4379
+ const len = dv.getUint16(i + 2);
4380
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
4381
+ return ratio(dv.getUint16(i + 7), dv.getUint16(i + 5));
4382
+ }
4383
+ i += 2 + len;
4384
+ }
4385
+ return null;
4386
+ }
4387
+ return null;
4388
+ }
4389
+ function ratio(w, h) {
4390
+ return w > 0 && h > 0 ? w / h : null;
4391
+ }
4392
+ async function fetchBrandMarks(takeDir, roles) {
4393
+ const out = { light: null, dark: null, notes: [] };
4394
+ if (!roles) return out;
4395
+ const one = async (role, name) => {
4396
+ const src = (roles[role] ?? "").trim();
4397
+ if (!src) return null;
4398
+ const ext = markExtension(src);
4399
+ if (!ext) {
4400
+ out.notes.push(
4401
+ `${role}: ${src} is not an svg, png, webp or jpg; the wordmark stands in`
4402
+ );
4403
+ return null;
4404
+ }
4405
+ let bytes;
4406
+ try {
4407
+ if (/^https?:/i.test(src)) {
4408
+ const res = await fetch(src);
4409
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
4410
+ bytes = new Uint8Array(await res.arrayBuffer());
4411
+ } else {
4412
+ const file = isAbsolute(src) ? src : resolve3(takeDir, src);
4413
+ if (!existsSync5(file)) throw new Error("no such file");
4414
+ bytes = new Uint8Array(await readFile5(file));
4415
+ }
4416
+ } catch (e) {
4417
+ out.notes.push(
4418
+ `${role}: ${src} could not be read (${e instanceof Error ? e.message : String(e)}); the wordmark stands in`
4419
+ );
4420
+ return null;
4421
+ }
4422
+ const aspect = imageAspect(bytes, ext);
4423
+ if (!aspect) {
4424
+ out.notes.push(
4425
+ `${role}: ${src} has no readable size; the wordmark stands in`
4426
+ );
4427
+ return null;
4428
+ }
4429
+ await mkdir4(join5(takeDir, "brand"), { recursive: true });
4430
+ await writeFile6(join5(takeDir, "brand", `${name}.${ext}`), bytes);
4431
+ return { key: `/brand/${name}.${ext}`, aspect };
4432
+ };
4433
+ out.light = await one("logoUrl", "mark");
4434
+ out.dark = await one("logoOnDarkUrl", "mark-on-dark");
4435
+ return out;
4436
+ }
4437
+
4273
4438
  // src/plugin/motionPlan.ts
4274
4439
  import { ratedSegments as ratedSegments4, spanOutputExtent as spanOutputExtent4 } from "@vosjs/studio-core";
4275
4440
  var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
@@ -4325,10 +4490,13 @@ function planMotion(input) {
4325
4490
  if (headline) card.headline = headline;
4326
4491
  if (sub && sub !== headline) card.sub = sub;
4327
4492
  if (brand) card.wordmark = brand;
4493
+ if (input.mark) card.mark = input.mark;
4328
4494
  set.push(`endCard=${JSON.stringify(card)}`);
4329
4495
  notes.push("end card");
4330
4496
  } else {
4331
- skipped.push(`${d.id}: no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)`);
4497
+ skipped.push(
4498
+ `${d.id}: no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)`
4499
+ );
4332
4500
  }
4333
4501
  }
4334
4502
  if (!loop && d.text !== "none" && input.captions.length && !off(launch.captions)) {
@@ -4336,7 +4504,9 @@ function planMotion(input) {
4336
4504
  const steps = doc.source.meta.steps ?? [];
4337
4505
  const clips = [];
4338
4506
  for (const c of input.captions) {
4339
- const step = steps.find((s) => c.id !== void 0 && s.id === c.id || s.step === c.step);
4507
+ const step = steps.find(
4508
+ (s) => c.id !== void 0 && s.id === c.id || s.step === c.step
4509
+ );
4340
4510
  if (!step || step.skipped) continue;
4341
4511
  const t = stepOutputTime(rated, step, 0.2);
4342
4512
  if (t === null || t < range[0] || t > range[1] - 1) continue;
@@ -4384,7 +4554,9 @@ function planMotion(input) {
4384
4554
  });
4385
4555
  notes.push(`bed ${track.slug}`);
4386
4556
  } else if (launch.music && !off(launch.music)) {
4387
- skipped.push(`${d.id}: music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`);
4557
+ skipped.push(
4558
+ `${d.id}: music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`
4559
+ );
4388
4560
  }
4389
4561
  const click = catalog?.sfx.find((s) => s.slug === "sfx-click");
4390
4562
  if (click && !doc.source.micKey && !off(launch.clicks)) {
@@ -4444,19 +4616,19 @@ function resolveChannels(raw) {
4444
4616
  return out;
4445
4617
  }
4446
4618
  async function readBrandBesideTake(dir, explicit) {
4447
- const candidates = explicit ? [explicit] : [join5(dir, "BRAND.md"), join5(dir, "..", "BRAND.md")];
4619
+ const candidates = explicit ? [explicit] : [join6(dir, "BRAND.md"), join6(dir, "..", "BRAND.md")];
4448
4620
  for (const file of candidates) {
4449
- if (!existsSync5(file)) continue;
4450
- const roles = parseFrontmatter(await readFile5(file, "utf8"));
4621
+ if (!existsSync6(file)) continue;
4622
+ const roles = parseFrontmatter(await readFile6(file, "utf8"));
4451
4623
  return { file, roles };
4452
4624
  }
4453
4625
  return null;
4454
4626
  }
4455
4627
  async function readLaunchBesideTake(dir, explicit) {
4456
- const candidates = explicit ? [explicit] : [join5(dir, "LAUNCH.md"), join5(dir, "..", "LAUNCH.md")];
4628
+ const candidates = explicit ? [explicit] : [join6(dir, "LAUNCH.md"), join6(dir, "..", "LAUNCH.md")];
4457
4629
  for (const file of candidates) {
4458
- if (!existsSync5(file)) continue;
4459
- return { file, roles: parseFrontmatter(await readFile5(file, "utf8")) };
4630
+ if (!existsSync6(file)) continue;
4631
+ return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
4460
4632
  }
4461
4633
  return null;
4462
4634
  }
@@ -4559,7 +4731,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4559
4731
  const meta = doc.source.meta;
4560
4732
  const vw = meta.captureWidth ?? meta.width;
4561
4733
  const vh = meta.captureHeight ?? meta.height;
4562
- const probeDir = await mkdtemp(join5(tmpdir(), "vos-moments-"));
4734
+ const probeDir = await mkdtemp(join6(tmpdir(), "vos-moments-"));
4563
4735
  try {
4564
4736
  const probe = await framesTake(browser, dir, {
4565
4737
  times: candidates.map((c) => c.time),
@@ -4573,7 +4745,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
4573
4745
  });
4574
4746
  const byTime = /* @__PURE__ */ new Map();
4575
4747
  for (const frame of probe.frames) {
4576
- const img = decodePng(new Uint8Array(await readFile5(frame.file)));
4748
+ const img = decodePng(new Uint8Array(await readFile6(frame.file)));
4577
4749
  if (!img) continue;
4578
4750
  const whole = { x: 0, y: 0, w: img.w, h: img.h };
4579
4751
  byTime.set(+frame.time.toFixed(3), {
@@ -4644,10 +4816,16 @@ async function deliverTake(browser, dir, opts) {
4644
4816
  const take = await loadTake(dir);
4645
4817
  if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
4646
4818
  const doc = take.doc;
4819
+ const marks = await fetchBrandMarks(dir, opts.brandRoles);
4820
+ for (const n of marks.notes) opts.onPhase?.(`note: ${n}`);
4821
+ const darkGround = opts.look?.kind === "dark";
4822
+ const mark = darkGround ? marks.dark : marks.light;
4823
+ if (mark)
4824
+ opts.onPhase?.(`brand mark: ${mark.key} (${mark.aspect.toFixed(2)}:1)`);
4647
4825
  const duration = totalDuration(ratedSegments5(doc));
4648
4826
  const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
4649
- const outDir = resolve3(opts.outDir ?? join5(dir, "kit"));
4650
- await mkdir4(outDir, { recursive: true });
4827
+ const outDir = resolve4(opts.outDir ?? join6(dir, "kit"));
4828
+ await mkdir5(outDir, { recursive: true });
4651
4829
  const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
4652
4830
  const assets = [];
4653
4831
  const skipped = [];
@@ -4689,7 +4867,8 @@ async function deliverTake(browser, dir, opts) {
4689
4867
  launch: opts.launchRoles ?? {},
4690
4868
  captions: opts.captions ?? [],
4691
4869
  catalog: opts.catalog ?? null,
4692
- ink: endCardInk(opts.look, opts.brandRoles)
4870
+ ink: endCardInk(opts.look, opts.brandRoles),
4871
+ mark
4693
4872
  });
4694
4873
  set.push(...plan.set);
4695
4874
  if (plan.notes.length) opts.onPhase?.(`${d.id}: ${plan.notes.join(", ")}`);
@@ -4710,10 +4889,14 @@ async function deliverTake(browser, dir, opts) {
4710
4889
  const meta = doc.source.meta;
4711
4890
  const heroTime = opts.shotTime ?? (stillTimes.length ? stillTimes[0] : duration / 2);
4712
4891
  const fill = posterValues(opts.brandRoles, opts.words ?? {});
4892
+ if (mark) {
4893
+ fill.values.logoKey = mark.key;
4894
+ fill.values.logoAspect = mark.aspect;
4895
+ }
4713
4896
  opts.onPhase?.(
4714
4897
  `poster shot (full bleed at ${heroTime.toFixed(2)}s), baked as an object`
4715
4898
  );
4716
- const serveDir = await mkdtemp(join5(tmpdir(), "vos-poster-"));
4899
+ const serveDir = await mkdtemp(join6(tmpdir(), "vos-poster-"));
4717
4900
  try {
4718
4901
  const shot = await framesTake(browser, dir, {
4719
4902
  times: [heroTime],
@@ -4735,7 +4918,7 @@ async function deliverTake(browser, dir, opts) {
4735
4918
  ]
4736
4919
  }
4737
4920
  });
4738
- const raw = decodePng(new Uint8Array(await readFile5(shot.frames[0].file)));
4921
+ const raw = decodePng(new Uint8Array(await readFile6(shot.frames[0].file)));
4739
4922
  if (!raw) throw new Error("the poster shot could not be decoded");
4740
4923
  const PAD = 0.06;
4741
4924
  const baked = bakeShot(raw, {
@@ -4743,7 +4926,7 @@ async function deliverTake(browser, dir, opts) {
4743
4926
  hairline: fill.lightGround ? 0.14 : 0,
4744
4927
  shadow: fill.lightGround ? 0.28 : 0.4
4745
4928
  });
4746
- await writeFile6(join5(serveDir, "shot.png"), encodePng(baked));
4929
+ await writeFile7(join6(serveDir, "shot.png"), encodePng(baked));
4747
4930
  const shotAspect = raw.w / raw.h;
4748
4931
  for (const { d, plan } of cardPlans) {
4749
4932
  if (plan.stage) {
@@ -4759,7 +4942,7 @@ async function deliverTake(browser, dir, opts) {
4759
4942
  opts.onPhase?.(
4760
4943
  `${d.channel} ${d.asset} (${specWords(d)}) from ${plan.from}`
4761
4944
  );
4762
- const shotDir = await mkdtemp(join5(tmpdir(), "vos-stage-"));
4945
+ const shotDir = await mkdtemp(join6(tmpdir(), "vos-stage-"));
4763
4946
  try {
4764
4947
  const captured = await framesTake(browser, dir, {
4765
4948
  times: [heroTime],
@@ -4771,7 +4954,7 @@ async function deliverTake(browser, dir, opts) {
4771
4954
  set: [...staged.set, ...opts.overrides?.set ?? []]
4772
4955
  }
4773
4956
  });
4774
- const to2 = join5(outDir, `${d.id}.png`);
4957
+ const to2 = join6(outDir, `${d.id}.png`);
4775
4958
  await rename4(captured.frames[0].file, to2);
4776
4959
  const bytes2 = (await stat(to2)).size;
4777
4960
  if (d.maxBytes !== void 0 && bytes2 > d.maxBytes) {
@@ -4843,8 +5026,8 @@ async function deliverTake(browser, dir, opts) {
4843
5026
  [{ name: `${d.id}.png`, width: d.px.w, height: d.px.h }],
4844
5027
  time
4845
5028
  );
4846
- const from = join5(serveDir, `${d.id}.png`);
4847
- const to = join5(outDir, `${d.id}.png`);
5029
+ const from = join6(serveDir, `${d.id}.png`);
5030
+ const to = join6(outDir, `${d.id}.png`);
4848
5031
  await rename4(from, to);
4849
5032
  const bytes = (await stat(to)).size;
4850
5033
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -4905,7 +5088,7 @@ async function deliverTake(browser, dir, opts) {
4905
5088
  }
4906
5089
  }
4907
5090
  opts.onPhase?.(`${label} (${specWords(d)})`);
4908
- const outFile = join5(outDir, `${d.id}.${d.format}`);
5091
+ const outFile = join6(outDir, `${d.id}.${d.format}`);
4909
5092
  const bitrate = d.maxBytes !== void 0 ? Math.min(
4910
5093
  1e7,
4911
5094
  Math.floor(d.maxBytes * 8 / seconds * 0.85)
@@ -4952,7 +5135,7 @@ async function deliverTake(browser, dir, opts) {
4952
5135
  for (let i = 0; i < captured.frames.length; i++) {
4953
5136
  const frame = captured.frames[i];
4954
5137
  const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
4955
- const to = join5(outDir, name);
5138
+ const to = join6(outDir, name);
4956
5139
  await rename4(frame.file, to);
4957
5140
  const bytes = (await stat(to)).size;
4958
5141
  if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
@@ -4990,8 +5173,8 @@ async function deliverTake(browser, dir, opts) {
4990
5173
  skipped,
4991
5174
  assets
4992
5175
  };
4993
- const kitFile = join5(outDir, "kit.json");
4994
- await writeFile6(kitFile, JSON.stringify(kit, null, 2));
5176
+ const kitFile = join6(outDir, "kit.json");
5177
+ await writeFile7(kitFile, JSON.stringify(kit, null, 2));
4995
5178
  return { kit, kitFile, outDir };
4996
5179
  }
4997
5180
 
@@ -5008,9 +5191,9 @@ async function fetchMusicCatalog(origin) {
5008
5191
  }
5009
5192
 
5010
5193
  // src/plugin/judge.ts
5011
- import { existsSync as existsSync6 } from "fs";
5012
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
5013
- import { dirname, isAbsolute, join as join6, resolve as resolve4 } from "path";
5194
+ import { existsSync as existsSync7 } from "fs";
5195
+ import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
5196
+ import { dirname, isAbsolute as isAbsolute2, join as join7, resolve as resolve5 } from "path";
5014
5197
  import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
5015
5198
  var RUBRIC = [
5016
5199
  "A plate, and room on it: the subject sits at 74 to 89% of the width with headroom; nobody bleeds a window on all four sides.",
@@ -5114,26 +5297,26 @@ function composeSheet(left, right, height = 540, gutter = 32) {
5114
5297
  return { w, h, data: out };
5115
5298
  }
5116
5299
  async function judgeKit(kitPath, manifestPath, outDir) {
5117
- const kit = JSON.parse(await readFile6(kitPath, "utf8"));
5118
- const manifest = JSON.parse(await readFile6(manifestPath, "utf8"));
5119
- const manifestDir = dirname(resolve4(manifestPath));
5120
- const kitDir = dirname(resolve4(kitPath));
5121
- const out = resolve4(outDir ?? join6(kitDir, "judge"));
5122
- await mkdir5(out, { recursive: true });
5300
+ const kit = JSON.parse(await readFile7(kitPath, "utf8"));
5301
+ const manifest = JSON.parse(await readFile7(manifestPath, "utf8"));
5302
+ const manifestDir = dirname(resolve5(manifestPath));
5303
+ const kitDir = dirname(resolve5(kitPath));
5304
+ const out = resolve5(outDir ?? join7(kitDir, "judge"));
5305
+ await mkdir6(out, { recursive: true });
5123
5306
  const sheets = [];
5124
5307
  const skipped = [];
5125
5308
  const refCache = /* @__PURE__ */ new Map();
5126
5309
  const loadRef = async (file) => {
5127
5310
  if (refCache.has(file)) return refCache.get(file) ?? null;
5128
- const p = join6(manifestDir, file);
5129
- const img = existsSync6(p) ? decodePng(new Uint8Array(await readFile6(p))) : null;
5311
+ const p = join7(manifestDir, file);
5312
+ const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
5130
5313
  refCache.set(file, img);
5131
5314
  return img;
5132
5315
  };
5133
5316
  for (const a of kit.assets) {
5134
5317
  if (!/\.png$/i.test(a.path)) continue;
5135
- const file = isAbsolute(a.path) && existsSync6(a.path) ? a.path : join6(kitDir, a.path.split("/").pop() ?? a.path);
5136
- const img = existsSync6(file) ? decodePng(new Uint8Array(await readFile6(file))) : null;
5318
+ const file = isAbsolute2(a.path) && existsSync7(a.path) ? a.path : join7(kitDir, a.path.split("/").pop() ?? a.path);
5319
+ const img = existsSync7(file) ? decodePng(new Uint8Array(await readFile7(file))) : null;
5137
5320
  if (!img) {
5138
5321
  skipped.push(`${a.destination ?? a.path}: unreadable`);
5139
5322
  continue;
@@ -5153,8 +5336,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5153
5336
  const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
5154
5337
  const nameA = `${id}--A.png`;
5155
5338
  const nameB = `${id}--B.png`;
5156
- await writeFile7(join6(out, nameA), encodePng(composeSheet(img, refImg)));
5157
- await writeFile7(join6(out, nameB), encodePng(composeSheet(refImg, img)));
5339
+ await writeFile8(join7(out, nameA), encodePng(composeSheet(img, refImg)));
5340
+ await writeFile8(join7(out, nameB), encodePng(composeSheet(refImg, img)));
5158
5341
  const rubric = [
5159
5342
  `# ${id} against ${ref.id} (${ref.file})`,
5160
5343
  "",
@@ -5174,12 +5357,12 @@ async function judgeKit(kitPath, manifestPath, outDir) {
5174
5357
  "In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
5175
5358
  ""
5176
5359
  ].join("\n");
5177
- await writeFile7(join6(out, `${id}.md`), rubric);
5360
+ await writeFile8(join7(out, `${id}.md`), rubric);
5178
5361
  sheets.push({ asset: id, reference: ref.id, sheetA: nameA, sheetB: nameB, rubric: `${id}.md` });
5179
5362
  }
5180
- const verdictFile = join6(out, "judge.json");
5181
- if (!existsSync6(verdictFile)) {
5182
- await writeFile7(
5363
+ const verdictFile = join7(out, "judge.json");
5364
+ if (!existsSync7(verdictFile)) {
5365
+ await writeFile8(
5183
5366
  verdictFile,
5184
5367
  JSON.stringify(
5185
5368
  {
@@ -5205,7 +5388,7 @@ function winRate(verdicts) {
5205
5388
  // src/plugin/kitPicture.ts
5206
5389
  import { execFile } from "child_process";
5207
5390
  import { promisify } from "util";
5208
- import { readFile as readFile7 } from "fs/promises";
5391
+ import { readFile as readFile8 } from "fs/promises";
5209
5392
  var execFileP = promisify(execFile);
5210
5393
  var SUBJECT_BAND = { min: 0.6, max: 0.92 };
5211
5394
  var BLANK_INK = 0.12;
@@ -5481,7 +5664,7 @@ async function pictureChecks(assets) {
5481
5664
  let ffmpeg = null;
5482
5665
  for (const a of assets) {
5483
5666
  if (/\.png$/i.test(a.file)) {
5484
- const img = decodePng(new Uint8Array(await readFile7(a.file)));
5667
+ const img = decodePng(new Uint8Array(await readFile8(a.file)));
5485
5668
  if (!img) {
5486
5669
  findings.push({
5487
5670
  code: "unreadable",
@@ -5574,9 +5757,9 @@ function formatFinding(f) {
5574
5757
  }
5575
5758
 
5576
5759
  // src/plugin/validateKit.ts
5577
- import { existsSync as existsSync7 } from "fs";
5578
- import { readFile as readFile8, stat as stat2 } from "fs/promises";
5579
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join7 } from "path";
5760
+ import { existsSync as existsSync8 } from "fs";
5761
+ import { readFile as readFile9, stat as stat2 } from "fs/promises";
5762
+ import { dirname as dirname2, isAbsolute as isAbsolute3, join as join8 } from "path";
5580
5763
  import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
5581
5764
  var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
5582
5765
  function pngDimensions(bytes) {
@@ -5595,7 +5778,7 @@ async function probeVideo(path) {
5595
5778
  const MB = await import("mediabunny");
5596
5779
  const input = new MB.Input({
5597
5780
  formats: MB.ALL_FORMATS,
5598
- source: new MB.BufferSource(new Uint8Array(await readFile8(path)))
5781
+ source: new MB.BufferSource(new Uint8Array(await readFile9(path)))
5599
5782
  });
5600
5783
  try {
5601
5784
  const track = await input.getPrimaryVideoTrack();
@@ -5616,7 +5799,7 @@ async function validateKit(kitPath, opts = {}) {
5616
5799
  const measured = [];
5617
5800
  let kit;
5618
5801
  try {
5619
- kit = JSON.parse(await readFile8(kitPath, "utf8"));
5802
+ kit = JSON.parse(await readFile9(kitPath, "utf8"));
5620
5803
  } catch (e) {
5621
5804
  return {
5622
5805
  valid: false,
@@ -5635,8 +5818,8 @@ async function validateKit(kitPath, opts = {}) {
5635
5818
  }
5636
5819
  const base = dirname2(kitPath);
5637
5820
  const resolvePath = (p) => {
5638
- if (isAbsolute2(p) && existsSync7(p)) return p;
5639
- const beside = join7(base, p.split("/").pop() ?? p);
5821
+ if (isAbsolute3(p) && existsSync8(p)) return p;
5822
+ const beside = join8(base, p.split("/").pop() ?? p);
5640
5823
  return beside;
5641
5824
  };
5642
5825
  const perDestination = /* @__PURE__ */ new Map();
@@ -5662,7 +5845,7 @@ async function validateKit(kitPath, opts = {}) {
5662
5845
  let h = null;
5663
5846
  let seconds = null;
5664
5847
  if (/\.(png|jpe?g|webp)$/i.test(file)) {
5665
- const head = Buffer.from(await readFile8(file));
5848
+ const head = Buffer.from(await readFile9(file));
5666
5849
  const kind = sniffImage(head);
5667
5850
  if (file.toLowerCase().endsWith(".png") && kind !== "png")
5668
5851
  problems.push(
@@ -5773,7 +5956,7 @@ async function validateKit(kitPath, opts = {}) {
5773
5956
  // src/plugin/platform.ts
5774
5957
  import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5775
5958
  import { homedir } from "os";
5776
- import { join as join8 } from "path";
5959
+ import { join as join9 } from "path";
5777
5960
  function platformOrigin(flags = {}) {
5778
5961
  const legacyEnv = process.env.VOS_API_BASE?.trim();
5779
5962
  const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
@@ -5806,7 +5989,7 @@ function parseVosId(input) {
5806
5989
  function deriveSlug(title) {
5807
5990
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
5808
5991
  }
5809
- var CREDENTIALS_PATH = join8(homedir(), ".config", "vos", "credentials");
5992
+ var CREDENTIALS_PATH = join9(homedir(), ".config", "vos", "credentials");
5810
5993
  function resolveCredential(explicit) {
5811
5994
  const flag = explicit?.trim();
5812
5995
  if (flag) return flag;
@@ -5829,7 +6012,7 @@ function requireCredential(explicit) {
5829
6012
  return key;
5830
6013
  }
5831
6014
  function writeCredential(key) {
5832
- mkdirSync(join8(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
6015
+ mkdirSync(join9(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
5833
6016
  writeFileSync(CREDENTIALS_PATH, `${key.trim()}
5834
6017
  `, { mode: 384 });
5835
6018
  return CREDENTIALS_PATH;
@@ -5873,7 +6056,7 @@ function readJsonFile(file) {
5873
6056
  }
5874
6057
  }
5875
6058
  function readSyncState(dir) {
5876
- const own = readJsonFile(join8(dir, SYNC_STATE_NAME));
6059
+ const own = readJsonFile(join9(dir, SYNC_STATE_NAME));
5877
6060
  if (own && typeof own.vosId === "string") {
5878
6061
  return {
5879
6062
  vosId: own.vosId,
@@ -5884,7 +6067,7 @@ function readSyncState(dir) {
5884
6067
  ...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
5885
6068
  };
5886
6069
  }
5887
- const push = readJsonFile(join8(dir, "push.json"));
6070
+ const push = readJsonFile(join9(dir, "push.json"));
5888
6071
  if (push && typeof push.vosId === "string") {
5889
6072
  return {
5890
6073
  vosId: push.vosId,
@@ -5892,7 +6075,7 @@ function readSyncState(dir) {
5892
6075
  ...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
5893
6076
  };
5894
6077
  }
5895
- const meta = readJsonFile(join8(dir, "meta.json"));
6078
+ const meta = readJsonFile(join9(dir, "meta.json"));
5896
6079
  if (meta && typeof meta.id === "string") {
5897
6080
  return {
5898
6081
  vosId: meta.id,
@@ -5910,7 +6093,7 @@ function writeSyncState(dir, patch) {
5910
6093
  ...patch
5911
6094
  };
5912
6095
  writeFileSync(
5913
- join8(dir, SYNC_STATE_NAME),
6096
+ join9(dir, SYNC_STATE_NAME),
5914
6097
  `${JSON.stringify(merged, null, 2)}
5915
6098
  `
5916
6099
  );
@@ -5931,7 +6114,7 @@ function formatChanges(changes) {
5931
6114
 
5932
6115
  // src/plugin/recorder.ts
5933
6116
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5934
- import { join as join9 } from "path";
6117
+ import { join as join10 } from "path";
5935
6118
 
5936
6119
  // src/plugin/recordingCap.ts
5937
6120
  var HOSTED_RECORDING_CAP_SECONDS = 30 * 60;
@@ -6019,7 +6202,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6019
6202
  cdp.on("Page.screencastFrame", (ev) => {
6020
6203
  const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
6021
6204
  const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
6022
- writeFileSync2(join9(paths.framesDir, file), Buffer.from(ev.data, "base64"));
6205
+ writeFileSync2(join10(paths.framesDir, file), Buffer.from(ev.data, "base64"));
6023
6206
  frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
6024
6207
  cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
6025
6208
  });
@@ -6242,7 +6425,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6242
6425
  await sleep(200);
6243
6426
  const pageTitle = await page.title().catch(() => "");
6244
6427
  await context.close();
6245
- const firstFrame = frames[0] ? jpegDims(readFileSync3(join9(paths.framesDir, frames[0].file))) : null;
6428
+ const firstFrame = frames[0] ? jpegDims(readFileSync3(join10(paths.framesDir, frames[0].file))) : null;
6246
6429
  const meta = {
6247
6430
  dpr: 1,
6248
6431
  zoom: 1,
@@ -6350,9 +6533,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
6350
6533
  }
6351
6534
 
6352
6535
  // src/plugin/plan.ts
6353
- import { existsSync as existsSync8 } from "fs";
6354
- import { readFile as readFile9 } from "fs/promises";
6355
- import { join as join10 } from "path";
6536
+ import { existsSync as existsSync9 } from "fs";
6537
+ import { readFile as readFile10 } from "fs/promises";
6538
+ import { join as join11 } from "path";
6356
6539
  import {
6357
6540
  DEFAULT_FRAME_STYLE,
6358
6541
  STYLE_FIELDS,
@@ -6545,10 +6728,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
6545
6728
  // src/plugin/plan.ts
6546
6729
  var overlaps = (a, b) => a.in < b.out && b.in < a.out;
6547
6730
  async function readDigestActivity(dir) {
6548
- const file = join10(dir, "digest", "digest.json");
6549
- if (!existsSync8(file)) return null;
6731
+ const file = join11(dir, "digest", "digest.json");
6732
+ if (!existsSync9(file)) return null;
6550
6733
  try {
6551
- const d = JSON.parse(await readFile9(file, "utf8"));
6734
+ const d = JSON.parse(await readFile10(file, "utf8"));
6552
6735
  return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
6553
6736
  } catch {
6554
6737
  return null;
@@ -6661,16 +6844,16 @@ async function planTake(dir, opts = {}) {
6661
6844
 
6662
6845
  // src/plugin/sync.ts
6663
6846
  import { createHash } from "crypto";
6664
- import { existsSync as existsSync10 } from "fs";
6665
- import { readFile as readFile10 } from "fs/promises";
6847
+ import { existsSync as existsSync11 } from "fs";
6848
+ import { readFile as readFile11 } from "fs/promises";
6666
6849
  import { createInterface } from "readline/promises";
6667
- import { basename, join as join13 } from "path";
6850
+ import { basename, join as join14 } from "path";
6668
6851
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
6669
6852
 
6670
6853
  // src/plugin/media.ts
6671
- import { createWriteStream, existsSync as existsSync9 } from "fs";
6672
- import { writeFile as writeFile8 } from "fs/promises";
6673
- import { join as join11 } from "path";
6854
+ import { createWriteStream, existsSync as existsSync10 } from "fs";
6855
+ import { writeFile as writeFile9 } from "fs/promises";
6856
+ import { join as join12 } from "path";
6674
6857
  import { Readable } from "stream";
6675
6858
  import { pipeline } from "stream/promises";
6676
6859
  var MIC_NAME = "mic.webm";
@@ -6691,8 +6874,8 @@ async function pullMedia(ctx, dir, doc, log) {
6691
6874
  const url = doc.source[key];
6692
6875
  const assetId = assetIdOf(url);
6693
6876
  if (!assetId) continue;
6694
- const target = join11(dir, file);
6695
- if (existsSync9(target)) {
6877
+ const target = join12(dir, file);
6878
+ if (existsSync10(target)) {
6696
6879
  result.kept.push(file);
6697
6880
  } else {
6698
6881
  const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
@@ -6714,17 +6897,17 @@ async function pullMedia(ctx, dir, doc, log) {
6714
6897
  }
6715
6898
  doc.source[key] = file;
6716
6899
  }
6717
- const metaPath = join11(dir, "meta.json");
6718
- if (!existsSync9(metaPath)) await writeJson(metaPath, doc.source.meta, true);
6719
- const cursorPath = join11(dir, "cursor.json");
6720
- if (!existsSync9(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
6721
- await writeFile8(join11(dir, "doc.json"), JSON.stringify(doc, null, 2));
6900
+ const metaPath = join12(dir, "meta.json");
6901
+ if (!existsSync10(metaPath)) await writeJson(metaPath, doc.source.meta, true);
6902
+ const cursorPath = join12(dir, "cursor.json");
6903
+ if (!existsSync10(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
6904
+ await writeFile9(join12(dir, "doc.json"), JSON.stringify(doc, null, 2));
6722
6905
  return result;
6723
6906
  }
6724
6907
 
6725
6908
  // src/plugin/folder.ts
6726
- import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
6727
- import { join as join12 } from "path";
6909
+ import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
6910
+ import { join as join13 } from "path";
6728
6911
  import { recipeHints } from "@vosjs/shared/frontmatter";
6729
6912
  import { migrateHostedDoc } from "@vosjs/studio-core";
6730
6913
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
@@ -6900,25 +7083,25 @@ async function cmdPull(argv) {
6900
7083
  throw new Error(apiError(`pull folder ${folder.slug}`, res));
6901
7084
  const payload = res.body;
6902
7085
  const out = strFlag(flags, "out") ?? folder.slug;
6903
- await mkdir6(join12(out, "recipes"), { recursive: true });
7086
+ await mkdir7(join13(out, "recipes"), { recursive: true });
6904
7087
  const lines = [];
6905
7088
  const recipes = [];
6906
7089
  for (const rec of payload.recipes) {
6907
- const path = join12(out, "recipes", safeName(rec.filename));
6908
- await writeFile9(path, rec.body ?? "");
7090
+ const path = join13(out, "recipes", safeName(rec.filename));
7091
+ await writeFile10(path, rec.body ?? "");
6909
7092
  recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
6910
7093
  }
6911
7094
  for (const rec of payload.inheritedRecipes) {
6912
7095
  const from = safeName(rec.folderSlug ?? "inherited");
6913
- await mkdir6(join12(out, "recipes", "_inherited", from), { recursive: true });
6914
- const path = join12(
7096
+ await mkdir7(join13(out, "recipes", "_inherited", from), { recursive: true });
7097
+ const path = join13(
6915
7098
  out,
6916
7099
  "recipes",
6917
7100
  "_inherited",
6918
7101
  from,
6919
7102
  safeName(rec.filename)
6920
7103
  );
6921
- await writeFile9(path, rec.body ?? "");
7104
+ await writeFile10(path, rec.body ?? "");
6922
7105
  recipes.push({
6923
7106
  path,
6924
7107
  line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
@@ -6926,15 +7109,15 @@ async function cmdPull(argv) {
6926
7109
  }
6927
7110
  const members = [];
6928
7111
  for (const v of payload.voses) {
6929
- const dir = join12(out, "members", safeName(v.slug || v.title || v.id));
6930
- await mkdir6(dir, { recursive: true });
7112
+ const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
7113
+ await mkdir7(dir, { recursive: true });
6931
7114
  const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
6932
7115
  if (cfg.status !== 200) {
6933
7116
  lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
6934
7117
  continue;
6935
7118
  }
6936
- await writeFile9(
6937
- join12(dir, "config.json"),
7119
+ await writeFile10(
7120
+ join13(dir, "config.json"),
6938
7121
  JSON.stringify(cfg.body.config, null, 2)
6939
7122
  );
6940
7123
  let take = false;
@@ -6945,8 +7128,8 @@ async function cmdPull(argv) {
6945
7128
  { key }
6946
7129
  );
6947
7130
  if (doc.status === 200) {
6948
- await writeFile9(
6949
- join12(dir, "doc.json"),
7131
+ await writeFile10(
7132
+ join13(dir, "doc.json"),
6950
7133
  JSON.stringify(doc.body, null, 2)
6951
7134
  );
6952
7135
  take = true;
@@ -7063,7 +7246,7 @@ async function pushTake(dir, flags, r) {
7063
7246
  throw new Error(`doc.json fails lint:
7064
7247
  ${lint.problems.join("\n ")}`);
7065
7248
  }
7066
- if (!existsSync10(take.paths.recording)) {
7249
+ if (!existsSync11(take.paths.recording)) {
7067
7250
  throw new Error("no recording.webm in this take");
7068
7251
  }
7069
7252
  const ctx = apiContext(flags);
@@ -7095,7 +7278,7 @@ async function pushTake(dir, flags, r) {
7095
7278
  throw new Error("push cancelled");
7096
7279
  }
7097
7280
  }
7098
- const bytes = await readFile10(take.paths.recording);
7281
+ const bytes = await readFile11(take.paths.recording);
7099
7282
  const hash = createHash("sha256").update(bytes).digest("hex");
7100
7283
  r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
7101
7284
  const upload = await api(ctx, "/assets/recording", {
@@ -7246,9 +7429,9 @@ async function pullTake(dir, flags, r) {
7246
7429
  };
7247
7430
  }
7248
7431
  let media2;
7249
- if (flags.media && existsSync10(join13(dir, "doc.json"))) {
7432
+ if (flags.media && existsSync11(join14(dir, "doc.json"))) {
7250
7433
  const local = JSON.parse(
7251
- await readFile10(join13(dir, "doc.json"), "utf8")
7434
+ await readFile11(join14(dir, "doc.json"), "utf8")
7252
7435
  );
7253
7436
  media2 = await pullMedia(ctx, dir, local, r.log);
7254
7437
  }
@@ -7308,10 +7491,10 @@ async function pullTake(dir, flags, r) {
7308
7491
  }
7309
7492
  const hostedDoc = migrateHostedDoc2(docRes.json);
7310
7493
  const doc = hostedDoc;
7311
- if (existsSync10(join13(dir, RECORDING_NAME))) {
7494
+ if (existsSync11(join14(dir, RECORDING_NAME))) {
7312
7495
  doc.source.videoKey = RECORDING_NAME;
7313
7496
  }
7314
- await writeJson(join13(dir, "doc.json"), doc, true);
7497
+ await writeJson(join14(dir, "doc.json"), doc, true);
7315
7498
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
7316
7499
  const versions = await api(ctx, `/vos/${vosId}/versions`);
7317
7500
  const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
@@ -7330,10 +7513,10 @@ async function pullTake(dir, flags, r) {
7330
7513
  }
7331
7514
 
7332
7515
  // src/plugin/program.ts
7333
- import { existsSync as existsSync11 } from "fs";
7334
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile10 } from "fs/promises";
7516
+ import { existsSync as existsSync12 } from "fs";
7517
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile11 } from "fs/promises";
7335
7518
  import { createInterface as createInterface2 } from "readline/promises";
7336
- import { basename as basename2, dirname as dirname3, join as join14 } from "path";
7519
+ import { basename as basename2, dirname as dirname3, join as join15 } from "path";
7337
7520
  import {
7338
7521
  CURRENT_CONFIG_VERSION,
7339
7522
  migrateConfig,
@@ -7503,8 +7686,8 @@ function preflightConfig(parsed) {
7503
7686
  }
7504
7687
  function resolveConfigPath(target) {
7505
7688
  if (target.endsWith(".json")) return target;
7506
- const inDir = join14(target, "config.json");
7507
- if (existsSync11(inDir)) return inDir;
7689
+ const inDir = join15(target, "config.json");
7690
+ if (existsSync12(inDir)) return inDir;
7508
7691
  throw new UsageError(
7509
7692
  `${target} has no config.json (and is not a take \u2014 no doc.json)`
7510
7693
  );
@@ -7530,9 +7713,9 @@ async function cmdFetch(argv) {
7530
7713
  throw new Error(apiError(`fetch config for ${vosId}`, cfg));
7531
7714
  const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
7532
7715
  const out = strFlag(flags, "out") ?? slug;
7533
- await mkdir7(out, { recursive: true });
7534
- await writeFile10(
7535
- join14(out, "config.json"),
7716
+ await mkdir8(out, { recursive: true });
7717
+ await writeFile11(
7718
+ join15(out, "config.json"),
7536
7719
  JSON.stringify(cfg.body.config, null, 2)
7537
7720
  );
7538
7721
  const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
@@ -7557,12 +7740,12 @@ async function cmdFetch(argv) {
7557
7740
  const hosted = migrateHostedDoc3(doc.body);
7558
7741
  const { config: own } = await writeProgramDoc(out, hosted);
7559
7742
  if (own) {
7560
- await writeFile10(join14(out, "config.json"), JSON.stringify(own, null, 2));
7743
+ await writeFile11(join15(out, "config.json"), JSON.stringify(own, null, 2));
7561
7744
  }
7562
7745
  } else if (doc.status === 200) {
7563
7746
  take = true;
7564
7747
  const hosted = migrateHostedDoc3(doc.body);
7565
- await writeFile10(join14(out, "doc.json"), JSON.stringify(hosted, null, 2));
7748
+ await writeFile11(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
7566
7749
  if (flags.media === true) {
7567
7750
  const media = await pullMedia({ origin, key }, out, hosted, r.log);
7568
7751
  mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
@@ -7674,7 +7857,7 @@ async function cmdPushProgram(argv) {
7674
7857
  api: strFlag(flags, "api")
7675
7858
  });
7676
7859
  const dir = dirname3(source);
7677
- const parsed = JSON.parse(await readFile11(source, "utf8"));
7860
+ const parsed = JSON.parse(await readFile12(source, "utf8"));
7678
7861
  const pre = preflightConfig(parsed);
7679
7862
  if (!pre.ok || !pre.config) {
7680
7863
  for (const issue of pre.issues) r.log(`error ${issue}`);
@@ -7907,10 +8090,10 @@ async function cmdPullProgram(argv) {
7907
8090
  const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
7908
8091
  if (cfg.status !== 200)
7909
8092
  throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
7910
- const configPath = join14(dir, "config.json");
8093
+ const configPath = join15(dir, "config.json");
7911
8094
  let backedUp = false;
7912
- if (existsSync11(configPath)) {
7913
- await writeFile10(join14(dir, "config.backup.json"), await readFile11(configPath));
8095
+ if (existsSync12(configPath)) {
8096
+ await writeFile11(join15(dir, "config.backup.json"), await readFile12(configPath));
7914
8097
  backedUp = true;
7915
8098
  }
7916
8099
  let headConfig = cfg.body.config;
@@ -7929,7 +8112,7 @@ async function cmdPullProgram(argv) {
7929
8112
  if (own) headConfig = own;
7930
8113
  }
7931
8114
  }
7932
- await writeFile10(configPath, JSON.stringify(headConfig, null, 2));
8115
+ await writeFile11(configPath, JSON.stringify(headConfig, null, 2));
7933
8116
  writeSyncState(dir, {
7934
8117
  vosId,
7935
8118
  versionId: typeof head.id === "string" ? head.id : since
@@ -7943,7 +8126,7 @@ async function cmdPullProgram(argv) {
7943
8126
  protected: protectedIds,
7944
8127
  changes,
7945
8128
  out: configPath,
7946
- backup: backedUp ? join14(dir, "config.backup.json") : null
8129
+ backup: backedUp ? join15(dir, "config.backup.json") : null
7947
8130
  },
7948
8131
  `Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
7949
8132
  Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
@@ -8000,9 +8183,9 @@ function isTakeDir(target) {
8000
8183
  return directoryKind(target) === "take";
8001
8184
  }
8002
8185
  async function readProgramDoc(dir, config) {
8003
- const docPath = join14(dir, "doc.json");
8004
- if (!existsSync11(docPath)) return null;
8005
- const parsed = JSON.parse(await readFile11(docPath, "utf8"));
8186
+ const docPath = join15(dir, "doc.json");
8187
+ if (!existsSync12(docPath)) return null;
8188
+ const parsed = JSON.parse(await readFile12(docPath, "utf8"));
8006
8189
  if (typeof parsed !== "object" || parsed === null || "source" in parsed)
8007
8190
  return null;
8008
8191
  const raw = parsed;
@@ -8013,7 +8196,7 @@ async function writeProgramDoc(dir, hosted) {
8013
8196
  const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
8014
8197
  const { config, ...rest } = program;
8015
8198
  const onDisk = { ...hosted, program: rest };
8016
- await writeFile10(join14(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8199
+ await writeFile11(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
8017
8200
  return {
8018
8201
  config: config && typeof config === "object" ? config : null
8019
8202
  };
@@ -8220,8 +8403,8 @@ async function cmdRecipe(argv) {
8220
8403
  }
8221
8404
 
8222
8405
  // src/plugin/brand.ts
8223
- import { writeFile as writeFile11 } from "fs/promises";
8224
- import { resolve as resolve5 } from "path";
8406
+ import { writeFile as writeFile12 } from "fs/promises";
8407
+ import { resolve as resolve6 } from "path";
8225
8408
  import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
8226
8409
  import { lookKindForGround } from "@vosjs/studio-core";
8227
8410
  var HEX_RE = /#(?:[0-9a-f]{6}|[0-9a-f]{3})\b/gi;
@@ -8425,7 +8608,14 @@ function composeBrand(input) {
8425
8608
  const designBody = design?.fonts.slice(1).find((f) => !/\bmono\b/i.test(f));
8426
8609
  const fontBody = designBody ?? firstFamily2(w.body.fontFamily);
8427
8610
  p.fontBody = designBody ? `named in design.md` : `the body's computed face`;
8428
- const logoUrl = design?.logos.find((u) => /wordmark|logo/i.test(u)) ?? null;
8611
+ const isIcon = (u) => /favicon|apple-touch|icon/i.test(u);
8612
+ const onDark = (u) => /on-dark|white/i.test(u);
8613
+ const logoUrl = design?.logos.find(
8614
+ (u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && !onDark(u)
8615
+ ) ?? null;
8616
+ const logoOnDarkUrl = design?.logos.find(
8617
+ (u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && onDark(u)
8618
+ ) ?? null;
8429
8619
  const iconUrl = w.icons.find((u) => /apple-touch/i.test(u)) ?? (w.icons.length ? w.icons[0] : null);
8430
8620
  p.logoUrl = logoUrl ? `linked from design.md` : `none linked; the icon stands in`;
8431
8621
  p.iconUrl = iconUrl ? `the page's icon link` : `no icon link`;
@@ -8444,6 +8634,7 @@ function composeBrand(input) {
8444
8634
  fontDisplay,
8445
8635
  fontBody,
8446
8636
  logoUrl: logoUrl ?? iconUrl,
8637
+ logoOnDarkUrl,
8447
8638
  iconUrl,
8448
8639
  ogImage: w.ogImage,
8449
8640
  wordmark,
@@ -8473,6 +8664,7 @@ function renderBrandMd(c, claim) {
8473
8664
  `fontDisplay: ${q(k.fontDisplay)}`,
8474
8665
  `fontBody: ${q(k.fontBody)}`,
8475
8666
  `logoUrl: ${q(k.logoUrl)}`,
8667
+ `logoOnDarkUrl: ${q(k.logoOnDarkUrl)}`,
8476
8668
  `iconUrl: ${q(k.iconUrl)}`,
8477
8669
  `ogImage: ${q(k.ogImage)}`,
8478
8670
  `wordmark: ${q(k.wordmark)}`,
@@ -8498,7 +8690,7 @@ function renderBrandMd(c, claim) {
8498
8690
  "",
8499
8691
  "## Use",
8500
8692
  "",
8501
- `The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
8693
+ `The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark (placed beside the wordmark on the cards and the end card; \`logoOnDarkUrl\` stands in on a dark ground). A site that publishes only a favicon or an app icon gets the icon here, tile and all: publish a bare mark and name it in design.md. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
8502
8694
  ];
8503
8695
  if (c.avoid.length) {
8504
8696
  lines.push("", "## Avoid (the site says)", "");
@@ -8529,7 +8721,7 @@ async function cmdBrand(argv) {
8529
8721
  throw new UsageError(`not a URL: ${raw}`);
8530
8722
  }
8531
8723
  const r = createReporter(flags.json === true);
8532
- const out = resolve5(strFlag(flags, "out") ?? "BRAND.md");
8724
+ const out = resolve6(strFlag(flags, "out") ?? "BRAND.md");
8533
8725
  r.log(`reading ${origin}/design.md and /llms.txt\u2026`);
8534
8726
  const [designText, llmsText] = await Promise.all([
8535
8727
  fetchText(`${origin}/design.md`),
@@ -8563,7 +8755,7 @@ async function cmdBrand(argv) {
8563
8755
  composition,
8564
8756
  llms?.claim ?? design?.description ?? null
8565
8757
  );
8566
- await writeFile11(out, md);
8758
+ await writeFile12(out, md);
8567
8759
  const k = composition.kit;
8568
8760
  r.done(
8569
8761
  {
@@ -8581,8 +8773,8 @@ async function cmdBrand(argv) {
8581
8773
  }
8582
8774
 
8583
8775
  // src/plugin/agentBrowser.ts
8584
- import { writeFile as writeFile12 } from "fs/promises";
8585
- import { resolve as resolve6 } from "path";
8776
+ import { writeFile as writeFile13 } from "fs/promises";
8777
+ import { resolve as resolve7 } from "path";
8586
8778
  function parseAgentBrowserLog(text) {
8587
8779
  const records = [];
8588
8780
  const problems = [];
@@ -9052,7 +9244,7 @@ async function cmdActions(argv) {
9052
9244
  );
9053
9245
  }
9054
9246
  const r = createReporter(flags.json === true);
9055
- const out = resolve6(strFlag(flags, "out") ?? "actions.json");
9247
+ const out = resolve7(strFlag(flags, "out") ?? "actions.json");
9056
9248
  const viewportFlag = strFlag(flags, "viewport");
9057
9249
  let viewport;
9058
9250
  if (viewportFlag) {
@@ -9060,8 +9252,8 @@ async function cmdActions(argv) {
9060
9252
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
9061
9253
  viewport = { width: Number(m[1]), height: Number(m[2]) };
9062
9254
  }
9063
- const { readFile: readFile13 } = await import("fs/promises");
9064
- const text = await readFile13(resolve6(input), "utf8");
9255
+ const { readFile: readFile14 } = await import("fs/promises");
9256
+ const text = await readFile14(resolve7(input), "utf8");
9065
9257
  const { records, problems } = parseAgentBrowserLog(text);
9066
9258
  for (const p of problems) r.log(` ${p}`);
9067
9259
  const result = convertAgentBrowser(records, {
@@ -9078,7 +9270,7 @@ async function cmdActions(argv) {
9078
9270
  );
9079
9271
  return EXIT_ERROR;
9080
9272
  }
9081
- await writeFile12(out, `${JSON.stringify(result.actions, null, 2)}
9273
+ await writeFile13(out, `${JSON.stringify(result.actions, null, 2)}
9082
9274
  `);
9083
9275
  r.done(
9084
9276
  { ok: true, out, url: result.actions.url ?? null, ...summary(result) },
@@ -9349,7 +9541,7 @@ lint-gated, so a bad override fails like a bad doc.json):
9349
9541
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
9350
9542
  `;
9351
9543
  async function loadActions(file) {
9352
- const raw = JSON.parse(await readFile12(file, "utf8"));
9544
+ const raw = JSON.parse(await readFile13(file, "utf8"));
9353
9545
  const errors = validateActions(raw);
9354
9546
  if (errors.length)
9355
9547
  throw new UsageError(`invalid actions file:
@@ -9405,16 +9597,16 @@ async function cmdRecord(argv) {
9405
9597
  const url = strFlag(flags, "url") ?? actions.url;
9406
9598
  if (!url)
9407
9599
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
9408
- const outDir = resolve7(strFlag(flags, "out") ?? "take");
9600
+ const outDir = resolve8(strFlag(flags, "out") ?? "take");
9409
9601
  const backdrop = await takeBackdrop(flags, r);
9410
9602
  const maxDurationSeconds = await maxDuration(flags, r);
9411
- if (existsSync12(join15(outDir, "meta.json"))) {
9603
+ if (existsSync13(join16(outDir, "meta.json"))) {
9412
9604
  const { prevDoc, kept } = await prepareReRecord(outDir);
9413
9605
  r.log(
9414
9606
  `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" : "")
9415
9607
  );
9416
9608
  }
9417
- await mkdir8(outDir, { recursive: true });
9609
+ await mkdir9(outDir, { recursive: true });
9418
9610
  const paths = await ensureTakeDir(outDir);
9419
9611
  const browser = await launchBrowser();
9420
9612
  try {
@@ -9455,7 +9647,7 @@ async function cmdRecord(argv) {
9455
9647
  },
9456
9648
  strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
9457
9649
  ${(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${rec.freezePct >= 25 ? " \u26A0 keep motion in frame or trim" : ""}${skippedNote}
9458
- Next: edit ${join15(outDir, "doc.json")} (optional), then: vos render ${outDir}`
9650
+ Next: edit ${join16(outDir, "doc.json")} (optional), then: vos render ${outDir}`
9459
9651
  );
9460
9652
  return strictFail ? EXIT_USAGE : EXIT_OK;
9461
9653
  } finally {
@@ -9479,25 +9671,25 @@ async function cmdCreate2(argv) {
9479
9671
  const url = strFlag(flags, "url") ?? actions.url;
9480
9672
  if (!url)
9481
9673
  throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
9482
- const outDir = resolve7(strFlag(flags, "out") ?? "take");
9674
+ const outDir = resolve8(strFlag(flags, "out") ?? "take");
9483
9675
  const fmtRaw = strFlag(flags, "format") ?? "webm";
9484
9676
  if (fmtRaw !== "webm" && fmtRaw !== "mp4")
9485
9677
  throw new UsageError("--format must be webm or mp4");
9486
9678
  const format = fmtRaw;
9487
- const out = positionals[0] ?? join15(outDir, `out.${format}`);
9679
+ const out = positionals[0] ?? join16(outDir, `out.${format}`);
9488
9680
  const parallel = numFlag(flags, "parallel", 1);
9489
9681
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
9490
9682
  throw new UsageError("--parallel expects an integer between 1 and 16");
9491
9683
  }
9492
9684
  const backdrop = await takeBackdrop(flags, r);
9493
9685
  const maxDurationSeconds = await maxDuration(flags, r);
9494
- if (existsSync12(join15(outDir, "meta.json"))) {
9686
+ if (existsSync13(join16(outDir, "meta.json"))) {
9495
9687
  const { prevDoc, kept } = await prepareReRecord(outDir);
9496
9688
  r.log(
9497
9689
  `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" : "")
9498
9690
  );
9499
9691
  }
9500
- await mkdir8(outDir, { recursive: true });
9692
+ await mkdir9(outDir, { recursive: true });
9501
9693
  const paths = await ensureTakeDir(outDir);
9502
9694
  const browser = await launchBrowser();
9503
9695
  try {
@@ -9578,24 +9770,24 @@ async function cmdPlan(argv) {
9578
9770
  );
9579
9771
  const r = createReporter(flags.json === true);
9580
9772
  if (flags.fresh === true) {
9581
- await rm4(join15(dir, "doc.json"), { force: true });
9773
+ await rm4(join16(dir, "doc.json"), { force: true });
9582
9774
  r.log("note: --fresh discarded the existing doc.json");
9583
9775
  }
9584
9776
  let reuse;
9585
9777
  if (flags.reuse === true) {
9586
- const from = resolve7(strFlag(flags, "from") ?? join15(dir, PREV_DOC_NAME));
9587
- if (!existsSync12(from)) {
9778
+ const from = resolve8(strFlag(flags, "from") ?? join16(dir, PREV_DOC_NAME));
9779
+ if (!existsSync13(from)) {
9588
9780
  throw new UsageError(
9589
9781
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
9590
9782
  );
9591
9783
  }
9592
9784
  reuse = {
9593
9785
  from,
9594
- doc: JSON.parse(await readFile12(from, "utf8"))
9786
+ doc: JSON.parse(await readFile13(from, "utf8"))
9595
9787
  };
9596
9788
  }
9597
9789
  const style = await resolveStyleRef(flags);
9598
- const backdrop = existsSync12(join15(dir, "doc.json")) ? null : await takeBackdrop(flags, r);
9790
+ const backdrop = existsSync13(join16(dir, "doc.json")) ? null : await takeBackdrop(flags, r);
9599
9791
  const s = await planTake(dir, {
9600
9792
  ...style ? { style } : {},
9601
9793
  ...reuse ? { reuse } : {},
@@ -9607,7 +9799,7 @@ async function cmdPlan(argv) {
9607
9799
  ${s.reuse.flagged.join("\n ")}` : "") : "";
9608
9800
  r.done(
9609
9801
  {
9610
- take: resolve7(dir),
9802
+ take: resolve8(dir),
9611
9803
  fresh: s.fresh,
9612
9804
  cursorKept: s.cursorKept,
9613
9805
  zoomAuto: s.zoomAuto,
@@ -9617,7 +9809,7 @@ async function cmdPlan(argv) {
9617
9809
  ...s.styleFrom ? { styleFrom: s.styleFrom, styleFields: s.styleFields } : {},
9618
9810
  ...s.reuse ? { reuse: s.reuse } : {}
9619
9811
  },
9620
- `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join15(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${reuseLines}`
9812
+ `${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join16(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${reuseLines}`
9621
9813
  );
9622
9814
  return EXIT_OK;
9623
9815
  }
@@ -9637,7 +9829,7 @@ async function cmdRender(argv) {
9637
9829
  if (fmtRaw !== "webm" && fmtRaw !== "mp4")
9638
9830
  throw new UsageError("--format must be webm or mp4");
9639
9831
  const format = fmtRaw;
9640
- const out = positionals[1] ?? join15(dir, `out.${format}`);
9832
+ const out = positionals[1] ?? join16(dir, `out.${format}`);
9641
9833
  const parallel = numFlag(flags, "parallel", 1);
9642
9834
  if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
9643
9835
  throw new UsageError("--parallel expects an integer between 1 and 16");
@@ -9822,10 +10014,10 @@ async function cmdDeliver(argv) {
9822
10014
  } else if (posterRef !== void 0 && templateByName(posterRef)) {
9823
10015
  poster = { from: `template ${posterRef}`, config: templateByName(posterRef) };
9824
10016
  } else if (posterRef !== void 0) {
9825
- if (existsSync12(posterRef)) {
10017
+ if (existsSync13(posterRef)) {
9826
10018
  poster = {
9827
- from: resolve7(posterRef),
9828
- config: JSON.parse(await readFile12(posterRef, "utf8"))
10019
+ from: resolve8(posterRef),
10020
+ config: JSON.parse(await readFile13(posterRef, "utf8"))
9829
10021
  };
9830
10022
  } else {
9831
10023
  const origin = platformOrigin({
@@ -9951,14 +10143,14 @@ Store uploads stay manual: hand the human this directory and the manifest.`
9951
10143
  async function resolveStyleRef(flags) {
9952
10144
  const styleRef = strFlag(flags, "style");
9953
10145
  if (!styleRef) return null;
9954
- const file = existsSync12(styleRef) ? resolve7(
10146
+ const file = existsSync13(styleRef) ? resolve8(
9955
10147
  styleRef,
9956
- existsSync12(join15(styleRef, "doc.json")) ? "doc.json" : ""
10148
+ existsSync13(join16(styleRef, "doc.json")) ? "doc.json" : ""
9957
10149
  ) : null;
9958
- if (file && existsSync12(file)) {
10150
+ if (file && existsSync13(file)) {
9959
10151
  return {
9960
10152
  from: file,
9961
- doc: JSON.parse(await readFile12(file, "utf8"))
10153
+ doc: JSON.parse(await readFile13(file, "utf8"))
9962
10154
  };
9963
10155
  }
9964
10156
  const origin = platformOrigin({
@@ -9995,7 +10187,7 @@ async function cmdDigest(argv) {
9995
10187
  const take = await loadTake(dir);
9996
10188
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
9997
10189
  const transcriptPath = strFlag(flags, "transcript");
9998
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile12(transcriptPath, "utf8"))) : null;
10190
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile13(transcriptPath, "utf8"))) : null;
9999
10191
  const style = await resolveStyleRef(flags);
10000
10192
  const noFrames = flags["no-frames"] === true;
10001
10193
  let browser = null;
@@ -10026,7 +10218,7 @@ async function cmdDigest(argv) {
10026
10218
  kinds,
10027
10219
  frames: d.images.full,
10028
10220
  crops: d.images.crop,
10029
- sheet: d.images.sheet ? join15(result.outDir, d.images.sheet) : null,
10221
+ sheet: d.images.sheet ? join16(result.outDir, d.images.sheet) : null,
10030
10222
  scenes: kinds.scene ?? 0,
10031
10223
  sourceDuration: d.take.sourceDuration,
10032
10224
  outputDuration: d.take.outputDuration,
@@ -10050,14 +10242,14 @@ async function cmdOpen(argv) {
10050
10242
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
10051
10243
  const r = createReporter(flags.json === true);
10052
10244
  const take = await loadTake(dir);
10053
- if (!existsSync12(take.paths.recording)) {
10245
+ if (!existsSync13(take.paths.recording)) {
10054
10246
  throw new UsageError(`${dir} has no recording.webm \u2014 re-run record`);
10055
10247
  }
10056
10248
  const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
10057
10249
  /\/+$/,
10058
10250
  ""
10059
10251
  );
10060
- const server = await startTakeServer(resolve7(dir), {});
10252
+ const server = await startTakeServer(resolve8(dir), {});
10061
10253
  const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
10062
10254
  r.event({ event: "open", server: server.base, url });
10063
10255
  r.log(`take served at ${server.base}`);
@@ -10091,11 +10283,11 @@ async function cmdJudge(argv) {
10091
10283
  "vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
10092
10284
  );
10093
10285
  const r = createReporter(flags.json === true);
10094
- const kitFile = target.endsWith("kit.json") ? target : join15(target, "kit.json");
10095
- if (!existsSync12(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
10096
- if (!existsSync12(against)) throw new UsageError(`${against}: no reference manifest`);
10286
+ const kitFile = target.endsWith("kit.json") ? target : join16(target, "kit.json");
10287
+ if (!existsSync13(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
10288
+ if (!existsSync13(against)) throw new UsageError(`${against}: no reference manifest`);
10097
10289
  const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
10098
- const verdicts = JSON.parse(await readFile12(result.verdictFile, "utf8")).verdicts;
10290
+ const verdicts = JSON.parse(await readFile13(result.verdictFile, "utf8")).verdicts;
10099
10291
  const rate = winRate(verdicts);
10100
10292
  const lines = result.sheets.map(
10101
10293
  (s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
@@ -10115,7 +10307,7 @@ async function cmdValidate(argv) {
10115
10307
  const target = positionals[0];
10116
10308
  if (!target) throw new UsageError("vos validate <actions.json|take>");
10117
10309
  const r = createReporter(flags.json === true);
10118
- const kitTarget = target.endsWith("kit.json") ? target : existsSync12(join15(target, "kit.json")) && !isTakeDir(target) ? join15(target, "kit.json") : null;
10310
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync13(join16(target, "kit.json")) && !isTakeDir(target) ? join16(target, "kit.json") : null;
10119
10311
  if (kitTarget) {
10120
10312
  const picture = flags.picture === true;
10121
10313
  const verdict = await validateKit(kitTarget, { picture });
@@ -10147,9 +10339,9 @@ async function cmdValidate(argv) {
10147
10339
  r.done({ valid: true, target }, `${target}: valid actions file`);
10148
10340
  return EXIT_OK;
10149
10341
  }
10150
- if (!isTakeDir(target) && existsSync12(join15(target, "config.json"))) {
10342
+ if (!isTakeDir(target) && existsSync13(join16(target, "config.json"))) {
10151
10343
  const parsed = JSON.parse(
10152
- await readFile12(join15(target, "config.json"), "utf8")
10344
+ await readFile13(join16(target, "config.json"), "utf8")
10153
10345
  );
10154
10346
  const pre = preflightConfig(parsed);
10155
10347
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -10190,7 +10382,7 @@ async function cmdValidate(argv) {
10190
10382
  const take = await loadTake(target);
10191
10383
  const problems = [];
10192
10384
  const warnings = [];
10193
- if (!existsSync12(take.paths.recording))
10385
+ if (!existsSync13(take.paths.recording))
10194
10386
  problems.push("missing recording.webm (re-run record)");
10195
10387
  if (!take.doc) problems.push("missing doc.json (run plan)");
10196
10388
  if (take.doc) {
@@ -10235,7 +10427,7 @@ async function cmdPush3(argv) {
10235
10427
  const r = createReporter(flags.json === true);
10236
10428
  const overrides = multi.override;
10237
10429
  const result = await pushTake(
10238
- resolve7(dir),
10430
+ resolve8(dir),
10239
10431
  {
10240
10432
  key: strFlag(flags, "key"),
10241
10433
  api: strFlag(flags, "api"),
@@ -10262,7 +10454,7 @@ async function cmdPull2(argv) {
10262
10454
  const dir = positionals[0] ?? ".";
10263
10455
  const r = createReporter(flags.json === true);
10264
10456
  const result = await pullTake(
10265
- resolve7(dir),
10457
+ resolve8(dir),
10266
10458
  {
10267
10459
  key: strFlag(flags, "key"),
10268
10460
  api: strFlag(flags, "api"),
@@ -10397,4 +10589,4 @@ export {
10397
10589
  convertAgentBrowser,
10398
10590
  run
10399
10591
  };
10400
- //# sourceMappingURL=chunk-PKHORUJ2.js.map
10592
+ //# sourceMappingURL=chunk-Q5NF5EN2.js.map