@vosjs/cli 0.84.0 → 0.85.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.
@@ -149,8 +149,8 @@ function landedLines(l) {
149
149
 
150
150
  // src/plugin/run.ts
151
151
  import { mkdir as mkdir13, readFile as readFile19, rm as rm7 } from "fs/promises";
152
- import { existsSync as existsSync30 } from "fs";
153
- import { dirname as dirname12, join as join33, resolve as resolve21 } from "path";
152
+ import { existsSync as existsSync31 } from "fs";
153
+ import { dirname as dirname12, join as join33, resolve as resolve22 } from "path";
154
154
  import {
155
155
  END_CARD_FROM as END_CARD_FROM2,
156
156
  docOutputDuration as docOutputDuration5,
@@ -4157,6 +4157,7 @@ import {
4157
4157
  sameMedia,
4158
4158
  spanOutputExtent as spanOutputExtent3
4159
4159
  } from "@vosjs/studio-core";
4160
+ import { rankBeds, trackBySlug } from "@vosjs/shared/music";
4160
4161
 
4161
4162
  // src/plugin/captionFit.ts
4162
4163
  var CAPTION_MAX = 3;
@@ -4289,10 +4290,30 @@ function proposeTransitions(input, kind) {
4289
4290
  }
4290
4291
  return { doc, count, skipped };
4291
4292
  }
4292
- function pickTrack(catalog, ask) {
4293
+ function pickBed(catalog, ask, film) {
4293
4294
  if (!catalog || !ask || off(ask)) return null;
4294
4295
  const want = ask.trim().toLowerCase();
4295
- return catalog.tracks.find((t) => t.slug.toLowerCase() === want) ?? catalog.tracks.find((t) => (t.mood ?? "").toLowerCase() === want) ?? null;
4296
+ const exact = trackBySlug(catalog.tracks, want);
4297
+ if (exact) return { track: exact, via: "slug", ranked: null, runnerUp: null };
4298
+ if (film) {
4299
+ const oneWord = !/\s/.test(want);
4300
+ const ranked = rankBeds(catalog.tracks, {
4301
+ ...film,
4302
+ ...oneWord ? { mood: want } : { words: want }
4303
+ }).filter((r) => r.score !== null);
4304
+ const named = oneWord ? catalog.tracks.some(
4305
+ (t) => (t.mood ?? "").toLowerCase() === want || (t.tags ?? []).some((x) => x.toLowerCase() === want)
4306
+ ) : ranked.some((r) => (r.scores?.fit ?? 0) > 0);
4307
+ if (!named || !ranked.length) return null;
4308
+ return {
4309
+ track: ranked[0].track,
4310
+ via: "ranked",
4311
+ ranked: ranked[0],
4312
+ runnerUp: ranked[1] ?? null
4313
+ };
4314
+ }
4315
+ const first = catalog.tracks.find((t) => (t.mood ?? "").toLowerCase() === want) ?? null;
4316
+ return first ? { track: first, via: "first", ranked: null, runnerUp: null } : null;
4296
4317
  }
4297
4318
  function clickTimes(doc, range2) {
4298
4319
  const rated = ratedSegments4(doc);
@@ -4533,26 +4554,32 @@ function proposeMotion(input, opts) {
4533
4554
  const clips = (doc.audio ?? []).filter(
4534
4555
  (a) => a.id !== BED_ID && !a.id.startsWith(CLICK_ID_PREFIX)
4535
4556
  );
4536
- const track = pickTrack(catalog, launch.music);
4537
- if (track) {
4557
+ const picked = pickBed(catalog, launch.music, opts.film);
4558
+ if (picked) {
4559
+ const { track } = picked;
4538
4560
  const hasMic = !!doc.source.micKey;
4539
4561
  const fadeOut = Math.min(2.5, length * 0.15);
4562
+ const place2 = picked.ranked?.placement ?? null;
4563
+ const start = place2 ? place2.in : 0;
4564
+ const covers = track.duration - start >= length;
4540
4565
  clips.push({
4541
4566
  id: BED_ID,
4542
4567
  key: track.url,
4543
4568
  name: track.title,
4544
4569
  start: 0,
4545
- in: 0,
4546
- out: Math.min(track.duration, length),
4570
+ in: start,
4571
+ out: place2 ? place2.out : Math.min(track.duration, length),
4547
4572
  duration: track.duration,
4548
4573
  gain: hasMic ? 0.35 : 0.5,
4549
- fadeIn: 0.6,
4574
+ fadeIn: start > 0 ? 0.8 : 0.6,
4550
4575
  fadeOut,
4551
- loop: track.duration < length,
4552
- loopLen: track.duration < length ? Math.round(length * 1e3) / 1e3 : void 0,
4576
+ loop: place2 ? place2.loop : !covers,
4577
+ loopLen: place2 ? place2.loopLen : !covers ? Math.round(length * 1e3) / 1e3 : void 0,
4553
4578
  duck: hasMic
4554
4579
  });
4555
- notes.push(`bed ${track.slug}`);
4580
+ notes.push(
4581
+ picked.via === "ranked" && picked.ranked ? `bed ${track.slug} (${picked.ranked.reasons.slice(0, 2).join(", ") || "ranked"}${picked.runnerUp ? `; runner-up ${picked.runnerUp.slug}` : ""})` : `bed ${track.slug}`
4582
+ );
4556
4583
  } else if (launch.music && !off(launch.music)) {
4557
4584
  skipped.push(
4558
4585
  `music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`
@@ -6039,14 +6066,14 @@ function chromeTree(rows, pid) {
6039
6066
  }
6040
6067
  function readChromeTree(pid) {
6041
6068
  if (process.platform === "win32") return Promise.resolve(null);
6042
- return new Promise((resolve22) => {
6069
+ return new Promise((resolve23) => {
6043
6070
  execFile2(
6044
6071
  "ps",
6045
6072
  ["-A", "-o", "pid=", "-o", "ppid=", "-o", "command="],
6046
6073
  { maxBuffer: 16 * 1024 * 1024, timeout: 5e3 },
6047
6074
  (err, stdout) => {
6048
- if (err) resolve22(null);
6049
- else resolve22(chromeTree(parseProcessTable(stdout), pid));
6075
+ if (err) resolve23(null);
6076
+ else resolve23(chromeTree(parseProcessTable(stdout), pid));
6050
6077
  }
6051
6078
  );
6052
6079
  });
@@ -6148,7 +6175,7 @@ function enterPressed() {
6148
6175
  } };
6149
6176
  const rl = createInterface({ input: process.stdin });
6150
6177
  const pressed = new Promise(
6151
- (resolve22) => rl.once("line", () => resolve22())
6178
+ (resolve23) => rl.once("line", () => resolve23())
6152
6179
  );
6153
6180
  return { pressed, close: () => rl.close() };
6154
6181
  }
@@ -6321,14 +6348,14 @@ async function openSessionWindow(opts) {
6321
6348
  const child = spawn(exe, args, { stdio: "ignore" });
6322
6349
  let failed = null;
6323
6350
  let code = null;
6324
- const exited = new Promise((resolve22) => {
6351
+ const exited = new Promise((resolve23) => {
6325
6352
  child.once("exit", (c) => {
6326
6353
  code = c;
6327
- resolve22();
6354
+ resolve23();
6328
6355
  });
6329
6356
  child.once("error", (e) => {
6330
6357
  failed = e;
6331
- resolve22();
6358
+ resolve23();
6332
6359
  });
6333
6360
  });
6334
6361
  try {
@@ -6776,12 +6803,13 @@ async function encodeRecording(browser, takeDir, onProgress) {
6776
6803
  // src/plugin/plan.ts
6777
6804
  import { existsSync as existsSync11 } from "fs";
6778
6805
  import { readFile as readFile9 } from "fs/promises";
6779
- import { join as join11 } from "path";
6806
+ import { basename, join as join11 } from "path";
6780
6807
  import {
6781
6808
  DEFAULT_FRAME_STYLE,
6782
6809
  STYLE_FIELDS,
6783
6810
  copyLayout,
6784
6811
  copyStyle,
6812
+ filmSummaryOf,
6785
6813
  isRejected,
6786
6814
  isStageClip,
6787
6815
  layoutOf,
@@ -7382,7 +7410,10 @@ async function planTake(dir, opts = {}) {
7382
7410
  withPageSound(doc, meta.media);
7383
7411
  let motion;
7384
7412
  if (opts.motion && (fresh || opts.motion.again)) {
7385
- const proposed = proposeMotion(doc, opts.motion);
7413
+ const proposed = proposeMotion(doc, {
7414
+ ...opts.motion,
7415
+ film: filmSummaryOf(doc, { seed: basename(join11(dir)) })
7416
+ });
7386
7417
  doc = proposed.doc;
7387
7418
  motion = {
7388
7419
  notes: proposed.notes,
@@ -7415,14 +7446,14 @@ import { createHash } from "crypto";
7415
7446
  import { existsSync as existsSync13, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
7416
7447
  import { readFile as readFile11 } from "fs/promises";
7417
7448
  import { createInterface as createInterface2 } from "readline/promises";
7418
- import { basename as basename2, join as join14 } from "path";
7449
+ import { basename as basename3, join as join14 } from "path";
7419
7450
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7420
7451
 
7421
7452
  // src/plugin/takeScript.ts
7422
7453
  import { existsSync as existsSync12 } from "fs";
7423
7454
  import { readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
7424
7455
  import { readFileSync as readFileSync2 } from "fs";
7425
- import { basename, join as join12 } from "path";
7456
+ import { basename as basename2, join as join12 } from "path";
7426
7457
  function scriptFromText(text) {
7427
7458
  try {
7428
7459
  const v = JSON.parse(text);
@@ -7468,7 +7499,7 @@ function publicPeers(peers) {
7468
7499
  if (!p || typeof p !== "object") return p;
7469
7500
  const peer = p;
7470
7501
  if (typeof peer.storageState !== "string") return peer;
7471
- return { ...peer, storageState: basename(peer.storageState) };
7502
+ return { ...peer, storageState: basename2(peer.storageState) };
7472
7503
  });
7473
7504
  }
7474
7505
  function embedScript(doc, dir) {
@@ -8098,7 +8129,7 @@ async function pushTake(dir, flags, r) {
8098
8129
  const lowered = lowerToComposition3(docForPush);
8099
8130
  const config = { ...lowered.config, data: lowered.data };
8100
8131
  if (!state) {
8101
- const title = flags.title ?? `Take ${basename2(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8132
+ const title = flags.title ?? `Take ${basename3(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8102
8133
  const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
8103
8134
  const created = await api(ctx, "/vos", {
8104
8135
  method: "POST",
@@ -8290,7 +8321,7 @@ async function pullTake(dir, flags, r) {
8290
8321
  const doc = hostedDoc;
8291
8322
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
8292
8323
  if (existsSync13(localRecording)) {
8293
- doc.source.videoKey = basename2(localRecording);
8324
+ doc.source.videoKey = basename3(localRecording);
8294
8325
  }
8295
8326
  await writeJson(join14(dir, "doc.json"), doc, true);
8296
8327
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
@@ -8390,7 +8421,7 @@ async function pushTakeClaimable(dir, flags, r) {
8390
8421
  }
8391
8422
  const lowered = lowerToComposition3(docForPush);
8392
8423
  const config = { ...lowered.config, data: lowered.data };
8393
- const title = (flags.title ?? (state?.title ? `${state.title} remix` : basename2(dir))).slice(0, 200);
8424
+ const title = (flags.title ?? (state?.title ? `${state.title} remix` : basename3(dir))).slice(0, 200);
8394
8425
  const body = { title, config, doc: docForPush };
8395
8426
  if (state?.vosId) body.remixOfId = state.vosId;
8396
8427
  if (flags.prompt) body.prompt = flags.prompt;
@@ -8418,7 +8449,7 @@ async function pushTakeClaimable(dir, flags, r) {
8418
8449
  import { existsSync as existsSync15 } from "fs";
8419
8450
  import { mkdir as mkdir7, readFile as readFile12 } from "fs/promises";
8420
8451
  import { createInterface as createInterface3 } from "readline/promises";
8421
- import { basename as basename4, dirname as dirname2, join as join15 } from "path";
8452
+ import { basename as basename5, dirname as dirname2, join as join15 } from "path";
8422
8453
  import {
8423
8454
  CURRENT_CONFIG_VERSION,
8424
8455
  migrateConfig,
@@ -8427,7 +8458,7 @@ import {
8427
8458
  import { lowerProgramDoc, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
8428
8459
 
8429
8460
  // src/plugin/programFiles.ts
8430
- import { basename as basename3, extname as extname2, relative as relative2 } from "path";
8461
+ import { basename as basename4, extname as extname2, relative as relative2 } from "path";
8431
8462
  import { existsSync as existsSync14, realpathSync as realpathSync2 } from "fs";
8432
8463
  function isLocalName(value) {
8433
8464
  if (/^(https?:|data:|blob:|asset:|media:)/.test(value)) return false;
@@ -8438,7 +8469,7 @@ function isLocalName(value) {
8438
8469
  }
8439
8470
  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;
8440
8471
  function manifestName(file, taken) {
8441
- const stem = basename3(file, extname2(file)).replace(/[^A-Za-z0-9_]/g, "_").replace(/^(?=[0-9])/, "_") || "file";
8472
+ const stem = basename4(file, extname2(file)).replace(/[^A-Za-z0-9_]/g, "_").replace(/^(?=[0-9])/, "_") || "file";
8442
8473
  let name = stem;
8443
8474
  for (let n = 2; taken.has(name); n++) name = `${stem}_${n}`;
8444
8475
  taken.add(name);
@@ -8857,7 +8888,7 @@ async function cmdPushProgram(argv) {
8857
8888
  "--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
8858
8889
  );
8859
8890
  }
8860
- const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
8891
+ const fallback = state?.title ? `${state.title} remix` : basename5(source).replace(/\.json$/i, "") || "vos program";
8861
8892
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8862
8893
  const files2 = await collectFiles(config, programDoc, dir, r);
8863
8894
  let uploadToken;
@@ -9031,7 +9062,7 @@ ${landedLines(landed2)}` : "")
9031
9062
  return EXIT_OK;
9032
9063
  }
9033
9064
  const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
9034
- const fallbackTitle = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos remix";
9065
+ const fallbackTitle = state?.title ? `${state.title} remix` : basename5(source).replace(/\.json$/i, "") || "vos remix";
9035
9066
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
9036
9067
  const pushFolder = folderRef ? await resolvePushFolder(origin, key, folderRef) : void 0;
9037
9068
  const folderId = pushFolder?.id;
@@ -9357,7 +9388,7 @@ async function writeProgramDoc(dir, hosted) {
9357
9388
  // src/plugin/asset.ts
9358
9389
  import { createHash as createHash2 } from "crypto";
9359
9390
  import { readFileSync as readFileSync4 } from "fs";
9360
- import { basename as basename5 } from "path";
9391
+ import { basename as basename6 } from "path";
9361
9392
 
9362
9393
  // src/plugin/assetWords.ts
9363
9394
  function hostedFile(raw) {
@@ -9622,7 +9653,7 @@ async function cmdPush(argv) {
9622
9653
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9623
9654
  const uploaded = [];
9624
9655
  for (const file of positionals) {
9625
- const name = basename5(file);
9656
+ const name = basename6(file);
9626
9657
  const body = readFileSync4(file);
9627
9658
  const media = resolveMediaType({
9628
9659
  head: body.subarray(0, MEDIA_HEAD_BYTES),
@@ -10140,7 +10171,7 @@ function trackingCwd(vosId) {
10140
10171
 
10141
10172
  // src/plugin/buildProgram.ts
10142
10173
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
10143
- import { basename as basename6, dirname as dirname3, join as join17, resolve as resolve8 } from "path";
10174
+ import { basename as basename7, dirname as dirname3, join as join17, resolve as resolve8 } from "path";
10144
10175
  import { pathToFileURL as pathToFileURL2 } from "url";
10145
10176
  import { parse } from "acorn";
10146
10177
  import { fullAncestor } from "acorn-walk";
@@ -10348,7 +10379,7 @@ async function cmdBuild(argv) {
10348
10379
  throw new UsageError(
10349
10380
  `${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
10350
10381
  );
10351
- const built = buildProgram(program, basename6(file), text);
10382
+ const built = buildProgram(program, basename7(file), text);
10352
10383
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
10353
10384
  if (built.problems.length) {
10354
10385
  r.done(
@@ -10583,7 +10614,7 @@ import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
10583
10614
  import { existsSync as existsSync18, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
10584
10615
  import { createServer as createServer2 } from "http";
10585
10616
  import { createRequire } from "module";
10586
- import { basename as basename7, extname as extname3, join as join19 } from "path";
10617
+ import { basename as basename8, extname as extname3, join as join19 } from "path";
10587
10618
  function themeColors(sources) {
10588
10619
  const out = {};
10589
10620
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -10863,7 +10894,7 @@ async function readRemotion(dir, log) {
10863
10894
  const sources = sourceTexts(dir);
10864
10895
  const publicDir = join19(dir, "public");
10865
10896
  const media = tree.audio.map((a) => {
10866
- const name = basename7(a.src.split("?")[0]);
10897
+ const name = basename8(a.src.split("?")[0]);
10867
10898
  const local = existsSync18(join19(publicDir, name)) ? `public/${name}` : a.src;
10868
10899
  return {
10869
10900
  kind: "audio",
@@ -11366,7 +11397,7 @@ async function readInventory(source, opts) {
11366
11397
  // src/plugin/portScaffold.ts
11367
11398
  import { spawnSync as spawnSync3 } from "child_process";
11368
11399
  import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11369
- import { basename as basename8, dirname as dirname6, join as join21, resolve as resolve11 } from "path";
11400
+ import { basename as basename9, dirname as dirname6, join as join21, resolve as resolve11 } from "path";
11370
11401
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
11371
11402
 
11372
11403
  // src/plugin/painterLib.ts
@@ -11776,7 +11807,7 @@ function planScaffold(inv, opts) {
11776
11807
  const copies = [];
11777
11808
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
11778
11809
  const from = resolve11(opts.sourceDir, m.src);
11779
- const name = basename8(from);
11810
+ const name = basename9(from);
11780
11811
  if (!existsSync20(from)) {
11781
11812
  report.push(
11782
11813
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
@@ -11929,7 +11960,7 @@ ${sceneTodos}${springHint}
11929
11960
  }
11930
11961
  function reportText(inv, r) {
11931
11962
  const lines = [
11932
- `# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
11963
+ `# Port report: ${inv.source === "." && inv.root ? basename9(inv.root) : inv.source}`,
11933
11964
  "",
11934
11965
  `${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
11935
11966
  "",
@@ -12093,11 +12124,11 @@ function scaffoldSourceDir(inv) {
12093
12124
  // src/plugin/recipe.ts
12094
12125
  import { createHash as createHash3 } from "crypto";
12095
12126
  import { readFileSync as readFileSync9 } from "fs";
12096
- import { basename as basename9 } from "path";
12127
+ import { basename as basename10 } from "path";
12097
12128
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
12098
12129
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
12099
12130
  function recipeFilename(file) {
12100
- const name = basename9(file);
12131
+ const name = basename10(file);
12101
12132
  if (!/\.md$/i.test(name)) {
12102
12133
  throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
12103
12134
  }
@@ -13203,7 +13234,7 @@ async function cmdActionsScript(input, flags) {
13203
13234
  // src/plugin/ingest.ts
13204
13235
  import { existsSync as existsSync22 } from "fs";
13205
13236
  import { copyFile, mkdir as mkdir8, readFile as readFile13, stat as stat2 } from "fs/promises";
13206
- import { basename as basename10, join as join23, resolve as resolve15 } from "path";
13237
+ import { basename as basename11, join as join23, resolve as resolve15 } from "path";
13207
13238
 
13208
13239
  // src/plugin/traceInflate.ts
13209
13240
  import { inflateRawSync } from "zlib";
@@ -13330,7 +13361,7 @@ async function copyRecording(src, dest, ext2) {
13330
13361
  }
13331
13362
  }
13332
13363
  async function readTraceFile(path, offsetMs) {
13333
- const kind = traceKindOf(basename10(path));
13364
+ const kind = traceKindOf(basename11(path));
13334
13365
  if (!kind) {
13335
13366
  throw new UsageError(
13336
13367
  `${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
@@ -13351,7 +13382,7 @@ async function readTraceFile(path, offsetMs) {
13351
13382
  return cursorFromPlaywrightTrace(text2, { offsetMs });
13352
13383
  }
13353
13384
  const text = await readFile13(path, "utf8");
13354
- return cursorFromTrace(basename10(path), text, { offsetMs });
13385
+ return cursorFromTrace(basename11(path), text, { offsetMs });
13355
13386
  }
13356
13387
  function parseViewport(v) {
13357
13388
  if (!v) return null;
@@ -13382,7 +13413,7 @@ async function cmdIngest(argv) {
13382
13413
  "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
13383
13414
  );
13384
13415
  const backdrop = footage ? null : await takeBackdrop(flags, r);
13385
- r.log(`probing ${basename10(videoPath)}\u2026`);
13416
+ r.log(`probing ${basename11(videoPath)}\u2026`);
13386
13417
  const probed = await probeVideoFile(videoPath);
13387
13418
  if (!(probed.durationMs > 0)) {
13388
13419
  throw new UsageError(
@@ -13431,13 +13462,13 @@ async function cmdIngest(argv) {
13431
13462
  const clicks = events.filter((e) => e.type === "down").length;
13432
13463
  const seconds = (probed.durationMs / 1e3).toFixed(1);
13433
13464
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
13434
- const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename10(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
13465
+ const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename11(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
13435
13466
  const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
13436
13467
  const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
13437
13468
  r.done(
13438
13469
  {
13439
13470
  out: outDir,
13440
- recording: basename10(recording),
13471
+ recording: basename11(recording),
13441
13472
  durationMs: probed.durationMs,
13442
13473
  width: probed.width,
13443
13474
  height: probed.height,
@@ -13446,7 +13477,7 @@ async function cmdIngest(argv) {
13446
13477
  hasAudio: probed.hasAudio,
13447
13478
  remuxed,
13448
13479
  cursor: trace ? {
13449
- file: basename10(cursorPath),
13480
+ file: basename11(cursorPath),
13450
13481
  kind: trace.kind,
13451
13482
  events: events.length,
13452
13483
  clicks,
@@ -13458,7 +13489,7 @@ async function cmdIngest(argv) {
13458
13489
  backdrop: plan.backdrop ?? null,
13459
13490
  as: footage ? "footage" : "take"
13460
13491
  },
13461
- `Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
13492
+ `Ingested ${basename11(videoPath)} \u2192 ${outDir}/${basename11(recording)} (${facts})
13462
13493
  ${opened}
13463
13494
  ${cursorLine}
13464
13495
  ${next}`
@@ -13470,7 +13501,7 @@ async function cmdIngest(argv) {
13470
13501
  import { createRequire as createRequire2 } from "module";
13471
13502
  import { existsSync as existsSync23 } from "fs";
13472
13503
  import { mkdir as mkdir9, readFile as readFile14, stat as stat3, writeFile as writeFile7 } from "fs/promises";
13473
- import { basename as basename11, dirname as dirname8, extname as extname4, join as join24, resolve as resolve16 } from "path";
13504
+ import { basename as basename12, dirname as dirname8, extname as extname4, join as join24, resolve as resolve16 } from "path";
13474
13505
  import { ALL_FORMATS as ALL_FORMATS2, FilePathSource as FilePathSource2, Input as Input2 } from "mediabunny";
13475
13506
 
13476
13507
  // src/plugin/videoChecks.ts
@@ -13992,7 +14023,7 @@ async function checkVideo(browser, file, outDir) {
13992
14023
  void page.evaluate(decodeInPage, {
13993
14024
  mbCode: await mediabunnyBundle(),
13994
14025
  meterSrc: String(createLoudnessMeter),
13995
- videoUrl: `/${encodeURIComponent(basename11(file))}`,
14026
+ videoUrl: `/${encodeURIComponent(basename12(file))}`,
13996
14027
  sampleFps,
13997
14028
  thumbW: side(facts.width),
13998
14029
  thumbH: side(facts.height),
@@ -14100,7 +14131,7 @@ async function cmdCheckVideo(argv) {
14100
14131
  const r = createReporter(flags.json === true);
14101
14132
  const strict = flags.strict === true;
14102
14133
  const outDir = resolve16(
14103
- strFlag(flags, "out") ?? join24(dirname8(file), `${basename11(file, extname4(file))}-check`)
14134
+ strFlag(flags, "out") ?? join24(dirname8(file), `${basename12(file, extname4(file))}-check`)
14104
14135
  );
14105
14136
  const browser = await launchBrowser();
14106
14137
  try {
@@ -14118,7 +14149,7 @@ async function cmdCheckVideo(argv) {
14118
14149
  import { existsSync as existsSync26, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
14119
14150
  import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile17, rm as rm4 } from "fs/promises";
14120
14151
  import {
14121
- basename as basename13,
14152
+ basename as basename14,
14122
14153
  dirname as dirname9,
14123
14154
  isAbsolute as isAbsolute4,
14124
14155
  join as join27,
@@ -14149,13 +14180,15 @@ import {
14149
14180
  writeFile as writeFile8
14150
14181
  } from "fs/promises";
14151
14182
  import { tmpdir as tmpdir2 } from "os";
14152
- import { basename as basename12, isAbsolute as isAbsolute3, join as join25, relative as relative4, resolve as resolve17, sep as sep3 } from "path";
14183
+ import { basename as basename13, isAbsolute as isAbsolute3, join as join25, relative as relative4, resolve as resolve17, sep as sep3 } from "path";
14184
+ import { rankBeds as rankBeds2 } from "@vosjs/shared/music";
14153
14185
  import {
14154
14186
  NORMALIZE_MAX_LUFS as NORMALIZE_MAX_LUFS2,
14155
14187
  NORMALIZE_MIN_LUFS as NORMALIZE_MIN_LUFS2,
14156
14188
  clipGain as clipGain2,
14157
14189
  docMediaList,
14158
14190
  docOutputDuration as docOutputDuration3,
14191
+ filmSummaryOf as filmSummaryOf2,
14159
14192
  findStep as findStep2,
14160
14193
  ratedSegments as ratedSegments7,
14161
14194
  resolveAnchor as resolveAnchor4
@@ -14464,7 +14497,7 @@ async function measureInBrowser(path) {
14464
14497
  const bytes = await readFile15(path);
14465
14498
  if (bytes.length > BROWSER_DECODE_MAX_BYTES)
14466
14499
  throw new Error(
14467
- `${basename12(path)} is ${Math.round(bytes.length / 1048576)} MB, past the ${BROWSER_DECODE_MAX_BYTES / 1048576} MB the browser decode takes whole`
14500
+ `${basename13(path)} is ${Math.round(bytes.length / 1048576)} MB, past the ${BROWSER_DECODE_MAX_BYTES / 1048576} MB the browser decode takes whole`
14468
14501
  );
14469
14502
  const browser = await launchBrowser();
14470
14503
  try {
@@ -14568,12 +14601,12 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14568
14601
  if (existsSync24(local)) {
14569
14602
  const rel = relative4(resolve17(dir), local);
14570
14603
  const inside = !!rel && !rel.startsWith(`..${sep3}`) && rel !== ".." && !isAbsolute3(rel);
14571
- const key = inside ? rel.split(sep3).join("/") : `music/${basename12(local)}`;
14604
+ const key = inside ? rel.split(sep3).join("/") : `music/${basename13(local)}`;
14572
14605
  if (!inside && !dryRun) {
14573
14606
  await mkdir10(join25(dir, "music"), { recursive: true });
14574
14607
  await copyFile2(local, join25(dir, key));
14575
14608
  }
14576
- return { path: local, key, name: basename12(local) };
14609
+ return { path: local, key, name: basename13(local) };
14577
14610
  }
14578
14611
  const origin = platformOrigin({
14579
14612
  origin: strFlag(flags, "origin"),
@@ -14588,7 +14621,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14588
14621
  const res = await fetch(track.url);
14589
14622
  if (!res.ok) throw new Error(`GET ${track.url} answered ${res.status}`);
14590
14623
  const tmp = await mkdtemp(join25(tmpdir2(), "vos-score-"));
14591
- const path = join25(tmp, basename12(new URL(track.url).pathname) || "track");
14624
+ const path = join25(tmp, basename13(new URL(track.url).pathname) || "track");
14592
14625
  await writeFile8(path, new Uint8Array(await res.arrayBuffer()));
14593
14626
  return {
14594
14627
  path,
@@ -14598,7 +14631,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14598
14631
  };
14599
14632
  }
14600
14633
  var BOOLEAN_FLAGS12 = /* @__PURE__ */ new Set(["json", "help", "dry-run"]);
14601
- var USAGE2 = "vos score <take> --track <slug|file> [--at <step id|output seconds>] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]";
14634
+ var USAGE2 = 'vos score <take> --track <slug|file|best> [--at <step id|output seconds>] [--mood <slug|tag>] [--words "\u2026"] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]';
14602
14635
  async function cmdScore(argv) {
14603
14636
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14604
14637
  const dir = positionals[0];
@@ -14625,8 +14658,39 @@ async function cmdScore(argv) {
14625
14658
  throw new UsageError(`${dir} has no doc.json; run vos plan first`);
14626
14659
  const doc = take.doc;
14627
14660
  const atRaw = strFlag(flags, "at");
14628
- const at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
14629
- const track = await resolveTrack(dir, trackRef, flags, dryRun);
14661
+ let at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
14662
+ let chosen = trackRef;
14663
+ if (trackRef === "best") {
14664
+ const origin = platformOrigin({
14665
+ origin: strFlag(flags, "origin"),
14666
+ api: strFlag(flags, "api")
14667
+ });
14668
+ const catalog = await fetchMusicCatalog(origin);
14669
+ const film2 = filmSummaryOf2(doc, {
14670
+ seed: readSyncState(dir)?.vosId ?? basename13(resolve17(dir)),
14671
+ ...at2 ? { payoff: at2.at } : {},
14672
+ mood: strFlag(flags, "mood"),
14673
+ words: strFlag(flags, "words")
14674
+ });
14675
+ const ranked = rankBeds2(catalog.tracks, film2).filter(
14676
+ (x) => x.score !== null
14677
+ );
14678
+ const best = ranked[0];
14679
+ if (!best)
14680
+ throw new UsageError(
14681
+ `no bed in the library fits this ${film2.duration.toFixed(1)}s film (every track refused); compose a score instead, or name a track with --track <slug>`
14682
+ );
14683
+ chosen = best.slug;
14684
+ if (!at2 && film2.payoff !== void 0)
14685
+ at2 = {
14686
+ at: film2.payoff,
14687
+ via: `the film's payoff (${film2.payoff.toFixed(2)}s)`
14688
+ };
14689
+ r.log(
14690
+ `picked ${best.slug} (${best.reasons.join("; ") || "ranked first"})${ranked[1] ? `; runner-up ${ranked[1].slug}` : ""}`
14691
+ );
14692
+ }
14693
+ const track = await resolveTrack(dir, chosen, flags, dryRun);
14630
14694
  let measured;
14631
14695
  try {
14632
14696
  r.log(`measuring ${track.name}\u2026`);
@@ -16220,7 +16284,7 @@ function joinDocs(parts, opts = {}) {
16220
16284
  {
16221
16285
  id,
16222
16286
  key: d.source.videoKey,
16223
- name: basename13(part.file.path),
16287
+ name: basename14(part.file.path),
16224
16288
  start: round33(at2.start),
16225
16289
  in: round33(seg.in),
16226
16290
  out: round33(seg.out),
@@ -16465,7 +16529,7 @@ async function cmdJoin(argv) {
16465
16529
  const remuxed = await copyRecording(c.from, to, c.remux);
16466
16530
  if (!remuxed && c.remux !== "mov")
16467
16531
  plan.notes.push(
16468
- `could not read ${basename13(c.from)}'s audio in Node, so it was copied as it is; its length comes from the container`
16532
+ `could not read ${basename14(c.from)}'s audio in Node, so it was copied as it is; its length comes from the container`
16469
16533
  );
16470
16534
  } else await copyFile3(c.from, to);
16471
16535
  }
@@ -16839,7 +16903,7 @@ async function cmdTakeInfo(argv) {
16839
16903
  // src/plugin/audio.ts
16840
16904
  import { mkdtemp as mkdtemp2, rm as rm5, writeFile as writeFile9 } from "fs/promises";
16841
16905
  import { tmpdir as tmpdir3 } from "os";
16842
- import { basename as basename14, join as join29 } from "path";
16906
+ import { basename as basename15, join as join29 } from "path";
16843
16907
  import { clipGain as clipGain3 } from "@vosjs/studio-core";
16844
16908
  var USAGE4 = "vos audio measure <take> [--json]";
16845
16909
  async function fileFor(dir, key, fetchImpl) {
@@ -16852,7 +16916,7 @@ async function fileFor(dir, key, fetchImpl) {
16852
16916
  const res = await fetchImpl(key);
16853
16917
  if (!res.ok) return { skipped: `GET ${key} answered ${res.status}` };
16854
16918
  const tmp = await mkdtemp2(join29(tmpdir3(), "vos-audio-"));
16855
- const path = join29(tmp, basename14(new URL(key).pathname) || "audio");
16919
+ const path = join29(tmp, basename15(new URL(key).pathname) || "audio");
16856
16920
  await writeFile9(path, new Uint8Array(await res.arrayBuffer()));
16857
16921
  return { path, cleanup: () => rm5(tmp, { recursive: true, force: true }) };
16858
16922
  }
@@ -16932,11 +16996,238 @@ async function cmdAudio(argv) {
16932
16996
  return failed.length ? EXIT_ERROR : EXIT_OK;
16933
16997
  }
16934
16998
 
16999
+ // src/plugin/musicCmd.ts
17000
+ import { existsSync as existsSync27, readFileSync as readFileSync10, statSync as statSync8 } from "fs";
17001
+ import { basename as basename16, resolve as resolve20 } from "path";
17002
+ import { rankBeds as rankBeds3, trackBySlug as trackBySlug2 } from "@vosjs/shared/music";
17003
+ import { filmSummaryOf as filmSummaryOf3, musicSlugsOfDoc } from "@vosjs/studio-core";
17004
+ var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
17005
+ "json",
17006
+ "help",
17007
+ "loops",
17008
+ "voice",
17009
+ "no-voice",
17010
+ "fade-in"
17011
+ ]);
17012
+ var MULTI_FLAGS3 = /* @__PURE__ */ new Set(["like"]);
17013
+ var USAGE5 = 'vos music ls [--mood <slug>] [--vocals none|chops|lead] [--loops] [--bpm <lo>-<hi>] [--min-length <s>] [--q <words>] [--json]\nvos music pick <take|config.json> [--at <step id|seconds>] [--mood <slug|tag>] [--words "\u2026"] [--voice|--no-voice] [--like <vos id|slug>]... [--top <n>] [--json]';
17014
+ var mmss = (s) => {
17015
+ const whole = Math.round(s);
17016
+ return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
17017
+ };
17018
+ var VOCAL_WORDS = {
17019
+ none: "no vocals",
17020
+ chops: "vocal chops",
17021
+ lead: "sung lead"
17022
+ };
17023
+ function trackLine(t) {
17024
+ const bpm = t.bpm === null || t.bpm === void 0 ? "\u2014" : `${t.bpm} bpm${t.beatConfidence === "low" ? "?" : t.beatConfidence === "confirmed" ? "" : ""}`;
17025
+ return [
17026
+ t.slug.padEnd(26),
17027
+ (t.mood ?? "").padEnd(10),
17028
+ mmss(t.duration).padStart(5),
17029
+ bpm.padStart(10),
17030
+ VOCAL_WORDS[t.vocals ?? "none"].padEnd(11),
17031
+ t.loops ? "loops" : " ",
17032
+ t.description ? JSON.stringify(t.description) : ""
17033
+ ].join(" ");
17034
+ }
17035
+ function bpmRange(raw) {
17036
+ if (!raw) return null;
17037
+ const m = /^(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)$/.exec(raw.trim());
17038
+ if (m) return [Number(m[1]), Number(m[2])];
17039
+ const one = Number(raw);
17040
+ if (Number.isFinite(one)) return [one, Infinity];
17041
+ throw new UsageError(`--bpm expects <lo>-<hi>, got "${raw}"`);
17042
+ }
17043
+ function filterTracks(tracks, f) {
17044
+ const q = f.q?.toLowerCase().trim();
17045
+ const words2 = q ? q.split(/\s+/).filter(Boolean) : [];
17046
+ return tracks.filter((t) => {
17047
+ if (t.removedAt) return false;
17048
+ if (f.mood && (t.mood ?? "").toLowerCase() !== f.mood.toLowerCase()) {
17049
+ if (!(t.tags ?? []).some((x) => x.toLowerCase() === f.mood.toLowerCase()))
17050
+ return false;
17051
+ }
17052
+ if (f.vocals && (t.vocals ?? "none") !== f.vocals) return false;
17053
+ if (f.loops && !t.loops) return false;
17054
+ if (f.bpm) {
17055
+ if (t.bpm === null || t.bpm === void 0) return false;
17056
+ if (t.bpm < f.bpm[0] || t.bpm > f.bpm[1]) return false;
17057
+ }
17058
+ if (f.minLength !== void 0 && t.duration < f.minLength && !t.loops)
17059
+ return false;
17060
+ if (words2.length) {
17061
+ const hay = [
17062
+ t.slug,
17063
+ t.title,
17064
+ t.artist ?? "",
17065
+ t.mood ?? "",
17066
+ ...t.tags ?? [],
17067
+ t.description ?? ""
17068
+ ].join(" ").toLowerCase();
17069
+ if (!words2.every((w) => hay.includes(w))) return false;
17070
+ }
17071
+ return true;
17072
+ });
17073
+ }
17074
+ async function loadFilm(ref) {
17075
+ const path = resolve20(ref);
17076
+ if (!existsSync27(path)) throw new UsageError(`${ref}: no such take or file`);
17077
+ if (statSync8(path).isDirectory()) {
17078
+ const take = await loadTake(path);
17079
+ if (!take.doc)
17080
+ throw new UsageError(`${ref} has no doc.json; run vos plan first`);
17081
+ const sync = readSyncState(path);
17082
+ return { doc: take.doc, seed: sync?.vosId ?? basename16(path), dir: path };
17083
+ }
17084
+ let config;
17085
+ try {
17086
+ config = JSON.parse(readFileSync10(path, "utf8"));
17087
+ } catch {
17088
+ throw new UsageError(`${ref}: not a take directory or a JSON config`);
17089
+ }
17090
+ if (!config || typeof config !== "object" || !("version" in config))
17091
+ throw new UsageError(`${ref}: a program's config.json carries a version`);
17092
+ const doc = {
17093
+ program: { config },
17094
+ audio: [],
17095
+ overlays: [],
17096
+ objects: []
17097
+ };
17098
+ return { doc, seed: basename16(path, ".json"), dir: null };
17099
+ }
17100
+ async function likedSlugs(refs, origin) {
17101
+ const slugs = /* @__PURE__ */ new Set();
17102
+ const notes = [];
17103
+ for (const ref of refs) {
17104
+ const id = parseVosId(ref);
17105
+ if (!id) {
17106
+ slugs.add(ref.toLowerCase());
17107
+ continue;
17108
+ }
17109
+ try {
17110
+ const vos = await (await fetch(`${origin}/api/vos/${id}`)).json();
17111
+ if (!vos.currentVersionId) throw new Error("no version");
17112
+ const doc = await (await fetch(
17113
+ `${origin}/api/vos/${id}/versions/${vos.currentVersionId}/doc`
17114
+ )).json();
17115
+ for (const s of musicSlugsOfDoc(doc)) slugs.add(s);
17116
+ } catch {
17117
+ notes.push(
17118
+ `--like ${ref}: could not read that vos's document (is it public?)`
17119
+ );
17120
+ }
17121
+ }
17122
+ return { slugs: [...slugs], notes };
17123
+ }
17124
+ function scoreLine(take, r, film) {
17125
+ const at2 = r.placement?.swellAt ?? film.payoff;
17126
+ return `vos score ${take ?? "<take>"} --track ${r.slug}${at2 !== void 0 && at2 !== null ? ` --at ${at2}` : ""}`;
17127
+ }
17128
+ function pickWords(picks, all, film, take) {
17129
+ const refused = all.filter((r) => r.score === null);
17130
+ const head = `${film.duration.toFixed(1)}s film, ${film.cuts.length} cut${film.cuts.length === 1 ? "" : "s"}${film.payoff !== void 0 ? `, payoff at ${mmss(film.payoff)}` : ""}${film.voice ? ", a voice" : ""}${film.mood ? `, asked ${film.mood}` : ""}${film.words ? `, asked "${film.words}"` : ""}`;
17131
+ if (!picks.length) {
17132
+ return `${head}
17133
+ No bed in the library fits: ${refused.length} refused (${[...new Set(refused.map((r) => r.refused))].slice(0, 3).join("; ")}). Compose a score instead (the vos-score skill), or ask with --words.`;
17134
+ }
17135
+ const lines = picks.map((r, i) => {
17136
+ const t = r.track;
17137
+ const facts = [
17138
+ t.mood,
17139
+ t.bpm !== null && t.bpm !== void 0 ? `${t.bpm} bpm${t.beatConfidence === "low" ? "?" : ""}` : null,
17140
+ VOCAL_WORDS[t.vocals ?? "none"],
17141
+ t.loops ? "loops" : null
17142
+ ].filter(Boolean).join(", ");
17143
+ return ` ${i + 1}. ${r.slug} ${t.title} ${(r.score ?? 0).toFixed(2)} (${facts})
17144
+ ${r.reasons.join("; ") || "fits"}
17145
+ ${scoreLine(take, r, film)}`;
17146
+ });
17147
+ const tail = refused.length ? `
17148
+ ${refused.length} refused: ${refused.slice(0, 4).map((r) => `${r.slug} (${r.refused})`).join("; ")}${refused.length > 4 ? "; \u2026" : ""}` : "";
17149
+ return `${head}
17150
+ ${lines.join("\n")}${tail}`;
17151
+ }
17152
+ async function cmdMusic(argv) {
17153
+ const { positionals, flags, multi } = parseArgs(
17154
+ argv,
17155
+ BOOLEAN_FLAGS14,
17156
+ MULTI_FLAGS3
17157
+ );
17158
+ const verb = positionals[0];
17159
+ if (flags.help === true || !verb || !["ls", "pick"].includes(verb))
17160
+ throw new UsageError(USAGE5);
17161
+ const r = createReporter(flags.json === true);
17162
+ const origin = platformOrigin({
17163
+ origin: strFlag(flags, "origin"),
17164
+ api: strFlag(flags, "api")
17165
+ });
17166
+ const catalog = await fetchMusicCatalog(origin);
17167
+ if (verb === "ls") {
17168
+ const vocals = strFlag(flags, "vocals");
17169
+ if (vocals && !["none", "chops", "lead"].includes(vocals))
17170
+ throw new UsageError("--vocals expects none, chops or lead");
17171
+ const kept = filterTracks(catalog.tracks, {
17172
+ mood: strFlag(flags, "mood"),
17173
+ vocals,
17174
+ loops: flags.loops === true,
17175
+ bpm: bpmRange(strFlag(flags, "bpm")),
17176
+ minLength: strFlag(flags, "min-length") !== void 0 ? numFlag(flags, "min-length", 0) : void 0,
17177
+ q: strFlag(flags, "q")
17178
+ });
17179
+ r.done(
17180
+ { origin, tracks: kept, total: catalog.tracks.length },
17181
+ kept.length ? kept.map(trackLine).join("\n") : `No track matches; the library holds ${catalog.tracks.length} (GET ${origin}/api/music).`
17182
+ );
17183
+ return EXIT_OK;
17184
+ }
17185
+ const ref = positionals[1];
17186
+ if (!ref) throw new UsageError(USAGE5);
17187
+ const { doc, seed, dir } = await loadFilm(ref);
17188
+ const atRaw = strFlag(flags, "at");
17189
+ const at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
17190
+ if (flags.voice === true && flags["no-voice"] === true)
17191
+ throw new UsageError("--voice and --no-voice together say nothing");
17192
+ const liked = await likedSlugs(multi.like ?? [], origin);
17193
+ const film = filmSummaryOf3(doc, {
17194
+ seed,
17195
+ ...at2 ? { payoff: at2.at } : {},
17196
+ ...flags.voice === true ? { voice: true } : flags["no-voice"] === true ? { voice: false } : {},
17197
+ mood: strFlag(flags, "mood"),
17198
+ words: strFlag(flags, "words"),
17199
+ like: liked.slugs,
17200
+ fadeIn: flags["fade-in"] === true
17201
+ });
17202
+ const top = Math.max(1, Math.round(numFlag(flags, "top", 3)));
17203
+ const all = rankBeds3(catalog.tracks, film);
17204
+ const picks = all.filter((x) => x.score !== null).slice(0, top);
17205
+ for (const n of liked.notes) r.warn(n);
17206
+ const asked = strFlag(flags, "mood");
17207
+ if (asked && trackBySlug2(catalog.tracks, asked))
17208
+ r.warn(
17209
+ `--mood ${asked} is a track's slug; to place that track, run vos score --track ${asked}`
17210
+ );
17211
+ r.done(
17212
+ {
17213
+ origin,
17214
+ film,
17215
+ picks: picks.map((x) => ({
17216
+ ...x,
17217
+ command: scoreLine(dir ? ref : null, x, film)
17218
+ })),
17219
+ all
17220
+ },
17221
+ pickWords(picks, all, film, dir ? ref : null)
17222
+ );
17223
+ return EXIT_OK;
17224
+ }
17225
+
16935
17226
  // src/plugin/look.ts
16936
17227
  import { mkdir as mkdir12, mkdtemp as mkdtemp3, readFile as readFile18, rm as rm6 } from "fs/promises";
16937
- import { existsSync as existsSync27 } from "fs";
17228
+ import { existsSync as existsSync28 } from "fs";
16938
17229
  import { tmpdir as tmpdir4 } from "os";
16939
- import { dirname as dirname10, join as join30, resolve as resolve20 } from "path";
17230
+ import { dirname as dirname10, join as join30, resolve as resolve21 } from "path";
16940
17231
  import { headersFor, urlSchemeProblem } from "@vosjs/render-core/record";
16941
17232
  function quote(value) {
16942
17233
  return `"${value.replace(/\s+/g, " ").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -17455,7 +17746,7 @@ async function inspectPage(page, opts) {
17455
17746
  settled
17456
17747
  };
17457
17748
  }
17458
- var USAGE5 = "vos look <url> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--click <selector>]... [--type <selector>=<text>]... [--wait <ms>] [--out <dir>] [--json]\n vos look [url] --actions actions.json --until <step id> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--wait <ms>] [--out <dir>] [--json]";
17749
+ var USAGE6 = "vos look <url> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--click <selector>]... [--type <selector>=<text>]... [--wait <ms>] [--out <dir>] [--json]\n vos look [url] --actions actions.json --until <step id> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--wait <ms>] [--out <dir>] [--json]";
17459
17750
  function untilProblem(given, steps) {
17460
17751
  if (given.until !== void 0 && given.actions === void 0)
17461
17752
  return "--until names a step of a script: pass the script with --actions actions.json";
@@ -17514,7 +17805,7 @@ async function cmdLook(argv) {
17514
17805
  if (early) throw new UsageError(early);
17515
17806
  let actions;
17516
17807
  if (actionsArg) {
17517
- actions = await loadLookActions(resolve20(actionsArg));
17808
+ actions = await loadLookActions(resolve21(actionsArg));
17518
17809
  const bad = untilProblem(
17519
17810
  { actions: actionsArg, until: untilId, clicks: 0 },
17520
17811
  actions.steps
@@ -17522,21 +17813,21 @@ async function cmdLook(argv) {
17522
17813
  if (bad) throw new UsageError(bad);
17523
17814
  }
17524
17815
  const url = positionals[0] ?? actions?.url;
17525
- if (!url) throw new UsageError(USAGE5);
17816
+ if (!url) throw new UsageError(USAGE6);
17526
17817
  const scheme = urlSchemeProblem(url);
17527
17818
  if (scheme) throw new UsageError(scheme);
17528
17819
  const r = createReporter(flags.json === true);
17529
17820
  const viewportFlag = strFlag(flags, "viewport");
17530
17821
  const viewport = !viewportFlag && actions?.viewport ? actions.viewport : parseViewport2(viewportFlag);
17531
- const outDir = resolve20(strFlag(flags, "out") ?? "look");
17822
+ const outDir = resolve21(strFlag(flags, "out") ?? "look");
17532
17823
  const headed = flags.headed === true;
17533
17824
  const waitRaw = strFlag(flags, "wait");
17534
17825
  const waitMs = waitRaw === void 0 ? 0 : Number(waitRaw);
17535
17826
  if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > 6e4)
17536
17827
  throw new UsageError("--wait expects milliseconds, 0 to 60000");
17537
17828
  const storageRaw = strFlag(flags, "storage-state");
17538
- const storageState = storageRaw ? resolve20(storageRaw) : void 0;
17539
- if (storageState && !existsSync27(storageState))
17829
+ const storageState = storageRaw ? resolve21(storageRaw) : void 0;
17830
+ if (storageState && !existsSync28(storageState))
17540
17831
  throw new UsageError(`--storage-state: no such file: ${storageState}`);
17541
17832
  const sessionName = strFlag(flags, "session");
17542
17833
  if (sessionName) {
@@ -17546,7 +17837,7 @@ async function cmdLook(argv) {
17546
17837
  throw new UsageError(
17547
17838
  "--session and --storage-state are two doors to one page: a persistent profile cannot take a storage state. Pass one"
17548
17839
  );
17549
- if (!existsSync27(sessionDir(sessionName)))
17840
+ if (!existsSync28(sessionDir(sessionName)))
17550
17841
  throw new UsageError(
17551
17842
  `no session "${sessionName}". Make one: vos session open <url> --name ${sessionName} (a Chrome window opens; sign in and close it)`
17552
17843
  );
@@ -17563,7 +17854,7 @@ async function cmdLook(argv) {
17563
17854
  r,
17564
17855
  url,
17565
17856
  actions,
17566
- actionsPath: resolve20(actionsArg),
17857
+ actionsPath: resolve21(actionsArg),
17567
17858
  untilId,
17568
17859
  viewport,
17569
17860
  outDir,
@@ -17727,9 +18018,9 @@ async function lookUntil(o) {
17727
18018
  import { spawnSync as spawnSync4 } from "child_process";
17728
18019
  import {
17729
18020
  cpSync,
17730
- existsSync as existsSync29,
18021
+ existsSync as existsSync30,
17731
18022
  mkdirSync as mkdirSync6,
17732
- readFileSync as readFileSync11,
18023
+ readFileSync as readFileSync12,
17733
18024
  rmSync as rmSync3,
17734
18025
  writeFileSync as writeFileSync6
17735
18026
  } from "fs";
@@ -17738,7 +18029,7 @@ import { join as join32 } from "path";
17738
18029
  import { chromium as chromium2 } from "playwright";
17739
18030
 
17740
18031
  // src/plugin/machine.ts
17741
- import { existsSync as existsSync28, readFileSync as readFileSync10, readdirSync as readdirSync6, statSync as statSync8 } from "fs";
18032
+ import { existsSync as existsSync29, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync9 } from "fs";
17742
18033
  import { dirname as dirname11, join as join31 } from "path";
17743
18034
  var AGENT_NAMES = [
17744
18035
  "claude",
@@ -17759,7 +18050,7 @@ var HOME_DIRS = {
17759
18050
  copilot: [".copilot"]
17760
18051
  };
17761
18052
  function detectAgents(root, opts = {}) {
17762
- const exists = opts.exists ?? existsSync28;
18053
+ const exists = opts.exists ?? existsSync29;
17763
18054
  const out = [];
17764
18055
  for (const agent of AGENT_NAMES) {
17765
18056
  for (const rel of PROJECT_DIRS[agent]) {
@@ -17806,7 +18097,7 @@ function skillsDirFor(home) {
17806
18097
  function fallbackSkillsHome(root) {
17807
18098
  return { agent: "codex", dir: join31(root, ".agents"), scope: "project" };
17808
18099
  }
17809
- function installedSkills(dir, exists = existsSync28, list = (p) => {
18100
+ function installedSkills(dir, exists = existsSync29, list = (p) => {
17810
18101
  try {
17811
18102
  return readdirSync6(p);
17812
18103
  } catch {
@@ -17847,7 +18138,7 @@ ${RULES_BLOCK}
17847
18138
  }
17848
18139
  return { text, changed: text !== current };
17849
18140
  }
17850
- function rulesFileFor(root, exists = existsSync28) {
18141
+ function rulesFileFor(root, exists = existsSync29) {
17851
18142
  const agents = join31(root, "AGENTS.md");
17852
18143
  if (exists(agents)) return agents;
17853
18144
  const claude = join31(root, "CLAUDE.md");
@@ -17952,7 +18243,7 @@ function doctorReport(facts) {
17952
18243
  }
17953
18244
  function bundledSkillsVersion(dir) {
17954
18245
  try {
17955
- const v = readFileSync10(join31(dir, "VERSION"), "utf8").trim();
18246
+ const v = readFileSync11(join31(dir, "VERSION"), "utf8").trim();
17956
18247
  return /^\d+\.\d+\.\d+$/.test(v) ? v : null;
17957
18248
  } catch {
17958
18249
  return null;
@@ -17962,12 +18253,12 @@ function bundledSkillsDir(from) {
17962
18253
  let dir = dirname11(from);
17963
18254
  for (let i = 0; i < 6; i++) {
17964
18255
  const pkg = join31(dir, "package.json");
17965
- if (existsSync28(pkg)) {
18256
+ if (existsSync29(pkg)) {
17966
18257
  try {
17967
- const name = JSON.parse(readFileSync10(pkg, "utf8")).name;
18258
+ const name = JSON.parse(readFileSync11(pkg, "utf8")).name;
17968
18259
  if (name === "@vosjs/cli") {
17969
18260
  const skills = join31(dir, "skills");
17970
- return existsSync28(skills) && statSync8(skills).isDirectory() ? skills : null;
18261
+ return existsSync29(skills) && statSync9(skills).isDirectory() ? skills : null;
17971
18262
  }
17972
18263
  } catch {
17973
18264
  }
@@ -17980,7 +18271,7 @@ function bundledSkillsDir(from) {
17980
18271
  }
17981
18272
 
17982
18273
  // src/plugin/machineCmd.ts
17983
- var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
18274
+ var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
17984
18275
  "json",
17985
18276
  "help",
17986
18277
  "global",
@@ -18003,7 +18294,7 @@ function npx() {
18003
18294
  function bundledChromium() {
18004
18295
  try {
18005
18296
  const p = chromium2.executablePath();
18006
- return p && existsSync29(p) ? p : null;
18297
+ return p && existsSync30(p) ? p : null;
18007
18298
  } catch {
18008
18299
  return null;
18009
18300
  }
@@ -18012,7 +18303,7 @@ function probe2() {
18012
18303
  return probeBrowser({
18013
18304
  platform: process.platform,
18014
18305
  env: process.env,
18015
- exists: existsSync29,
18306
+ exists: existsSync30,
18016
18307
  bundled: bundledChromium
18017
18308
  });
18018
18309
  }
@@ -18063,7 +18354,7 @@ async function doctorFacts(opts) {
18063
18354
  };
18064
18355
  }
18065
18356
  async function cmdSetup(argv) {
18066
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18357
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18067
18358
  const r = reporterFor(flags);
18068
18359
  const root = process.cwd();
18069
18360
  const global = flags.global === true;
@@ -18165,7 +18456,7 @@ async function cmdSetup(argv) {
18165
18456
  let rules = null;
18166
18457
  if (!noRules) {
18167
18458
  const file = rulesFileFor(root);
18168
- const existing = existsSync29(file) ? readFileSync11(file, "utf8") : null;
18459
+ const existing = existsSync30(file) ? readFileSync12(file, "utf8") : null;
18169
18460
  const { text, changed } = applyRulesBlock(existing);
18170
18461
  if (changed) writeFileSync6(file, text);
18171
18462
  rules = { file, changed };
@@ -18196,7 +18487,7 @@ Next: ${report.next_step}`
18196
18487
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18197
18488
  }
18198
18489
  async function cmdDoctor(argv) {
18199
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18490
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18200
18491
  const r = reporterFor(flags);
18201
18492
  const root = process.cwd();
18202
18493
  const homes = detectAgents(root, { home: homedir2(), global: true });
@@ -18215,7 +18506,7 @@ Next: ${report.next_step}`
18215
18506
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18216
18507
  }
18217
18508
  async function cmdWhoami(argv) {
18218
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18509
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18219
18510
  const r = reporterFor(flags);
18220
18511
  const origin = platformOrigin({
18221
18512
  origin: strFlag(flags, "origin"),
@@ -18277,9 +18568,9 @@ async function cmdWhoami(argv) {
18277
18568
  return EXIT_OK;
18278
18569
  }
18279
18570
  function cmdLogout(argv) {
18280
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18571
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18281
18572
  const r = reporterFor(flags);
18282
- const had = existsSync29(CREDENTIALS_PATH);
18573
+ const had = existsSync30(CREDENTIALS_PATH);
18283
18574
  rmSync3(CREDENTIALS_PATH, { force: true });
18284
18575
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18285
18576
  r.done(
@@ -18295,7 +18586,7 @@ function cmdLogout(argv) {
18295
18586
  }
18296
18587
 
18297
18588
  // src/plugin/run.ts
18298
- var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
18589
+ var BOOLEAN_FLAGS16 = /* @__PURE__ */ new Set([
18299
18590
  "json",
18300
18591
  "help",
18301
18592
  "wait",
@@ -18359,8 +18650,12 @@ Take pipeline
18359
18650
  vos join <take> <take|file.mp4>... --out <dir> [--keep-edits|--force] [--captions] [--zooms per-beat] [--mute <file>]... [--json]
18360
18651
  several takes (and plain video files) as ONE take, cut in argument order: the first is the primary (its frame, cursor, export and sound), each next one a media m1, m2, \u2026 with its footage copied, its spans tagged and renamed, its layers moved to where it now starts; a file opens full-bleed and its own sound plays as an audio clip (--mute <file> leaves it out). --captions lands every step's caption anchored to its step, starting as the step begins (anchor.at "start"), each ending with its clip. --zooms per-beat keeps a beat's zoom on its steps marked "zoom": true (as vos plan --zooms per-beat). The result must pass vos validate before anything is written
18361
18652
  --keep-edits joins again over an edited joined take and keeps its edits: inputs are matched to their media by source file (join.json records each input's path and footage hash), a kept input keeps its id and everything on it (segments in their edited order, spans, rejections, anchored layers, its sound), the frame, export, styles and still stay, a new input is joined fresh under a new id (and only it is captioned or re-planned), a re-recorded input (changed footage at its path, or the same script at a new one) keeps its edits re-timed by its steps as vos plan --reuse does, and an input that is gone takes its edits with it, each named. A new input's segments go before the next kept input's; reordering the kept inputs regroups the segments in argument order. The first input must stay the primary. --force replaces the take instead
18362
- vos score <take> --track <slug|file> [--at <step id|output seconds>] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]
18363
- the film's music as one audio clip (id score): reads the track's loudness, finds its strongest swell and offsets the track so the swell lands at --at (a step, resolved through the cut, or output seconds); without --at it starts where the track first rises above its median level. The clip carries its measured loudness and a target, --normalize (default -16 LUFS), with --gain (default 0.5) a trim on it; --normalize off plays it at --gain alone
18653
+ vos music ls [--mood <slug>] [--vocals none|chops|lead] [--loops] [--bpm <lo>-<hi>] [--min-length <s>] [--q <words>] [--json]
18654
+ the library as an agent reads it, one line per track: slug, genre, length, tempo (a ? on an uncertain beat), vocals, loops, the sentence a person wrote. The facts are GET /api/music's
18655
+ vos music pick <take|config.json> [--at <step id|seconds>] [--mood <slug|tag>] [--words "\u2026"] [--voice|--no-voice] [--like <vos id|slug>]... [--top 3] [--json]
18656
+ the beds that fit THIS film, best first, each with its reasons and the vos score line that places it: the film's length, its cuts (captions, joins, page changes, zoom marks), its payoff (--at, else the last step marked "zoom": true, else the last cut) and its voice are read from the document and every track is scored (a swell on the payoff, cuts on the beat, the asked mood or words, calm under a voice, an ending that meets the film's) after the hard rules (covers the film or loops, no quiet intro under frame one, no sung lead under a voice). --json carries every track, the refused with their reason. Ties fall to the take, so two films asking the same thing get different beds
18657
+ vos score <take> --track <slug|file|best> [--at <step id|output seconds>] [--mood <slug|tag>] [--words "\u2026"] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]
18658
+ the film's music as one audio clip (id score): reads the track's loudness, finds its strongest swell and offsets the track so the swell lands at --at (a step, resolved through the cut, or output seconds); without --at it starts where the track first rises above its median level. --track best runs vos music pick and places its first pick, its payoff as --at unless one is given. The clip carries its measured loudness and a target, --normalize (default -16 LUFS), with --gain (default 0.5) a trim on it; --normalize off plays it at --gain alone
18364
18659
  vos audio measure <take> [--json]
18365
18660
  every audio clip's loudness (BS.1770 integrated LUFS and sample peak, the meter check-video reads a render with), written beside it in doc.json as loudness { lufs, peakDb, at, src }; a clip's normalize (a target in LUFS, -30..-6) plays it at the gain that reaches it, past 1 if need be, capped so its peak stays under -1 dBFS, and only once it is measured for its own key
18366
18661
  vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
@@ -18860,8 +19155,8 @@ function takeBrowserArgs(multi) {
18860
19155
  function takeStorageState(flags) {
18861
19156
  const raw = strFlag(flags, "storage-state");
18862
19157
  if (!raw) return void 0;
18863
- const path = resolve21(raw);
18864
- if (!existsSync30(path))
19158
+ const path = resolve22(raw);
19159
+ if (!existsSync31(path))
18865
19160
  throw new UsageError(`--storage-state: no such file: ${path}`);
18866
19161
  return path;
18867
19162
  }
@@ -18874,7 +19169,7 @@ function takeSessionName(flags, storageState) {
18874
19169
  throw new UsageError(
18875
19170
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
18876
19171
  );
18877
- if (!existsSync30(sessionDir(name)))
19172
+ if (!existsSync31(sessionDir(name)))
18878
19173
  throw new UsageError(
18879
19174
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
18880
19175
  );
@@ -18892,14 +19187,14 @@ async function inSession(name, actions, fn) {
18892
19187
  }
18893
19188
  function reRecord(outDir, flags, r) {
18894
19189
  const wantsReuse = flags.reuse === true;
18895
- if (wantsReuse && !existsSync30(join33(outDir, "doc.json")))
19190
+ if (wantsReuse && !existsSync31(join33(outDir, "doc.json")))
18896
19191
  throw new UsageError(
18897
19192
  `--reuse: ${outDir} has no cut to keep (no doc.json); record without --reuse`
18898
19193
  );
18899
19194
  let previousCut = null;
18900
19195
  return {
18901
19196
  prepare: async () => {
18902
- if (existsSync30(join33(outDir, "meta.json"))) {
19197
+ if (existsSync31(join33(outDir, "meta.json"))) {
18903
19198
  const { prevDoc, kept } = await prepareReRecord(outDir);
18904
19199
  if (prevDoc) previousCut = join33(outDir, PREV_DOC_NAME);
18905
19200
  r.log(
@@ -18987,7 +19282,7 @@ function wallGate(flags, r, opts) {
18987
19282
  async function cmdRecord(argv) {
18988
19283
  const { positionals, flags, multi } = parseArgs(
18989
19284
  argv,
18990
- BOOLEAN_FLAGS15,
19285
+ BOOLEAN_FLAGS16,
18991
19286
  MULTI_FLAGS2
18992
19287
  );
18993
19288
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -19003,7 +19298,7 @@ async function cmdRecord(argv) {
19003
19298
  const scheme = urlSchemeProblem2(url);
19004
19299
  if (scheme) throw new UsageError(scheme);
19005
19300
  announceEnvNames(actions, r);
19006
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19301
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
19007
19302
  const storageState = takeStorageState(flags);
19008
19303
  const sessionName = takeSessionName(flags, storageState);
19009
19304
  const browserArgs = takeBrowserArgs(multi);
@@ -19023,7 +19318,7 @@ async function cmdRecord(argv) {
19023
19318
  );
19024
19319
  const hostedActions = await scriptForHost(
19025
19320
  actions,
19026
- dirname12(resolve21(actionsPath))
19321
+ dirname12(resolve22(actionsPath))
19027
19322
  ).catch((e) => {
19028
19323
  throw new UsageError(e instanceof Error ? e.message : String(e));
19029
19324
  });
@@ -19133,7 +19428,7 @@ async function cmdRecord(argv) {
19133
19428
  sessionName,
19134
19429
  actions,
19135
19430
  (context2) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
19136
- actionsDir: dirname12(resolve21(actionsPath)),
19431
+ actionsDir: dirname12(resolve22(actionsPath)),
19137
19432
  storageState,
19138
19433
  headers,
19139
19434
  context: context2,
@@ -19199,7 +19494,7 @@ async function cmdRecord(argv) {
19199
19494
  ...flags["allow-wall"] === true ? ["--allow-wall"] : [],
19200
19495
  ...confirm.map((c) => `--confirm ${c}`),
19201
19496
  // A take that already has a cut keeps it on the new footage.
19202
- ...existsSync30(join33(outDir, "doc.json")) ? ["--reuse"] : []
19497
+ ...existsSync31(join33(outDir, "doc.json")) ? ["--reuse"] : []
19203
19498
  ].join(" ");
19204
19499
  const nextOut = strFlag(flags, "out") ?? "take";
19205
19500
  const guardNote = !unconfirmed.ok ? unconfirmed.unconfirmed.length ? ` (it refuses until you add ${unconfirmed.unconfirmed.map((id) => `--confirm ${id}`).join(" ")}: ${unconfirmed.unconfirmed.length === 1 ? "that step spends" : "those steps spend"} something real)` : ` (${unconfirmed.unknown.map((c) => `--confirm ${c}`).join(", ")} names no guarded step)` : "";
@@ -19241,7 +19536,7 @@ ${lines.join("\n")}
19241
19536
  sessionName,
19242
19537
  actions,
19243
19538
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19244
- actionsDir: dirname12(resolve21(actionsPath)),
19539
+ actionsDir: dirname12(resolve22(actionsPath)),
19245
19540
  maxDurationSeconds,
19246
19541
  storageState,
19247
19542
  headers,
@@ -19339,7 +19634,7 @@ ${lines.join("\n")}
19339
19634
  async function cmdCreate2(argv) {
19340
19635
  const { positionals, flags, multi } = parseArgs(
19341
19636
  argv,
19342
- BOOLEAN_FLAGS15,
19637
+ BOOLEAN_FLAGS16,
19343
19638
  MULTI_FLAGS2
19344
19639
  );
19345
19640
  const actionsPath = strFlag(flags, "actions");
@@ -19356,7 +19651,7 @@ async function cmdCreate2(argv) {
19356
19651
  const scheme = urlSchemeProblem2(url);
19357
19652
  if (scheme) throw new UsageError(scheme);
19358
19653
  announceEnvNames(actions, r);
19359
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19654
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
19360
19655
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
19361
19656
  const out = positionals[0] ?? join33(outDir, `out.${format}`);
19362
19657
  const parallel = numFlag(flags, "parallel", 1);
@@ -19380,7 +19675,7 @@ async function cmdCreate2(argv) {
19380
19675
  sessionName,
19381
19676
  actions,
19382
19677
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19383
- actionsDir: dirname12(resolve21(actionsPath)),
19678
+ actionsDir: dirname12(resolve22(actionsPath)),
19384
19679
  maxDurationSeconds,
19385
19680
  storageState,
19386
19681
  headers,
@@ -19497,7 +19792,7 @@ async function cmdPlan(argvIn) {
19497
19792
  const argv = bareCaptionsFlag(argvIn);
19498
19793
  const { positionals, flags, multi } = parseArgs(
19499
19794
  argv,
19500
- BOOLEAN_FLAGS15,
19795
+ BOOLEAN_FLAGS16,
19501
19796
  /* @__PURE__ */ new Set(["with"])
19502
19797
  );
19503
19798
  const dir = positionals[0];
@@ -19512,8 +19807,8 @@ async function cmdPlan(argvIn) {
19512
19807
  }
19513
19808
  let reuse;
19514
19809
  if (flags.reuse === true) {
19515
- const from = resolve21(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
19516
- if (!existsSync30(from)) {
19810
+ const from = resolve22(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
19811
+ if (!existsSync31(from)) {
19517
19812
  throw new UsageError(
19518
19813
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
19519
19814
  );
@@ -19524,7 +19819,7 @@ async function cmdPlan(argvIn) {
19524
19819
  };
19525
19820
  }
19526
19821
  const style = await resolveStyleRef(flags);
19527
- const hasDoc = existsSync30(join33(dir, "doc.json"));
19822
+ const hasDoc = existsSync31(join33(dir, "doc.json"));
19528
19823
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
19529
19824
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
19530
19825
  const stepCaptions = strFlag(flags, "captions") === "steps";
@@ -19604,7 +19899,7 @@ async function cmdPlan(argvIn) {
19604
19899
  ${s.reuse.flagged.join("\n ")}` : "") : "";
19605
19900
  r.done(
19606
19901
  {
19607
- take: resolve21(dir),
19902
+ take: resolve22(dir),
19608
19903
  fresh: s.fresh,
19609
19904
  cursorKept: s.cursorKept,
19610
19905
  zoomAuto,
@@ -19733,7 +20028,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
19733
20028
  async function cmdRender(argv) {
19734
20029
  const { positionals, flags, multi } = parseArgs(
19735
20030
  argv,
19736
- BOOLEAN_FLAGS15,
20031
+ BOOLEAN_FLAGS16,
19737
20032
  MULTI_FLAGS2
19738
20033
  );
19739
20034
  const dir = positionals[0];
@@ -19801,7 +20096,7 @@ async function cmdRender(argv) {
19801
20096
  async function cmdFrames(argv) {
19802
20097
  const { positionals, flags, multi } = parseArgs(
19803
20098
  argv,
19804
- BOOLEAN_FLAGS15,
20099
+ BOOLEAN_FLAGS16,
19805
20100
  MULTI_FLAGS2
19806
20101
  );
19807
20102
  const dir = positionals[0];
@@ -19884,7 +20179,7 @@ async function cmdFrames(argv) {
19884
20179
  async function cmdDeliver(argv) {
19885
20180
  const { positionals, flags, multi } = parseArgs(
19886
20181
  argv,
19887
- BOOLEAN_FLAGS15,
20182
+ BOOLEAN_FLAGS16,
19888
20183
  MULTI_FLAGS2
19889
20184
  );
19890
20185
  const dir = positionals[0];
@@ -19984,11 +20279,11 @@ async function resolveStyleRef(flags) {
19984
20279
  }
19985
20280
  async function resolveDocRef(ref, flags, what) {
19986
20281
  const styleRef = ref;
19987
- const file = existsSync30(styleRef) ? resolve21(
20282
+ const file = existsSync31(styleRef) ? resolve22(
19988
20283
  styleRef,
19989
- existsSync30(join33(styleRef, "doc.json")) ? "doc.json" : ""
20284
+ existsSync31(join33(styleRef, "doc.json")) ? "doc.json" : ""
19990
20285
  ) : null;
19991
- if (file && existsSync30(file)) {
20286
+ if (file && existsSync31(file)) {
19992
20287
  return {
19993
20288
  from: file,
19994
20289
  doc: migrateHostedDoc4(
@@ -20034,7 +20329,7 @@ async function officialTemplate(origin, ref) {
20034
20329
  }
20035
20330
  }
20036
20331
  async function cmdDigest(argv) {
20037
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20332
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20038
20333
  const dir = positionals[0];
20039
20334
  if (!dir)
20040
20335
  throw new UsageError(
@@ -20098,19 +20393,19 @@ async function cmdDigest(argv) {
20098
20393
  }
20099
20394
  }
20100
20395
  async function cmdOpen(argv) {
20101
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20396
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20102
20397
  const dir = positionals[0];
20103
20398
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
20104
20399
  const r = createReporter(flags.json === true);
20105
20400
  const take = await loadTake(dir);
20106
- if (!existsSync30(take.paths.recording)) {
20401
+ if (!existsSync31(take.paths.recording)) {
20107
20402
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
20108
20403
  }
20109
20404
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
20110
20405
  /\/+$/,
20111
20406
  ""
20112
20407
  );
20113
- const server = await startTakeServer(resolve21(dir), {});
20408
+ const server = await startTakeServer(resolve22(dir), {});
20114
20409
  const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
20115
20410
  r.event({ event: "open", server: server.base, url });
20116
20411
  r.log(`take served at ${server.base}`);
@@ -20133,7 +20428,7 @@ async function cmdOpen(argv) {
20133
20428
  return EXIT_OK;
20134
20429
  }
20135
20430
  async function cmdCallout(argv) {
20136
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20431
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20137
20432
  const dir = positionals[0];
20138
20433
  const shape = positionals[1];
20139
20434
  if (!dir || !shape || !isCalloutShape(shape))
@@ -20232,11 +20527,11 @@ async function cmdCallout(argv) {
20232
20527
  return EXIT_OK;
20233
20528
  }
20234
20529
  async function cmdValidate(argv) {
20235
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20530
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20236
20531
  const target = positionals[0];
20237
20532
  if (!target) throw new UsageError("vos validate <actions.json|take>");
20238
20533
  const r = createReporter(flags.json === true);
20239
- const kitTarget = target.endsWith("kit.json") ? target : existsSync30(join33(target, "kit.json")) && !isTakeDir(target) ? join33(target, "kit.json") : null;
20534
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync31(join33(target, "kit.json")) && !isTakeDir(target) ? join33(target, "kit.json") : null;
20240
20535
  if (kitTarget) {
20241
20536
  const picture = flags.picture === true;
20242
20537
  const verdict = await validateKit(kitTarget, { picture });
@@ -20268,7 +20563,7 @@ async function cmdValidate(argv) {
20268
20563
  r.done({ valid: true, target }, `${target}: valid actions file`);
20269
20564
  return EXIT_OK;
20270
20565
  }
20271
- if (!isTakeDir(target) && existsSync30(join33(target, "config.json"))) {
20566
+ if (!isTakeDir(target) && existsSync31(join33(target, "config.json"))) {
20272
20567
  const parsed = JSON.parse(
20273
20568
  await readFile19(join33(target, "config.json"), "utf8")
20274
20569
  );
@@ -20312,7 +20607,7 @@ async function cmdValidate(argv) {
20312
20607
  const problems = [];
20313
20608
  const warnings = [];
20314
20609
  const notes = [];
20315
- if (!existsSync30(take.paths.recording)) {
20610
+ if (!existsSync31(take.paths.recording)) {
20316
20611
  problems.push("missing recording (re-run record)");
20317
20612
  } else {
20318
20613
  const mismatch = containerMismatch(
@@ -20353,7 +20648,7 @@ async function cmdValidate(argv) {
20353
20648
  const found = localFile(rel, target);
20354
20649
  if (!("refused" in found)) continue;
20355
20650
  problems.push(
20356
- existsSync30(resolve21(target, rel)) ? `doc.json: ${ref.where}: ${ref.key} ${found.refused}` : `doc.json: ${ref.where}: ${ref.key} is not a file in the take, so a render plays the film without it (put the file there, or fix the key)`
20651
+ existsSync31(resolve22(target, rel)) ? `doc.json: ${ref.where}: ${ref.key} ${found.refused}` : `doc.json: ${ref.where}: ${ref.key} is not a file in the take, so a render plays the film without it (put the file there, or fix the key)`
20357
20652
  );
20358
20653
  }
20359
20654
  if (flags.picture === true && (take.doc.overlays ?? []).some((o) => o.kind === "html")) {
@@ -20422,7 +20717,7 @@ async function cmdValidate(argv) {
20422
20717
  async function cmdPush3(argv) {
20423
20718
  const { positionals, flags, multi } = parseArgs(
20424
20719
  argv,
20425
- BOOLEAN_FLAGS15,
20720
+ BOOLEAN_FLAGS16,
20426
20721
  MULTI_FLAGS2
20427
20722
  );
20428
20723
  const dir = positionals[0];
@@ -20432,7 +20727,7 @@ async function cmdPush3(argv) {
20432
20727
  );
20433
20728
  const refusal = takePushRefusal({
20434
20729
  vos: strFlag(flags, "vos"),
20435
- trackedVosId: readSyncState(resolve21(dir))?.vosId ?? null,
20730
+ trackedVosId: readSyncState(resolve22(dir))?.vosId ?? null,
20436
20731
  programOnly: ["slug", "tags", "base", "remix-of", "still"].filter(
20437
20732
  (name) => hasFlag(flags, name)
20438
20733
  )
@@ -20451,7 +20746,7 @@ async function cmdPush3(argv) {
20451
20746
  `--claimable creates a NEW claimable vos; ${iterating.map((f) => `--${f}`).join(", ")} ${iterating.length > 1 ? "apply" : "applies"} to a keyed push`
20452
20747
  );
20453
20748
  const claim = await pushTakeClaimable(
20454
- resolve21(dir),
20749
+ resolve22(dir),
20455
20750
  {
20456
20751
  api: strFlag(flags, "api"),
20457
20752
  origin: strFlag(flags, "origin"),
@@ -20475,7 +20770,7 @@ reference and the only credential.`
20475
20770
  }
20476
20771
  const overrides = multi.override;
20477
20772
  const result = await pushTake(
20478
- resolve21(dir),
20773
+ resolve22(dir),
20479
20774
  {
20480
20775
  key: strFlag(flags, "key"),
20481
20776
  api: strFlag(flags, "api"),
@@ -20527,11 +20822,11 @@ ${landedLines(landed)}` : "")
20527
20822
  return EXIT_OK;
20528
20823
  }
20529
20824
  async function cmdPull2(argv) {
20530
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15, MULTI_FLAGS2);
20825
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16, MULTI_FLAGS2);
20531
20826
  const dir = positionals[0] ?? ".";
20532
20827
  const r = createReporter(flags.json === true);
20533
20828
  const result = await pullTake(
20534
- resolve21(dir),
20829
+ resolve22(dir),
20535
20830
  {
20536
20831
  key: strFlag(flags, "key"),
20537
20832
  api: strFlag(flags, "api"),
@@ -20666,6 +20961,8 @@ async function run(argv) {
20666
20961
  return await cmdScore(rest);
20667
20962
  case "audio":
20668
20963
  return await cmdAudio(rest);
20964
+ case "music":
20965
+ return await cmdMusic(rest);
20669
20966
  case "look":
20670
20967
  return await cmdLook(rest);
20671
20968
  default:
@@ -20708,7 +21005,7 @@ function verdictExit(v, strict, none) {
20708
21005
  async function cmdSession(argv) {
20709
21006
  const { positionals, flags } = parseArgs(
20710
21007
  argv,
20711
- /* @__PURE__ */ new Set([...BOOLEAN_FLAGS15, "headed"])
21008
+ /* @__PURE__ */ new Set([...BOOLEAN_FLAGS16, "headed"])
20712
21009
  );
20713
21010
  const [sub, arg] = positionals;
20714
21011
  const r = createReporter(flags.json === true);
@@ -20753,7 +21050,7 @@ async function cmdSession(argv) {
20753
21050
  if (!arg) throw new UsageError(usage);
20754
21051
  const bad = sessionNameVerdict(arg);
20755
21052
  if (bad) throw new UsageError(bad);
20756
- if (!existsSync30(sessionDir(arg)))
21053
+ if (!existsSync31(sessionDir(arg)))
20757
21054
  throw new UsageError(`no session "${arg}" (vos session list)`);
20758
21055
  const summary3 = await summarizeSession(arg);
20759
21056
  const url = strFlag(flags, "url");
@@ -20787,7 +21084,7 @@ async function cmdSession(argv) {
20787
21084
  if (!arg || !scriptPath) throw new UsageError(usage);
20788
21085
  const bad = sessionNameVerdict(arg);
20789
21086
  if (bad) throw new UsageError(bad);
20790
- if (!existsSync30(sessionDir(arg)))
21087
+ if (!existsSync31(sessionDir(arg)))
20791
21088
  throw new UsageError(`no session "${arg}" (vos session list)`);
20792
21089
  const url = strFlag(flags, "url");
20793
21090
  if (url && !/^https?:\/\//.test(url))
@@ -20880,4 +21177,4 @@ export {
20880
21177
  verbHelp,
20881
21178
  run
20882
21179
  };
20883
- //# sourceMappingURL=chunk-DM7ADA77.js.map
21180
+ //# sourceMappingURL=chunk-NJBUBXFE.js.map