@vosjs/cli 0.83.2 → 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.
@@ -148,9 +148,9 @@ function landedLines(l) {
148
148
  }
149
149
 
150
150
  // src/plugin/run.ts
151
- import { mkdir as mkdir13, readFile as readFile18, rm as rm6 } from "fs/promises";
152
- import { existsSync as existsSync30 } from "fs";
153
- import { dirname as dirname11, join as join33, resolve as resolve21 } from "path";
151
+ import { mkdir as mkdir13, readFile as readFile19, rm as rm7 } from "fs/promises";
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) {
@@ -7926,10 +7957,19 @@ function printChanges(r, changes, protectedIds) {
7926
7957
  r.event({ event: "protected", nodes: protectedIds });
7927
7958
  }
7928
7959
  }
7960
+ var DESCRIPTION_MAX = 500;
7961
+ function descriptionProblem(desc) {
7962
+ if (desc === void 0) return null;
7963
+ if (!desc.trim())
7964
+ return "--desc is empty: give the sentence that says what the video shows";
7965
+ if (desc.length > DESCRIPTION_MAX)
7966
+ return `--desc is ${desc.length} characters and a vos keeps ${DESCRIPTION_MAX}: shorten it`;
7967
+ return null;
7968
+ }
7929
7969
  function takePushRefusal(ask) {
7930
7970
  if (ask.programOnly.length) {
7931
7971
  const list = ask.programOnly.map((f) => `--${f}`).join(", ");
7932
- return `${list} ${ask.programOnly.length > 1 ? "belong" : "belongs"} to a PROGRAM push (a config.json); a take push reads --vos, --title, --label, --note, --folder, --override and --yes`;
7972
+ return `${list} ${ask.programOnly.length > 1 ? "belong" : "belongs"} to a PROGRAM push (a config.json); a take push reads --vos, --title, --desc, --prompt, --label, --note, --folder, --override and --yes`;
7933
7973
  }
7934
7974
  if (ask.vos !== void 0 && ask.trackedVosId !== null && ask.trackedVosId !== ask.vos)
7935
7975
  return `this take tracks vos ${ask.trackedVosId} (its vos.json), not ${ask.vos}. Drop --vos to push there, or remove vos.json to adopt ${ask.vos} instead`;
@@ -8089,7 +8129,7 @@ async function pushTake(dir, flags, r) {
8089
8129
  const lowered = lowerToComposition3(docForPush);
8090
8130
  const config = { ...lowered.config, data: lowered.data };
8091
8131
  if (!state) {
8092
- 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)}`;
8093
8133
  const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
8094
8134
  const created = await api(ctx, "/vos", {
8095
8135
  method: "POST",
@@ -8104,7 +8144,8 @@ async function pushTake(dir, flags, r) {
8104
8144
  ...folderId ? { folderId } : {},
8105
8145
  ...flags.label ? { label: flags.label } : {},
8106
8146
  ...flags.note ? { note: flags.note } : {},
8107
- ...flags.prompt ? { prompt: flags.prompt } : {}
8147
+ ...flags.prompt ? { prompt: flags.prompt } : {},
8148
+ ...flags.description ? { description: flags.description } : {}
8108
8149
  }
8109
8150
  });
8110
8151
  if (created.status !== 201) {
@@ -8155,14 +8196,18 @@ async function pushTake(dir, flags, r) {
8155
8196
  );
8156
8197
  }
8157
8198
  serverWarnings(pushed.json, (l) => r.log(l));
8158
- if (flags.prompt) {
8199
+ const words2 = {
8200
+ ...flags.prompt ? { prompt: flags.prompt } : {},
8201
+ ...flags.description ? { description: flags.description } : {}
8202
+ };
8203
+ if (Object.keys(words2).length) {
8159
8204
  const set = await api(ctx, `/vos/${state.vosId}`, {
8160
8205
  method: "PATCH",
8161
- body: { prompt: flags.prompt }
8206
+ body: words2
8162
8207
  });
8163
8208
  if (set.status !== 200)
8164
8209
  r.warn(
8165
- `could not set the prompt (${set.status}): ${String(set.json.error ?? "")}${detailLine(set.json)}`
8210
+ `could not set the ${Object.keys(words2).join(" and ")} (${set.status}): ${String(set.json.error ?? "")}${detailLine(set.json)}`
8166
8211
  );
8167
8212
  }
8168
8213
  const version = pushed.json.version;
@@ -8276,7 +8321,7 @@ async function pullTake(dir, flags, r) {
8276
8321
  const doc = hostedDoc;
8277
8322
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
8278
8323
  if (existsSync13(localRecording)) {
8279
- doc.source.videoKey = basename2(localRecording);
8324
+ doc.source.videoKey = basename3(localRecording);
8280
8325
  }
8281
8326
  await writeJson(join14(dir, "doc.json"), doc, true);
8282
8327
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
@@ -8376,10 +8421,11 @@ async function pushTakeClaimable(dir, flags, r) {
8376
8421
  }
8377
8422
  const lowered = lowerToComposition3(docForPush);
8378
8423
  const config = { ...lowered.config, data: lowered.data };
8379
- 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);
8380
8425
  const body = { title, config, doc: docForPush };
8381
8426
  if (state?.vosId) body.remixOfId = state.vosId;
8382
8427
  if (flags.prompt) body.prompt = flags.prompt;
8428
+ if (flags.description) body.description = flags.description;
8383
8429
  if (flags.share) body.share = true;
8384
8430
  const origin = platformOrigin(flags);
8385
8431
  r.log(`creating a claimable push of ${title}\u2026`);
@@ -8403,7 +8449,7 @@ async function pushTakeClaimable(dir, flags, r) {
8403
8449
  import { existsSync as existsSync15 } from "fs";
8404
8450
  import { mkdir as mkdir7, readFile as readFile12 } from "fs/promises";
8405
8451
  import { createInterface as createInterface3 } from "readline/promises";
8406
- 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";
8407
8453
  import {
8408
8454
  CURRENT_CONFIG_VERSION,
8409
8455
  migrateConfig,
@@ -8412,7 +8458,7 @@ import {
8412
8458
  import { lowerProgramDoc, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
8413
8459
 
8414
8460
  // src/plugin/programFiles.ts
8415
- 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";
8416
8462
  import { existsSync as existsSync14, realpathSync as realpathSync2 } from "fs";
8417
8463
  function isLocalName(value) {
8418
8464
  if (/^(https?:|data:|blob:|asset:|media:)/.test(value)) return false;
@@ -8423,7 +8469,7 @@ function isLocalName(value) {
8423
8469
  }
8424
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;
8425
8471
  function manifestName(file, taken) {
8426
- 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";
8427
8473
  let name = stem;
8428
8474
  for (let n = 2; taken.has(name); n++) name = `${stem}_${n}`;
8429
8475
  taken.add(name);
@@ -8842,7 +8888,7 @@ async function cmdPushProgram(argv) {
8842
8888
  "--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
8843
8889
  );
8844
8890
  }
8845
- 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";
8846
8892
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8847
8893
  const files2 = await collectFiles(config, programDoc, dir, r);
8848
8894
  let uploadToken;
@@ -8918,9 +8964,11 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8918
8964
  });
8919
8965
  if (pushTarget.kind === "refuse") throw new UsageError(pushTarget.why);
8920
8966
  const vosId = pushTarget.kind === "version" ? pushTarget.vosId : null;
8921
- if (vosId && (desc || tagsFlag || folderRef)) {
8967
+ const descProblem = descriptionProblem(desc);
8968
+ if (descProblem) throw new UsageError(descProblem);
8969
+ if (vosId && (tagsFlag || folderRef)) {
8922
8970
  throw new UsageError(
8923
- "--desc/--tags/--folder apply when CREATING a vos. On an existing one: vos folder move <id> --to <folder> for filing; edit title/description/tags on vos.so"
8971
+ "--tags/--folder apply when CREATING a vos. On an existing one: vos folder move <id> --to <folder> for filing; edit tags on vos.so"
8924
8972
  );
8925
8973
  }
8926
8974
  if (vosId) {
@@ -8966,13 +9014,18 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8966
9014
  if (res.status !== 201)
8967
9015
  throw new Error(apiError(`push version to ${vosId}`, res));
8968
9016
  serverWarnings(res.body, say(r));
8969
- if (prompt) {
9017
+ const words2 = {
9018
+ ...prompt ? { prompt } : {},
9019
+ ...desc ? { description: desc } : {}
9020
+ };
9021
+ if (Object.keys(words2).length) {
8970
9022
  const set = await apiJson(origin, `/api/vos/${vosId}`, {
8971
9023
  method: "PATCH",
8972
9024
  key,
8973
- body: { prompt }
9025
+ body: words2
8974
9026
  });
8975
- if (set.status !== 200) r.warn(apiError("set the prompt", set));
9027
+ if (set.status !== 200)
9028
+ r.warn(apiError(`set the ${Object.keys(words2).join(" and ")}`, set));
8976
9029
  }
8977
9030
  const version = res.body.version ?? {};
8978
9031
  if (typeof version.id === "string") {
@@ -9009,7 +9062,7 @@ ${landedLines(landed2)}` : "")
9009
9062
  return EXIT_OK;
9010
9063
  }
9011
9064
  const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
9012
- 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";
9013
9066
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
9014
9067
  const pushFolder = folderRef ? await resolvePushFolder(origin, key, folderRef) : void 0;
9015
9068
  const folderId = pushFolder?.id;
@@ -9335,7 +9388,7 @@ async function writeProgramDoc(dir, hosted) {
9335
9388
  // src/plugin/asset.ts
9336
9389
  import { createHash as createHash2 } from "crypto";
9337
9390
  import { readFileSync as readFileSync4 } from "fs";
9338
- import { basename as basename5 } from "path";
9391
+ import { basename as basename6 } from "path";
9339
9392
 
9340
9393
  // src/plugin/assetWords.ts
9341
9394
  function hostedFile(raw) {
@@ -9600,7 +9653,7 @@ async function cmdPush(argv) {
9600
9653
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9601
9654
  const uploaded = [];
9602
9655
  for (const file of positionals) {
9603
- const name = basename5(file);
9656
+ const name = basename6(file);
9604
9657
  const body = readFileSync4(file);
9605
9658
  const media = resolveMediaType({
9606
9659
  head: body.subarray(0, MEDIA_HEAD_BYTES),
@@ -10118,7 +10171,7 @@ function trackingCwd(vosId) {
10118
10171
 
10119
10172
  // src/plugin/buildProgram.ts
10120
10173
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
10121
- 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";
10122
10175
  import { pathToFileURL as pathToFileURL2 } from "url";
10123
10176
  import { parse } from "acorn";
10124
10177
  import { fullAncestor } from "acorn-walk";
@@ -10326,7 +10379,7 @@ async function cmdBuild(argv) {
10326
10379
  throw new UsageError(
10327
10380
  `${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
10328
10381
  );
10329
- const built = buildProgram(program, basename6(file), text);
10382
+ const built = buildProgram(program, basename7(file), text);
10330
10383
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
10331
10384
  if (built.problems.length) {
10332
10385
  r.done(
@@ -10561,7 +10614,7 @@ import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
10561
10614
  import { existsSync as existsSync18, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
10562
10615
  import { createServer as createServer2 } from "http";
10563
10616
  import { createRequire } from "module";
10564
- 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";
10565
10618
  function themeColors(sources) {
10566
10619
  const out = {};
10567
10620
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -10841,7 +10894,7 @@ async function readRemotion(dir, log) {
10841
10894
  const sources = sourceTexts(dir);
10842
10895
  const publicDir = join19(dir, "public");
10843
10896
  const media = tree.audio.map((a) => {
10844
- const name = basename7(a.src.split("?")[0]);
10897
+ const name = basename8(a.src.split("?")[0]);
10845
10898
  const local = existsSync18(join19(publicDir, name)) ? `public/${name}` : a.src;
10846
10899
  return {
10847
10900
  kind: "audio",
@@ -11344,7 +11397,7 @@ async function readInventory(source, opts) {
11344
11397
  // src/plugin/portScaffold.ts
11345
11398
  import { spawnSync as spawnSync3 } from "child_process";
11346
11399
  import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11347
- 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";
11348
11401
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
11349
11402
 
11350
11403
  // src/plugin/painterLib.ts
@@ -11754,7 +11807,7 @@ function planScaffold(inv, opts) {
11754
11807
  const copies = [];
11755
11808
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
11756
11809
  const from = resolve11(opts.sourceDir, m.src);
11757
- const name = basename8(from);
11810
+ const name = basename9(from);
11758
11811
  if (!existsSync20(from)) {
11759
11812
  report.push(
11760
11813
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
@@ -11907,7 +11960,7 @@ ${sceneTodos}${springHint}
11907
11960
  }
11908
11961
  function reportText(inv, r) {
11909
11962
  const lines = [
11910
- `# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
11963
+ `# Port report: ${inv.source === "." && inv.root ? basename9(inv.root) : inv.source}`,
11911
11964
  "",
11912
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}\`.`,
11913
11966
  "",
@@ -12071,11 +12124,11 @@ function scaffoldSourceDir(inv) {
12071
12124
  // src/plugin/recipe.ts
12072
12125
  import { createHash as createHash3 } from "crypto";
12073
12126
  import { readFileSync as readFileSync9 } from "fs";
12074
- import { basename as basename9 } from "path";
12127
+ import { basename as basename10 } from "path";
12075
12128
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
12076
12129
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
12077
12130
  function recipeFilename(file) {
12078
- const name = basename9(file);
12131
+ const name = basename10(file);
12079
12132
  if (!/\.md$/i.test(name)) {
12080
12133
  throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
12081
12134
  }
@@ -13129,8 +13182,8 @@ async function cmdActions(argv) {
13129
13182
  if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
13130
13183
  viewport = { width: Number(m[1]), height: Number(m[2]) };
13131
13184
  }
13132
- const { readFile: readFile19 } = await import("fs/promises");
13133
- const text = await readFile19(resolve14(input), "utf8");
13185
+ const { readFile: readFile20 } = await import("fs/promises");
13186
+ const text = await readFile20(resolve14(input), "utf8");
13134
13187
  const { records, problems } = parseAgentBrowserLog(text);
13135
13188
  for (const p of problems) r.log(` ${p}`);
13136
13189
  const result = convertAgentBrowser(records, {
@@ -13167,9 +13220,9 @@ function summary2(result) {
13167
13220
  }
13168
13221
  async function cmdActionsScript(input, flags) {
13169
13222
  const r = createReporter(flags.json === true);
13170
- const { readFile: readFile19 } = await import("fs/promises");
13223
+ const { readFile: readFile20 } = await import("fs/promises");
13171
13224
  const { shotList, formatShotList } = await import("./shotList-PL7XP6AC.js");
13172
- const raw = JSON.parse(await readFile19(resolve14(input), "utf8"));
13225
+ const raw = JSON.parse(await readFile20(resolve14(input), "utf8"));
13173
13226
  const errors = validateActions(raw);
13174
13227
  if (errors.length) throw new UsageError(`${input}:
13175
13228
  ${errors.join("\n ")}`);
@@ -13181,7 +13234,7 @@ async function cmdActionsScript(input, flags) {
13181
13234
  // src/plugin/ingest.ts
13182
13235
  import { existsSync as existsSync22 } from "fs";
13183
13236
  import { copyFile, mkdir as mkdir8, readFile as readFile13, stat as stat2 } from "fs/promises";
13184
- 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";
13185
13238
 
13186
13239
  // src/plugin/traceInflate.ts
13187
13240
  import { inflateRawSync } from "zlib";
@@ -13308,7 +13361,7 @@ async function copyRecording(src, dest, ext2) {
13308
13361
  }
13309
13362
  }
13310
13363
  async function readTraceFile(path, offsetMs) {
13311
- const kind = traceKindOf(basename10(path));
13364
+ const kind = traceKindOf(basename11(path));
13312
13365
  if (!kind) {
13313
13366
  throw new UsageError(
13314
13367
  `${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
@@ -13329,7 +13382,7 @@ async function readTraceFile(path, offsetMs) {
13329
13382
  return cursorFromPlaywrightTrace(text2, { offsetMs });
13330
13383
  }
13331
13384
  const text = await readFile13(path, "utf8");
13332
- return cursorFromTrace(basename10(path), text, { offsetMs });
13385
+ return cursorFromTrace(basename11(path), text, { offsetMs });
13333
13386
  }
13334
13387
  function parseViewport(v) {
13335
13388
  if (!v) return null;
@@ -13360,7 +13413,7 @@ async function cmdIngest(argv) {
13360
13413
  "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
13361
13414
  );
13362
13415
  const backdrop = footage ? null : await takeBackdrop(flags, r);
13363
- r.log(`probing ${basename10(videoPath)}\u2026`);
13416
+ r.log(`probing ${basename11(videoPath)}\u2026`);
13364
13417
  const probed = await probeVideoFile(videoPath);
13365
13418
  if (!(probed.durationMs > 0)) {
13366
13419
  throw new UsageError(
@@ -13409,13 +13462,13 @@ async function cmdIngest(argv) {
13409
13462
  const clicks = events.filter((e) => e.type === "down").length;
13410
13463
  const seconds = (probed.durationMs / 1e3).toFixed(1);
13411
13464
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
13412
- 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";
13413
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)`;
13414
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`;
13415
13468
  r.done(
13416
13469
  {
13417
13470
  out: outDir,
13418
- recording: basename10(recording),
13471
+ recording: basename11(recording),
13419
13472
  durationMs: probed.durationMs,
13420
13473
  width: probed.width,
13421
13474
  height: probed.height,
@@ -13424,7 +13477,7 @@ async function cmdIngest(argv) {
13424
13477
  hasAudio: probed.hasAudio,
13425
13478
  remuxed,
13426
13479
  cursor: trace ? {
13427
- file: basename10(cursorPath),
13480
+ file: basename11(cursorPath),
13428
13481
  kind: trace.kind,
13429
13482
  events: events.length,
13430
13483
  clicks,
@@ -13436,7 +13489,7 @@ async function cmdIngest(argv) {
13436
13489
  backdrop: plan.backdrop ?? null,
13437
13490
  as: footage ? "footage" : "take"
13438
13491
  },
13439
- `Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
13492
+ `Ingested ${basename11(videoPath)} \u2192 ${outDir}/${basename11(recording)} (${facts})
13440
13493
  ${opened}
13441
13494
  ${cursorLine}
13442
13495
  ${next}`
@@ -13448,7 +13501,7 @@ async function cmdIngest(argv) {
13448
13501
  import { createRequire as createRequire2 } from "module";
13449
13502
  import { existsSync as existsSync23 } from "fs";
13450
13503
  import { mkdir as mkdir9, readFile as readFile14, stat as stat3, writeFile as writeFile7 } from "fs/promises";
13451
- 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";
13452
13505
  import { ALL_FORMATS as ALL_FORMATS2, FilePathSource as FilePathSource2, Input as Input2 } from "mediabunny";
13453
13506
 
13454
13507
  // src/plugin/videoChecks.ts
@@ -13970,7 +14023,7 @@ async function checkVideo(browser, file, outDir) {
13970
14023
  void page.evaluate(decodeInPage, {
13971
14024
  mbCode: await mediabunnyBundle(),
13972
14025
  meterSrc: String(createLoudnessMeter),
13973
- videoUrl: `/${encodeURIComponent(basename11(file))}`,
14026
+ videoUrl: `/${encodeURIComponent(basename12(file))}`,
13974
14027
  sampleFps,
13975
14028
  thumbW: side(facts.width),
13976
14029
  thumbH: side(facts.height),
@@ -14078,7 +14131,7 @@ async function cmdCheckVideo(argv) {
14078
14131
  const r = createReporter(flags.json === true);
14079
14132
  const strict = flags.strict === true;
14080
14133
  const outDir = resolve16(
14081
- strFlag(flags, "out") ?? join24(dirname8(file), `${basename11(file, extname4(file))}-check`)
14134
+ strFlag(flags, "out") ?? join24(dirname8(file), `${basename12(file, extname4(file))}-check`)
14082
14135
  );
14083
14136
  const browser = await launchBrowser();
14084
14137
  try {
@@ -14096,7 +14149,7 @@ async function cmdCheckVideo(argv) {
14096
14149
  import { existsSync as existsSync26, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
14097
14150
  import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile17, rm as rm4 } from "fs/promises";
14098
14151
  import {
14099
- basename as basename13,
14152
+ basename as basename14,
14100
14153
  dirname as dirname9,
14101
14154
  isAbsolute as isAbsolute4,
14102
14155
  join as join27,
@@ -14127,13 +14180,15 @@ import {
14127
14180
  writeFile as writeFile8
14128
14181
  } from "fs/promises";
14129
14182
  import { tmpdir as tmpdir2 } from "os";
14130
- 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";
14131
14185
  import {
14132
14186
  NORMALIZE_MAX_LUFS as NORMALIZE_MAX_LUFS2,
14133
14187
  NORMALIZE_MIN_LUFS as NORMALIZE_MIN_LUFS2,
14134
14188
  clipGain as clipGain2,
14135
14189
  docMediaList,
14136
14190
  docOutputDuration as docOutputDuration3,
14191
+ filmSummaryOf as filmSummaryOf2,
14137
14192
  findStep as findStep2,
14138
14193
  ratedSegments as ratedSegments7,
14139
14194
  resolveAnchor as resolveAnchor4
@@ -14442,7 +14497,7 @@ async function measureInBrowser(path) {
14442
14497
  const bytes = await readFile15(path);
14443
14498
  if (bytes.length > BROWSER_DECODE_MAX_BYTES)
14444
14499
  throw new Error(
14445
- `${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`
14446
14501
  );
14447
14502
  const browser = await launchBrowser();
14448
14503
  try {
@@ -14546,12 +14601,12 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14546
14601
  if (existsSync24(local)) {
14547
14602
  const rel = relative4(resolve17(dir), local);
14548
14603
  const inside = !!rel && !rel.startsWith(`..${sep3}`) && rel !== ".." && !isAbsolute3(rel);
14549
- const key = inside ? rel.split(sep3).join("/") : `music/${basename12(local)}`;
14604
+ const key = inside ? rel.split(sep3).join("/") : `music/${basename13(local)}`;
14550
14605
  if (!inside && !dryRun) {
14551
14606
  await mkdir10(join25(dir, "music"), { recursive: true });
14552
14607
  await copyFile2(local, join25(dir, key));
14553
14608
  }
14554
- return { path: local, key, name: basename12(local) };
14609
+ return { path: local, key, name: basename13(local) };
14555
14610
  }
14556
14611
  const origin = platformOrigin({
14557
14612
  origin: strFlag(flags, "origin"),
@@ -14566,7 +14621,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14566
14621
  const res = await fetch(track.url);
14567
14622
  if (!res.ok) throw new Error(`GET ${track.url} answered ${res.status}`);
14568
14623
  const tmp = await mkdtemp(join25(tmpdir2(), "vos-score-"));
14569
- const path = join25(tmp, basename12(new URL(track.url).pathname) || "track");
14624
+ const path = join25(tmp, basename13(new URL(track.url).pathname) || "track");
14570
14625
  await writeFile8(path, new Uint8Array(await res.arrayBuffer()));
14571
14626
  return {
14572
14627
  path,
@@ -14576,7 +14631,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14576
14631
  };
14577
14632
  }
14578
14633
  var BOOLEAN_FLAGS12 = /* @__PURE__ */ new Set(["json", "help", "dry-run"]);
14579
- 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]';
14580
14635
  async function cmdScore(argv) {
14581
14636
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14582
14637
  const dir = positionals[0];
@@ -14603,8 +14658,39 @@ async function cmdScore(argv) {
14603
14658
  throw new UsageError(`${dir} has no doc.json; run vos plan first`);
14604
14659
  const doc = take.doc;
14605
14660
  const atRaw = strFlag(flags, "at");
14606
- const at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
14607
- 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);
14608
14694
  let measured;
14609
14695
  try {
14610
14696
  r.log(`measuring ${track.name}\u2026`);
@@ -16198,7 +16284,7 @@ function joinDocs(parts, opts = {}) {
16198
16284
  {
16199
16285
  id,
16200
16286
  key: d.source.videoKey,
16201
- name: basename13(part.file.path),
16287
+ name: basename14(part.file.path),
16202
16288
  start: round33(at2.start),
16203
16289
  in: round33(seg.in),
16204
16290
  out: round33(seg.out),
@@ -16443,7 +16529,7 @@ async function cmdJoin(argv) {
16443
16529
  const remuxed = await copyRecording(c.from, to, c.remux);
16444
16530
  if (!remuxed && c.remux !== "mov")
16445
16531
  plan.notes.push(
16446
- `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`
16447
16533
  );
16448
16534
  } else await copyFile3(c.from, to);
16449
16535
  }
@@ -16817,7 +16903,7 @@ async function cmdTakeInfo(argv) {
16817
16903
  // src/plugin/audio.ts
16818
16904
  import { mkdtemp as mkdtemp2, rm as rm5, writeFile as writeFile9 } from "fs/promises";
16819
16905
  import { tmpdir as tmpdir3 } from "os";
16820
- import { basename as basename14, join as join29 } from "path";
16906
+ import { basename as basename15, join as join29 } from "path";
16821
16907
  import { clipGain as clipGain3 } from "@vosjs/studio-core";
16822
16908
  var USAGE4 = "vos audio measure <take> [--json]";
16823
16909
  async function fileFor(dir, key, fetchImpl) {
@@ -16830,7 +16916,7 @@ async function fileFor(dir, key, fetchImpl) {
16830
16916
  const res = await fetchImpl(key);
16831
16917
  if (!res.ok) return { skipped: `GET ${key} answered ${res.status}` };
16832
16918
  const tmp = await mkdtemp2(join29(tmpdir3(), "vos-audio-"));
16833
- const path = join29(tmp, basename14(new URL(key).pathname) || "audio");
16919
+ const path = join29(tmp, basename15(new URL(key).pathname) || "audio");
16834
16920
  await writeFile9(path, new Uint8Array(await res.arrayBuffer()));
16835
16921
  return { path, cleanup: () => rm5(tmp, { recursive: true, force: true }) };
16836
16922
  }
@@ -16910,10 +16996,238 @@ async function cmdAudio(argv) {
16910
16996
  return failed.length ? EXIT_ERROR : EXIT_OK;
16911
16997
  }
16912
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
+
16913
17226
  // src/plugin/look.ts
16914
- import { mkdir as mkdir12 } from "fs/promises";
16915
- import { existsSync as existsSync27 } from "fs";
16916
- import { join as join30, resolve as resolve20 } from "path";
17227
+ import { mkdir as mkdir12, mkdtemp as mkdtemp3, readFile as readFile18, rm as rm6 } from "fs/promises";
17228
+ import { existsSync as existsSync28 } from "fs";
17229
+ import { tmpdir as tmpdir4 } from "os";
17230
+ import { dirname as dirname10, join as join30, resolve as resolve21 } from "path";
16917
17231
  import { headersFor, urlSchemeProblem } from "@vosjs/render-core/record";
16918
17232
  function quote(value) {
16919
17233
  return `"${value.replace(/\s+/g, " ").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -17316,19 +17630,19 @@ async function lookAt(page, url, opts) {
17316
17630
  });
17317
17631
  const ran = [];
17318
17632
  if (!wall || wall.level === "soft") {
17319
- for (const step2 of opts.steps ?? []) {
17320
- const all = page.locator(step2.selector);
17633
+ for (const step of opts.steps ?? []) {
17634
+ const all = page.locator(step.selector);
17321
17635
  const target = all.first();
17322
17636
  try {
17323
17637
  await target.waitFor({ state: "visible", timeout: STEP_TIMEOUT_MS });
17324
17638
  } catch {
17325
17639
  throw new UsageError(
17326
- `--${step2.do} ${step2.selector}: no visible element matches it after ${STEP_TIMEOUT_MS / 1e3} s. Look without the step and take a selector from the list`
17640
+ `--${step.do} ${step.selector}: no visible element matches it after ${STEP_TIMEOUT_MS / 1e3} s. Look without the step and take a selector from the list`
17327
17641
  );
17328
17642
  }
17329
17643
  const count = await all.count();
17330
17644
  const note = count > 1 ? `the first of ${count} matches` : void 0;
17331
- if (step2.do === "click") {
17645
+ if (step.do === "click") {
17332
17646
  const words2 = await target.evaluate(
17333
17647
  (el) => [
17334
17648
  el.getAttribute("aria-label"),
@@ -17344,25 +17658,37 @@ async function lookAt(page, url, opts) {
17344
17658
  if (word) {
17345
17659
  const label = words2.find((w) => spendWord([w])) ?? word;
17346
17660
  throw new LookRefusal(
17347
- refusalLine(step2.selector, label.slice(0, 60), word)
17661
+ refusalLine(step.selector, label.slice(0, 60), word)
17348
17662
  );
17349
17663
  }
17350
- log(`click ${step2.selector}${note ? ` (${note})` : ""}`);
17664
+ log(`click ${step.selector}${note ? ` (${note})` : ""}`);
17351
17665
  await target.click({ timeout: STEP_TIMEOUT_MS });
17352
17666
  } else {
17353
17667
  log(
17354
- `type ${step2.text.length} chars into ${step2.selector}${note ? ` (${note})` : ""}`
17668
+ `type ${step.text.length} chars into ${step.selector}${note ? ` (${note})` : ""}`
17355
17669
  );
17356
- await target.fill(step2.text, { timeout: STEP_TIMEOUT_MS });
17670
+ await target.fill(step.text, { timeout: STEP_TIMEOUT_MS });
17357
17671
  }
17358
17672
  ran.push({
17359
- do: step2.do,
17360
- selector: step2.selector,
17673
+ do: step.do,
17674
+ selector: step.selector,
17361
17675
  ...note ? { note } : {}
17362
17676
  });
17363
17677
  settled = await settle(page);
17364
17678
  }
17365
17679
  }
17680
+ return inspectPage(page, {
17681
+ url,
17682
+ outDir: opts.outDir,
17683
+ waitMs: opts.waitMs,
17684
+ wall,
17685
+ steps: ran,
17686
+ settled
17687
+ });
17688
+ }
17689
+ async function inspectPage(page, opts) {
17690
+ const { url, wall, settled } = opts;
17691
+ const ran = opts.steps;
17366
17692
  if (opts.waitMs && opts.waitMs > 0) await page.waitForTimeout(opts.waitMs);
17367
17693
  await mkdir12(opts.outDir, { recursive: true });
17368
17694
  const screenshot = join30(opts.outDir, "look.png");
@@ -17420,7 +17746,27 @@ async function lookAt(page, url, opts) {
17420
17746
  settled
17421
17747
  };
17422
17748
  }
17423
- var USAGE5 = "vos look <url> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--click <selector>]... [--type <selector>=<text>]... [--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]";
17750
+ function untilProblem(given, steps) {
17751
+ if (given.until !== void 0 && given.actions === void 0)
17752
+ return "--until names a step of a script: pass the script with --actions actions.json";
17753
+ if (given.actions === void 0) return null;
17754
+ if (given.clicks)
17755
+ return "--actions walks the script; --click and --type walk by hand. Pass one";
17756
+ if (given.until === void 0)
17757
+ return "--actions needs --until <step id>: the step whose page you want to see";
17758
+ if (!steps) return null;
17759
+ if (steps.some((s) => s.id === given.until)) return null;
17760
+ const ids = steps.map((s) => s.id).filter((id) => !!id);
17761
+ return `--until ${given.until}: no step in ${given.actions} has that id. ${ids.length ? `Its ids: ${ids.join(", ")}` : 'Its steps have no ids; give the step you want an "id"'}`;
17762
+ }
17763
+ function walkedSteps(steps) {
17764
+ return steps.map((s) => ({
17765
+ do: s.id ? `${s.do} (${s.id})` : s.do,
17766
+ selector: s.selector ?? "",
17767
+ ...s.skipped ? { note: `SKIPPED: ${s.reason ?? "not found"}` } : s.guard ? { note: `guarded (${s.guard}), hovered, not pressed` } : {}
17768
+ }));
17769
+ }
17424
17770
  function lookReport(r) {
17425
17771
  const inDialog = r.controls.filter((c) => c.inDialog).length;
17426
17772
  const head = `Looked at ${r.landed} (${r.viewport.width}\xD7${r.viewport.height}): ${r.screenshot}${r.settled ? "" : "\n note: the page never held still for 0.8 s; looked at it as it was"}`;
@@ -17449,21 +17795,39 @@ async function cmdLook(argv) {
17449
17795
  );
17450
17796
  void multi.click;
17451
17797
  void multi.type;
17452
- const url = positionals[0];
17453
- if (!url) throw new UsageError(USAGE5);
17798
+ const actionsArg = strFlag(flags, "actions");
17799
+ const untilId = strFlag(flags, "until");
17800
+ const early = untilProblem({
17801
+ actions: actionsArg,
17802
+ until: untilId,
17803
+ clicks: steps.length
17804
+ });
17805
+ if (early) throw new UsageError(early);
17806
+ let actions;
17807
+ if (actionsArg) {
17808
+ actions = await loadLookActions(resolve21(actionsArg));
17809
+ const bad = untilProblem(
17810
+ { actions: actionsArg, until: untilId, clicks: 0 },
17811
+ actions.steps
17812
+ );
17813
+ if (bad) throw new UsageError(bad);
17814
+ }
17815
+ const url = positionals[0] ?? actions?.url;
17816
+ if (!url) throw new UsageError(USAGE6);
17454
17817
  const scheme = urlSchemeProblem(url);
17455
17818
  if (scheme) throw new UsageError(scheme);
17456
17819
  const r = createReporter(flags.json === true);
17457
- const viewport = parseViewport2(strFlag(flags, "viewport"));
17458
- const outDir = resolve20(strFlag(flags, "out") ?? "look");
17820
+ const viewportFlag = strFlag(flags, "viewport");
17821
+ const viewport = !viewportFlag && actions?.viewport ? actions.viewport : parseViewport2(viewportFlag);
17822
+ const outDir = resolve21(strFlag(flags, "out") ?? "look");
17459
17823
  const headed = flags.headed === true;
17460
17824
  const waitRaw = strFlag(flags, "wait");
17461
17825
  const waitMs = waitRaw === void 0 ? 0 : Number(waitRaw);
17462
17826
  if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > 6e4)
17463
17827
  throw new UsageError("--wait expects milliseconds, 0 to 60000");
17464
17828
  const storageRaw = strFlag(flags, "storage-state");
17465
- const storageState = storageRaw ? resolve20(storageRaw) : void 0;
17466
- if (storageState && !existsSync27(storageState))
17829
+ const storageState = storageRaw ? resolve21(storageRaw) : void 0;
17830
+ if (storageState && !existsSync28(storageState))
17467
17831
  throw new UsageError(`--storage-state: no such file: ${storageState}`);
17468
17832
  const sessionName = strFlag(flags, "session");
17469
17833
  if (sessionName) {
@@ -17473,7 +17837,7 @@ async function cmdLook(argv) {
17473
17837
  throw new UsageError(
17474
17838
  "--session and --storage-state are two doors to one page: a persistent profile cannot take a storage state. Pass one"
17475
17839
  );
17476
- if (!existsSync27(sessionDir(sessionName)))
17840
+ if (!existsSync28(sessionDir(sessionName)))
17477
17841
  throw new UsageError(
17478
17842
  `no session "${sessionName}". Make one: vos session open <url> --name ${sessionName} (a Chrome window opens; sign in and close it)`
17479
17843
  );
@@ -17485,6 +17849,22 @@ async function cmdLook(argv) {
17485
17849
  throw new UsageError(e instanceof Error ? e.message : String(e));
17486
17850
  }
17487
17851
  const browserArgs = (multi["browser-arg"] ?? []).filter(Boolean);
17852
+ if (actions && untilId && actionsArg)
17853
+ return lookUntil({
17854
+ r,
17855
+ url,
17856
+ actions,
17857
+ actionsPath: resolve21(actionsArg),
17858
+ untilId,
17859
+ viewport,
17860
+ outDir,
17861
+ waitMs,
17862
+ headed,
17863
+ storageState,
17864
+ sessionName,
17865
+ headers,
17866
+ browserArgs
17867
+ });
17488
17868
  const inContext = async (ctx) => {
17489
17869
  if (Object.keys(headers).length)
17490
17870
  await ctx.route("**/*", async (route) => {
@@ -17531,14 +17911,116 @@ async function cmdLook(argv) {
17531
17911
  );
17532
17912
  return result.wall?.level === "hard" ? EXIT_WALL : EXIT_OK;
17533
17913
  }
17914
+ async function loadLookActions(file) {
17915
+ let raw;
17916
+ try {
17917
+ raw = JSON.parse(await readFile18(file, "utf8"));
17918
+ } catch (e) {
17919
+ throw new UsageError(
17920
+ `--actions ${file}: ${e instanceof Error ? e.message : String(e)}`
17921
+ );
17922
+ }
17923
+ const errors = validateActions(raw);
17924
+ if (!errors.length) errors.push(...jsSyntaxProblems(raw));
17925
+ if (errors.length)
17926
+ throw new UsageError(`invalid actions file:
17927
+ ${errors.join("\n ")}`);
17928
+ return raw;
17929
+ }
17930
+ async function lookUntil(o) {
17931
+ const { r } = o;
17932
+ const actions = { ...o.actions, viewport: o.viewport };
17933
+ let wall = null;
17934
+ let looked = null;
17935
+ const scratch = await mkdtemp3(join30(tmpdir4(), "vos-look-"));
17936
+ r.log(
17937
+ `walking ${o.actionsPath} to step ${o.untilId} (a rehearsal: guarded steps hovered, never pressed)\u2026`
17938
+ );
17939
+ const browser = await launchBrowser(o.browserArgs, { headless: !o.headed });
17940
+ let rec;
17941
+ try {
17942
+ const walk = (context2) => recordTake(browser, o.url, actions, takePaths(scratch), r.log, {
17943
+ actionsDir: dirname10(o.actionsPath),
17944
+ storageState: o.storageState,
17945
+ headers: o.headers,
17946
+ context: context2,
17947
+ dryRun: true,
17948
+ stopOnHang: true,
17949
+ onEvent: (e) => r.event(e),
17950
+ onArrival: (arrival) => {
17951
+ const verdict = wallVerdict(arrival);
17952
+ if (verdict && verdict.level === "hard") throw new WallError(verdict);
17953
+ if (verdict) r.log(` WARNING: ${verdict.message}`);
17954
+ wall = verdict;
17955
+ return verdict;
17956
+ },
17957
+ until: {
17958
+ id: o.untilId,
17959
+ at: async (raw) => {
17960
+ const page = raw;
17961
+ const settled = await settle(page);
17962
+ looked = await inspectPage(page, {
17963
+ url: o.url,
17964
+ outDir: o.outDir,
17965
+ waitMs: o.waitMs,
17966
+ wall,
17967
+ steps: [],
17968
+ settled
17969
+ });
17970
+ }
17971
+ }
17972
+ });
17973
+ rec = o.sessionName ? await withSession(o.sessionName, (ctx) => walk(ctx), {
17974
+ headless: !o.headed,
17975
+ viewport: o.viewport
17976
+ }) : await walk(void 0);
17977
+ } finally {
17978
+ await browser.close();
17979
+ await rm6(scratch, { recursive: true, force: true });
17980
+ }
17981
+ const result = looked;
17982
+ const ran = walkedSteps(rec.meta.steps ?? []);
17983
+ const skipped = rec.skipped.length;
17984
+ const teardown = rec.teardown ? `
17985
+ ${teardownLine(rec.teardown)}` : "";
17986
+ if (!result) {
17987
+ const why = rec.stopped ? rec.stopped.reason : "the walk ended before it reached that step";
17988
+ r.done(
17989
+ {
17990
+ ok: false,
17991
+ until: o.untilId,
17992
+ steps: ran,
17993
+ skipped: rec.skipped,
17994
+ ...rec.teardown ? { teardown: rec.teardown } : {}
17995
+ },
17996
+ `NOT LOOKED: step ${o.untilId} was never reached: ${why}.${ran.length ? `
17997
+ walked: ${ran.map((s) => `${s.do}${s.selector ? ` ${s.selector}` : ""}${s.note ? ` (${s.note})` : ""}`).join(", then ")}` : ""}
17998
+ Rehearse the script (vos record --actions ${o.actionsPath} --dry-run) to see where it stops.${teardown}`
17999
+ );
18000
+ return EXIT_USAGE;
18001
+ }
18002
+ const full = { ...result, steps: ran };
18003
+ const warn = skipped ? `
18004
+ WARNING: ${skipped} step${skipped === 1 ? " was" : "s were"} SKIPPED on the way, so this may not be the page the script reaches. Fix ${skipped === 1 ? "it" : "them"}, then look again.` : "";
18005
+ r.done(
18006
+ {
18007
+ ok: skipped === 0 && (!full.wall || full.wall.level === "soft"),
18008
+ until: o.untilId,
18009
+ ...full,
18010
+ ...rec.teardown ? { teardown: rec.teardown } : {}
18011
+ },
18012
+ `${lookReport(full)}${warn}${teardown}`
18013
+ );
18014
+ return skipped ? EXIT_USAGE : EXIT_OK;
18015
+ }
17534
18016
 
17535
18017
  // src/plugin/machineCmd.ts
17536
18018
  import { spawnSync as spawnSync4 } from "child_process";
17537
18019
  import {
17538
18020
  cpSync,
17539
- existsSync as existsSync29,
18021
+ existsSync as existsSync30,
17540
18022
  mkdirSync as mkdirSync6,
17541
- readFileSync as readFileSync11,
18023
+ readFileSync as readFileSync12,
17542
18024
  rmSync as rmSync3,
17543
18025
  writeFileSync as writeFileSync6
17544
18026
  } from "fs";
@@ -17547,8 +18029,8 @@ import { join as join32 } from "path";
17547
18029
  import { chromium as chromium2 } from "playwright";
17548
18030
 
17549
18031
  // src/plugin/machine.ts
17550
- import { existsSync as existsSync28, readFileSync as readFileSync10, readdirSync as readdirSync6, statSync as statSync8 } from "fs";
17551
- import { dirname as dirname10, join as join31 } from "path";
18032
+ import { existsSync as existsSync29, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync9 } from "fs";
18033
+ import { dirname as dirname11, join as join31 } from "path";
17552
18034
  var AGENT_NAMES = [
17553
18035
  "claude",
17554
18036
  "cursor",
@@ -17568,7 +18050,7 @@ var HOME_DIRS = {
17568
18050
  copilot: [".copilot"]
17569
18051
  };
17570
18052
  function detectAgents(root, opts = {}) {
17571
- const exists = opts.exists ?? existsSync28;
18053
+ const exists = opts.exists ?? existsSync29;
17572
18054
  const out = [];
17573
18055
  for (const agent of AGENT_NAMES) {
17574
18056
  for (const rel of PROJECT_DIRS[agent]) {
@@ -17615,7 +18097,7 @@ function skillsDirFor(home) {
17615
18097
  function fallbackSkillsHome(root) {
17616
18098
  return { agent: "codex", dir: join31(root, ".agents"), scope: "project" };
17617
18099
  }
17618
- function installedSkills(dir, exists = existsSync28, list = (p) => {
18100
+ function installedSkills(dir, exists = existsSync29, list = (p) => {
17619
18101
  try {
17620
18102
  return readdirSync6(p);
17621
18103
  } catch {
@@ -17656,7 +18138,7 @@ ${RULES_BLOCK}
17656
18138
  }
17657
18139
  return { text, changed: text !== current };
17658
18140
  }
17659
- function rulesFileFor(root, exists = existsSync28) {
18141
+ function rulesFileFor(root, exists = existsSync29) {
17660
18142
  const agents = join31(root, "AGENTS.md");
17661
18143
  if (exists(agents)) return agents;
17662
18144
  const claude = join31(root, "CLAUDE.md");
@@ -17761,27 +18243,27 @@ function doctorReport(facts) {
17761
18243
  }
17762
18244
  function bundledSkillsVersion(dir) {
17763
18245
  try {
17764
- const v = readFileSync10(join31(dir, "VERSION"), "utf8").trim();
18246
+ const v = readFileSync11(join31(dir, "VERSION"), "utf8").trim();
17765
18247
  return /^\d+\.\d+\.\d+$/.test(v) ? v : null;
17766
18248
  } catch {
17767
18249
  return null;
17768
18250
  }
17769
18251
  }
17770
18252
  function bundledSkillsDir(from) {
17771
- let dir = dirname10(from);
18253
+ let dir = dirname11(from);
17772
18254
  for (let i = 0; i < 6; i++) {
17773
18255
  const pkg = join31(dir, "package.json");
17774
- if (existsSync28(pkg)) {
18256
+ if (existsSync29(pkg)) {
17775
18257
  try {
17776
- const name = JSON.parse(readFileSync10(pkg, "utf8")).name;
18258
+ const name = JSON.parse(readFileSync11(pkg, "utf8")).name;
17777
18259
  if (name === "@vosjs/cli") {
17778
18260
  const skills = join31(dir, "skills");
17779
- return existsSync28(skills) && statSync8(skills).isDirectory() ? skills : null;
18261
+ return existsSync29(skills) && statSync9(skills).isDirectory() ? skills : null;
17780
18262
  }
17781
18263
  } catch {
17782
18264
  }
17783
18265
  }
17784
- const up = dirname10(dir);
18266
+ const up = dirname11(dir);
17785
18267
  if (up === dir) break;
17786
18268
  dir = up;
17787
18269
  }
@@ -17789,7 +18271,7 @@ function bundledSkillsDir(from) {
17789
18271
  }
17790
18272
 
17791
18273
  // src/plugin/machineCmd.ts
17792
- var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
18274
+ var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
17793
18275
  "json",
17794
18276
  "help",
17795
18277
  "global",
@@ -17812,7 +18294,7 @@ function npx() {
17812
18294
  function bundledChromium() {
17813
18295
  try {
17814
18296
  const p = chromium2.executablePath();
17815
- return p && existsSync29(p) ? p : null;
18297
+ return p && existsSync30(p) ? p : null;
17816
18298
  } catch {
17817
18299
  return null;
17818
18300
  }
@@ -17821,7 +18303,7 @@ function probe2() {
17821
18303
  return probeBrowser({
17822
18304
  platform: process.platform,
17823
18305
  env: process.env,
17824
- exists: existsSync29,
18306
+ exists: existsSync30,
17825
18307
  bundled: bundledChromium
17826
18308
  });
17827
18309
  }
@@ -17872,7 +18354,7 @@ async function doctorFacts(opts) {
17872
18354
  };
17873
18355
  }
17874
18356
  async function cmdSetup(argv) {
17875
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18357
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
17876
18358
  const r = reporterFor(flags);
17877
18359
  const root = process.cwd();
17878
18360
  const global = flags.global === true;
@@ -17974,7 +18456,7 @@ async function cmdSetup(argv) {
17974
18456
  let rules = null;
17975
18457
  if (!noRules) {
17976
18458
  const file = rulesFileFor(root);
17977
- const existing = existsSync29(file) ? readFileSync11(file, "utf8") : null;
18459
+ const existing = existsSync30(file) ? readFileSync12(file, "utf8") : null;
17978
18460
  const { text, changed } = applyRulesBlock(existing);
17979
18461
  if (changed) writeFileSync6(file, text);
17980
18462
  rules = { file, changed };
@@ -18005,7 +18487,7 @@ Next: ${report.next_step}`
18005
18487
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18006
18488
  }
18007
18489
  async function cmdDoctor(argv) {
18008
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18490
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18009
18491
  const r = reporterFor(flags);
18010
18492
  const root = process.cwd();
18011
18493
  const homes = detectAgents(root, { home: homedir2(), global: true });
@@ -18024,7 +18506,7 @@ Next: ${report.next_step}`
18024
18506
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18025
18507
  }
18026
18508
  async function cmdWhoami(argv) {
18027
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18509
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18028
18510
  const r = reporterFor(flags);
18029
18511
  const origin = platformOrigin({
18030
18512
  origin: strFlag(flags, "origin"),
@@ -18086,9 +18568,9 @@ async function cmdWhoami(argv) {
18086
18568
  return EXIT_OK;
18087
18569
  }
18088
18570
  function cmdLogout(argv) {
18089
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18571
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18090
18572
  const r = reporterFor(flags);
18091
- const had = existsSync29(CREDENTIALS_PATH);
18573
+ const had = existsSync30(CREDENTIALS_PATH);
18092
18574
  rmSync3(CREDENTIALS_PATH, { force: true });
18093
18575
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18094
18576
  r.done(
@@ -18104,7 +18586,7 @@ function cmdLogout(argv) {
18104
18586
  }
18105
18587
 
18106
18588
  // src/plugin/run.ts
18107
- var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
18589
+ var BOOLEAN_FLAGS16 = /* @__PURE__ */ new Set([
18108
18590
  "json",
18109
18591
  "help",
18110
18592
  "wait",
@@ -18168,8 +18650,12 @@ Take pipeline
18168
18650
  vos join <take> <take|file.mp4>... --out <dir> [--keep-edits|--force] [--captions] [--zooms per-beat] [--mute <file>]... [--json]
18169
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
18170
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
18171
- 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]
18172
- 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
18173
18659
  vos audio measure <take> [--json]
18174
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
18175
18661
  vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
@@ -18503,7 +18989,7 @@ lint-gated, so a bad override fails like a bad doc.json):
18503
18989
  first ready loop (the house backdrop the studio opens on), or a flat ground offline
18504
18990
  `;
18505
18991
  async function loadActions(file) {
18506
- const raw = JSON.parse(await readFile18(file, "utf8"));
18992
+ const raw = JSON.parse(await readFile19(file, "utf8"));
18507
18993
  const errors = validateActions(raw);
18508
18994
  if (!errors.length) errors.push(...jsSyntaxProblems(raw));
18509
18995
  if (errors.length)
@@ -18669,8 +19155,8 @@ function takeBrowserArgs(multi) {
18669
19155
  function takeStorageState(flags) {
18670
19156
  const raw = strFlag(flags, "storage-state");
18671
19157
  if (!raw) return void 0;
18672
- const path = resolve21(raw);
18673
- if (!existsSync30(path))
19158
+ const path = resolve22(raw);
19159
+ if (!existsSync31(path))
18674
19160
  throw new UsageError(`--storage-state: no such file: ${path}`);
18675
19161
  return path;
18676
19162
  }
@@ -18683,7 +19169,7 @@ function takeSessionName(flags, storageState) {
18683
19169
  throw new UsageError(
18684
19170
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
18685
19171
  );
18686
- if (!existsSync30(sessionDir(name)))
19172
+ if (!existsSync31(sessionDir(name)))
18687
19173
  throw new UsageError(
18688
19174
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
18689
19175
  );
@@ -18701,14 +19187,14 @@ async function inSession(name, actions, fn) {
18701
19187
  }
18702
19188
  function reRecord(outDir, flags, r) {
18703
19189
  const wantsReuse = flags.reuse === true;
18704
- if (wantsReuse && !existsSync30(join33(outDir, "doc.json")))
19190
+ if (wantsReuse && !existsSync31(join33(outDir, "doc.json")))
18705
19191
  throw new UsageError(
18706
19192
  `--reuse: ${outDir} has no cut to keep (no doc.json); record without --reuse`
18707
19193
  );
18708
19194
  let previousCut = null;
18709
19195
  return {
18710
19196
  prepare: async () => {
18711
- if (existsSync30(join33(outDir, "meta.json"))) {
19197
+ if (existsSync31(join33(outDir, "meta.json"))) {
18712
19198
  const { prevDoc, kept } = await prepareReRecord(outDir);
18713
19199
  if (prevDoc) previousCut = join33(outDir, PREV_DOC_NAME);
18714
19200
  r.log(
@@ -18725,7 +19211,7 @@ function reRecord(outDir, flags, r) {
18725
19211
  ...from ? {
18726
19212
  reuse: {
18727
19213
  from,
18728
- doc: JSON.parse(await readFile18(from, "utf8"))
19214
+ doc: JSON.parse(await readFile19(from, "utf8"))
18729
19215
  }
18730
19216
  } : {}
18731
19217
  });
@@ -18796,7 +19282,7 @@ function wallGate(flags, r, opts) {
18796
19282
  async function cmdRecord(argv) {
18797
19283
  const { positionals, flags, multi } = parseArgs(
18798
19284
  argv,
18799
- BOOLEAN_FLAGS15,
19285
+ BOOLEAN_FLAGS16,
18800
19286
  MULTI_FLAGS2
18801
19287
  );
18802
19288
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -18812,7 +19298,7 @@ async function cmdRecord(argv) {
18812
19298
  const scheme = urlSchemeProblem2(url);
18813
19299
  if (scheme) throw new UsageError(scheme);
18814
19300
  announceEnvNames(actions, r);
18815
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19301
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
18816
19302
  const storageState = takeStorageState(flags);
18817
19303
  const sessionName = takeSessionName(flags, storageState);
18818
19304
  const browserArgs = takeBrowserArgs(multi);
@@ -18832,7 +19318,7 @@ async function cmdRecord(argv) {
18832
19318
  );
18833
19319
  const hostedActions = await scriptForHost(
18834
19320
  actions,
18835
- dirname11(resolve21(actionsPath))
19321
+ dirname12(resolve22(actionsPath))
18836
19322
  ).catch((e) => {
18837
19323
  throw new UsageError(e instanceof Error ? e.message : String(e));
18838
19324
  });
@@ -18942,7 +19428,7 @@ async function cmdRecord(argv) {
18942
19428
  sessionName,
18943
19429
  actions,
18944
19430
  (context2) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
18945
- actionsDir: dirname11(resolve21(actionsPath)),
19431
+ actionsDir: dirname12(resolve22(actionsPath)),
18946
19432
  storageState,
18947
19433
  headers,
18948
19434
  context: context2,
@@ -19008,7 +19494,7 @@ async function cmdRecord(argv) {
19008
19494
  ...flags["allow-wall"] === true ? ["--allow-wall"] : [],
19009
19495
  ...confirm.map((c) => `--confirm ${c}`),
19010
19496
  // A take that already has a cut keeps it on the new footage.
19011
- ...existsSync30(join33(outDir, "doc.json")) ? ["--reuse"] : []
19497
+ ...existsSync31(join33(outDir, "doc.json")) ? ["--reuse"] : []
19012
19498
  ].join(" ");
19013
19499
  const nextOut = strFlag(flags, "out") ?? "take";
19014
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)` : "";
@@ -19050,7 +19536,7 @@ ${lines.join("\n")}
19050
19536
  sessionName,
19051
19537
  actions,
19052
19538
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19053
- actionsDir: dirname11(resolve21(actionsPath)),
19539
+ actionsDir: dirname12(resolve22(actionsPath)),
19054
19540
  maxDurationSeconds,
19055
19541
  storageState,
19056
19542
  headers,
@@ -19148,7 +19634,7 @@ ${lines.join("\n")}
19148
19634
  async function cmdCreate2(argv) {
19149
19635
  const { positionals, flags, multi } = parseArgs(
19150
19636
  argv,
19151
- BOOLEAN_FLAGS15,
19637
+ BOOLEAN_FLAGS16,
19152
19638
  MULTI_FLAGS2
19153
19639
  );
19154
19640
  const actionsPath = strFlag(flags, "actions");
@@ -19165,7 +19651,7 @@ async function cmdCreate2(argv) {
19165
19651
  const scheme = urlSchemeProblem2(url);
19166
19652
  if (scheme) throw new UsageError(scheme);
19167
19653
  announceEnvNames(actions, r);
19168
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19654
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
19169
19655
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
19170
19656
  const out = positionals[0] ?? join33(outDir, `out.${format}`);
19171
19657
  const parallel = numFlag(flags, "parallel", 1);
@@ -19189,7 +19675,7 @@ async function cmdCreate2(argv) {
19189
19675
  sessionName,
19190
19676
  actions,
19191
19677
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19192
- actionsDir: dirname11(resolve21(actionsPath)),
19678
+ actionsDir: dirname12(resolve22(actionsPath)),
19193
19679
  maxDurationSeconds,
19194
19680
  storageState,
19195
19681
  headers,
@@ -19306,7 +19792,7 @@ async function cmdPlan(argvIn) {
19306
19792
  const argv = bareCaptionsFlag(argvIn);
19307
19793
  const { positionals, flags, multi } = parseArgs(
19308
19794
  argv,
19309
- BOOLEAN_FLAGS15,
19795
+ BOOLEAN_FLAGS16,
19310
19796
  /* @__PURE__ */ new Set(["with"])
19311
19797
  );
19312
19798
  const dir = positionals[0];
@@ -19316,24 +19802,24 @@ async function cmdPlan(argvIn) {
19316
19802
  );
19317
19803
  const r = createReporter(flags.json === true);
19318
19804
  if (flags.fresh === true) {
19319
- await rm6(join33(dir, "doc.json"), { force: true });
19805
+ await rm7(join33(dir, "doc.json"), { force: true });
19320
19806
  r.log("note: --fresh discarded the existing doc.json");
19321
19807
  }
19322
19808
  let reuse;
19323
19809
  if (flags.reuse === true) {
19324
- const from = resolve21(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
19325
- if (!existsSync30(from)) {
19810
+ const from = resolve22(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
19811
+ if (!existsSync31(from)) {
19326
19812
  throw new UsageError(
19327
19813
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
19328
19814
  );
19329
19815
  }
19330
19816
  reuse = {
19331
19817
  from,
19332
- doc: JSON.parse(await readFile18(from, "utf8"))
19818
+ doc: JSON.parse(await readFile19(from, "utf8"))
19333
19819
  };
19334
19820
  }
19335
19821
  const style = await resolveStyleRef(flags);
19336
- const hasDoc = existsSync30(join33(dir, "doc.json"));
19822
+ const hasDoc = existsSync31(join33(dir, "doc.json"));
19337
19823
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
19338
19824
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
19339
19825
  const stepCaptions = strFlag(flags, "captions") === "steps";
@@ -19413,7 +19899,7 @@ async function cmdPlan(argvIn) {
19413
19899
  ${s.reuse.flagged.join("\n ")}` : "") : "";
19414
19900
  r.done(
19415
19901
  {
19416
- take: resolve21(dir),
19902
+ take: resolve22(dir),
19417
19903
  fresh: s.fresh,
19418
19904
  cursorKept: s.cursorKept,
19419
19905
  zoomAuto,
@@ -19542,7 +20028,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
19542
20028
  async function cmdRender(argv) {
19543
20029
  const { positionals, flags, multi } = parseArgs(
19544
20030
  argv,
19545
- BOOLEAN_FLAGS15,
20031
+ BOOLEAN_FLAGS16,
19546
20032
  MULTI_FLAGS2
19547
20033
  );
19548
20034
  const dir = positionals[0];
@@ -19610,7 +20096,7 @@ async function cmdRender(argv) {
19610
20096
  async function cmdFrames(argv) {
19611
20097
  const { positionals, flags, multi } = parseArgs(
19612
20098
  argv,
19613
- BOOLEAN_FLAGS15,
20099
+ BOOLEAN_FLAGS16,
19614
20100
  MULTI_FLAGS2
19615
20101
  );
19616
20102
  const dir = positionals[0];
@@ -19693,7 +20179,7 @@ async function cmdFrames(argv) {
19693
20179
  async function cmdDeliver(argv) {
19694
20180
  const { positionals, flags, multi } = parseArgs(
19695
20181
  argv,
19696
- BOOLEAN_FLAGS15,
20182
+ BOOLEAN_FLAGS16,
19697
20183
  MULTI_FLAGS2
19698
20184
  );
19699
20185
  const dir = positionals[0];
@@ -19793,15 +20279,15 @@ async function resolveStyleRef(flags) {
19793
20279
  }
19794
20280
  async function resolveDocRef(ref, flags, what) {
19795
20281
  const styleRef = ref;
19796
- const file = existsSync30(styleRef) ? resolve21(
20282
+ const file = existsSync31(styleRef) ? resolve22(
19797
20283
  styleRef,
19798
- existsSync30(join33(styleRef, "doc.json")) ? "doc.json" : ""
20284
+ existsSync31(join33(styleRef, "doc.json")) ? "doc.json" : ""
19799
20285
  ) : null;
19800
- if (file && existsSync30(file)) {
20286
+ if (file && existsSync31(file)) {
19801
20287
  return {
19802
20288
  from: file,
19803
20289
  doc: migrateHostedDoc4(
19804
- JSON.parse(await readFile18(file, "utf8"))
20290
+ JSON.parse(await readFile19(file, "utf8"))
19805
20291
  )
19806
20292
  };
19807
20293
  }
@@ -19843,7 +20329,7 @@ async function officialTemplate(origin, ref) {
19843
20329
  }
19844
20330
  }
19845
20331
  async function cmdDigest(argv) {
19846
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20332
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
19847
20333
  const dir = positionals[0];
19848
20334
  if (!dir)
19849
20335
  throw new UsageError(
@@ -19853,7 +20339,7 @@ async function cmdDigest(argv) {
19853
20339
  const take = await loadTake(dir);
19854
20340
  if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
19855
20341
  const transcriptPath = strFlag(flags, "transcript");
19856
- const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile18(transcriptPath, "utf8"))) : null;
20342
+ const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile19(transcriptPath, "utf8"))) : null;
19857
20343
  const style = await resolveStyleRef(flags);
19858
20344
  const noFrames = flags["no-frames"] === true;
19859
20345
  let browser = null;
@@ -19907,19 +20393,19 @@ async function cmdDigest(argv) {
19907
20393
  }
19908
20394
  }
19909
20395
  async function cmdOpen(argv) {
19910
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20396
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
19911
20397
  const dir = positionals[0];
19912
20398
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
19913
20399
  const r = createReporter(flags.json === true);
19914
20400
  const take = await loadTake(dir);
19915
- if (!existsSync30(take.paths.recording)) {
20401
+ if (!existsSync31(take.paths.recording)) {
19916
20402
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
19917
20403
  }
19918
20404
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
19919
20405
  /\/+$/,
19920
20406
  ""
19921
20407
  );
19922
- const server = await startTakeServer(resolve21(dir), {});
20408
+ const server = await startTakeServer(resolve22(dir), {});
19923
20409
  const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
19924
20410
  r.event({ event: "open", server: server.base, url });
19925
20411
  r.log(`take served at ${server.base}`);
@@ -19942,7 +20428,7 @@ async function cmdOpen(argv) {
19942
20428
  return EXIT_OK;
19943
20429
  }
19944
20430
  async function cmdCallout(argv) {
19945
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20431
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
19946
20432
  const dir = positionals[0];
19947
20433
  const shape = positionals[1];
19948
20434
  if (!dir || !shape || !isCalloutShape(shape))
@@ -19966,7 +20452,7 @@ async function cmdCallout(argv) {
19966
20452
  `no register: put a BRAND.md beside the take (vos brand <url> --out BRAND.md; its bgA and accent are the ground and the accent), or pass --ground #hex --accent #hex`
19967
20453
  );
19968
20454
  const codeFile = strFlag(flags, "code-file");
19969
- const code = codeFile ? await readFile18(codeFile, "utf8") : strFlag(flags, "code");
20455
+ const code = codeFile ? await readFile19(codeFile, "utf8") : strFlag(flags, "code");
19970
20456
  const words2 = {
19971
20457
  kicker: strFlag(flags, "kicker"),
19972
20458
  title: strFlag(flags, "title"),
@@ -20041,11 +20527,11 @@ async function cmdCallout(argv) {
20041
20527
  return EXIT_OK;
20042
20528
  }
20043
20529
  async function cmdValidate(argv) {
20044
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20530
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20045
20531
  const target = positionals[0];
20046
20532
  if (!target) throw new UsageError("vos validate <actions.json|take>");
20047
20533
  const r = createReporter(flags.json === true);
20048
- 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;
20049
20535
  if (kitTarget) {
20050
20536
  const picture = flags.picture === true;
20051
20537
  const verdict = await validateKit(kitTarget, { picture });
@@ -20077,9 +20563,9 @@ async function cmdValidate(argv) {
20077
20563
  r.done({ valid: true, target }, `${target}: valid actions file`);
20078
20564
  return EXIT_OK;
20079
20565
  }
20080
- if (!isTakeDir(target) && existsSync30(join33(target, "config.json"))) {
20566
+ if (!isTakeDir(target) && existsSync31(join33(target, "config.json"))) {
20081
20567
  const parsed = JSON.parse(
20082
- await readFile18(join33(target, "config.json"), "utf8")
20568
+ await readFile19(join33(target, "config.json"), "utf8")
20083
20569
  );
20084
20570
  const pre = preflightConfig(parsed);
20085
20571
  const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
@@ -20121,7 +20607,7 @@ async function cmdValidate(argv) {
20121
20607
  const problems = [];
20122
20608
  const warnings = [];
20123
20609
  const notes = [];
20124
- if (!existsSync30(take.paths.recording)) {
20610
+ if (!existsSync31(take.paths.recording)) {
20125
20611
  problems.push("missing recording (re-run record)");
20126
20612
  } else {
20127
20613
  const mismatch = containerMismatch(
@@ -20162,7 +20648,7 @@ async function cmdValidate(argv) {
20162
20648
  const found = localFile(rel, target);
20163
20649
  if (!("refused" in found)) continue;
20164
20650
  problems.push(
20165
- 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)`
20166
20652
  );
20167
20653
  }
20168
20654
  if (flags.picture === true && (take.doc.overlays ?? []).some((o) => o.kind === "html")) {
@@ -20231,22 +20717,25 @@ async function cmdValidate(argv) {
20231
20717
  async function cmdPush3(argv) {
20232
20718
  const { positionals, flags, multi } = parseArgs(
20233
20719
  argv,
20234
- BOOLEAN_FLAGS15,
20720
+ BOOLEAN_FLAGS16,
20235
20721
  MULTI_FLAGS2
20236
20722
  );
20237
20723
  const dir = positionals[0];
20238
20724
  if (!dir)
20239
20725
  throw new UsageError(
20240
- "vos push <take> [--vos|--title|--label|--note|--prompt|--folder|--override|--yes|--key|--api|--bake|--claimable [--share]]"
20726
+ "vos push <take> [--vos|--title|--desc|--label|--note|--prompt|--folder|--override|--yes|--key|--api|--bake|--claimable [--share]]"
20241
20727
  );
20242
20728
  const refusal = takePushRefusal({
20243
20729
  vos: strFlag(flags, "vos"),
20244
- trackedVosId: readSyncState(resolve21(dir))?.vosId ?? null,
20245
- programOnly: ["slug", "desc", "tags", "base", "remix-of", "still"].filter(
20730
+ trackedVosId: readSyncState(resolve22(dir))?.vosId ?? null,
20731
+ programOnly: ["slug", "tags", "base", "remix-of", "still"].filter(
20246
20732
  (name) => hasFlag(flags, name)
20247
20733
  )
20248
20734
  });
20249
20735
  if (refusal) throw new UsageError(refusal);
20736
+ const description = strFlag(flags, "desc");
20737
+ const descProblem = descriptionProblem(description);
20738
+ if (descProblem) throw new UsageError(descProblem);
20250
20739
  const r = createReporter(flags.json === true);
20251
20740
  if (flags.claimable === true) {
20252
20741
  const iterating = ["vos", "folder", "override", "base"].filter(
@@ -20257,12 +20746,13 @@ async function cmdPush3(argv) {
20257
20746
  `--claimable creates a NEW claimable vos; ${iterating.map((f) => `--${f}`).join(", ")} ${iterating.length > 1 ? "apply" : "applies"} to a keyed push`
20258
20747
  );
20259
20748
  const claim = await pushTakeClaimable(
20260
- resolve21(dir),
20749
+ resolve22(dir),
20261
20750
  {
20262
20751
  api: strFlag(flags, "api"),
20263
20752
  origin: strFlag(flags, "origin"),
20264
20753
  title: strFlag(flags, "title"),
20265
20754
  prompt: strFlag(flags, "prompt"),
20755
+ description,
20266
20756
  share: flags.share === true,
20267
20757
  bake: flags.bake === true
20268
20758
  },
@@ -20280,7 +20770,7 @@ reference and the only credential.`
20280
20770
  }
20281
20771
  const overrides = multi.override;
20282
20772
  const result = await pushTake(
20283
- resolve21(dir),
20773
+ resolve22(dir),
20284
20774
  {
20285
20775
  key: strFlag(flags, "key"),
20286
20776
  api: strFlag(flags, "api"),
@@ -20293,6 +20783,7 @@ reference and the only credential.`
20293
20783
  folder: strFlag(flags, "folder"),
20294
20784
  overrides,
20295
20785
  prompt: strFlag(flags, "prompt"),
20786
+ description,
20296
20787
  bake: flags.bake === true
20297
20788
  },
20298
20789
  r
@@ -20331,11 +20822,11 @@ ${landedLines(landed)}` : "")
20331
20822
  return EXIT_OK;
20332
20823
  }
20333
20824
  async function cmdPull2(argv) {
20334
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15, MULTI_FLAGS2);
20825
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16, MULTI_FLAGS2);
20335
20826
  const dir = positionals[0] ?? ".";
20336
20827
  const r = createReporter(flags.json === true);
20337
20828
  const result = await pullTake(
20338
- resolve21(dir),
20829
+ resolve22(dir),
20339
20830
  {
20340
20831
  key: strFlag(flags, "key"),
20341
20832
  api: strFlag(flags, "api"),
@@ -20470,6 +20961,8 @@ async function run(argv) {
20470
20961
  return await cmdScore(rest);
20471
20962
  case "audio":
20472
20963
  return await cmdAudio(rest);
20964
+ case "music":
20965
+ return await cmdMusic(rest);
20473
20966
  case "look":
20474
20967
  return await cmdLook(rest);
20475
20968
  default:
@@ -20512,7 +21005,7 @@ function verdictExit(v, strict, none) {
20512
21005
  async function cmdSession(argv) {
20513
21006
  const { positionals, flags } = parseArgs(
20514
21007
  argv,
20515
- /* @__PURE__ */ new Set([...BOOLEAN_FLAGS15, "headed"])
21008
+ /* @__PURE__ */ new Set([...BOOLEAN_FLAGS16, "headed"])
20516
21009
  );
20517
21010
  const [sub, arg] = positionals;
20518
21011
  const r = createReporter(flags.json === true);
@@ -20557,7 +21050,7 @@ async function cmdSession(argv) {
20557
21050
  if (!arg) throw new UsageError(usage);
20558
21051
  const bad = sessionNameVerdict(arg);
20559
21052
  if (bad) throw new UsageError(bad);
20560
- if (!existsSync30(sessionDir(arg)))
21053
+ if (!existsSync31(sessionDir(arg)))
20561
21054
  throw new UsageError(`no session "${arg}" (vos session list)`);
20562
21055
  const summary3 = await summarizeSession(arg);
20563
21056
  const url = strFlag(flags, "url");
@@ -20591,7 +21084,7 @@ async function cmdSession(argv) {
20591
21084
  if (!arg || !scriptPath) throw new UsageError(usage);
20592
21085
  const bad = sessionNameVerdict(arg);
20593
21086
  if (bad) throw new UsageError(bad);
20594
- if (!existsSync30(sessionDir(arg)))
21087
+ if (!existsSync31(sessionDir(arg)))
20595
21088
  throw new UsageError(`no session "${arg}" (vos session list)`);
20596
21089
  const url = strFlag(flags, "url");
20597
21090
  if (url && !/^https?:\/\//.test(url))
@@ -20684,4 +21177,4 @@ export {
20684
21177
  verbHelp,
20685
21178
  run
20686
21179
  };
20687
- //# sourceMappingURL=chunk-HDJFCTOG.js.map
21180
+ //# sourceMappingURL=chunk-NJBUBXFE.js.map