@vosjs/cli 0.84.0 → 0.86.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.
Files changed (30) hide show
  1. package/README.md +15 -5
  2. package/dist/{assetManifest-32VI7OK7.js → assetManifest-QFJ7X6TT.js} +3 -3
  3. package/dist/{chunk-Q47FUCYN.js → chunk-2LAXJZCG.js} +2 -2
  4. package/dist/{chunk-OWYOJB7K.js → chunk-7A3PZARL.js} +14 -3
  5. package/dist/chunk-7A3PZARL.js.map +1 -0
  6. package/dist/{chunk-EPSYIF2S.js → chunk-PZP76YJI.js} +298 -58
  7. package/dist/chunk-PZP76YJI.js.map +1 -0
  8. package/dist/{chunk-M5ZBQFPG.js → chunk-V3RUNUNP.js} +2 -2
  9. package/dist/{chunk-DM7ADA77.js → chunk-ZXFO32KW.js} +722 -203
  10. package/dist/chunk-ZXFO32KW.js.map +1 -0
  11. package/dist/cli.js +7 -7
  12. package/dist/index.d.ts +37 -13
  13. package/dist/index.js +5 -5
  14. package/dist/manifest-OLVFUZTW.js +8 -0
  15. package/dist/{run-J5Z32LEB.js → run-KB36L2ZI.js} +5 -5
  16. package/package.json +7 -7
  17. package/skills/VERSION +1 -1
  18. package/skills/launch-kit/SKILL.md +6 -2
  19. package/skills/product-video/SKILL.md +13 -3
  20. package/skills/vos-cut/SKILL.md +6 -2
  21. package/skills/vos-score/SKILL.md +15 -5
  22. package/dist/chunk-DM7ADA77.js.map +0 -1
  23. package/dist/chunk-EPSYIF2S.js.map +0 -1
  24. package/dist/chunk-OWYOJB7K.js.map +0 -1
  25. package/dist/manifest-VTOIEUHJ.js +0 -8
  26. /package/dist/{assetManifest-32VI7OK7.js.map → assetManifest-QFJ7X6TT.js.map} +0 -0
  27. /package/dist/{chunk-Q47FUCYN.js.map → chunk-2LAXJZCG.js.map} +0 -0
  28. /package/dist/{chunk-M5ZBQFPG.js.map → chunk-V3RUNUNP.js.map} +0 -0
  29. /package/dist/{manifest-VTOIEUHJ.js.map → manifest-OLVFUZTW.js.map} +0 -0
  30. /package/dist/{run-J5Z32LEB.js.map → run-KB36L2ZI.js.map} +0 -0
@@ -10,7 +10,7 @@ import {
10
10
  reencodeStill,
11
11
  renderStill,
12
12
  videoFormat
13
- } from "./chunk-Q47FUCYN.js";
13
+ } from "./chunk-2LAXJZCG.js";
14
14
  import {
15
15
  PREV_DOC_NAME,
16
16
  PasteFileRefusal,
@@ -46,47 +46,62 @@ import {
46
46
  uploadDocRefs,
47
47
  uploadManifest,
48
48
  writeJson
49
- } from "./chunk-M5ZBQFPG.js";
49
+ } from "./chunk-V3RUNUNP.js";
50
50
  import {
51
+ ACCOUNT_ENV,
51
52
  CREDENTIALS_PATH,
53
+ DEFAULT_STORE,
52
54
  MEDIA_HEAD_BYTES,
53
55
  SYNC_STATE_NAME,
54
56
  UsageError,
57
+ accountFrom,
58
+ accountLabel,
59
+ activate,
55
60
  apiError,
56
61
  apiJson,
57
62
  bearerFor,
58
63
  clientId,
59
64
  containerMismatch,
65
+ credentialFlags,
60
66
  deriveSlug,
61
67
  detailLine,
68
+ findStored,
62
69
  formatChanges,
63
70
  hasFlag,
64
71
  hostedAssetId,
72
+ isActive,
73
+ lastCredential,
74
+ listStored,
65
75
  localFile,
66
76
  manifestRefs,
67
77
  mintAt,
68
78
  nameForType,
69
79
  numFlag2 as numFlag,
80
+ originOf,
70
81
  parseArgs2 as parseArgs,
71
82
  parseVosId,
72
83
  platformOrigin,
73
84
  prepareOutput,
74
85
  programAssets,
75
86
  promptHint,
87
+ readActive,
76
88
  readSyncState,
77
89
  refuseSymlink,
90
+ removeAll,
91
+ removeStored,
78
92
  requireCredential,
79
93
  resolveCredential,
80
94
  resolveMediaType,
81
95
  serverWarnings,
82
96
  sniffMediaType,
83
97
  strFlag,
98
+ switchTarget,
84
99
  writeCredential,
85
100
  writeFileSafe,
86
101
  writeOutput,
87
102
  writeOutputSync,
88
103
  writeSyncState
89
- } from "./chunk-EPSYIF2S.js";
104
+ } from "./chunk-PZP76YJI.js";
90
105
  import {
91
106
  deadLine,
92
107
  deadWarns
@@ -149,8 +164,8 @@ function landedLines(l) {
149
164
 
150
165
  // src/plugin/run.ts
151
166
  import { mkdir as mkdir13, readFile as readFile19, rm as rm7 } from "fs/promises";
152
- import { existsSync as existsSync30 } from "fs";
153
- import { dirname as dirname12, join as join33, resolve as resolve21 } from "path";
167
+ import { existsSync as existsSync31 } from "fs";
168
+ import { dirname as dirname12, join as join33, resolve as resolve22 } from "path";
154
169
  import {
155
170
  END_CARD_FROM as END_CARD_FROM2,
156
171
  docOutputDuration as docOutputDuration5,
@@ -4157,6 +4172,7 @@ import {
4157
4172
  sameMedia,
4158
4173
  spanOutputExtent as spanOutputExtent3
4159
4174
  } from "@vosjs/studio-core";
4175
+ import { rankBeds, trackBySlug } from "@vosjs/shared/music";
4160
4176
 
4161
4177
  // src/plugin/captionFit.ts
4162
4178
  var CAPTION_MAX = 3;
@@ -4289,10 +4305,30 @@ function proposeTransitions(input, kind) {
4289
4305
  }
4290
4306
  return { doc, count, skipped };
4291
4307
  }
4292
- function pickTrack(catalog, ask) {
4308
+ function pickBed(catalog, ask, film) {
4293
4309
  if (!catalog || !ask || off(ask)) return null;
4294
4310
  const want = ask.trim().toLowerCase();
4295
- return catalog.tracks.find((t) => t.slug.toLowerCase() === want) ?? catalog.tracks.find((t) => (t.mood ?? "").toLowerCase() === want) ?? null;
4311
+ const exact = trackBySlug(catalog.tracks, want);
4312
+ if (exact) return { track: exact, via: "slug", ranked: null, runnerUp: null };
4313
+ if (film) {
4314
+ const oneWord = !/\s/.test(want);
4315
+ const ranked = rankBeds(catalog.tracks, {
4316
+ ...film,
4317
+ ...oneWord ? { mood: want } : { words: want }
4318
+ }).filter((r) => r.score !== null);
4319
+ const named = oneWord ? catalog.tracks.some(
4320
+ (t) => (t.mood ?? "").toLowerCase() === want || (t.tags ?? []).some((x) => x.toLowerCase() === want)
4321
+ ) : ranked.some((r) => (r.scores?.fit ?? 0) > 0);
4322
+ if (!named || !ranked.length) return null;
4323
+ return {
4324
+ track: ranked[0].track,
4325
+ via: "ranked",
4326
+ ranked: ranked[0],
4327
+ runnerUp: ranked[1] ?? null
4328
+ };
4329
+ }
4330
+ const first = catalog.tracks.find((t) => (t.mood ?? "").toLowerCase() === want) ?? null;
4331
+ return first ? { track: first, via: "first", ranked: null, runnerUp: null } : null;
4296
4332
  }
4297
4333
  function clickTimes(doc, range2) {
4298
4334
  const rated = ratedSegments4(doc);
@@ -4533,26 +4569,32 @@ function proposeMotion(input, opts) {
4533
4569
  const clips = (doc.audio ?? []).filter(
4534
4570
  (a) => a.id !== BED_ID && !a.id.startsWith(CLICK_ID_PREFIX)
4535
4571
  );
4536
- const track = pickTrack(catalog, launch.music);
4537
- if (track) {
4572
+ const picked = pickBed(catalog, launch.music, opts.film);
4573
+ if (picked) {
4574
+ const { track } = picked;
4538
4575
  const hasMic = !!doc.source.micKey;
4539
4576
  const fadeOut = Math.min(2.5, length * 0.15);
4577
+ const place2 = picked.ranked?.placement ?? null;
4578
+ const start = place2 ? place2.in : 0;
4579
+ const covers = track.duration - start >= length;
4540
4580
  clips.push({
4541
4581
  id: BED_ID,
4542
4582
  key: track.url,
4543
4583
  name: track.title,
4544
4584
  start: 0,
4545
- in: 0,
4546
- out: Math.min(track.duration, length),
4585
+ in: start,
4586
+ out: place2 ? place2.out : Math.min(track.duration, length),
4547
4587
  duration: track.duration,
4548
4588
  gain: hasMic ? 0.35 : 0.5,
4549
- fadeIn: 0.6,
4589
+ fadeIn: start > 0 ? 0.8 : 0.6,
4550
4590
  fadeOut,
4551
- loop: track.duration < length,
4552
- loopLen: track.duration < length ? Math.round(length * 1e3) / 1e3 : void 0,
4591
+ loop: place2 ? place2.loop : !covers,
4592
+ loopLen: place2 ? place2.loopLen : !covers ? Math.round(length * 1e3) / 1e3 : void 0,
4553
4593
  duck: hasMic
4554
4594
  });
4555
- notes.push(`bed ${track.slug}`);
4595
+ notes.push(
4596
+ 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}`
4597
+ );
4556
4598
  } else if (launch.music && !off(launch.music)) {
4557
4599
  skipped.push(
4558
4600
  `music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`
@@ -4829,6 +4871,7 @@ async function deliverTake(browser, dir, opts) {
4829
4871
 
4830
4872
  // src/plugin/login.ts
4831
4873
  import { hostname } from "os";
4874
+ var storeWith = (store) => store ?? ((key, origin, account) => writeCredential(key, origin, void 0, account));
4832
4875
  var LoginUnsupportedError = class extends Error {
4833
4876
  constructor() {
4834
4877
  super("this origin does not support browser login");
@@ -4905,12 +4948,14 @@ async function browserLogin(origin, r, opts) {
4905
4948
  continue;
4906
4949
  }
4907
4950
  if (res.status === 200 && status === "ok") {
4908
- const path = (opts.store ?? writeCredential)(
4951
+ const account = accountFrom(res.body.user);
4952
+ const path = storeWith(opts.store)(
4909
4953
  res.body.key,
4910
- origin
4954
+ origin,
4955
+ account
4911
4956
  );
4912
4957
  const user = typeof res.body.user === "object" && res.body.user !== null ? res.body.user.name ?? "" : "";
4913
- return { path, user };
4958
+ return { path, user, account };
4914
4959
  }
4915
4960
  if (res.status === 429) {
4916
4961
  if (status === "key_limit" && !capNoted) {
@@ -4948,16 +4993,19 @@ async function handoffLogin(url, r, opts = {}) {
4948
4993
  body: { token: parsed.token, hostname: hostname().slice(0, 64) }
4949
4994
  });
4950
4995
  if (res.status === 200 && res.body.status === "ok") {
4951
- const path = (opts.store ?? writeCredential)(
4996
+ const account = accountFrom(res.body.user);
4997
+ const path = storeWith(opts.store)(
4952
4998
  res.body.key,
4953
- parsed.origin
4999
+ parsed.origin,
5000
+ account
4954
5001
  );
4955
5002
  const user = typeof res.body.user === "object" && res.body.user !== null ? res.body.user.name ?? "" : "";
4956
5003
  return {
4957
5004
  path,
4958
5005
  user,
4959
5006
  keyName: String(res.body.keyName ?? ""),
4960
- origin: parsed.origin
5007
+ origin: parsed.origin,
5008
+ account
4961
5009
  };
4962
5010
  }
4963
5011
  if (res.status === 404 && !res.body.status) {
@@ -6039,14 +6087,14 @@ function chromeTree(rows, pid) {
6039
6087
  }
6040
6088
  function readChromeTree(pid) {
6041
6089
  if (process.platform === "win32") return Promise.resolve(null);
6042
- return new Promise((resolve22) => {
6090
+ return new Promise((resolve23) => {
6043
6091
  execFile2(
6044
6092
  "ps",
6045
6093
  ["-A", "-o", "pid=", "-o", "ppid=", "-o", "command="],
6046
6094
  { maxBuffer: 16 * 1024 * 1024, timeout: 5e3 },
6047
6095
  (err, stdout) => {
6048
- if (err) resolve22(null);
6049
- else resolve22(chromeTree(parseProcessTable(stdout), pid));
6096
+ if (err) resolve23(null);
6097
+ else resolve23(chromeTree(parseProcessTable(stdout), pid));
6050
6098
  }
6051
6099
  );
6052
6100
  });
@@ -6148,7 +6196,7 @@ function enterPressed() {
6148
6196
  } };
6149
6197
  const rl = createInterface({ input: process.stdin });
6150
6198
  const pressed = new Promise(
6151
- (resolve22) => rl.once("line", () => resolve22())
6199
+ (resolve23) => rl.once("line", () => resolve23())
6152
6200
  );
6153
6201
  return { pressed, close: () => rl.close() };
6154
6202
  }
@@ -6321,14 +6369,14 @@ async function openSessionWindow(opts) {
6321
6369
  const child = spawn(exe, args, { stdio: "ignore" });
6322
6370
  let failed = null;
6323
6371
  let code = null;
6324
- const exited = new Promise((resolve22) => {
6372
+ const exited = new Promise((resolve23) => {
6325
6373
  child.once("exit", (c) => {
6326
6374
  code = c;
6327
- resolve22();
6375
+ resolve23();
6328
6376
  });
6329
6377
  child.once("error", (e) => {
6330
6378
  failed = e;
6331
- resolve22();
6379
+ resolve23();
6332
6380
  });
6333
6381
  });
6334
6382
  try {
@@ -6776,12 +6824,13 @@ async function encodeRecording(browser, takeDir, onProgress) {
6776
6824
  // src/plugin/plan.ts
6777
6825
  import { existsSync as existsSync11 } from "fs";
6778
6826
  import { readFile as readFile9 } from "fs/promises";
6779
- import { join as join11 } from "path";
6827
+ import { basename, join as join11 } from "path";
6780
6828
  import {
6781
6829
  DEFAULT_FRAME_STYLE,
6782
6830
  STYLE_FIELDS,
6783
6831
  copyLayout,
6784
6832
  copyStyle,
6833
+ filmSummaryOf,
6785
6834
  isRejected,
6786
6835
  isStageClip,
6787
6836
  layoutOf,
@@ -7382,7 +7431,10 @@ async function planTake(dir, opts = {}) {
7382
7431
  withPageSound(doc, meta.media);
7383
7432
  let motion;
7384
7433
  if (opts.motion && (fresh || opts.motion.again)) {
7385
- const proposed = proposeMotion(doc, opts.motion);
7434
+ const proposed = proposeMotion(doc, {
7435
+ ...opts.motion,
7436
+ film: filmSummaryOf(doc, { seed: basename(join11(dir)) })
7437
+ });
7386
7438
  doc = proposed.doc;
7387
7439
  motion = {
7388
7440
  notes: proposed.notes,
@@ -7415,14 +7467,14 @@ import { createHash } from "crypto";
7415
7467
  import { existsSync as existsSync13, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
7416
7468
  import { readFile as readFile11 } from "fs/promises";
7417
7469
  import { createInterface as createInterface2 } from "readline/promises";
7418
- import { basename as basename2, join as join14 } from "path";
7470
+ import { basename as basename3, join as join14 } from "path";
7419
7471
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7420
7472
 
7421
7473
  // src/plugin/takeScript.ts
7422
7474
  import { existsSync as existsSync12 } from "fs";
7423
7475
  import { readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
7424
7476
  import { readFileSync as readFileSync2 } from "fs";
7425
- import { basename, join as join12 } from "path";
7477
+ import { basename as basename2, join as join12 } from "path";
7426
7478
  function scriptFromText(text) {
7427
7479
  try {
7428
7480
  const v = JSON.parse(text);
@@ -7468,7 +7520,7 @@ function publicPeers(peers) {
7468
7520
  if (!p || typeof p !== "object") return p;
7469
7521
  const peer = p;
7470
7522
  if (typeof peer.storageState !== "string") return peer;
7471
- return { ...peer, storageState: basename(peer.storageState) };
7523
+ return { ...peer, storageState: basename2(peer.storageState) };
7472
7524
  });
7473
7525
  }
7474
7526
  function embedScript(doc, dir) {
@@ -7572,7 +7624,7 @@ async function cmdList(argv) {
7572
7624
  origin: strFlag(flags, "origin"),
7573
7625
  api: strFlag(flags, "api")
7574
7626
  });
7575
- const key = requireCredential(strFlag(flags, "key"));
7627
+ const key = requireCredential(credentialFlags(flags));
7576
7628
  const rows = await listFolders(origin, key);
7577
7629
  if (r.json) {
7578
7630
  r.done({ folders: rows }, "");
@@ -7597,7 +7649,7 @@ async function cmdCreate(argv) {
7597
7649
  origin: strFlag(flags, "origin"),
7598
7650
  api: strFlag(flags, "api")
7599
7651
  });
7600
- const key = requireCredential(strFlag(flags, "key"));
7652
+ const key = requireCredential(credentialFlags(flags));
7601
7653
  const parentRef = strFlag(flags, "parent");
7602
7654
  let parentId;
7603
7655
  if (parentRef) {
@@ -7632,7 +7684,7 @@ async function cmdMove(argv) {
7632
7684
  origin: strFlag(flags, "origin"),
7633
7685
  api: strFlag(flags, "api")
7634
7686
  });
7635
- const key = requireCredential(strFlag(flags, "key"));
7687
+ const key = requireCredential(credentialFlags(flags));
7636
7688
  const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
7637
7689
  let failed = 0;
7638
7690
  const moved = [];
@@ -7692,7 +7744,7 @@ async function cmdPull(argv) {
7692
7744
  origin: strFlag(flags, "origin"),
7693
7745
  api: strFlag(flags, "api")
7694
7746
  });
7695
- const key = requireCredential(strFlag(flags, "key"));
7747
+ const key = requireCredential(credentialFlags(flags));
7696
7748
  const folder = resolveFolder(await listFolders(origin, key), ref);
7697
7749
  const res = await apiJson(origin, `/api/folders/${folder.id}`, { key });
7698
7750
  if (res.status !== 200)
@@ -7901,9 +7953,10 @@ function hostOf(origin) {
7901
7953
 
7902
7954
  // src/plugin/sync.ts
7903
7955
  function apiContext(flags) {
7956
+ const origin = platformOrigin(flags);
7904
7957
  return {
7905
- origin: platformOrigin(flags),
7906
- key: requireCredential(flags.key, platformOrigin(flags))
7958
+ origin,
7959
+ key: requireCredential({ key: flags.key, account: flags.account, origin })
7907
7960
  };
7908
7961
  }
7909
7962
  async function api(ctx, path, init) {
@@ -8098,7 +8151,7 @@ async function pushTake(dir, flags, r) {
8098
8151
  const lowered = lowerToComposition3(docForPush);
8099
8152
  const config = { ...lowered.config, data: lowered.data };
8100
8153
  if (!state) {
8101
- const title = flags.title ?? `Take ${basename2(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8154
+ const title = flags.title ?? `Take ${basename3(dir)} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8102
8155
  const slug = `${title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "take"}-${Date.now().toString(36)}`;
8103
8156
  const created = await api(ctx, "/vos", {
8104
8157
  method: "POST",
@@ -8290,7 +8343,7 @@ async function pullTake(dir, flags, r) {
8290
8343
  const doc = hostedDoc;
8291
8344
  const localRecording = takeMediaFile(dir, "recording", RECORDING_NAME);
8292
8345
  if (existsSync13(localRecording)) {
8293
- doc.source.videoKey = basename2(localRecording);
8346
+ doc.source.videoKey = basename3(localRecording);
8294
8347
  }
8295
8348
  await writeJson(join14(dir, "doc.json"), doc, true);
8296
8349
  const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
@@ -8390,7 +8443,7 @@ async function pushTakeClaimable(dir, flags, r) {
8390
8443
  }
8391
8444
  const lowered = lowerToComposition3(docForPush);
8392
8445
  const config = { ...lowered.config, data: lowered.data };
8393
- const title = (flags.title ?? (state?.title ? `${state.title} remix` : basename2(dir))).slice(0, 200);
8446
+ const title = (flags.title ?? (state?.title ? `${state.title} remix` : basename3(dir))).slice(0, 200);
8394
8447
  const body = { title, config, doc: docForPush };
8395
8448
  if (state?.vosId) body.remixOfId = state.vosId;
8396
8449
  if (flags.prompt) body.prompt = flags.prompt;
@@ -8418,7 +8471,7 @@ async function pushTakeClaimable(dir, flags, r) {
8418
8471
  import { existsSync as existsSync15 } from "fs";
8419
8472
  import { mkdir as mkdir7, readFile as readFile12 } from "fs/promises";
8420
8473
  import { createInterface as createInterface3 } from "readline/promises";
8421
- import { basename as basename4, dirname as dirname2, join as join15 } from "path";
8474
+ import { basename as basename5, dirname as dirname2, join as join15 } from "path";
8422
8475
  import {
8423
8476
  CURRENT_CONFIG_VERSION,
8424
8477
  migrateConfig,
@@ -8427,7 +8480,7 @@ import {
8427
8480
  import { lowerProgramDoc, migrateHostedDoc as migrateHostedDoc3 } from "@vosjs/studio-core";
8428
8481
 
8429
8482
  // src/plugin/programFiles.ts
8430
- import { basename as basename3, extname as extname2, relative as relative2 } from "path";
8483
+ import { basename as basename4, extname as extname2, relative as relative2 } from "path";
8431
8484
  import { existsSync as existsSync14, realpathSync as realpathSync2 } from "fs";
8432
8485
  function isLocalName(value) {
8433
8486
  if (/^(https?:|data:|blob:|asset:|media:)/.test(value)) return false;
@@ -8438,7 +8491,7 @@ function isLocalName(value) {
8438
8491
  }
8439
8492
  var FILE_EXT = /\.(png|jpe?g|webp|gif|avif|svg|mp4|webm|mov|mp3|m4a|ogg|wav|flac|aac|glb|gltf|woff2?|ttf|otf|hdr|exr)$/i;
8440
8493
  function manifestName(file, taken) {
8441
- const stem = basename3(file, extname2(file)).replace(/[^A-Za-z0-9_]/g, "_").replace(/^(?=[0-9])/, "_") || "file";
8494
+ const stem = basename4(file, extname2(file)).replace(/[^A-Za-z0-9_]/g, "_").replace(/^(?=[0-9])/, "_") || "file";
8442
8495
  let name = stem;
8443
8496
  for (let n = 2; taken.has(name); n++) name = `${stem}_${n}`;
8444
8497
  taken.add(name);
@@ -8857,7 +8910,7 @@ async function cmdPushProgram(argv) {
8857
8910
  "--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
8858
8911
  );
8859
8912
  }
8860
- const fallback = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos program";
8913
+ const fallback = state?.title ? `${state.title} remix` : basename5(source).replace(/\.json$/i, "") || "vos program";
8861
8914
  const title2 = (strFlag(flags, "title") ?? fallback).slice(0, 100);
8862
8915
  const files2 = await collectFiles(config, programDoc, dir, r);
8863
8916
  let uploadToken;
@@ -8913,7 +8966,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8913
8966
  );
8914
8967
  return EXIT_OK;
8915
8968
  }
8916
- const key = requireCredential(strFlag(flags, "key"));
8969
+ const key = requireCredential(credentialFlags(flags));
8917
8970
  void flags.yes;
8918
8971
  const files = await collectFiles(config, programDoc, dir, r);
8919
8972
  await files.upload({ origin, key });
@@ -9031,7 +9084,7 @@ ${landedLines(landed2)}` : "")
9031
9084
  return EXIT_OK;
9032
9085
  }
9033
9086
  const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
9034
- const fallbackTitle = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos remix";
9087
+ const fallbackTitle = state?.title ? `${state.title} remix` : basename5(source).replace(/\.json$/i, "") || "vos remix";
9035
9088
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
9036
9089
  const pushFolder = folderRef ? await resolvePushFolder(origin, key, folderRef) : void 0;
9037
9090
  const folderId = pushFolder?.id;
@@ -9128,7 +9181,7 @@ async function cmdPullProgram(argv) {
9128
9181
  `no base version in ${dir}/vos.json \u2014 pass --since <versionId>`
9129
9182
  );
9130
9183
  }
9131
- const key = requireCredential(strFlag(flags, "key"));
9184
+ const key = requireCredential(credentialFlags(flags));
9132
9185
  const res = await apiJson(
9133
9186
  origin,
9134
9187
  `/api/vos/${vosId}/changes?since=${encodeURIComponent(since)}`,
@@ -9227,16 +9280,21 @@ async function cmdLogin(argv) {
9227
9280
  });
9228
9281
  const handoffUrl = strFlag(flags, "handoff");
9229
9282
  if (handoffUrl) {
9283
+ const before = await nameActiveCredential(
9284
+ parseHandoffUrl(handoffUrl)?.origin ?? origin
9285
+ );
9230
9286
  const done = await handoffLogin(handoffUrl, r);
9231
9287
  r.done(
9232
9288
  {
9233
9289
  path: done.path,
9234
9290
  origin: done.origin,
9235
9291
  user: done.user,
9292
+ account: done.account,
9236
9293
  keyName: done.keyName,
9294
+ ...setAsideFacts(before, done.account),
9237
9295
  next_step: "the link is spent; vos push when you want the work hosted"
9238
9296
  },
9239
- `Signed in${done.user ? ` as ${done.user}` : ""} \u2014 key "${done.keyName}" stored at ${done.path} (used by every vos platform verb). The link is spent.`
9297
+ `Signed in${signedAs(done.user, done.account)} \u2014 key "${done.keyName}" stored at ${done.path} (used by every vos platform verb). The link is spent.${setAsideLine(before, done.account)}`
9240
9298
  );
9241
9299
  return EXIT_OK;
9242
9300
  }
@@ -9244,13 +9302,14 @@ async function cmdLogin(argv) {
9244
9302
  if (explicit) return storeKey(explicit, origin, r);
9245
9303
  try {
9246
9304
  const openBrowser = process.stdout.isTTY === true && !process.env.SSH_CONNECTION && !process.env.SSH_TTY && flags.json !== true && flags["no-browser"] !== true;
9247
- const { path, user } = await browserLogin(origin, r, {
9305
+ const before = await nameActiveCredential(origin);
9306
+ const { path, user, account } = await browserLogin(origin, r, {
9248
9307
  label: strFlag(flags, "label"),
9249
9308
  openBrowser
9250
9309
  });
9251
9310
  r.done(
9252
- { path, origin, user },
9253
- `Signed in${user ? ` as ${user}` : ""} \u2014 key stored at ${path} (used by every vos platform verb)`
9311
+ { path, origin, user, account, ...setAsideFacts(before, account) },
9312
+ `Signed in${signedAs(user, account)} \u2014 key stored at ${path} (used by every vos platform verb).${setAsideLine(before, account)}`
9254
9313
  );
9255
9314
  return EXIT_OK;
9256
9315
  } catch (e) {
@@ -9268,17 +9327,57 @@ async function cmdLogin(argv) {
9268
9327
  return storeKey(key, origin, r);
9269
9328
  }
9270
9329
  async function storeKey(key, origin, r) {
9271
- const probe3 = await apiJson(origin, "/api/folders", { key });
9272
- if (probe3.status === 401 || probe3.status === 403) {
9273
- throw new Error(apiError("validate key", probe3));
9330
+ const who = await apiJson(origin, "/api/user/whoami", { key });
9331
+ if (who.status === 401 || who.status === 403) {
9332
+ throw new Error(apiError("validate key", who));
9333
+ }
9334
+ let account = null;
9335
+ if (who.status === 200) account = accountFrom(who.body.user);
9336
+ else {
9337
+ const probe3 = await apiJson(origin, "/api/folders", { key });
9338
+ if (probe3.status === 401 || probe3.status === 403) {
9339
+ throw new Error(apiError("validate key", probe3));
9340
+ }
9274
9341
  }
9275
- const path = writeCredential(key, origin);
9342
+ const before = await nameActiveCredential(origin);
9343
+ const path = writeCredential(key, origin, void 0, account);
9276
9344
  r.done(
9277
- { path, origin },
9278
- `Signed in \u2014 key stored at ${path} (used by every vos platform verb)`
9345
+ { path, origin, account, ...setAsideFacts(before, account) },
9346
+ `Signed in${signedAs("", account)} \u2014 key stored at ${path} (used by every vos platform verb).${setAsideLine(before, account)}`
9279
9347
  );
9280
9348
  return EXIT_OK;
9281
9349
  }
9350
+ function signedAs(user, account) {
9351
+ if (account) return ` as ${accountLabel(account)}`;
9352
+ return user ? ` as ${user}` : "";
9353
+ }
9354
+ async function nameActiveCredential(origin) {
9355
+ const active = readActive(DEFAULT_STORE);
9356
+ if (!active || active.origin !== originOf(origin)) return active;
9357
+ if (active.account) return active;
9358
+ try {
9359
+ const who = await apiJson(origin, "/api/user/whoami", { key: active.key });
9360
+ const account = who.status === 200 ? accountFrom(who.body.user) : null;
9361
+ if (!account) return active;
9362
+ const named = { ...active, account };
9363
+ activate(DEFAULT_STORE, named);
9364
+ return named;
9365
+ } catch {
9366
+ return active;
9367
+ }
9368
+ }
9369
+ function setAsideFacts(before, account) {
9370
+ if (!before || account && before.account?.id === account.id) return {};
9371
+ return before.account ? { setAside: before.account } : { replaced: true };
9372
+ }
9373
+ function setAsideLine(before, account) {
9374
+ const facts = setAsideFacts(before, account);
9375
+ if (facts.setAside)
9376
+ return ` ${accountLabel(facts.setAside)} stays stored; vos switch ${facts.setAside.handle ?? facts.setAside.id} when you need it.`;
9377
+ if (facts.replaced)
9378
+ return " The credential stored before had no name on file and was replaced.";
9379
+ return "";
9380
+ }
9282
9381
  function isTakeDir(target) {
9283
9382
  return directoryKind(target) === "take";
9284
9383
  }
@@ -9357,7 +9456,7 @@ async function writeProgramDoc(dir, hosted) {
9357
9456
  // src/plugin/asset.ts
9358
9457
  import { createHash as createHash2 } from "crypto";
9359
9458
  import { readFileSync as readFileSync4 } from "fs";
9360
- import { basename as basename5 } from "path";
9459
+ import { basename as basename6 } from "path";
9361
9460
 
9362
9461
  // src/plugin/assetWords.ts
9363
9462
  function hostedFile(raw) {
@@ -9594,7 +9693,7 @@ async function cmdRename(argv) {
9594
9693
  origin: strFlag(flags, "origin"),
9595
9694
  api: strFlag(flags, "api")
9596
9695
  });
9597
- const key = requireCredential(strFlag(flags, "key"));
9696
+ const key = requireCredential(credentialFlags(flags));
9598
9697
  const res = await apiJson(origin, `/api/assets/${assetId}`, {
9599
9698
  method: "PATCH",
9600
9699
  key,
@@ -9617,12 +9716,12 @@ async function cmdPush(argv) {
9617
9716
  origin: strFlag(flags, "origin"),
9618
9717
  api: strFlag(flags, "api")
9619
9718
  });
9620
- const key = requireCredential(strFlag(flags, "key"));
9719
+ const key = requireCredential(credentialFlags(flags));
9621
9720
  const folderRef = strFlag(flags, "folder");
9622
9721
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9623
9722
  const uploaded = [];
9624
9723
  for (const file of positionals) {
9625
- const name = basename5(file);
9724
+ const name = basename6(file);
9626
9725
  const body = readFileSync4(file);
9627
9726
  const media = resolveMediaType({
9628
9727
  head: body.subarray(0, MEDIA_HEAD_BYTES),
@@ -9698,7 +9797,7 @@ async function cmdImport(argv) {
9698
9797
  origin: strFlag(flags, "origin"),
9699
9798
  api: strFlag(flags, "api")
9700
9799
  });
9701
- const key = requireCredential(strFlag(flags, "key"));
9800
+ const key = requireCredential(credentialFlags(flags));
9702
9801
  const folderRef = strFlag(flags, "folder");
9703
9802
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9704
9803
  const refused = fetchRefusal(url);
@@ -9782,7 +9881,7 @@ async function cmdList2(argv) {
9782
9881
  origin: strFlag(flags, "origin"),
9783
9882
  api: strFlag(flags, "api")
9784
9883
  });
9785
- const key = requireCredential(strFlag(flags, "key"));
9884
+ const key = requireCredential(credentialFlags(flags));
9786
9885
  const params = new URLSearchParams({
9787
9886
  limit: String(Math.min(200, Math.max(1, numFlag(flags, "limit", 50))))
9788
9887
  });
@@ -9815,7 +9914,7 @@ async function cmdUsage(argv) {
9815
9914
  origin: strFlag(flags, "origin"),
9816
9915
  api: strFlag(flags, "api")
9817
9916
  });
9818
- const key = requireCredential(strFlag(flags, "key"));
9917
+ const key = requireCredential(credentialFlags(flags));
9819
9918
  const res = await apiJson(origin, "/api/user/usage", { key });
9820
9919
  if (res.status !== 200) throw new Error(apiError("read usage", res));
9821
9920
  const usage = res.body.usage ?? {};
@@ -9842,7 +9941,7 @@ async function cmdWhy(argv) {
9842
9941
  origin: strFlag(flags, "origin"),
9843
9942
  api: strFlag(flags, "api")
9844
9943
  });
9845
- const key = requireCredential(strFlag(flags, "key"));
9944
+ const key = requireCredential(credentialFlags(flags));
9846
9945
  const res = await apiJson(origin, `/api/assets/${assetId}`, { key });
9847
9946
  if (res.status !== 200) throw new Error(apiError(`read ${assetId}`, res));
9848
9947
  const asset = hostedFile(res.body.asset);
@@ -9937,7 +10036,7 @@ function context(flags) {
9937
10036
  origin: strFlag(flags, "origin"),
9938
10037
  api: strFlag(flags, "api")
9939
10038
  }).replace(/\/+$/, "");
9940
- return { origin, key: requireCredential(strFlag(flags, "key")) };
10039
+ return { origin, key: requireCredential(credentialFlags(flags)) };
9941
10040
  }
9942
10041
  async function readTrash(ctx) {
9943
10042
  const res = await apiJson(ctx.origin, "/api/trash", { key: ctx.key });
@@ -10065,7 +10164,7 @@ async function cmdDelete(argv) {
10065
10164
  origin: strFlag(flags, "origin"),
10066
10165
  api: strFlag(flags, "api")
10067
10166
  }).replace(/\/+$/, "");
10068
- const key = requireCredential(strFlag(flags, "key"));
10167
+ const key = requireCredential(credentialFlags(flags));
10069
10168
  const dir = existsSync16(target) && statSync3(target).isDirectory() ? target : null;
10070
10169
  const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
10071
10170
  if (!vosId)
@@ -10140,7 +10239,7 @@ function trackingCwd(vosId) {
10140
10239
 
10141
10240
  // src/plugin/buildProgram.ts
10142
10241
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
10143
- import { basename as basename6, dirname as dirname3, join as join17, resolve as resolve8 } from "path";
10242
+ import { basename as basename7, dirname as dirname3, join as join17, resolve as resolve8 } from "path";
10144
10243
  import { pathToFileURL as pathToFileURL2 } from "url";
10145
10244
  import { parse } from "acorn";
10146
10245
  import { fullAncestor } from "acorn-walk";
@@ -10348,7 +10447,7 @@ async function cmdBuild(argv) {
10348
10447
  throw new UsageError(
10349
10448
  `${source} exports no program: \`export default { version: 2, duration, camera, createContent, createTimeline, \u2026 }\``
10350
10449
  );
10351
- const built = buildProgram(program, basename6(file), text);
10450
+ const built = buildProgram(program, basename7(file), text);
10352
10451
  for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
10353
10452
  if (built.problems.length) {
10354
10453
  r.done(
@@ -10583,7 +10682,7 @@ import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
10583
10682
  import { existsSync as existsSync18, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
10584
10683
  import { createServer as createServer2 } from "http";
10585
10684
  import { createRequire } from "module";
10586
- import { basename as basename7, extname as extname3, join as join19 } from "path";
10685
+ import { basename as basename8, extname as extname3, join as join19 } from "path";
10587
10686
  function themeColors(sources) {
10588
10687
  const out = {};
10589
10688
  const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
@@ -10863,7 +10962,7 @@ async function readRemotion(dir, log) {
10863
10962
  const sources = sourceTexts(dir);
10864
10963
  const publicDir = join19(dir, "public");
10865
10964
  const media = tree.audio.map((a) => {
10866
- const name = basename7(a.src.split("?")[0]);
10965
+ const name = basename8(a.src.split("?")[0]);
10867
10966
  const local = existsSync18(join19(publicDir, name)) ? `public/${name}` : a.src;
10868
10967
  return {
10869
10968
  kind: "audio",
@@ -11366,7 +11465,7 @@ async function readInventory(source, opts) {
11366
11465
  // src/plugin/portScaffold.ts
11367
11466
  import { spawnSync as spawnSync3 } from "child_process";
11368
11467
  import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
11369
- import { basename as basename8, dirname as dirname6, join as join21, resolve as resolve11 } from "path";
11468
+ import { basename as basename9, dirname as dirname6, join as join21, resolve as resolve11 } from "path";
11370
11469
  import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
11371
11470
 
11372
11471
  // src/plugin/painterLib.ts
@@ -11776,7 +11875,7 @@ function planScaffold(inv, opts) {
11776
11875
  const copies = [];
11777
11876
  inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
11778
11877
  const from = resolve11(opts.sourceDir, m.src);
11779
- const name = basename8(from);
11878
+ const name = basename9(from);
11780
11879
  if (!existsSync20(from)) {
11781
11880
  report.push(
11782
11881
  `- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
@@ -11929,7 +12028,7 @@ ${sceneTodos}${springHint}
11929
12028
  }
11930
12029
  function reportText(inv, r) {
11931
12030
  const lines = [
11932
- `# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
12031
+ `# Port report: ${inv.source === "." && inv.root ? basename9(inv.root) : inv.source}`,
11933
12032
  "",
11934
12033
  `${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
11935
12034
  "",
@@ -12093,11 +12192,11 @@ function scaffoldSourceDir(inv) {
12093
12192
  // src/plugin/recipe.ts
12094
12193
  import { createHash as createHash3 } from "crypto";
12095
12194
  import { readFileSync as readFileSync9 } from "fs";
12096
- import { basename as basename9 } from "path";
12195
+ import { basename as basename10 } from "path";
12097
12196
  var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
12098
12197
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
12099
12198
  function recipeFilename(file) {
12100
- const name = basename9(file);
12199
+ const name = basename10(file);
12101
12200
  if (!/\.md$/i.test(name)) {
12102
12201
  throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
12103
12202
  }
@@ -12118,7 +12217,7 @@ async function cmdPush2(argv) {
12118
12217
  origin: strFlag(flags, "origin"),
12119
12218
  api: strFlag(flags, "api")
12120
12219
  });
12121
- const key = requireCredential(strFlag(flags, "key"));
12220
+ const key = requireCredential(credentialFlags(flags));
12122
12221
  if (assetId) {
12123
12222
  const res = await apiJson(origin, `/api/assets/${assetId}/file`, {
12124
12223
  method: "PUT",
@@ -13203,7 +13302,7 @@ async function cmdActionsScript(input, flags) {
13203
13302
  // src/plugin/ingest.ts
13204
13303
  import { existsSync as existsSync22 } from "fs";
13205
13304
  import { copyFile, mkdir as mkdir8, readFile as readFile13, stat as stat2 } from "fs/promises";
13206
- import { basename as basename10, join as join23, resolve as resolve15 } from "path";
13305
+ import { basename as basename11, join as join23, resolve as resolve15 } from "path";
13207
13306
 
13208
13307
  // src/plugin/traceInflate.ts
13209
13308
  import { inflateRawSync } from "zlib";
@@ -13330,7 +13429,7 @@ async function copyRecording(src, dest, ext2) {
13330
13429
  }
13331
13430
  }
13332
13431
  async function readTraceFile(path, offsetMs) {
13333
- const kind = traceKindOf(basename10(path));
13432
+ const kind = traceKindOf(basename11(path));
13334
13433
  if (!kind) {
13335
13434
  throw new UsageError(
13336
13435
  `${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
@@ -13351,7 +13450,7 @@ async function readTraceFile(path, offsetMs) {
13351
13450
  return cursorFromPlaywrightTrace(text2, { offsetMs });
13352
13451
  }
13353
13452
  const text = await readFile13(path, "utf8");
13354
- return cursorFromTrace(basename10(path), text, { offsetMs });
13453
+ return cursorFromTrace(basename11(path), text, { offsetMs });
13355
13454
  }
13356
13455
  function parseViewport(v) {
13357
13456
  if (!v) return null;
@@ -13382,7 +13481,7 @@ async function cmdIngest(argv) {
13382
13481
  "note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
13383
13482
  );
13384
13483
  const backdrop = footage ? null : await takeBackdrop(flags, r);
13385
- r.log(`probing ${basename10(videoPath)}\u2026`);
13484
+ r.log(`probing ${basename11(videoPath)}\u2026`);
13386
13485
  const probed = await probeVideoFile(videoPath);
13387
13486
  if (!(probed.durationMs > 0)) {
13388
13487
  throw new UsageError(
@@ -13431,13 +13530,13 @@ async function cmdIngest(argv) {
13431
13530
  const clicks = events.filter((e) => e.type === "down").length;
13432
13531
  const seconds = (probed.durationMs / 1e3).toFixed(1);
13433
13532
  const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
13434
- const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename10(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
13533
+ const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename11(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
13435
13534
  const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
13436
13535
  const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
13437
13536
  r.done(
13438
13537
  {
13439
13538
  out: outDir,
13440
- recording: basename10(recording),
13539
+ recording: basename11(recording),
13441
13540
  durationMs: probed.durationMs,
13442
13541
  width: probed.width,
13443
13542
  height: probed.height,
@@ -13446,7 +13545,7 @@ async function cmdIngest(argv) {
13446
13545
  hasAudio: probed.hasAudio,
13447
13546
  remuxed,
13448
13547
  cursor: trace ? {
13449
- file: basename10(cursorPath),
13548
+ file: basename11(cursorPath),
13450
13549
  kind: trace.kind,
13451
13550
  events: events.length,
13452
13551
  clicks,
@@ -13458,7 +13557,7 @@ async function cmdIngest(argv) {
13458
13557
  backdrop: plan.backdrop ?? null,
13459
13558
  as: footage ? "footage" : "take"
13460
13559
  },
13461
- `Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
13560
+ `Ingested ${basename11(videoPath)} \u2192 ${outDir}/${basename11(recording)} (${facts})
13462
13561
  ${opened}
13463
13562
  ${cursorLine}
13464
13563
  ${next}`
@@ -13470,7 +13569,7 @@ async function cmdIngest(argv) {
13470
13569
  import { createRequire as createRequire2 } from "module";
13471
13570
  import { existsSync as existsSync23 } from "fs";
13472
13571
  import { mkdir as mkdir9, readFile as readFile14, stat as stat3, writeFile as writeFile7 } from "fs/promises";
13473
- import { basename as basename11, dirname as dirname8, extname as extname4, join as join24, resolve as resolve16 } from "path";
13572
+ import { basename as basename12, dirname as dirname8, extname as extname4, join as join24, resolve as resolve16 } from "path";
13474
13573
  import { ALL_FORMATS as ALL_FORMATS2, FilePathSource as FilePathSource2, Input as Input2 } from "mediabunny";
13475
13574
 
13476
13575
  // src/plugin/videoChecks.ts
@@ -13992,7 +14091,7 @@ async function checkVideo(browser, file, outDir) {
13992
14091
  void page.evaluate(decodeInPage, {
13993
14092
  mbCode: await mediabunnyBundle(),
13994
14093
  meterSrc: String(createLoudnessMeter),
13995
- videoUrl: `/${encodeURIComponent(basename11(file))}`,
14094
+ videoUrl: `/${encodeURIComponent(basename12(file))}`,
13996
14095
  sampleFps,
13997
14096
  thumbW: side(facts.width),
13998
14097
  thumbH: side(facts.height),
@@ -14100,7 +14199,7 @@ async function cmdCheckVideo(argv) {
14100
14199
  const r = createReporter(flags.json === true);
14101
14200
  const strict = flags.strict === true;
14102
14201
  const outDir = resolve16(
14103
- strFlag(flags, "out") ?? join24(dirname8(file), `${basename11(file, extname4(file))}-check`)
14202
+ strFlag(flags, "out") ?? join24(dirname8(file), `${basename12(file, extname4(file))}-check`)
14104
14203
  );
14105
14204
  const browser = await launchBrowser();
14106
14205
  try {
@@ -14118,7 +14217,7 @@ async function cmdCheckVideo(argv) {
14118
14217
  import { existsSync as existsSync26, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
14119
14218
  import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile17, rm as rm4 } from "fs/promises";
14120
14219
  import {
14121
- basename as basename13,
14220
+ basename as basename14,
14122
14221
  dirname as dirname9,
14123
14222
  isAbsolute as isAbsolute4,
14124
14223
  join as join27,
@@ -14149,13 +14248,15 @@ import {
14149
14248
  writeFile as writeFile8
14150
14249
  } from "fs/promises";
14151
14250
  import { tmpdir as tmpdir2 } from "os";
14152
- import { basename as basename12, isAbsolute as isAbsolute3, join as join25, relative as relative4, resolve as resolve17, sep as sep3 } from "path";
14251
+ import { basename as basename13, isAbsolute as isAbsolute3, join as join25, relative as relative4, resolve as resolve17, sep as sep3 } from "path";
14252
+ import { rankBeds as rankBeds2 } from "@vosjs/shared/music";
14153
14253
  import {
14154
14254
  NORMALIZE_MAX_LUFS as NORMALIZE_MAX_LUFS2,
14155
14255
  NORMALIZE_MIN_LUFS as NORMALIZE_MIN_LUFS2,
14156
14256
  clipGain as clipGain2,
14157
14257
  docMediaList,
14158
14258
  docOutputDuration as docOutputDuration3,
14259
+ filmSummaryOf as filmSummaryOf2,
14159
14260
  findStep as findStep2,
14160
14261
  ratedSegments as ratedSegments7,
14161
14262
  resolveAnchor as resolveAnchor4
@@ -14464,7 +14565,7 @@ async function measureInBrowser(path) {
14464
14565
  const bytes = await readFile15(path);
14465
14566
  if (bytes.length > BROWSER_DECODE_MAX_BYTES)
14466
14567
  throw new Error(
14467
- `${basename12(path)} is ${Math.round(bytes.length / 1048576)} MB, past the ${BROWSER_DECODE_MAX_BYTES / 1048576} MB the browser decode takes whole`
14568
+ `${basename13(path)} is ${Math.round(bytes.length / 1048576)} MB, past the ${BROWSER_DECODE_MAX_BYTES / 1048576} MB the browser decode takes whole`
14468
14569
  );
14469
14570
  const browser = await launchBrowser();
14470
14571
  try {
@@ -14568,12 +14669,12 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14568
14669
  if (existsSync24(local)) {
14569
14670
  const rel = relative4(resolve17(dir), local);
14570
14671
  const inside = !!rel && !rel.startsWith(`..${sep3}`) && rel !== ".." && !isAbsolute3(rel);
14571
- const key = inside ? rel.split(sep3).join("/") : `music/${basename12(local)}`;
14672
+ const key = inside ? rel.split(sep3).join("/") : `music/${basename13(local)}`;
14572
14673
  if (!inside && !dryRun) {
14573
14674
  await mkdir10(join25(dir, "music"), { recursive: true });
14574
14675
  await copyFile2(local, join25(dir, key));
14575
14676
  }
14576
- return { path: local, key, name: basename12(local) };
14677
+ return { path: local, key, name: basename13(local) };
14577
14678
  }
14578
14679
  const origin = platformOrigin({
14579
14680
  origin: strFlag(flags, "origin"),
@@ -14588,7 +14689,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14588
14689
  const res = await fetch(track.url);
14589
14690
  if (!res.ok) throw new Error(`GET ${track.url} answered ${res.status}`);
14590
14691
  const tmp = await mkdtemp(join25(tmpdir2(), "vos-score-"));
14591
- const path = join25(tmp, basename12(new URL(track.url).pathname) || "track");
14692
+ const path = join25(tmp, basename13(new URL(track.url).pathname) || "track");
14592
14693
  await writeFile8(path, new Uint8Array(await res.arrayBuffer()));
14593
14694
  return {
14594
14695
  path,
@@ -14598,7 +14699,7 @@ async function resolveTrack(dir, ref, flags, dryRun) {
14598
14699
  };
14599
14700
  }
14600
14701
  var BOOLEAN_FLAGS12 = /* @__PURE__ */ new Set(["json", "help", "dry-run"]);
14601
- var USAGE2 = "vos score <take> --track <slug|file> [--at <step id|output seconds>] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]";
14702
+ var USAGE2 = 'vos score <take> --track <slug|file|best> [--at <step id|output seconds>] [--mood <slug|tag>] [--words "\u2026"] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]';
14602
14703
  async function cmdScore(argv) {
14603
14704
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14604
14705
  const dir = positionals[0];
@@ -14625,8 +14726,39 @@ async function cmdScore(argv) {
14625
14726
  throw new UsageError(`${dir} has no doc.json; run vos plan first`);
14626
14727
  const doc = take.doc;
14627
14728
  const atRaw = strFlag(flags, "at");
14628
- const at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
14629
- const track = await resolveTrack(dir, trackRef, flags, dryRun);
14729
+ let at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
14730
+ let chosen = trackRef;
14731
+ if (trackRef === "best") {
14732
+ const origin = platformOrigin({
14733
+ origin: strFlag(flags, "origin"),
14734
+ api: strFlag(flags, "api")
14735
+ });
14736
+ const catalog = await fetchMusicCatalog(origin);
14737
+ const film2 = filmSummaryOf2(doc, {
14738
+ seed: readSyncState(dir)?.vosId ?? basename13(resolve17(dir)),
14739
+ ...at2 ? { payoff: at2.at } : {},
14740
+ mood: strFlag(flags, "mood"),
14741
+ words: strFlag(flags, "words")
14742
+ });
14743
+ const ranked = rankBeds2(catalog.tracks, film2).filter(
14744
+ (x) => x.score !== null
14745
+ );
14746
+ const best = ranked[0];
14747
+ if (!best)
14748
+ throw new UsageError(
14749
+ `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>`
14750
+ );
14751
+ chosen = best.slug;
14752
+ if (!at2 && film2.payoff !== void 0)
14753
+ at2 = {
14754
+ at: film2.payoff,
14755
+ via: `the film's payoff (${film2.payoff.toFixed(2)}s)`
14756
+ };
14757
+ r.log(
14758
+ `picked ${best.slug} (${best.reasons.join("; ") || "ranked first"})${ranked[1] ? `; runner-up ${ranked[1].slug}` : ""}`
14759
+ );
14760
+ }
14761
+ const track = await resolveTrack(dir, chosen, flags, dryRun);
14630
14762
  let measured;
14631
14763
  try {
14632
14764
  r.log(`measuring ${track.name}\u2026`);
@@ -16220,7 +16352,7 @@ function joinDocs(parts, opts = {}) {
16220
16352
  {
16221
16353
  id,
16222
16354
  key: d.source.videoKey,
16223
- name: basename13(part.file.path),
16355
+ name: basename14(part.file.path),
16224
16356
  start: round33(at2.start),
16225
16357
  in: round33(seg.in),
16226
16358
  out: round33(seg.out),
@@ -16465,7 +16597,7 @@ async function cmdJoin(argv) {
16465
16597
  const remuxed = await copyRecording(c.from, to, c.remux);
16466
16598
  if (!remuxed && c.remux !== "mov")
16467
16599
  plan.notes.push(
16468
- `could not read ${basename13(c.from)}'s audio in Node, so it was copied as it is; its length comes from the container`
16600
+ `could not read ${basename14(c.from)}'s audio in Node, so it was copied as it is; its length comes from the container`
16469
16601
  );
16470
16602
  } else await copyFile3(c.from, to);
16471
16603
  }
@@ -16839,7 +16971,7 @@ async function cmdTakeInfo(argv) {
16839
16971
  // src/plugin/audio.ts
16840
16972
  import { mkdtemp as mkdtemp2, rm as rm5, writeFile as writeFile9 } from "fs/promises";
16841
16973
  import { tmpdir as tmpdir3 } from "os";
16842
- import { basename as basename14, join as join29 } from "path";
16974
+ import { basename as basename15, join as join29 } from "path";
16843
16975
  import { clipGain as clipGain3 } from "@vosjs/studio-core";
16844
16976
  var USAGE4 = "vos audio measure <take> [--json]";
16845
16977
  async function fileFor(dir, key, fetchImpl) {
@@ -16852,7 +16984,7 @@ async function fileFor(dir, key, fetchImpl) {
16852
16984
  const res = await fetchImpl(key);
16853
16985
  if (!res.ok) return { skipped: `GET ${key} answered ${res.status}` };
16854
16986
  const tmp = await mkdtemp2(join29(tmpdir3(), "vos-audio-"));
16855
- const path = join29(tmp, basename14(new URL(key).pathname) || "audio");
16987
+ const path = join29(tmp, basename15(new URL(key).pathname) || "audio");
16856
16988
  await writeFile9(path, new Uint8Array(await res.arrayBuffer()));
16857
16989
  return { path, cleanup: () => rm5(tmp, { recursive: true, force: true }) };
16858
16990
  }
@@ -16932,11 +17064,238 @@ async function cmdAudio(argv) {
16932
17064
  return failed.length ? EXIT_ERROR : EXIT_OK;
16933
17065
  }
16934
17066
 
17067
+ // src/plugin/musicCmd.ts
17068
+ import { existsSync as existsSync27, readFileSync as readFileSync10, statSync as statSync8 } from "fs";
17069
+ import { basename as basename16, resolve as resolve20 } from "path";
17070
+ import { rankBeds as rankBeds3, trackBySlug as trackBySlug2 } from "@vosjs/shared/music";
17071
+ import { filmSummaryOf as filmSummaryOf3, musicSlugsOfDoc } from "@vosjs/studio-core";
17072
+ var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
17073
+ "json",
17074
+ "help",
17075
+ "loops",
17076
+ "voice",
17077
+ "no-voice",
17078
+ "fade-in"
17079
+ ]);
17080
+ var MULTI_FLAGS3 = /* @__PURE__ */ new Set(["like"]);
17081
+ 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]';
17082
+ var mmss = (s) => {
17083
+ const whole = Math.round(s);
17084
+ return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
17085
+ };
17086
+ var VOCAL_WORDS = {
17087
+ none: "no vocals",
17088
+ chops: "vocal chops",
17089
+ lead: "sung lead"
17090
+ };
17091
+ function trackLine(t) {
17092
+ const bpm = t.bpm === null || t.bpm === void 0 ? "\u2014" : `${t.bpm} bpm${t.beatConfidence === "low" ? "?" : t.beatConfidence === "confirmed" ? "" : ""}`;
17093
+ return [
17094
+ t.slug.padEnd(26),
17095
+ (t.mood ?? "").padEnd(10),
17096
+ mmss(t.duration).padStart(5),
17097
+ bpm.padStart(10),
17098
+ VOCAL_WORDS[t.vocals ?? "none"].padEnd(11),
17099
+ t.loops ? "loops" : " ",
17100
+ t.description ? JSON.stringify(t.description) : ""
17101
+ ].join(" ");
17102
+ }
17103
+ function bpmRange(raw) {
17104
+ if (!raw) return null;
17105
+ const m = /^(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)$/.exec(raw.trim());
17106
+ if (m) return [Number(m[1]), Number(m[2])];
17107
+ const one = Number(raw);
17108
+ if (Number.isFinite(one)) return [one, Infinity];
17109
+ throw new UsageError(`--bpm expects <lo>-<hi>, got "${raw}"`);
17110
+ }
17111
+ function filterTracks(tracks, f) {
17112
+ const q = f.q?.toLowerCase().trim();
17113
+ const words2 = q ? q.split(/\s+/).filter(Boolean) : [];
17114
+ return tracks.filter((t) => {
17115
+ if (t.removedAt) return false;
17116
+ if (f.mood && (t.mood ?? "").toLowerCase() !== f.mood.toLowerCase()) {
17117
+ if (!(t.tags ?? []).some((x) => x.toLowerCase() === f.mood.toLowerCase()))
17118
+ return false;
17119
+ }
17120
+ if (f.vocals && (t.vocals ?? "none") !== f.vocals) return false;
17121
+ if (f.loops && !t.loops) return false;
17122
+ if (f.bpm) {
17123
+ if (t.bpm === null || t.bpm === void 0) return false;
17124
+ if (t.bpm < f.bpm[0] || t.bpm > f.bpm[1]) return false;
17125
+ }
17126
+ if (f.minLength !== void 0 && t.duration < f.minLength && !t.loops)
17127
+ return false;
17128
+ if (words2.length) {
17129
+ const hay = [
17130
+ t.slug,
17131
+ t.title,
17132
+ t.artist ?? "",
17133
+ t.mood ?? "",
17134
+ ...t.tags ?? [],
17135
+ t.description ?? ""
17136
+ ].join(" ").toLowerCase();
17137
+ if (!words2.every((w) => hay.includes(w))) return false;
17138
+ }
17139
+ return true;
17140
+ });
17141
+ }
17142
+ async function loadFilm(ref) {
17143
+ const path = resolve20(ref);
17144
+ if (!existsSync27(path)) throw new UsageError(`${ref}: no such take or file`);
17145
+ if (statSync8(path).isDirectory()) {
17146
+ const take = await loadTake(path);
17147
+ if (!take.doc)
17148
+ throw new UsageError(`${ref} has no doc.json; run vos plan first`);
17149
+ const sync = readSyncState(path);
17150
+ return { doc: take.doc, seed: sync?.vosId ?? basename16(path), dir: path };
17151
+ }
17152
+ let config;
17153
+ try {
17154
+ config = JSON.parse(readFileSync10(path, "utf8"));
17155
+ } catch {
17156
+ throw new UsageError(`${ref}: not a take directory or a JSON config`);
17157
+ }
17158
+ if (!config || typeof config !== "object" || !("version" in config))
17159
+ throw new UsageError(`${ref}: a program's config.json carries a version`);
17160
+ const doc = {
17161
+ program: { config },
17162
+ audio: [],
17163
+ overlays: [],
17164
+ objects: []
17165
+ };
17166
+ return { doc, seed: basename16(path, ".json"), dir: null };
17167
+ }
17168
+ async function likedSlugs(refs, origin) {
17169
+ const slugs = /* @__PURE__ */ new Set();
17170
+ const notes = [];
17171
+ for (const ref of refs) {
17172
+ const id = parseVosId(ref);
17173
+ if (!id) {
17174
+ slugs.add(ref.toLowerCase());
17175
+ continue;
17176
+ }
17177
+ try {
17178
+ const vos = await (await fetch(`${origin}/api/vos/${id}`)).json();
17179
+ if (!vos.currentVersionId) throw new Error("no version");
17180
+ const doc = await (await fetch(
17181
+ `${origin}/api/vos/${id}/versions/${vos.currentVersionId}/doc`
17182
+ )).json();
17183
+ for (const s of musicSlugsOfDoc(doc)) slugs.add(s);
17184
+ } catch {
17185
+ notes.push(
17186
+ `--like ${ref}: could not read that vos's document (is it public?)`
17187
+ );
17188
+ }
17189
+ }
17190
+ return { slugs: [...slugs], notes };
17191
+ }
17192
+ function scoreLine(take, r, film) {
17193
+ const at2 = r.placement?.swellAt ?? film.payoff;
17194
+ return `vos score ${take ?? "<take>"} --track ${r.slug}${at2 !== void 0 && at2 !== null ? ` --at ${at2}` : ""}`;
17195
+ }
17196
+ function pickWords(picks, all, film, take) {
17197
+ const refused = all.filter((r) => r.score === null);
17198
+ 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}"` : ""}`;
17199
+ if (!picks.length) {
17200
+ return `${head}
17201
+ 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.`;
17202
+ }
17203
+ const lines = picks.map((r, i) => {
17204
+ const t = r.track;
17205
+ const facts = [
17206
+ t.mood,
17207
+ t.bpm !== null && t.bpm !== void 0 ? `${t.bpm} bpm${t.beatConfidence === "low" ? "?" : ""}` : null,
17208
+ VOCAL_WORDS[t.vocals ?? "none"],
17209
+ t.loops ? "loops" : null
17210
+ ].filter(Boolean).join(", ");
17211
+ return ` ${i + 1}. ${r.slug} ${t.title} ${(r.score ?? 0).toFixed(2)} (${facts})
17212
+ ${r.reasons.join("; ") || "fits"}
17213
+ ${scoreLine(take, r, film)}`;
17214
+ });
17215
+ const tail = refused.length ? `
17216
+ ${refused.length} refused: ${refused.slice(0, 4).map((r) => `${r.slug} (${r.refused})`).join("; ")}${refused.length > 4 ? "; \u2026" : ""}` : "";
17217
+ return `${head}
17218
+ ${lines.join("\n")}${tail}`;
17219
+ }
17220
+ async function cmdMusic(argv) {
17221
+ const { positionals, flags, multi } = parseArgs(
17222
+ argv,
17223
+ BOOLEAN_FLAGS14,
17224
+ MULTI_FLAGS3
17225
+ );
17226
+ const verb = positionals[0];
17227
+ if (flags.help === true || !verb || !["ls", "pick"].includes(verb))
17228
+ throw new UsageError(USAGE5);
17229
+ const r = createReporter(flags.json === true);
17230
+ const origin = platformOrigin({
17231
+ origin: strFlag(flags, "origin"),
17232
+ api: strFlag(flags, "api")
17233
+ });
17234
+ const catalog = await fetchMusicCatalog(origin);
17235
+ if (verb === "ls") {
17236
+ const vocals = strFlag(flags, "vocals");
17237
+ if (vocals && !["none", "chops", "lead"].includes(vocals))
17238
+ throw new UsageError("--vocals expects none, chops or lead");
17239
+ const kept = filterTracks(catalog.tracks, {
17240
+ mood: strFlag(flags, "mood"),
17241
+ vocals,
17242
+ loops: flags.loops === true,
17243
+ bpm: bpmRange(strFlag(flags, "bpm")),
17244
+ minLength: strFlag(flags, "min-length") !== void 0 ? numFlag(flags, "min-length", 0) : void 0,
17245
+ q: strFlag(flags, "q")
17246
+ });
17247
+ r.done(
17248
+ { origin, tracks: kept, total: catalog.tracks.length },
17249
+ kept.length ? kept.map(trackLine).join("\n") : `No track matches; the library holds ${catalog.tracks.length} (GET ${origin}/api/music).`
17250
+ );
17251
+ return EXIT_OK;
17252
+ }
17253
+ const ref = positionals[1];
17254
+ if (!ref) throw new UsageError(USAGE5);
17255
+ const { doc, seed, dir } = await loadFilm(ref);
17256
+ const atRaw = strFlag(flags, "at");
17257
+ const at2 = atRaw !== void 0 ? resolveAt(doc, atRaw) : null;
17258
+ if (flags.voice === true && flags["no-voice"] === true)
17259
+ throw new UsageError("--voice and --no-voice together say nothing");
17260
+ const liked = await likedSlugs(multi.like ?? [], origin);
17261
+ const film = filmSummaryOf3(doc, {
17262
+ seed,
17263
+ ...at2 ? { payoff: at2.at } : {},
17264
+ ...flags.voice === true ? { voice: true } : flags["no-voice"] === true ? { voice: false } : {},
17265
+ mood: strFlag(flags, "mood"),
17266
+ words: strFlag(flags, "words"),
17267
+ like: liked.slugs,
17268
+ fadeIn: flags["fade-in"] === true
17269
+ });
17270
+ const top = Math.max(1, Math.round(numFlag(flags, "top", 3)));
17271
+ const all = rankBeds3(catalog.tracks, film);
17272
+ const picks = all.filter((x) => x.score !== null).slice(0, top);
17273
+ for (const n of liked.notes) r.warn(n);
17274
+ const asked = strFlag(flags, "mood");
17275
+ if (asked && trackBySlug2(catalog.tracks, asked))
17276
+ r.warn(
17277
+ `--mood ${asked} is a track's slug; to place that track, run vos score --track ${asked}`
17278
+ );
17279
+ r.done(
17280
+ {
17281
+ origin,
17282
+ film,
17283
+ picks: picks.map((x) => ({
17284
+ ...x,
17285
+ command: scoreLine(dir ? ref : null, x, film)
17286
+ })),
17287
+ all
17288
+ },
17289
+ pickWords(picks, all, film, dir ? ref : null)
17290
+ );
17291
+ return EXIT_OK;
17292
+ }
17293
+
16935
17294
  // src/plugin/look.ts
16936
17295
  import { mkdir as mkdir12, mkdtemp as mkdtemp3, readFile as readFile18, rm as rm6 } from "fs/promises";
16937
- import { existsSync as existsSync27 } from "fs";
17296
+ import { existsSync as existsSync28 } from "fs";
16938
17297
  import { tmpdir as tmpdir4 } from "os";
16939
- import { dirname as dirname10, join as join30, resolve as resolve20 } from "path";
17298
+ import { dirname as dirname10, join as join30, resolve as resolve21 } from "path";
16940
17299
  import { headersFor, urlSchemeProblem } from "@vosjs/render-core/record";
16941
17300
  function quote(value) {
16942
17301
  return `"${value.replace(/\s+/g, " ").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -17455,7 +17814,7 @@ async function inspectPage(page, opts) {
17455
17814
  settled
17456
17815
  };
17457
17816
  }
17458
- var USAGE5 = "vos look <url> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--click <selector>]... [--type <selector>=<text>]... [--wait <ms>] [--out <dir>] [--json]\n vos look [url] --actions actions.json --until <step id> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--wait <ms>] [--out <dir>] [--json]";
17817
+ var USAGE6 = "vos look <url> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--click <selector>]... [--type <selector>=<text>]... [--wait <ms>] [--out <dir>] [--json]\n vos look [url] --actions actions.json --until <step id> [--session <name> | --storage-state <file>] [--viewport WxH] [--headed] [--wait <ms>] [--out <dir>] [--json]";
17459
17818
  function untilProblem(given, steps) {
17460
17819
  if (given.until !== void 0 && given.actions === void 0)
17461
17820
  return "--until names a step of a script: pass the script with --actions actions.json";
@@ -17514,7 +17873,7 @@ async function cmdLook(argv) {
17514
17873
  if (early) throw new UsageError(early);
17515
17874
  let actions;
17516
17875
  if (actionsArg) {
17517
- actions = await loadLookActions(resolve20(actionsArg));
17876
+ actions = await loadLookActions(resolve21(actionsArg));
17518
17877
  const bad = untilProblem(
17519
17878
  { actions: actionsArg, until: untilId, clicks: 0 },
17520
17879
  actions.steps
@@ -17522,21 +17881,21 @@ async function cmdLook(argv) {
17522
17881
  if (bad) throw new UsageError(bad);
17523
17882
  }
17524
17883
  const url = positionals[0] ?? actions?.url;
17525
- if (!url) throw new UsageError(USAGE5);
17884
+ if (!url) throw new UsageError(USAGE6);
17526
17885
  const scheme = urlSchemeProblem(url);
17527
17886
  if (scheme) throw new UsageError(scheme);
17528
17887
  const r = createReporter(flags.json === true);
17529
17888
  const viewportFlag = strFlag(flags, "viewport");
17530
17889
  const viewport = !viewportFlag && actions?.viewport ? actions.viewport : parseViewport2(viewportFlag);
17531
- const outDir = resolve20(strFlag(flags, "out") ?? "look");
17890
+ const outDir = resolve21(strFlag(flags, "out") ?? "look");
17532
17891
  const headed = flags.headed === true;
17533
17892
  const waitRaw = strFlag(flags, "wait");
17534
17893
  const waitMs = waitRaw === void 0 ? 0 : Number(waitRaw);
17535
17894
  if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > 6e4)
17536
17895
  throw new UsageError("--wait expects milliseconds, 0 to 60000");
17537
17896
  const storageRaw = strFlag(flags, "storage-state");
17538
- const storageState = storageRaw ? resolve20(storageRaw) : void 0;
17539
- if (storageState && !existsSync27(storageState))
17897
+ const storageState = storageRaw ? resolve21(storageRaw) : void 0;
17898
+ if (storageState && !existsSync28(storageState))
17540
17899
  throw new UsageError(`--storage-state: no such file: ${storageState}`);
17541
17900
  const sessionName = strFlag(flags, "session");
17542
17901
  if (sessionName) {
@@ -17546,7 +17905,7 @@ async function cmdLook(argv) {
17546
17905
  throw new UsageError(
17547
17906
  "--session and --storage-state are two doors to one page: a persistent profile cannot take a storage state. Pass one"
17548
17907
  );
17549
- if (!existsSync27(sessionDir(sessionName)))
17908
+ if (!existsSync28(sessionDir(sessionName)))
17550
17909
  throw new UsageError(
17551
17910
  `no session "${sessionName}". Make one: vos session open <url> --name ${sessionName} (a Chrome window opens; sign in and close it)`
17552
17911
  );
@@ -17563,7 +17922,7 @@ async function cmdLook(argv) {
17563
17922
  r,
17564
17923
  url,
17565
17924
  actions,
17566
- actionsPath: resolve20(actionsArg),
17925
+ actionsPath: resolve21(actionsArg),
17567
17926
  untilId,
17568
17927
  viewport,
17569
17928
  outDir,
@@ -17727,10 +18086,9 @@ async function lookUntil(o) {
17727
18086
  import { spawnSync as spawnSync4 } from "child_process";
17728
18087
  import {
17729
18088
  cpSync,
17730
- existsSync as existsSync29,
18089
+ existsSync as existsSync30,
17731
18090
  mkdirSync as mkdirSync6,
17732
- readFileSync as readFileSync11,
17733
- rmSync as rmSync3,
18091
+ readFileSync as readFileSync12,
17734
18092
  writeFileSync as writeFileSync6
17735
18093
  } from "fs";
17736
18094
  import { homedir as homedir2 } from "os";
@@ -17738,7 +18096,7 @@ import { join as join32 } from "path";
17738
18096
  import { chromium as chromium2 } from "playwright";
17739
18097
 
17740
18098
  // src/plugin/machine.ts
17741
- import { existsSync as existsSync28, readFileSync as readFileSync10, readdirSync as readdirSync6, statSync as statSync8 } from "fs";
18099
+ import { existsSync as existsSync29, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync9 } from "fs";
17742
18100
  import { dirname as dirname11, join as join31 } from "path";
17743
18101
  var AGENT_NAMES = [
17744
18102
  "claude",
@@ -17759,7 +18117,7 @@ var HOME_DIRS = {
17759
18117
  copilot: [".copilot"]
17760
18118
  };
17761
18119
  function detectAgents(root, opts = {}) {
17762
- const exists = opts.exists ?? existsSync28;
18120
+ const exists = opts.exists ?? existsSync29;
17763
18121
  const out = [];
17764
18122
  for (const agent of AGENT_NAMES) {
17765
18123
  for (const rel of PROJECT_DIRS[agent]) {
@@ -17806,7 +18164,7 @@ function skillsDirFor(home) {
17806
18164
  function fallbackSkillsHome(root) {
17807
18165
  return { agent: "codex", dir: join31(root, ".agents"), scope: "project" };
17808
18166
  }
17809
- function installedSkills(dir, exists = existsSync28, list = (p) => {
18167
+ function installedSkills(dir, exists = existsSync29, list = (p) => {
17810
18168
  try {
17811
18169
  return readdirSync6(p);
17812
18170
  } catch {
@@ -17824,7 +18182,7 @@ var RULES_BODY = `## Product videos (vosso)
17824
18182
  - Hand back: vos open take serves the take into the studio at vos.so, doc.json intact
17825
18183
  - Local by default: vos push only when asked for a link, the shelf or a cloud render
17826
18184
  - Deleting: vos delete moves a vos to Trash and vos restore brings it back; if you deleted by mistake, restore it and say so. Only the person can delete forever, at vos.so/app/trash
17827
- - Credentials: VOS_API_KEY, then ~/.config/vos/credentials, then vos login; never print one
18185
+ - Credentials: VOS_API_KEY, then the account in use at ~/.config/vos/credentials (vos whoami says which; vos switch <handle> changes it), then vos login; never print one
17828
18186
  - Reference: https://vos.so/llms-full.txt`;
17829
18187
  var RULES_BLOCK = `${RULES_BEGIN}
17830
18188
  ${RULES_BODY}
@@ -17847,7 +18205,7 @@ ${RULES_BLOCK}
17847
18205
  }
17848
18206
  return { text, changed: text !== current };
17849
18207
  }
17850
- function rulesFileFor(root, exists = existsSync28) {
18208
+ function rulesFileFor(root, exists = existsSync29) {
17851
18209
  const agents = join31(root, "AGENTS.md");
17852
18210
  if (exists(agents)) return agents;
17853
18211
  const claude = join31(root, "CLAUDE.md");
@@ -17904,6 +18262,18 @@ var NODE_MIN_MAJOR = 18;
17904
18262
  function major(version) {
17905
18263
  return parseInt(version.split(".")[0] ?? "0", 10);
17906
18264
  }
18265
+ function credentialLine(facts) {
18266
+ const accounts = facts.accounts ?? [];
18267
+ const active = accounts.find((a) => a.active);
18268
+ const others = accounts.filter((a) => !a.active).map((a) => a.label);
18269
+ if (active) {
18270
+ return `${active.label} \u2713${others.length ? ` (also ${others.join(", ")})` : ""}`;
18271
+ }
18272
+ if (facts.credential) {
18273
+ return `present \u2713${others.length ? ` (stored: ${others.join(", ")}; vos switch to use one)` : ""}`;
18274
+ }
18275
+ return `absent (vos login when you need hosting)${others.length ? `; stored: ${others.join(", ")}, vos switch to use one` : ""}`;
18276
+ }
17907
18277
  function doctorReport(facts) {
17908
18278
  const lines = [];
17909
18279
  const nodeOk = major(facts.node) >= NODE_MIN_MAJOR;
@@ -17929,9 +18299,7 @@ function doctorReport(facts) {
17929
18299
  } else {
17930
18300
  lines.push("skills none \u2717 run: npx vos setup");
17931
18301
  }
17932
- lines.push(
17933
- `credential ${facts.credential ? "present \u2713" : "absent (vos login when you need hosting)"}`
17934
- );
18302
+ lines.push(`credential ${credentialLine(facts)}`);
17935
18303
  if (facts.devServer) {
17936
18304
  const d = facts.devServer;
17937
18305
  lines.push(
@@ -17952,7 +18320,7 @@ function doctorReport(facts) {
17952
18320
  }
17953
18321
  function bundledSkillsVersion(dir) {
17954
18322
  try {
17955
- const v = readFileSync10(join31(dir, "VERSION"), "utf8").trim();
18323
+ const v = readFileSync11(join31(dir, "VERSION"), "utf8").trim();
17956
18324
  return /^\d+\.\d+\.\d+$/.test(v) ? v : null;
17957
18325
  } catch {
17958
18326
  return null;
@@ -17962,12 +18330,12 @@ function bundledSkillsDir(from) {
17962
18330
  let dir = dirname11(from);
17963
18331
  for (let i = 0; i < 6; i++) {
17964
18332
  const pkg = join31(dir, "package.json");
17965
- if (existsSync28(pkg)) {
18333
+ if (existsSync29(pkg)) {
17966
18334
  try {
17967
- const name = JSON.parse(readFileSync10(pkg, "utf8")).name;
18335
+ const name = JSON.parse(readFileSync11(pkg, "utf8")).name;
17968
18336
  if (name === "@vosjs/cli") {
17969
18337
  const skills = join31(dir, "skills");
17970
- return existsSync28(skills) && statSync8(skills).isDirectory() ? skills : null;
18338
+ return existsSync29(skills) && statSync9(skills).isDirectory() ? skills : null;
17971
18339
  }
17972
18340
  } catch {
17973
18341
  }
@@ -17980,7 +18348,7 @@ function bundledSkillsDir(from) {
17980
18348
  }
17981
18349
 
17982
18350
  // src/plugin/machineCmd.ts
17983
- var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
18351
+ var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
17984
18352
  "json",
17985
18353
  "help",
17986
18354
  "global",
@@ -17989,6 +18357,7 @@ var BOOLEAN_FLAGS14 = /* @__PURE__ */ new Set([
17989
18357
  "no-rules",
17990
18358
  "no-browser"
17991
18359
  ]);
18360
+ var LOGOUT_FLAGS = /* @__PURE__ */ new Set([...BOOLEAN_FLAGS15, "all"]);
17992
18361
  var SKILLS_SOURCE = "vosjs/skills";
17993
18362
  var SKILLS_CLI_VERSION = "1.7.2";
17994
18363
  function skillsCatalogAt(version) {
@@ -18003,7 +18372,7 @@ function npx() {
18003
18372
  function bundledChromium() {
18004
18373
  try {
18005
18374
  const p = chromium2.executablePath();
18006
- return p && existsSync29(p) ? p : null;
18375
+ return p && existsSync30(p) ? p : null;
18007
18376
  } catch {
18008
18377
  return null;
18009
18378
  }
@@ -18012,7 +18381,7 @@ function probe2() {
18012
18381
  return probeBrowser({
18013
18382
  platform: process.platform,
18014
18383
  env: process.env,
18015
- exists: existsSync29,
18384
+ exists: existsSync30,
18016
18385
  bundled: bundledChromium
18017
18386
  });
18018
18387
  }
@@ -18058,12 +18427,26 @@ async function doctorFacts(opts) {
18058
18427
  browser: probe2(),
18059
18428
  ffmpeg: ffmpegVersion(),
18060
18429
  skills: skillsFacts(opts.homes),
18061
- credential: resolveCredential() !== null,
18430
+ credential: safeResolve() !== null,
18431
+ accounts: storedAccounts(platformOrigin()),
18062
18432
  ...opts.url ? { devServer: await devServerFact(opts.url) } : {}
18063
18433
  };
18064
18434
  }
18435
+ function safeResolve() {
18436
+ try {
18437
+ return resolveCredential();
18438
+ } catch {
18439
+ return null;
18440
+ }
18441
+ }
18442
+ function storedAccounts(origin, store = DEFAULT_STORE) {
18443
+ return listStored(store, origin).map((c) => ({
18444
+ label: accountLabel(c.account),
18445
+ active: isActive(c, store)
18446
+ }));
18447
+ }
18065
18448
  async function cmdSetup(argv) {
18066
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18449
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18067
18450
  const r = reporterFor(flags);
18068
18451
  const root = process.cwd();
18069
18452
  const global = flags.global === true;
@@ -18165,7 +18548,7 @@ async function cmdSetup(argv) {
18165
18548
  let rules = null;
18166
18549
  if (!noRules) {
18167
18550
  const file = rulesFileFor(root);
18168
- const existing = existsSync29(file) ? readFileSync11(file, "utf8") : null;
18551
+ const existing = existsSync30(file) ? readFileSync12(file, "utf8") : null;
18169
18552
  const { text, changed } = applyRulesBlock(existing);
18170
18553
  if (changed) writeFileSync6(file, text);
18171
18554
  rules = { file, changed };
@@ -18196,7 +18579,7 @@ Next: ${report.next_step}`
18196
18579
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18197
18580
  }
18198
18581
  async function cmdDoctor(argv) {
18199
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18582
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18200
18583
  const r = reporterFor(flags);
18201
18584
  const root = process.cwd();
18202
18585
  const homes = detectAgents(root, { home: homedir2(), global: true });
@@ -18215,35 +18598,48 @@ Next: ${report.next_step}`
18215
18598
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
18216
18599
  }
18217
18600
  async function cmdWhoami(argv) {
18218
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18601
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18219
18602
  const r = reporterFor(flags);
18220
18603
  const origin = platformOrigin({
18221
18604
  origin: strFlag(flags, "origin"),
18222
18605
  api: strFlag(flags, "api")
18223
18606
  });
18224
- const key = resolveCredential(strFlag(flags, "key"));
18607
+ const stored = storedAccounts(origin);
18608
+ const others = stored.filter((a) => !a.active).map((a) => a.label);
18609
+ const alsoStored = others.length ? ` Also stored: ${others.join(", ")}; vos switch <name> to use one.` : "";
18610
+ const key = resolveCredential({
18611
+ key: strFlag(flags, "key"),
18612
+ account: strFlag(flags, "account"),
18613
+ origin
18614
+ });
18225
18615
  if (!key) {
18226
18616
  r.done(
18227
18617
  {
18228
18618
  signedIn: false,
18229
18619
  origin,
18620
+ accounts: stored,
18230
18621
  next_step: "vos login (only needed for hosting, sharing or cloud rendering)"
18231
18622
  },
18232
- `Not signed in to ${origin}. Recording and rendering need no account; vos login when you want a link.`
18623
+ `Not signed in to ${origin}. Recording and rendering need no account; vos login when you want a link.${alsoStored}`
18233
18624
  );
18234
18625
  return EXIT_ERROR;
18235
18626
  }
18627
+ const resolved = lastCredential();
18628
+ const via = resolved?.via ?? "stored";
18629
+ const viaLine = via === "flag" ? " (the key passed as --key)" : via === "env" ? " (from VOS_API_KEY, which outranks the stored credential)" : via === "account-flag" ? " (--account)" : via === "account-env" ? ` (${ACCOUNT_ENV})` : "";
18236
18630
  const res = await apiJson(origin, "/api/user/whoami", { key });
18237
18631
  if (res.status === 404) {
18238
18632
  r.done(
18239
18633
  {
18240
18634
  signedIn: true,
18241
18635
  origin,
18636
+ via,
18242
18637
  user: null,
18243
18638
  key: null,
18639
+ accounts: stored,
18244
18640
  next_step: "vos push when you want the work hosted"
18245
18641
  },
18246
- `A credential is present for ${origin} (this origin cannot say whose).`
18642
+ `A credential is present for ${origin}${viaLine} (this origin cannot say whose).${alsoStored}`
18247
18643
  );
18248
18644
  return EXIT_OK;
18249
18645
  }
@@ -18252,50 +18648,156 @@ async function cmdWhoami(argv) {
18252
18648
  {
18253
18649
  signedIn: false,
18254
18650
  origin,
18651
+ via,
18255
18652
  error: apiError("whoami", res),
18653
+ accounts: stored,
18256
18654
  next_step: "vos login"
18257
18655
  },
18258
- `The stored credential is refused by ${origin}: ${apiError("whoami", res)}. Run vos login.`
18656
+ `The stored credential is refused by ${origin}${viaLine}: ${apiError("whoami", res)}. Run vos login.${alsoStored}`
18259
18657
  );
18260
18658
  return EXIT_ERROR;
18261
18659
  }
18262
18660
  if (res.status !== 200) throw new Error(apiError("whoami", res));
18263
18661
  const user = res.body.user;
18662
+ const actor = res.body.actor;
18264
18663
  const k = res.body.key;
18265
18664
  const who = user?.username ? `@${user.username}` : user?.name ?? "an account";
18665
+ const orgLine = user?.kind === "org" ? ` (an organization${actor ? `, ${actor.username ? `@${actor.username}` : actor.name ?? "you"} recorded as the person behind it` : ""})` : "";
18266
18666
  const keyLine = k ? `key "${k.name ?? ""}" (${k.prefix ?? "\u2026"}, ${(k.scopes ?? []).join(", ") || "no scopes"})` : "a session, not a key";
18667
+ if (via === "stored" && resolved && !resolved.account && user?.id) {
18668
+ const active = readActive(DEFAULT_STORE);
18669
+ if (active && active.key === key) {
18670
+ activate(DEFAULT_STORE, {
18671
+ ...active,
18672
+ account: {
18673
+ id: user.id,
18674
+ kind: user.kind === "org" ? "org" : "person",
18675
+ handle: user.username ?? null
18676
+ }
18677
+ });
18678
+ }
18679
+ }
18267
18680
  r.done(
18268
18681
  {
18269
18682
  signedIn: true,
18270
18683
  origin,
18684
+ via,
18271
18685
  user,
18686
+ actor: actor ?? null,
18272
18687
  key: k,
18688
+ accounts: storedAccounts(origin),
18273
18689
  next_step: "vos push when you want the work hosted"
18274
18690
  },
18275
- `Signed in to ${origin} as ${who}, with ${keyLine}.`
18691
+ `Signed in to ${origin} as ${who}${orgLine}, with ${keyLine}${viaLine}.${alsoStored}`
18692
+ );
18693
+ return EXIT_OK;
18694
+ }
18695
+ function cmdSwitch(argv) {
18696
+ const { flags, positionals } = parseArgs(argv, BOOLEAN_FLAGS15);
18697
+ const r = reporterFor(flags);
18698
+ const origin = platformOrigin({
18699
+ origin: strFlag(flags, "origin"),
18700
+ api: strFlag(flags, "api")
18701
+ });
18702
+ const store = DEFAULT_STORE;
18703
+ const stored = listStored(store, origin);
18704
+ const active = readActive(store);
18705
+ const target = switchTarget(
18706
+ stored,
18707
+ active?.key ?? null,
18708
+ positionals[0],
18709
+ origin
18710
+ );
18711
+ if (!target.ok) throw new UsageError(target.error);
18712
+ const to = target.to;
18713
+ if (active && active.key === to.key) {
18714
+ r.done(
18715
+ {
18716
+ origin,
18717
+ account: to.account,
18718
+ changed: false,
18719
+ accounts: storedAccounts(origin, store),
18720
+ next_step: "vos push when you want the work hosted"
18721
+ },
18722
+ `${accountLabel(to.account)} is already the account in use on ${origin}.`
18723
+ );
18724
+ return EXIT_OK;
18725
+ }
18726
+ activate(store, to);
18727
+ const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18728
+ r.done(
18729
+ {
18730
+ origin,
18731
+ account: to.account,
18732
+ changed: true,
18733
+ accounts: storedAccounts(origin, store),
18734
+ next_step: envKey ? "unset VOS_API_KEY; it outranks the stored credential" : "vos push when you want the work hosted"
18735
+ },
18736
+ `Now ${accountLabel(to.account)} on ${origin}${active?.account ? ` (${accountLabel(active.account)} stays stored)` : ""}.${envKey ? " VOS_API_KEY is set in this shell and still outranks it." : ""}`
18276
18737
  );
18277
18738
  return EXIT_OK;
18278
18739
  }
18279
18740
  function cmdLogout(argv) {
18280
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS14);
18741
+ const { flags, positionals } = parseArgs(argv, LOGOUT_FLAGS);
18281
18742
  const r = reporterFor(flags);
18282
- const had = existsSync29(CREDENTIALS_PATH);
18283
- rmSync3(CREDENTIALS_PATH, { force: true });
18743
+ const origin = platformOrigin({
18744
+ origin: strFlag(flags, "origin"),
18745
+ api: strFlag(flags, "api")
18746
+ });
18747
+ const store = DEFAULT_STORE;
18284
18748
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18749
+ const envNote = envKey ? " VOS_API_KEY is still set in this shell and still resolves." : "";
18750
+ const envStep = envKey ? "unset VOS_API_KEY as well; it still resolves" : "vos login when you next need hosting";
18751
+ if (flags.all === true) {
18752
+ const { had } = removeAll(store);
18753
+ r.done(
18754
+ {
18755
+ path: CREDENTIALS_PATH,
18756
+ removed: had,
18757
+ all: true,
18758
+ envCredential: envKey,
18759
+ next_step: envStep
18760
+ },
18761
+ (had ? "Removed every stored credential." : "Nothing was stored.") + envNote
18762
+ );
18763
+ return EXIT_OK;
18764
+ }
18765
+ const ref = positionals[0];
18766
+ const target = ref ? findStored(store, origin, ref) : readActive(store);
18767
+ if (!target) {
18768
+ const have = storedAccounts(origin, store);
18769
+ r.done(
18770
+ {
18771
+ path: CREDENTIALS_PATH,
18772
+ removed: false,
18773
+ envCredential: envKey,
18774
+ accounts: have,
18775
+ next_step: envStep
18776
+ },
18777
+ (ref ? `Nothing stored for ${ref.startsWith("@") ? ref : `@${ref}`} on ${origin}.` : `Nothing stored at ${CREDENTIALS_PATH}.`) + (have.length ? ` Stored: ${have.map((a) => a.label).join(", ")}.` : "") + envNote
18778
+ );
18779
+ return EXIT_OK;
18780
+ }
18781
+ const { wasActive } = removeStored(store, target);
18782
+ const left = storedAccounts(origin, store);
18783
+ const leftLine = left.length ? wasActive ? ` Still stored: ${left.map((a) => a.label).join(", ")}; vos switch <name> to use one.` : ` ${left.find((a) => a.active)?.label ?? "another account"} stays in use.` : "";
18285
18784
  r.done(
18286
18785
  {
18287
18786
  path: CREDENTIALS_PATH,
18288
- removed: had,
18787
+ removed: true,
18788
+ account: target.account,
18789
+ wasActive,
18289
18790
  envCredential: envKey,
18290
- next_step: envKey ? "unset VOS_API_KEY as well; it still resolves" : "vos login when you next need hosting"
18791
+ accounts: left,
18792
+ next_step: left.length && wasActive ? "vos switch <name>" : envStep
18291
18793
  },
18292
- (had ? `Removed ${CREDENTIALS_PATH}.` : `Nothing stored at ${CREDENTIALS_PATH}.`) + (envKey ? " VOS_API_KEY is still set in this shell and still resolves." : "")
18794
+ `Removed ${accountLabel(target.account)} (${target.origin}).${leftLine}${envNote}`
18293
18795
  );
18294
18796
  return EXIT_OK;
18295
18797
  }
18296
18798
 
18297
18799
  // src/plugin/run.ts
18298
- var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
18800
+ var BOOLEAN_FLAGS16 = /* @__PURE__ */ new Set([
18299
18801
  "json",
18300
18802
  "help",
18301
18803
  "wait",
@@ -18359,8 +18861,12 @@ Take pipeline
18359
18861
  vos join <take> <take|file.mp4>... --out <dir> [--keep-edits|--force] [--captions] [--zooms per-beat] [--mute <file>]... [--json]
18360
18862
  several takes (and plain video files) as ONE take, cut in argument order: the first is the primary (its frame, cursor, export and sound), each next one a media m1, m2, \u2026 with its footage copied, its spans tagged and renamed, its layers moved to where it now starts; a file opens full-bleed and its own sound plays as an audio clip (--mute <file> leaves it out). --captions lands every step's caption anchored to its step, starting as the step begins (anchor.at "start"), each ending with its clip. --zooms per-beat keeps a beat's zoom on its steps marked "zoom": true (as vos plan --zooms per-beat). The result must pass vos validate before anything is written
18361
18863
  --keep-edits joins again over an edited joined take and keeps its edits: inputs are matched to their media by source file (join.json records each input's path and footage hash), a kept input keeps its id and everything on it (segments in their edited order, spans, rejections, anchored layers, its sound), the frame, export, styles and still stay, a new input is joined fresh under a new id (and only it is captioned or re-planned), a re-recorded input (changed footage at its path, or the same script at a new one) keeps its edits re-timed by its steps as vos plan --reuse does, and an input that is gone takes its edits with it, each named. A new input's segments go before the next kept input's; reordering the kept inputs regroups the segments in argument order. The first input must stay the primary. --force replaces the take instead
18362
- vos score <take> --track <slug|file> [--at <step id|output seconds>] [--gain 0..1] [--normalize <lufs|off>] [--fade-in <s>] [--fade-out <s>] [--dry-run] [--json]
18363
- the film's music as one audio clip (id score): reads the track's loudness, finds its strongest swell and offsets the track so the swell lands at --at (a step, resolved through the cut, or output seconds); without --at it starts where the track first rises above its median level. The clip carries its measured loudness and a target, --normalize (default -16 LUFS), with --gain (default 0.5) a trim on it; --normalize off plays it at --gain alone
18864
+ vos music ls [--mood <slug>] [--vocals none|chops|lead] [--loops] [--bpm <lo>-<hi>] [--min-length <s>] [--q <words>] [--json]
18865
+ 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
18866
+ 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]
18867
+ 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
18868
+ 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]
18869
+ the film's music as one audio clip (id score): reads the track's loudness, finds its strongest swell and offsets the track so the swell lands at --at (a step, resolved through the cut, or output seconds); without --at it starts where the track first rises above its median level. --track best runs vos music pick and places its first pick, its payoff as --at unless one is given. The clip carries its measured loudness and a target, --normalize (default -16 LUFS), with --gain (default 0.5) a trim on it; --normalize off plays it at --gain alone
18364
18870
  vos audio measure <take> [--json]
18365
18871
  every audio clip's loudness (BS.1770 integrated LUFS and sample peak, the meter check-video reads a render with), written beside it in doc.json as loudness { lufs, peakDb, at, src }; a clip's normalize (a target in LUFS, -30..-6) plays it at the gain that reaches it, past 1 if need be, capped so its peak stays under -1 dBFS, and only once it is measured for its own key
18366
18872
  vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
@@ -18394,15 +18900,22 @@ This machine (no account)
18394
18900
  agent, a credential (present or absent, never printed), and the
18395
18901
  dev server when --url names one. Exit 3 when no browser
18396
18902
  vos whoami [--json]
18397
- the key's name and the account it belongs to, never the key
18398
- vos logout [--json]
18399
- remove ~/.config/vos/credentials (VOS_API_KEY still resolves)
18903
+ the account in use (its key's name, which rung resolved it) and
18904
+ every other account stored for the origin, never a key
18905
+ vos switch [<handle>] [--json]
18906
+ use another stored account (with two stored, the other one);
18907
+ vos login once per account, approving as it, stores them all
18908
+ vos logout [<handle>] [--all] [--json]
18909
+ forget the account in use, a named one, or every stored
18910
+ credential (VOS_API_KEY still resolves)
18400
18911
 
18401
18912
  Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
18402
18913
  vos login [--key <vos_sk_\u2026>] [--handoff <url>] [--label <name>] [--no-browser]
18403
18914
  sign in via the browser (approve at vos.so/cli/auth; works
18404
18915
  headless \u2014 print the URL, a human approves, the key stores
18405
18916
  itself). --key skips the browser (mint at vos.so/app/api).
18917
+ Additive: the person picks the account on the approval page,
18918
+ and every account signed in stays stored (vos switch).
18406
18919
  --handoff https://vos.so/h/\u2026 exchanges the setup link a
18407
18920
  signed-in person's line carries for a key, once, no click
18408
18921
  vos fetch <vosId|watch-url> [--out dir] [--media] [--json]
@@ -18466,7 +18979,8 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
18466
18979
  kept) or the take's doc.json. ALWAYS pull before editing pushed work.
18467
18980
  --media also downloads the recording (+ mic/cam sidecars) beside
18468
18981
  the doc and re-anchors it, so digest/frames/render run locally
18469
- auth: --key, VOS_API_KEY, or ~/.config/vos/credentials; keys can
18982
+ auth: --key, --account <handle>, VOS_API_KEY, VOS_ACCOUNT, or the
18983
+ stored account in use (~/.config/vos/credentials); keys can
18470
18984
  never publish (visibility stays private; humans publish on vos.so)
18471
18985
  vos folder list
18472
18986
  vos folder create <name> [--parent <folderId|slug>] [--desc <text>]
@@ -18667,9 +19181,10 @@ doc.json edits arrive intact; a human can drag every zoom span. Keeps
18667
19181
  serving until Ctrl-C. The studio is https://vos.so by default (VOS_ORIGIN
18668
19182
  moves it; --studio http://localhost:6060 for a dev server).
18669
19183
 
18670
- push/pull \u2014 the hosted iteration loop (credentials: --key, VOS_API_KEY, or
18671
- ~/.config/vos/credentials via vos login; origin via VOS_ORIGIN, default
18672
- https://vos.so). A take push uploads the recording ONCE (content-addressed),
19184
+ push/pull \u2014 the hosted iteration loop (credentials: --key, --account <handle>,
19185
+ VOS_API_KEY, VOS_ACCOUNT, or the account in use at ~/.config/vos/credentials
19186
+ via vos login, one sign-in per account and vos switch between them; origin
19187
+ via VOS_ORIGIN, default https://vos.so). A take push uploads the recording ONCE (content-addressed),
18673
19188
  lowers doc.json, and creates a private vos (first push asks; --yes for
18674
19189
  headless) or a new version against the base in vos.json \u2014 a stale base
18675
19190
  prints the human's typed changelog and exits 1 (run pull, re-apply, push
@@ -18860,8 +19375,8 @@ function takeBrowserArgs(multi) {
18860
19375
  function takeStorageState(flags) {
18861
19376
  const raw = strFlag(flags, "storage-state");
18862
19377
  if (!raw) return void 0;
18863
- const path = resolve21(raw);
18864
- if (!existsSync30(path))
19378
+ const path = resolve22(raw);
19379
+ if (!existsSync31(path))
18865
19380
  throw new UsageError(`--storage-state: no such file: ${path}`);
18866
19381
  return path;
18867
19382
  }
@@ -18874,7 +19389,7 @@ function takeSessionName(flags, storageState) {
18874
19389
  throw new UsageError(
18875
19390
  "--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
18876
19391
  );
18877
- if (!existsSync30(sessionDir(name)))
19392
+ if (!existsSync31(sessionDir(name)))
18878
19393
  throw new UsageError(
18879
19394
  `no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
18880
19395
  );
@@ -18892,14 +19407,14 @@ async function inSession(name, actions, fn) {
18892
19407
  }
18893
19408
  function reRecord(outDir, flags, r) {
18894
19409
  const wantsReuse = flags.reuse === true;
18895
- if (wantsReuse && !existsSync30(join33(outDir, "doc.json")))
19410
+ if (wantsReuse && !existsSync31(join33(outDir, "doc.json")))
18896
19411
  throw new UsageError(
18897
19412
  `--reuse: ${outDir} has no cut to keep (no doc.json); record without --reuse`
18898
19413
  );
18899
19414
  let previousCut = null;
18900
19415
  return {
18901
19416
  prepare: async () => {
18902
- if (existsSync30(join33(outDir, "meta.json"))) {
19417
+ if (existsSync31(join33(outDir, "meta.json"))) {
18903
19418
  const { prevDoc, kept } = await prepareReRecord(outDir);
18904
19419
  if (prevDoc) previousCut = join33(outDir, PREV_DOC_NAME);
18905
19420
  r.log(
@@ -18987,7 +19502,7 @@ function wallGate(flags, r, opts) {
18987
19502
  async function cmdRecord(argv) {
18988
19503
  const { positionals, flags, multi } = parseArgs(
18989
19504
  argv,
18990
- BOOLEAN_FLAGS15,
19505
+ BOOLEAN_FLAGS16,
18991
19506
  MULTI_FLAGS2
18992
19507
  );
18993
19508
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -19003,7 +19518,7 @@ async function cmdRecord(argv) {
19003
19518
  const scheme = urlSchemeProblem2(url);
19004
19519
  if (scheme) throw new UsageError(scheme);
19005
19520
  announceEnvNames(actions, r);
19006
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19521
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
19007
19522
  const storageState = takeStorageState(flags);
19008
19523
  const sessionName = takeSessionName(flags, storageState);
19009
19524
  const browserArgs = takeBrowserArgs(multi);
@@ -19023,7 +19538,7 @@ async function cmdRecord(argv) {
19023
19538
  );
19024
19539
  const hostedActions = await scriptForHost(
19025
19540
  actions,
19026
- dirname12(resolve21(actionsPath))
19541
+ dirname12(resolve22(actionsPath))
19027
19542
  ).catch((e) => {
19028
19543
  throw new UsageError(e instanceof Error ? e.message : String(e));
19029
19544
  });
@@ -19133,7 +19648,7 @@ async function cmdRecord(argv) {
19133
19648
  sessionName,
19134
19649
  actions,
19135
19650
  (context2) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
19136
- actionsDir: dirname12(resolve21(actionsPath)),
19651
+ actionsDir: dirname12(resolve22(actionsPath)),
19137
19652
  storageState,
19138
19653
  headers,
19139
19654
  context: context2,
@@ -19199,7 +19714,7 @@ async function cmdRecord(argv) {
19199
19714
  ...flags["allow-wall"] === true ? ["--allow-wall"] : [],
19200
19715
  ...confirm.map((c) => `--confirm ${c}`),
19201
19716
  // A take that already has a cut keeps it on the new footage.
19202
- ...existsSync30(join33(outDir, "doc.json")) ? ["--reuse"] : []
19717
+ ...existsSync31(join33(outDir, "doc.json")) ? ["--reuse"] : []
19203
19718
  ].join(" ");
19204
19719
  const nextOut = strFlag(flags, "out") ?? "take";
19205
19720
  const guardNote = !unconfirmed.ok ? unconfirmed.unconfirmed.length ? ` (it refuses until you add ${unconfirmed.unconfirmed.map((id) => `--confirm ${id}`).join(" ")}: ${unconfirmed.unconfirmed.length === 1 ? "that step spends" : "those steps spend"} something real)` : ` (${unconfirmed.unknown.map((c) => `--confirm ${c}`).join(", ")} names no guarded step)` : "";
@@ -19241,7 +19756,7 @@ ${lines.join("\n")}
19241
19756
  sessionName,
19242
19757
  actions,
19243
19758
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19244
- actionsDir: dirname12(resolve21(actionsPath)),
19759
+ actionsDir: dirname12(resolve22(actionsPath)),
19245
19760
  maxDurationSeconds,
19246
19761
  storageState,
19247
19762
  headers,
@@ -19339,7 +19854,7 @@ ${lines.join("\n")}
19339
19854
  async function cmdCreate2(argv) {
19340
19855
  const { positionals, flags, multi } = parseArgs(
19341
19856
  argv,
19342
- BOOLEAN_FLAGS15,
19857
+ BOOLEAN_FLAGS16,
19343
19858
  MULTI_FLAGS2
19344
19859
  );
19345
19860
  const actionsPath = strFlag(flags, "actions");
@@ -19356,7 +19871,7 @@ async function cmdCreate2(argv) {
19356
19871
  const scheme = urlSchemeProblem2(url);
19357
19872
  if (scheme) throw new UsageError(scheme);
19358
19873
  announceEnvNames(actions, r);
19359
- const outDir = resolve21(strFlag(flags, "out") ?? "take");
19874
+ const outDir = resolve22(strFlag(flags, "out") ?? "take");
19360
19875
  const format = videoFormat(strFlag(flags, "format"), positionals[0]);
19361
19876
  const out = positionals[0] ?? join33(outDir, `out.${format}`);
19362
19877
  const parallel = numFlag(flags, "parallel", 1);
@@ -19380,7 +19895,7 @@ async function cmdCreate2(argv) {
19380
19895
  sessionName,
19381
19896
  actions,
19382
19897
  (context2) => recordTake(browser, url, actions, paths, r.log, {
19383
- actionsDir: dirname12(resolve21(actionsPath)),
19898
+ actionsDir: dirname12(resolve22(actionsPath)),
19384
19899
  maxDurationSeconds,
19385
19900
  storageState,
19386
19901
  headers,
@@ -19497,7 +20012,7 @@ async function cmdPlan(argvIn) {
19497
20012
  const argv = bareCaptionsFlag(argvIn);
19498
20013
  const { positionals, flags, multi } = parseArgs(
19499
20014
  argv,
19500
- BOOLEAN_FLAGS15,
20015
+ BOOLEAN_FLAGS16,
19501
20016
  /* @__PURE__ */ new Set(["with"])
19502
20017
  );
19503
20018
  const dir = positionals[0];
@@ -19512,8 +20027,8 @@ async function cmdPlan(argvIn) {
19512
20027
  }
19513
20028
  let reuse;
19514
20029
  if (flags.reuse === true) {
19515
- const from = resolve21(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
19516
- if (!existsSync30(from)) {
20030
+ const from = resolve22(strFlag(flags, "from") ?? join33(dir, PREV_DOC_NAME));
20031
+ if (!existsSync31(from)) {
19517
20032
  throw new UsageError(
19518
20033
  `--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
19519
20034
  );
@@ -19524,7 +20039,7 @@ async function cmdPlan(argvIn) {
19524
20039
  };
19525
20040
  }
19526
20041
  const style = await resolveStyleRef(flags);
19527
- const hasDoc = existsSync30(join33(dir, "doc.json"));
20042
+ const hasDoc = existsSync31(join33(dir, "doc.json"));
19528
20043
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
19529
20044
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
19530
20045
  const stepCaptions = strFlag(flags, "captions") === "steps";
@@ -19604,7 +20119,7 @@ async function cmdPlan(argvIn) {
19604
20119
  ${s.reuse.flagged.join("\n ")}` : "") : "";
19605
20120
  r.done(
19606
20121
  {
19607
- take: resolve21(dir),
20122
+ take: resolve22(dir),
19608
20123
  fresh: s.fresh,
19609
20124
  cursorKept: s.cursorKept,
19610
20125
  zoomAuto,
@@ -19733,7 +20248,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
19733
20248
  async function cmdRender(argv) {
19734
20249
  const { positionals, flags, multi } = parseArgs(
19735
20250
  argv,
19736
- BOOLEAN_FLAGS15,
20251
+ BOOLEAN_FLAGS16,
19737
20252
  MULTI_FLAGS2
19738
20253
  );
19739
20254
  const dir = positionals[0];
@@ -19801,7 +20316,7 @@ async function cmdRender(argv) {
19801
20316
  async function cmdFrames(argv) {
19802
20317
  const { positionals, flags, multi } = parseArgs(
19803
20318
  argv,
19804
- BOOLEAN_FLAGS15,
20319
+ BOOLEAN_FLAGS16,
19805
20320
  MULTI_FLAGS2
19806
20321
  );
19807
20322
  const dir = positionals[0];
@@ -19884,7 +20399,7 @@ async function cmdFrames(argv) {
19884
20399
  async function cmdDeliver(argv) {
19885
20400
  const { positionals, flags, multi } = parseArgs(
19886
20401
  argv,
19887
- BOOLEAN_FLAGS15,
20402
+ BOOLEAN_FLAGS16,
19888
20403
  MULTI_FLAGS2
19889
20404
  );
19890
20405
  const dir = positionals[0];
@@ -19984,11 +20499,11 @@ async function resolveStyleRef(flags) {
19984
20499
  }
19985
20500
  async function resolveDocRef(ref, flags, what) {
19986
20501
  const styleRef = ref;
19987
- const file = existsSync30(styleRef) ? resolve21(
20502
+ const file = existsSync31(styleRef) ? resolve22(
19988
20503
  styleRef,
19989
- existsSync30(join33(styleRef, "doc.json")) ? "doc.json" : ""
20504
+ existsSync31(join33(styleRef, "doc.json")) ? "doc.json" : ""
19990
20505
  ) : null;
19991
- if (file && existsSync30(file)) {
20506
+ if (file && existsSync31(file)) {
19992
20507
  return {
19993
20508
  from: file,
19994
20509
  doc: migrateHostedDoc4(
@@ -20034,7 +20549,7 @@ async function officialTemplate(origin, ref) {
20034
20549
  }
20035
20550
  }
20036
20551
  async function cmdDigest(argv) {
20037
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20552
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20038
20553
  const dir = positionals[0];
20039
20554
  if (!dir)
20040
20555
  throw new UsageError(
@@ -20098,19 +20613,19 @@ async function cmdDigest(argv) {
20098
20613
  }
20099
20614
  }
20100
20615
  async function cmdOpen(argv) {
20101
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20616
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20102
20617
  const dir = positionals[0];
20103
20618
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
20104
20619
  const r = createReporter(flags.json === true);
20105
20620
  const take = await loadTake(dir);
20106
- if (!existsSync30(take.paths.recording)) {
20621
+ if (!existsSync31(take.paths.recording)) {
20107
20622
  throw new UsageError(`${dir} has no recording \u2014 re-run record`);
20108
20623
  }
20109
20624
  const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
20110
20625
  /\/+$/,
20111
20626
  ""
20112
20627
  );
20113
- const server = await startTakeServer(resolve21(dir), {});
20628
+ const server = await startTakeServer(resolve22(dir), {});
20114
20629
  const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
20115
20630
  r.event({ event: "open", server: server.base, url });
20116
20631
  r.log(`take served at ${server.base}`);
@@ -20133,7 +20648,7 @@ async function cmdOpen(argv) {
20133
20648
  return EXIT_OK;
20134
20649
  }
20135
20650
  async function cmdCallout(argv) {
20136
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20651
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20137
20652
  const dir = positionals[0];
20138
20653
  const shape = positionals[1];
20139
20654
  if (!dir || !shape || !isCalloutShape(shape))
@@ -20232,11 +20747,11 @@ async function cmdCallout(argv) {
20232
20747
  return EXIT_OK;
20233
20748
  }
20234
20749
  async function cmdValidate(argv) {
20235
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15);
20750
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16);
20236
20751
  const target = positionals[0];
20237
20752
  if (!target) throw new UsageError("vos validate <actions.json|take>");
20238
20753
  const r = createReporter(flags.json === true);
20239
- const kitTarget = target.endsWith("kit.json") ? target : existsSync30(join33(target, "kit.json")) && !isTakeDir(target) ? join33(target, "kit.json") : null;
20754
+ const kitTarget = target.endsWith("kit.json") ? target : existsSync31(join33(target, "kit.json")) && !isTakeDir(target) ? join33(target, "kit.json") : null;
20240
20755
  if (kitTarget) {
20241
20756
  const picture = flags.picture === true;
20242
20757
  const verdict = await validateKit(kitTarget, { picture });
@@ -20268,7 +20783,7 @@ async function cmdValidate(argv) {
20268
20783
  r.done({ valid: true, target }, `${target}: valid actions file`);
20269
20784
  return EXIT_OK;
20270
20785
  }
20271
- if (!isTakeDir(target) && existsSync30(join33(target, "config.json"))) {
20786
+ if (!isTakeDir(target) && existsSync31(join33(target, "config.json"))) {
20272
20787
  const parsed = JSON.parse(
20273
20788
  await readFile19(join33(target, "config.json"), "utf8")
20274
20789
  );
@@ -20312,7 +20827,7 @@ async function cmdValidate(argv) {
20312
20827
  const problems = [];
20313
20828
  const warnings = [];
20314
20829
  const notes = [];
20315
- if (!existsSync30(take.paths.recording)) {
20830
+ if (!existsSync31(take.paths.recording)) {
20316
20831
  problems.push("missing recording (re-run record)");
20317
20832
  } else {
20318
20833
  const mismatch = containerMismatch(
@@ -20353,7 +20868,7 @@ async function cmdValidate(argv) {
20353
20868
  const found = localFile(rel, target);
20354
20869
  if (!("refused" in found)) continue;
20355
20870
  problems.push(
20356
- existsSync30(resolve21(target, rel)) ? `doc.json: ${ref.where}: ${ref.key} ${found.refused}` : `doc.json: ${ref.where}: ${ref.key} is not a file in the take, so a render plays the film without it (put the file there, or fix the key)`
20871
+ existsSync31(resolve22(target, rel)) ? `doc.json: ${ref.where}: ${ref.key} ${found.refused}` : `doc.json: ${ref.where}: ${ref.key} is not a file in the take, so a render plays the film without it (put the file there, or fix the key)`
20357
20872
  );
20358
20873
  }
20359
20874
  if (flags.picture === true && (take.doc.overlays ?? []).some((o) => o.kind === "html")) {
@@ -20422,7 +20937,7 @@ async function cmdValidate(argv) {
20422
20937
  async function cmdPush3(argv) {
20423
20938
  const { positionals, flags, multi } = parseArgs(
20424
20939
  argv,
20425
- BOOLEAN_FLAGS15,
20940
+ BOOLEAN_FLAGS16,
20426
20941
  MULTI_FLAGS2
20427
20942
  );
20428
20943
  const dir = positionals[0];
@@ -20432,7 +20947,7 @@ async function cmdPush3(argv) {
20432
20947
  );
20433
20948
  const refusal = takePushRefusal({
20434
20949
  vos: strFlag(flags, "vos"),
20435
- trackedVosId: readSyncState(resolve21(dir))?.vosId ?? null,
20950
+ trackedVosId: readSyncState(resolve22(dir))?.vosId ?? null,
20436
20951
  programOnly: ["slug", "tags", "base", "remix-of", "still"].filter(
20437
20952
  (name) => hasFlag(flags, name)
20438
20953
  )
@@ -20451,7 +20966,7 @@ async function cmdPush3(argv) {
20451
20966
  `--claimable creates a NEW claimable vos; ${iterating.map((f) => `--${f}`).join(", ")} ${iterating.length > 1 ? "apply" : "applies"} to a keyed push`
20452
20967
  );
20453
20968
  const claim = await pushTakeClaimable(
20454
- resolve21(dir),
20969
+ resolve22(dir),
20455
20970
  {
20456
20971
  api: strFlag(flags, "api"),
20457
20972
  origin: strFlag(flags, "origin"),
@@ -20475,7 +20990,7 @@ reference and the only credential.`
20475
20990
  }
20476
20991
  const overrides = multi.override;
20477
20992
  const result = await pushTake(
20478
- resolve21(dir),
20993
+ resolve22(dir),
20479
20994
  {
20480
20995
  key: strFlag(flags, "key"),
20481
20996
  api: strFlag(flags, "api"),
@@ -20499,7 +21014,7 @@ reference and the only credential.`
20499
21014
  }).replace(/\/+$/, "");
20500
21015
  const landed = flags.wait === true ? await waitForLanded({
20501
21016
  origin: pushedTo,
20502
- key: requireCredential(strFlag(flags, "key")),
21017
+ key: requireCredential(credentialFlags(flags)),
20503
21018
  vosId: result.vosId,
20504
21019
  versionId: result.versionId ?? null,
20505
21020
  log: (l) => r.log(l)
@@ -20527,11 +21042,11 @@ ${landedLines(landed)}` : "")
20527
21042
  return EXIT_OK;
20528
21043
  }
20529
21044
  async function cmdPull2(argv) {
20530
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS15, MULTI_FLAGS2);
21045
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS16, MULTI_FLAGS2);
20531
21046
  const dir = positionals[0] ?? ".";
20532
21047
  const r = createReporter(flags.json === true);
20533
21048
  const result = await pullTake(
20534
- resolve21(dir),
21049
+ resolve22(dir),
20535
21050
  {
20536
21051
  key: strFlag(flags, "key"),
20537
21052
  api: strFlag(flags, "api"),
@@ -20620,6 +21135,8 @@ async function run(argv) {
20620
21135
  return await cmdWhoami(rest);
20621
21136
  case "logout":
20622
21137
  return cmdLogout(rest);
21138
+ case "switch":
21139
+ return cmdSwitch(rest);
20623
21140
  case "push": {
20624
21141
  const target = rest.find((a) => !a.startsWith("-"));
20625
21142
  if (target && isTakeDir(target)) return await cmdPush3(rest);
@@ -20666,6 +21183,8 @@ async function run(argv) {
20666
21183
  return await cmdScore(rest);
20667
21184
  case "audio":
20668
21185
  return await cmdAudio(rest);
21186
+ case "music":
21187
+ return await cmdMusic(rest);
20669
21188
  case "look":
20670
21189
  return await cmdLook(rest);
20671
21190
  default:
@@ -20708,7 +21227,7 @@ function verdictExit(v, strict, none) {
20708
21227
  async function cmdSession(argv) {
20709
21228
  const { positionals, flags } = parseArgs(
20710
21229
  argv,
20711
- /* @__PURE__ */ new Set([...BOOLEAN_FLAGS15, "headed"])
21230
+ /* @__PURE__ */ new Set([...BOOLEAN_FLAGS16, "headed"])
20712
21231
  );
20713
21232
  const [sub, arg] = positionals;
20714
21233
  const r = createReporter(flags.json === true);
@@ -20753,7 +21272,7 @@ async function cmdSession(argv) {
20753
21272
  if (!arg) throw new UsageError(usage);
20754
21273
  const bad = sessionNameVerdict(arg);
20755
21274
  if (bad) throw new UsageError(bad);
20756
- if (!existsSync30(sessionDir(arg)))
21275
+ if (!existsSync31(sessionDir(arg)))
20757
21276
  throw new UsageError(`no session "${arg}" (vos session list)`);
20758
21277
  const summary3 = await summarizeSession(arg);
20759
21278
  const url = strFlag(flags, "url");
@@ -20787,7 +21306,7 @@ async function cmdSession(argv) {
20787
21306
  if (!arg || !scriptPath) throw new UsageError(usage);
20788
21307
  const bad = sessionNameVerdict(arg);
20789
21308
  if (bad) throw new UsageError(bad);
20790
- if (!existsSync30(sessionDir(arg)))
21309
+ if (!existsSync31(sessionDir(arg)))
20791
21310
  throw new UsageError(`no session "${arg}" (vos session list)`);
20792
21311
  const url = strFlag(flags, "url");
20793
21312
  if (url && !/^https?:\/\//.test(url))
@@ -20880,4 +21399,4 @@ export {
20880
21399
  verbHelp,
20881
21400
  run
20882
21401
  };
20883
- //# sourceMappingURL=chunk-DM7ADA77.js.map
21402
+ //# sourceMappingURL=chunk-ZXFO32KW.js.map