@vosjs/cli 0.58.0 → 0.59.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.
@@ -36,7 +36,7 @@ import {
36
36
  videoFormat,
37
37
  writeCredential,
38
38
  writeSyncState
39
- } from "./chunk-IH5JJBIB.js";
39
+ } from "./chunk-YC3LY4LW.js";
40
40
  import {
41
41
  deadLine,
42
42
  deadWarns
@@ -185,6 +185,37 @@ import {
185
185
  unionFitLevel
186
186
  } from "@vosjs/studio-core";
187
187
  import { TYPEFACE_CATALOG, findFontFamily, findTypeface } from "@vosjs/shared";
188
+ var PIN_NEAR_FRACTION = 0.08;
189
+ var PIN_SURFACE_AREA = 0.25;
190
+ var LABEL_STOPWORDS = /* @__PURE__ */ new Set([
191
+ "the",
192
+ "and",
193
+ "for",
194
+ "with",
195
+ "your",
196
+ "you",
197
+ "this",
198
+ "that",
199
+ "from",
200
+ "into",
201
+ "button",
202
+ "link"
203
+ ]);
204
+ function wordsOf(text) {
205
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((x) => x.length >= 3 && !LABEL_STOPWORDS.has(x));
206
+ }
207
+ function layerWords(o) {
208
+ const src = typeof o.html === "string" ? o.html.replace(/<[^>]*>/g, " ") : typeof o.text === "string" ? o.text : "";
209
+ return new Set(wordsOf(src));
210
+ }
211
+ function selectorLabelWords(selector) {
212
+ const out = [];
213
+ const re = /(?:has-text\(|text=|(?:aria-label|title|alt|placeholder|name)\s*[*^$~|]?=\s*)['"]([^'"]+)['"]/g;
214
+ for (let m = re.exec(selector); m; m = re.exec(selector)) {
215
+ out.push(...wordsOf(m[1]));
216
+ }
217
+ return [...new Set(out)];
218
+ }
188
219
  var STEP_KINDS_ALL = [
189
220
  "none",
190
221
  "fade",
@@ -248,6 +279,20 @@ function lintAnim(name, anim, kinds, problems) {
248
279
  if (key === "stagger" && (typeof obj.stagger !== "number" || obj.stagger < 0 || obj.stagger > 2))
249
280
  problems.push(`${name}.anim.${side}.stagger must be seconds in 0..2`);
250
281
  }
282
+ if (obj.at !== void 0) {
283
+ if (!kinds.card || side !== "enter")
284
+ problems.push(
285
+ `${name}.anim.${side}.at is the card's own: frame.anim.enter.at delays the card's entrance; a clip already starts at its own start`
286
+ );
287
+ else if (typeof obj.at !== "number" || !Number.isFinite(obj.at) || obj.at < 0 || obj.at > 30)
288
+ problems.push(
289
+ `${name}.anim.enter.at must be OUTPUT seconds in 0..30 (when the card arrives; the ground plays alone until then)`
290
+ );
291
+ else if (kind === "none")
292
+ problems.push(
293
+ `${name}.anim.enter.at has nothing to delay: the enter is "none", so the card is on screen from the first frame`
294
+ );
295
+ }
251
296
  if (obj.side !== void 0) {
252
297
  if (kind !== "slide")
253
298
  problems.push(`${name}.anim.${side}.side is for a slide`);
@@ -599,6 +644,7 @@ function lintDoc(docIn) {
599
644
  }
600
645
  const tilt = entries(doc.tilt);
601
646
  checkByMedia(tilt, "tilt");
647
+ const dramatic = [];
602
648
  for (const t of tilt) {
603
649
  const name = spanName("tilt", t);
604
650
  for (const ax of ["rx", "ry"]) {
@@ -611,8 +657,8 @@ function lintDoc(docIn) {
611
657
  }
612
658
  checkTransition(t, name, problems);
613
659
  if (isNum(t.rx) && isNum(t.ry) && Math.abs(t.rx) + Math.abs(t.ry) > 25) {
614
- warnings.push(
615
- `${name}: combined lean ${(Math.abs(t.rx) + Math.abs(t.ry)).toFixed(0)}\xB0 reads dramatic \u2014 the premium band is \xB15..18\xB0 per axis`
660
+ dramatic.push(
661
+ `${name} ${(Math.abs(t.rx) + Math.abs(t.ry)).toFixed(0)}\xB0`
616
662
  );
617
663
  }
618
664
  if (t.source !== void 0 && t.source !== "manual" && t.source !== "auto") {
@@ -624,6 +670,11 @@ function lintDoc(docIn) {
624
670
  );
625
671
  }
626
672
  }
673
+ if (dramatic.length) {
674
+ warnings.push(
675
+ `tilt: ${dramatic.length === 1 ? "a span leans" : `${dramatic.length} spans lean`} past 25\xB0 combined (${dramatic.join(", ")}) and read${dramatic.length === 1 ? "s" : ""} dramatic \u2014 the premium band is \xB15..18\xB0 per axis; keep it if the piece means it`
676
+ );
677
+ }
627
678
  if (typeof doc.zoomStyle === "string" && doc.zoomStyle in RETIRED_ZOOM_STYLES) {
628
679
  const r = RETIRED_ZOOM_STYLES[doc.zoomStyle];
629
680
  warnings.push(
@@ -836,7 +887,8 @@ function lintDoc(docIn) {
836
887
  enter: CARD_ENTER_KINDS,
837
888
  exit: CARD_EXIT_KINDS,
838
889
  idle: null,
839
- words: false
890
+ words: false,
891
+ card: true
840
892
  },
841
893
  problems
842
894
  );
@@ -930,9 +982,12 @@ function lintDoc(docIn) {
930
982
  }
931
983
  if (footageEnd !== null && footageEnd > 0 && isNum(o.start) && isNum(o.duration)) {
932
984
  const over = o.start + o.duration - footageEnd;
933
- if (over > 0.05) {
985
+ const frameAnim = isObj(docIn) && isObj(docIn.frame) && isObj(docIn.frame.anim) ? docIn.frame.anim : null;
986
+ const exit = frameAnim ? isObj(frameAnim.exit) ? frameAnim.exit.kind : frameAnim.exit : void 0;
987
+ const cardLeaves = exit !== void 0 && exit !== "none";
988
+ if (over > 0.05 && !cardLeaves) {
934
989
  warnings.push(
935
- o.start >= footageEnd ? `${name} starts at ${o.start.toFixed(2)}s, after the footage ends (${footageEnd.toFixed(2)}s): it plays on bare backdrop and lengthens the output by ${over.toFixed(2)}s. A trim moves the footage, never an OUTPUT-anchored layer: re-place it (vos callout with the same --id), or put a freeze under it` : `${name} ends ${over.toFixed(2)}s after the footage (${footageEnd.toFixed(2)}s): the output extends over bare backdrop. Shorten it, or put a freeze under it`
990
+ o.start >= footageEnd ? `${name} starts at ${o.start.toFixed(2)}s, after the footage ends (${footageEnd.toFixed(2)}s): it plays over the card's held last frame and lengthens the output by ${over.toFixed(2)}s. A trim moves the footage, never an OUTPUT-anchored layer: re-place it (vos callout with the same --id), put a freeze under it, or, for a closing card on the ground, let the card leave (frame.anim.exit: 'fade')` : `${name} ends ${over.toFixed(2)}s after the footage (${footageEnd.toFixed(2)}s): the card holds its last frame under it. Shorten it, put a freeze under it, or, for a closing card on the ground, let the card leave (frame.anim.exit: 'fade')`
936
991
  );
937
992
  }
938
993
  }
@@ -1604,13 +1659,67 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
1604
1659
  if (!tf || !isNum(tf.x) || !isNum(tf.y)) return;
1605
1660
  const pinned = pins.get(String(o.id));
1606
1661
  if (!pinned && o.pin === void 0 && (o.kind === "html" || o.kind === "image" || o.kind === "video")) {
1607
- const cands = pinCandidates(docIn, {
1662
+ const words2 = layerWords(o);
1663
+ let layerBox = null;
1664
+ try {
1665
+ const b = pinBox(o, layout);
1666
+ layerBox = {
1667
+ x: tf.x * layout.W - b.w / 2,
1668
+ y: tf.y * layout.H - b.h / 2,
1669
+ w: b.w,
1670
+ h: b.h
1671
+ };
1672
+ } catch {
1673
+ layerBox = null;
1674
+ }
1675
+ const about = [];
1676
+ for (const c of pinCandidates(docIn, {
1608
1677
  start: o.start,
1609
1678
  duration: o.duration
1610
- });
1611
- if (cands.length) {
1679
+ })) {
1680
+ const named = c.selector ? selectorLabelWords(c.selector).filter((x) => words2.has(x)) : [];
1681
+ if (named.length) {
1682
+ about.push({
1683
+ step: String(c.step),
1684
+ why: `its words name ${c.step}'s element ("${named.join(" ")}")`
1685
+ });
1686
+ continue;
1687
+ }
1688
+ if (!layerBox) continue;
1689
+ let ref;
1690
+ try {
1691
+ ref = pinReferent(docIn, { step: c.step });
1692
+ } catch {
1693
+ ref = null;
1694
+ }
1695
+ if (!ref || ref.rect.w * ref.rect.h > PIN_SURFACE_AREA) continue;
1696
+ const target = pinRectOnScreen(
1697
+ ref.rect,
1698
+ c.output.start,
1699
+ layout,
1700
+ camera,
1701
+ zoomTrack
1702
+ );
1703
+ const dx = Math.max(
1704
+ 0,
1705
+ target.x - (layerBox.x + layerBox.w),
1706
+ layerBox.x - (target.x + target.w)
1707
+ );
1708
+ const dy = Math.max(
1709
+ 0,
1710
+ target.y - (layerBox.y + layerBox.h),
1711
+ layerBox.y - (target.y + target.h)
1712
+ );
1713
+ if (Math.hypot(dx, dy) <= PIN_NEAR_FRACTION * layout.H) {
1714
+ about.push({
1715
+ step: String(c.step),
1716
+ why: `it sits beside ${c.step}'s element`
1717
+ });
1718
+ }
1719
+ }
1720
+ if (about.length) {
1612
1721
  warnings.push(
1613
- `overlays[${i}] is not pinned, and ${cands.length === 1 ? "a step with an element sits" : "steps with an element sit"} inside its window: ${cands.map((c) => `${String(c.step)} (${c.do}${c.selector ? ` ${c.selector}` : ""})`).join(", ")} \u2014 a callout about one of them says so: pin: { step: "${String(cands[0].step)}" }`
1722
+ `overlays[${i}] is not pinned, but ${about[0].why} \u2014 a callout about it says so, and follows it through the camera: pin: { step: "${about[0].step}" }`
1614
1723
  );
1615
1724
  }
1616
1725
  }
@@ -1933,6 +2042,60 @@ function isTakeRelativeKey(key) {
1933
2042
  return !!key && !/^(https?:|blob:|data:|media:|\/\/)/.test(key) && !key.startsWith("/api/");
1934
2043
  }
1935
2044
  var takeRelativeFile = (key) => key.replace(/^\/+/, "");
2045
+ function sourceSidecarRefs(doc, keep = isTakeRelativeKey) {
2046
+ const out = [];
2047
+ for (const [field, where] of [
2048
+ ["micKey", "mic track"],
2049
+ ["camKey", "cam track"]
2050
+ ]) {
2051
+ const key = doc.source[field];
2052
+ if (!key || !keep(key)) continue;
2053
+ out.push({
2054
+ where,
2055
+ key,
2056
+ set: (next) => {
2057
+ doc.source[field] = next;
2058
+ }
2059
+ });
2060
+ }
2061
+ return out;
2062
+ }
2063
+ function docObjectRefs(doc, keep = isTakeRelativeKey) {
2064
+ const out = [];
2065
+ for (const clip of doc.objects ?? []) {
2066
+ const asset = clip.asset;
2067
+ if (asset.kind !== "gltf" || !keep(asset.key)) continue;
2068
+ out.push({
2069
+ where: `object ${clip.id}`,
2070
+ key: asset.key,
2071
+ set: (next) => {
2072
+ asset.key = next;
2073
+ }
2074
+ });
2075
+ }
2076
+ return out;
2077
+ }
2078
+ function docOverlayRefs(doc, keep = isTakeRelativeKey) {
2079
+ const out = [];
2080
+ for (const clip of doc.overlays ?? []) {
2081
+ if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
2082
+ out.push({
2083
+ where: `overlay ${clip.id}`,
2084
+ key: clip.key,
2085
+ set: (next) => {
2086
+ clip.key = next;
2087
+ }
2088
+ });
2089
+ }
2090
+ return out;
2091
+ }
2092
+ function docLayerRefs(doc, keep = isTakeRelativeKey) {
2093
+ return [
2094
+ ...docOverlayRefs(doc, keep),
2095
+ ...docObjectRefs(doc, keep),
2096
+ ...docAudioRefs(doc, keep)
2097
+ ];
2098
+ }
1936
2099
  function docMediaRefs(doc, keep = isTakeRelativeKey) {
1937
2100
  const out = [];
1938
2101
  for (const m of doc.media ?? []) {
@@ -1963,16 +2126,8 @@ function docMediaRefs(doc, keep = isTakeRelativeKey) {
1963
2126
  }
1964
2127
  });
1965
2128
  }
1966
- for (const clip of doc.overlays ?? []) {
1967
- if (!isKeyedOverlay(clip) || !keep(clip.key)) continue;
1968
- out.push({
1969
- where: `overlay ${clip.id}`,
1970
- key: clip.key,
1971
- set: (next) => {
1972
- clip.key = next;
1973
- }
1974
- });
1975
- }
2129
+ out.push(...docOverlayRefs(doc, keep));
2130
+ out.push(...docObjectRefs(doc, keep));
1976
2131
  const mark = doc.endCard?.mark;
1977
2132
  if (mark && keep(mark.key))
1978
2133
  out.push({
@@ -2579,9 +2734,9 @@ async function renderInPage(opts) {
2579
2734
  w.__error = String(e instanceof Error && e.stack || e);
2580
2735
  }
2581
2736
  }
2582
- async function renderChunk(context, serverBase, chunk2, opts, onChunkProgress) {
2737
+ async function renderChunk(context2, serverBase, chunk2, opts, onChunkProgress) {
2583
2738
  const outName = `render-chunk-${chunk2.index}.tmp`;
2584
- const page = await context.newPage();
2739
+ const page = await context2.newPage();
2585
2740
  try {
2586
2741
  page.on("console", (m) => {
2587
2742
  if (m.type() === "error" || process.env.VOS_RENDER_DEBUG)
@@ -2646,7 +2801,7 @@ async function renderAnimation(browser, opts) {
2646
2801
  const server = await startTakeServer(opts.workDir, {
2647
2802
  "/render.html": renderPageHtml()
2648
2803
  });
2649
- const context = await browser.newContext({
2804
+ const context2 = await browser.newContext({
2650
2805
  viewport: { width: opts.width, height: opts.height }
2651
2806
  });
2652
2807
  try {
@@ -2660,7 +2815,7 @@ async function renderAnimation(browser, opts) {
2660
2815
  };
2661
2816
  const outNames = await Promise.all(
2662
2817
  chunks.map(
2663
- (chunk2) => renderChunk(context, server.base, chunk2, effOpts, (fraction) => {
2818
+ (chunk2) => renderChunk(context2, server.base, chunk2, effOpts, (fraction) => {
2664
2819
  progress[chunk2.index] = fraction;
2665
2820
  reportProgress();
2666
2821
  })
@@ -2684,7 +2839,7 @@ async function renderAnimation(browser, opts) {
2684
2839
  );
2685
2840
  return { bytes, totalFrames, chunks: chunks.length };
2686
2841
  } finally {
2687
- await context.close();
2842
+ await context2.close();
2688
2843
  server.close();
2689
2844
  }
2690
2845
  }
@@ -3188,11 +3343,11 @@ async function digestTake(browser, dir, opts = {}) {
3188
3343
  const server = await startTakeServer(dir, {
3189
3344
  "/digest.html": digestPageHtml()
3190
3345
  });
3191
- const context = await browser.newContext({
3346
+ const context2 = await browser.newContext({
3192
3347
  viewport: { width: 640, height: 360 }
3193
3348
  });
3194
3349
  try {
3195
- const page = await context.newPage();
3350
+ const page = await context2.newPage();
3196
3351
  page.on("console", (m) => {
3197
3352
  if (m.type() === "error")
3198
3353
  process.stderr.write(` [digest page] ${m.text()}
@@ -3260,7 +3415,7 @@ async function digestTake(browser, dir, opts = {}) {
3260
3415
  }
3261
3416
  if (sizes["sheet.png"]) sheet = "sheet.png";
3262
3417
  } finally {
3263
- await context.close();
3418
+ await context2.close();
3264
3419
  server.close();
3265
3420
  }
3266
3421
  } else {
@@ -3527,11 +3682,11 @@ async function framesTake(browser, dir, opts) {
3527
3682
  "/render.html": renderPageHtml()
3528
3683
  });
3529
3684
  const ss = stillSupersample(width, height);
3530
- const context = await browser.newContext({
3685
+ const context2 = await browser.newContext({
3531
3686
  viewport: { width: width * ss, height: height * ss }
3532
3687
  });
3533
3688
  try {
3534
- const page = await context.newPage();
3689
+ const page = await context2.newPage();
3535
3690
  page.on("console", (m) => {
3536
3691
  if (m.type() === "error")
3537
3692
  process.stderr.write(` [stills page] ${m.text()}
@@ -3574,7 +3729,7 @@ async function framesTake(browser, dir, opts) {
3574
3729
  }
3575
3730
  return { frames, width, height, duration, outDir };
3576
3731
  } finally {
3577
- await context.close();
3732
+ await context2.close();
3578
3733
  server.close();
3579
3734
  }
3580
3735
  }
@@ -4333,11 +4488,11 @@ var outputLength = (doc) => ratedSegments4(doc).reduce((acc, s) => {
4333
4488
  const rate = s.rate && s.rate > 0 ? s.rate : 1;
4334
4489
  return acc + (s.out - s.in) / rate;
4335
4490
  }, 0);
4336
- function templateWords(words) {
4337
- const headline = (words.headline ?? "").trim();
4338
- const kicker = (words.kicker ?? "").trim();
4339
- const brand = (words.brand ?? "").trim();
4340
- const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
4491
+ function templateWords(words2) {
4492
+ const headline = (words2.headline ?? "").trim();
4493
+ const kicker = (words2.kicker ?? "").trim();
4494
+ const brand = (words2.brand ?? "").trim();
4495
+ const sub = [brand, (words2.release ?? "").trim()].filter(Boolean).join(" ");
4341
4496
  const out = {};
4342
4497
  if (headline) {
4343
4498
  out["stage-title"] = headline;
@@ -4363,7 +4518,7 @@ var anchorWord = (at2) => typeof at2 === "object" ? `step ${at2.step}` : typeof
4363
4518
  var onWord = (v) => v === void 0 || /^(on|yes|true)$/i.test(v.trim());
4364
4519
  function proposeMotion(input, opts) {
4365
4520
  let doc = migrateMotion(structuredClone(input));
4366
- const { words, launch, catalog } = opts;
4521
+ const { words: words2, launch, catalog } = opts;
4367
4522
  const notes = [];
4368
4523
  const skipped = [];
4369
4524
  const footage = outputLength(doc);
@@ -4393,7 +4548,7 @@ function proposeMotion(input, opts) {
4393
4548
  (t) => !(t.from === END_CARD_FROM && off(endCardRole))
4394
4549
  );
4395
4550
  const namedEndCard = templates.some((t) => t.from === END_CARD_FROM);
4396
- const tWords = templateWords(words);
4551
+ const tWords = templateWords(words2);
4397
4552
  const keys = opts.mark ? { "stage-mark": opts.mark.key, "endcard-markimg": opts.mark.key } : void 0;
4398
4553
  for (const t of templates) {
4399
4554
  const at2 = resolveAnchor(doc, t.at);
@@ -4418,9 +4573,9 @@ function proposeMotion(input, opts) {
4418
4573
  else delete doc.frame.anim;
4419
4574
  }
4420
4575
  } else if (onWord(endCardRole) && !namedEndCard) {
4421
- const headline = (words.headline ?? "").trim();
4422
- const brand = (words.brand ?? "").trim();
4423
- const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
4576
+ const headline = (words2.headline ?? "").trim();
4577
+ const brand = (words2.brand ?? "").trim();
4578
+ const sub = [brand, (words2.release ?? "").trim()].filter(Boolean).join(" ");
4424
4579
  if (headline || brand) {
4425
4580
  const card = { seconds: END_CARD_SECONDS };
4426
4581
  if (opts.ink) card.ink = opts.ink;
@@ -6887,11 +7042,11 @@ async function readDigestActivity(dir) {
6887
7042
  return null;
6888
7043
  }
6889
7044
  }
6890
- function patchStageWords(doc, words) {
7045
+ function patchStageWords(doc, words2) {
6891
7046
  const map = {
6892
- "stage-title": words.headline,
6893
- "stage-kicker": words.kicker,
6894
- "stage-brand": words.brand
7047
+ "stage-title": words2.headline,
7048
+ "stage-kicker": words2.kicker,
7049
+ "stage-brand": words2.brand
6895
7050
  };
6896
7051
  for (const clip of doc.overlays ?? []) {
6897
7052
  if (clip.kind !== "text" || !isStageClip(clip)) continue;
@@ -7566,13 +7721,15 @@ async function pushTake(dir, flags, r) {
7566
7721
  if (upload.reused) r.log(" recording already hosted \u2014 reused");
7567
7722
  const docForPush = structuredClone(take.doc);
7568
7723
  docForPush.source.videoKey = assetUrl;
7569
- if (docForPush.source.camKey && !/^(https?:)?\//.test(docForPush.source.camKey)) {
7570
- r.log(" cam track is local-only \u2014 dropped from the push");
7571
- delete docForPush.source.camKey;
7572
- }
7573
- if (docForPush.source.micKey && !/^(https?:)?\//.test(docForPush.source.micKey)) {
7574
- r.log(" mic track is local-only \u2014 dropped from the push");
7575
- delete docForPush.source.micKey;
7724
+ await uploadDocRefs(sourceSidecarRefs(docForPush), dir, ctx, (l) => r.log(l));
7725
+ for (const field of ["micKey", "camKey"]) {
7726
+ const key = docForPush.source[field];
7727
+ if (key && isTakeRelativeKey(key)) {
7728
+ r.log(
7729
+ ` ${field === "micKey" ? "mic" : "cam"} track ${key} is not beside the take \u2014 left out of the push`
7730
+ );
7731
+ delete docForPush.source[field];
7732
+ }
7576
7733
  }
7577
7734
  await uploadDocRefs(docMediaRefs(docForPush), dir, ctx, (l) => r.log(l));
7578
7735
  const lowered = lowerToComposition3(docForPush);
@@ -8236,7 +8393,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8236
8393
  void flags.yes;
8237
8394
  if (programDoc) {
8238
8395
  await uploadDocRefs(
8239
- docAudioRefs(soundOf(programDoc)),
8396
+ docLayerRefs(layersOf(programDoc)),
8240
8397
  dir,
8241
8398
  { origin, key },
8242
8399
  (l) => r.log(l)
@@ -8583,6 +8740,13 @@ function isTakeDir(target) {
8583
8740
  function soundOf(doc) {
8584
8741
  return { audio: Array.isArray(doc.audio) ? doc.audio : [] };
8585
8742
  }
8743
+ function layersOf(doc) {
8744
+ return {
8745
+ ...soundOf(doc),
8746
+ overlays: Array.isArray(doc.overlays) ? doc.overlays : [],
8747
+ objects: Array.isArray(doc.objects) ? doc.objects : []
8748
+ };
8749
+ }
8586
8750
  function storedProgramConfig(config, programDoc) {
8587
8751
  if (!programDoc) return config;
8588
8752
  const doc = { audio: [], ...programDoc };
@@ -8763,12 +8927,177 @@ async function cmdAsset(argv) {
8763
8927
  import { existsSync as existsSync16, rmSync as rmSync2, statSync as statSync4 } from "fs";
8764
8928
  import { join as join18 } from "path";
8765
8929
  import { createInterface as createInterface3 } from "readline/promises";
8766
- var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help", "yes"]);
8767
- async function cmdDelete(argv) {
8930
+
8931
+ // src/plugin/trash.ts
8932
+ var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
8933
+ var MONTHS = [
8934
+ "Jan",
8935
+ "Feb",
8936
+ "Mar",
8937
+ "Apr",
8938
+ "May",
8939
+ "Jun",
8940
+ "Jul",
8941
+ "Aug",
8942
+ "Sep",
8943
+ "Oct",
8944
+ "Nov",
8945
+ "Dec"
8946
+ ];
8947
+ function restoreDate(iso, now = /* @__PURE__ */ new Date()) {
8948
+ const d = new Date(iso);
8949
+ if (Number.isNaN(d.getTime())) return iso;
8950
+ const day = `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;
8951
+ return d.getUTCFullYear() === now.getUTCFullYear() ? day : `${day} ${d.getUTCFullYear()}`;
8952
+ }
8953
+ function trashedBy(entry) {
8954
+ if (entry.deletedVia === "key") {
8955
+ return entry.deletedClient ?? entry.deletedKeyName ?? "a key";
8956
+ }
8957
+ if (entry.deletedVia === "admin") return "an admin";
8958
+ if (entry.deletedVia === "service") return "vos.so";
8959
+ return "you";
8960
+ }
8961
+ function sinceMoment(value, now = /* @__PURE__ */ new Date()) {
8962
+ const rel = /^(\d+(?:\.\d+)?)(s|m|h|d)$/.exec(value.trim());
8963
+ if (rel) {
8964
+ const unit = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 }[rel[2]];
8965
+ return new Date(now.getTime() - Number(rel[1]) * unit);
8966
+ }
8967
+ const at2 = new Date(value);
8968
+ return Number.isNaN(at2.getTime()) ? null : at2;
8969
+ }
8970
+ function words(kind) {
8971
+ return kind === "vos" ? "vos" : kind === "folder" ? "project" : "file";
8972
+ }
8973
+ function context(flags) {
8974
+ const origin = platformOrigin({
8975
+ origin: strFlag(flags, "origin"),
8976
+ api: strFlag(flags, "api")
8977
+ }).replace(/\/+$/, "");
8978
+ return { origin, key: requireCredential(strFlag(flags, "key")) };
8979
+ }
8980
+ async function readTrash(ctx) {
8981
+ const res = await apiJson(ctx.origin, "/api/trash", { key: ctx.key });
8982
+ if (res.status !== 200) throw new Error(apiError("read Trash", res));
8983
+ return res.body.entries ?? [];
8984
+ }
8985
+ async function restore(ctx, body) {
8986
+ let restored = [];
8987
+ let missing = [];
8988
+ for (let pass = 0; pass < 100; pass++) {
8989
+ const res = await apiJson(ctx.origin, "/api/trash/restore", {
8990
+ method: "POST",
8991
+ key: ctx.key,
8992
+ body
8993
+ });
8994
+ if (res.status !== 200) throw new Error(apiError("restore", res));
8995
+ if (pass === 0) {
8996
+ restored = res.body.restored ?? [];
8997
+ missing = res.body.missing ?? [];
8998
+ }
8999
+ if (res.body.done !== false) break;
9000
+ }
9001
+ return { restored, missing };
9002
+ }
9003
+ async function cmdTrash(argv) {
9004
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
9005
+ const r = createReporter(flags.json === true);
9006
+ const ctx = context(flags);
9007
+ if (positionals[0] === "restore") {
9008
+ const raw = strFlag(flags, "since");
9009
+ const since = raw ? sinceMoment(raw) : null;
9010
+ if (!since)
9011
+ throw new UsageError(
9012
+ "vos trash restore --since <30m|1h|2d|ISO time> [--by <key name>] [--json]"
9013
+ );
9014
+ const by = strFlag(flags, "by");
9015
+ let keyId = null;
9016
+ if (by) {
9017
+ const match = (await readTrash(ctx)).find(
9018
+ (e) => e.deletedKeyName === by || e.deletedKeyId?.startsWith(by)
9019
+ );
9020
+ keyId = match?.deletedKeyId ?? null;
9021
+ if (!keyId)
9022
+ throw new Error(`nothing in Trash was moved there by a key named ${by}`);
9023
+ } else {
9024
+ const me = await apiJson(ctx.origin, "/api/user/whoami", {
9025
+ key: ctx.key
9026
+ });
9027
+ keyId = (me.body.key ?? null)?.id ?? null;
9028
+ if (me.status !== 200 || !keyId)
9029
+ throw new Error(
9030
+ "could not tell which key this is: pass --by <key name> (vos trash lists them)"
9031
+ );
9032
+ }
9033
+ const done = await restore(ctx, { keyId, since: since.toISOString() });
9034
+ r.done(
9035
+ { restored: done.restored, count: done.restored.length },
9036
+ done.restored.length ? `restored ${done.restored.length} item${done.restored.length === 1 ? "" : "s"} moved to Trash since ${since.toISOString()}` : `nothing was moved to Trash by that key since ${since.toISOString()}`
9037
+ );
9038
+ return EXIT_OK;
9039
+ }
9040
+ if (positionals[0])
9041
+ throw new UsageError(
9042
+ `vos trash has no "${positionals[0]}": vos trash, or vos trash restore --since <time>. Trash is emptied by a person, at vos.so/app/trash`
9043
+ );
9044
+ const entries2 = await readTrash(ctx);
9045
+ if (flags.json === true) {
9046
+ r.done({ entries: entries2, count: entries2.length }, "");
9047
+ return EXIT_OK;
9048
+ }
9049
+ if (entries2.length === 0) {
9050
+ r.done({ entries: entries2, count: 0 }, "Trash is empty");
9051
+ return EXIT_OK;
9052
+ }
9053
+ const lines = entries2.map((e) => {
9054
+ const inside = e.members ? ` (${e.members.voses} vos, ${e.members.assets} files)` : "";
9055
+ return ` ${words(e.kind).padEnd(7)} ${e.id} "${e.title}"${inside}: moved by ${trashedBy(e)}, restorable until ${restoreDate(e.restoreUntil)}`;
9056
+ });
9057
+ r.done(
9058
+ { entries: entries2, count: entries2.length },
9059
+ `${entries2.length} in Trash
9060
+ ${lines.join("\n")}
9061
+ restore one: vos restore <id> (emptying Trash is the person's act, at vos.so/app/trash)`
9062
+ );
9063
+ return EXIT_OK;
9064
+ }
9065
+ async function cmdRestore(argv) {
8768
9066
  const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS4);
9067
+ if (positionals.length === 0)
9068
+ throw new UsageError("vos restore <id|watch-url> [...] [--json]");
9069
+ const r = createReporter(flags.json === true);
9070
+ const ctx = context(flags);
9071
+ const entries2 = await readTrash(ctx);
9072
+ const wanted = positionals.map((p) => parseVosId(p) ?? p);
9073
+ const units = wanted.map((id) => {
9074
+ const entry = entries2.find((e) => e.id === id);
9075
+ if (!entry)
9076
+ throw new Error(
9077
+ `${id} is not in Trash (vos trash lists what is; something inside a trashed project comes back with the project)`
9078
+ );
9079
+ return { kind: entry.kind, id: entry.id, title: entry.title };
9080
+ });
9081
+ const done = await restore(ctx, {
9082
+ ids: units.map(({ kind, id }) => ({ kind, id }))
9083
+ });
9084
+ const names = units.map((u) => `"${u.title}"`).join(", ");
9085
+ r.done(
9086
+ { restored: done.restored, count: done.restored.length },
9087
+ `restored ${names}`
9088
+ );
9089
+ return EXIT_OK;
9090
+ }
9091
+
9092
+ // src/plugin/remove.ts
9093
+ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help", "yes", "dry-run"]);
9094
+ async function cmdDelete(argv) {
9095
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
8769
9096
  const target = positionals[0];
8770
9097
  if (!target)
8771
- throw new UsageError("vos delete <vosId|watch-url|dir> [--yes] [--json]");
9098
+ throw new UsageError(
9099
+ "vos delete <vosId|watch-url|dir> [--yes] [--dry-run] [--json]"
9100
+ );
8772
9101
  const r = createReporter(flags.json === true);
8773
9102
  const origin = platformOrigin({
8774
9103
  origin: strFlag(flags, "origin"),
@@ -8785,24 +9114,33 @@ async function cmdDelete(argv) {
8785
9114
  if (meta.status !== 200) throw new Error(apiError(`read ${vosId}`, meta));
8786
9115
  const vos = meta.body.vos ?? {};
8787
9116
  const title = typeof vos.title === "string" ? vos.title : vosId;
9117
+ if (flags["dry-run"] === true) {
9118
+ r.done(
9119
+ { id: vosId, title, deleted: false, dryRun: true },
9120
+ `would move "${title}" (${vosId}) to Trash; nothing was changed`
9121
+ );
9122
+ return EXIT_OK;
9123
+ }
8788
9124
  if (flags.yes !== true) {
8789
9125
  if (!process.stdin.isTTY)
8790
9126
  throw new UsageError(
8791
- `deleting "${title}" (${vosId}) cannot be undone: re-run with --yes to confirm`
9127
+ `"${title}" (${vosId}) would move to Trash: re-run with --yes to confirm, or --dry-run to only look`
8792
9128
  );
8793
9129
  const rl = createInterface3({ input: process.stdin, output: process.stderr });
8794
9130
  const answer = await rl.question(
8795
- `Delete "${title}" (${vosId}) and every version of it? This cannot be undone. [y/N] `
9131
+ `Move "${title}" (${vosId}) to Trash? It stays restorable there for a while (vos restore ${vosId}). [y/N] `
8796
9132
  );
8797
9133
  rl.close();
8798
9134
  if (!/^y(es)?$/i.test(answer.trim())) {
8799
- r.done({ id: vosId, deleted: false }, "kept \u2014 nothing was deleted");
9135
+ r.done({ id: vosId, deleted: false }, "kept: nothing was deleted");
8800
9136
  return EXIT_OK;
8801
9137
  }
8802
9138
  }
8803
9139
  const res = await apiJson(origin, `/api/vos/${vosId}`, {
8804
9140
  method: "DELETE",
8805
- key
9141
+ key,
9142
+ // Who did it, for the person reading Trash later (display only).
9143
+ headers: { "x-vos-client": clientId() }
8806
9144
  });
8807
9145
  if (res.status !== 200 && res.status !== 204)
8808
9146
  throw new Error(apiError(`delete ${vosId}`, res));
@@ -8811,9 +9149,23 @@ async function cmdDelete(argv) {
8811
9149
  rmSync2(join18(dir, SYNC_STATE_NAME), { force: true });
8812
9150
  unlinked = true;
8813
9151
  }
9152
+ const trashed = res.body.trashed === true;
9153
+ const restoreUntil = typeof res.body.restoreUntil === "string" ? res.body.restoreUntil : null;
9154
+ const tail = unlinked ? `; ${dir} no longer tracks it` : "";
8814
9155
  r.done(
8815
- { id: vosId, title, deleted: true, unlinked },
8816
- `deleted "${title}" (${vosId})${unlinked ? `; ${dir} no longer tracks it` : ""}`
9156
+ {
9157
+ id: vosId,
9158
+ title,
9159
+ deleted: true,
9160
+ trashed,
9161
+ ...restoreUntil ? { restoreUntil } : {},
9162
+ unlinked
9163
+ },
9164
+ trashed ? `moved "${title}" (${vosId}) to Trash${restoreUntil ? `, restorable until ${restoreDate(restoreUntil)}` : ""}: vos restore ${vosId}${tail}` : (
9165
+ // A vos with no version (a refused hosted take's empty shell) has
9166
+ // nothing to restore, so the platform erases it.
9167
+ `deleted "${title}" (${vosId})${tail}`
9168
+ )
8817
9169
  );
8818
9170
  return EXIT_OK;
8819
9171
  }
@@ -9013,9 +9365,9 @@ function nonJson(value, path) {
9013
9365
  }
9014
9366
  return null;
9015
9367
  }
9016
- var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set(["json", "help"]);
9368
+ var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set(["json", "help"]);
9017
9369
  async function cmdBuild(argv) {
9018
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
9370
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS6);
9019
9371
  const source = positionals[0];
9020
9372
  if (!source)
9021
9373
  throw new UsageError("vos build <program.mjs> [--out config.json] [--json]");
@@ -9057,7 +9409,7 @@ async function cmdBuild(argv) {
9057
9409
  import { spawnSync } from "child_process";
9058
9410
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
9059
9411
  import { join as join20, resolve as resolve8 } from "path";
9060
- var BOOLEAN_FLAGS6 = /* @__PURE__ */ new Set(["json", "help"]);
9412
+ var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
9061
9413
  function sampleTimes(duration, every) {
9062
9414
  const step = every > 0 ? every : 1;
9063
9415
  const out = [];
@@ -9108,7 +9460,7 @@ function probeSize(video) {
9108
9460
  return m ? { width: Number(m[1]), height: Number(m[2]) } : null;
9109
9461
  }
9110
9462
  async function cmdCompare(argv) {
9111
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS6);
9463
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
9112
9464
  const source = positionals[0];
9113
9465
  const against = strFlag(flags, "against");
9114
9466
  if (!source || !against)
@@ -10253,8 +10605,8 @@ function toHex(css) {
10253
10605
  return `#${hex2(+m[1])}${hex2(+m[2])}${hex2(+m[3])}${a < 1 ? hex2(a * 255) : ""}`;
10254
10606
  }
10255
10607
  function keyFor(label, taken, prefix = "t") {
10256
- const words = label.replace(/[^A-Za-z0-9]+/g, " ").trim().split(" ").filter(Boolean).slice(0, 4);
10257
- let base = words.map(
10608
+ const words2 = label.replace(/[^A-Za-z0-9]+/g, " ").trim().split(" ").filter(Boolean).slice(0, 4);
10609
+ let base = words2.map(
10258
10610
  (w, i) => i === 0 ? w.toLowerCase() : w[0].toUpperCase() + w.slice(1).toLowerCase()
10259
10611
  ).join("");
10260
10612
  if (!base || /^[0-9]/.test(base)) base = prefix + (base || "");
@@ -10315,8 +10667,8 @@ function planScaffold(inv, opts) {
10315
10667
  for (const t of inv.texts) {
10316
10668
  const generic = /^(div|span|p|h[1-6]|li|a|b|i|em|strong)-\d+$/.test(t.id);
10317
10669
  const key = keyFor(generic ? t.text : t.id, taken);
10318
- const words = t.font.transform === "uppercase" ? t.text.toUpperCase() : t.font.transform === "lowercase" ? t.text.toLowerCase() : t.text;
10319
- data[key] = words;
10670
+ const words2 = t.font.transform === "uppercase" ? t.text.toUpperCase() : t.font.transform === "lowercase" ? t.text.toLowerCase() : t.text;
10671
+ data[key] = words2;
10320
10672
  const family = faceFor(t);
10321
10673
  elements.push({
10322
10674
  id: key,
@@ -10335,7 +10687,7 @@ function planScaffold(inv, opts) {
10335
10687
  } : {}
10336
10688
  }
10337
10689
  });
10338
- textKeys.push({ key, size: t.font.size, label: words.slice(0, 40) });
10690
+ textKeys.push({ key, size: t.font.size, label: words2.slice(0, 40) });
10339
10691
  if (t.scene && sceneNames.has(t.scene))
10340
10692
  (sceneElements[t.scene] ??= []).push(key);
10341
10693
  }
@@ -10633,7 +10985,7 @@ function reportText(inv, r) {
10633
10985
  "",
10634
10986
  "## Images and video",
10635
10987
  "",
10636
- "Not placed: upload each with `vos asset upload` and add an image or video element over its hosted URL.",
10988
+ "Not placed: upload each with `vos asset push` and add an image or video element over its hosted URL.",
10637
10989
  "",
10638
10990
  ...inv.media.filter((m) => m.kind === "image" || m.kind === "video").map((m) => `- ${m.kind} ${m.src ?? "(inline)"}`)
10639
10991
  ] : [],
@@ -10667,7 +11019,7 @@ function probeAudioDuration(file) {
10667
11019
  }
10668
11020
 
10669
11021
  // src/plugin/port.ts
10670
- var BOOLEAN_FLAGS7 = /* @__PURE__ */ new Set(["json", "help"]);
11022
+ var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set(["json", "help"]);
10671
11023
  async function cmdPort(argv) {
10672
11024
  const [sub, ...rest] = argv;
10673
11025
  if (sub === "inventory") return await inventory(rest);
@@ -10677,7 +11029,7 @@ async function cmdPort(argv) {
10677
11029
  );
10678
11030
  }
10679
11031
  async function inventory(argv) {
10680
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS7);
11032
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
10681
11033
  const source = positionals[0] ?? ".";
10682
11034
  const r = createReporter(flags.json === true);
10683
11035
  const outDir = resolve11(strFlag(flags, "out") ?? "port");
@@ -10718,7 +11070,7 @@ function summary(inv) {
10718
11070
  };
10719
11071
  }
10720
11072
  async function scaffold(argv) {
10721
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
11073
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS8);
10722
11074
  const r = createReporter(flags.json === true);
10723
11075
  const from = resolve11(strFlag(flags, "from") ?? join24("port", "inventory.json"));
10724
11076
  if (!existsSync20(from))
@@ -10757,7 +11109,7 @@ function scaffoldSourceDir(inv) {
10757
11109
  // src/plugin/recipe.ts
10758
11110
  import { readFileSync as readFileSync8 } from "fs";
10759
11111
  import { basename as basename9 } from "path";
10760
- var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set(["json", "help"]);
11112
+ var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
10761
11113
  var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
10762
11114
  function recipeFilename(file) {
10763
11115
  const name = basename9(file);
@@ -10767,7 +11119,7 @@ function recipeFilename(file) {
10767
11119
  return name;
10768
11120
  }
10769
11121
  async function cmdPush2(argv) {
10770
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS8);
11122
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS9);
10771
11123
  const [file] = positionals;
10772
11124
  const folderRef = strFlag(flags, "folder");
10773
11125
  const assetId = strFlag(flags, "asset");
@@ -11868,7 +12220,7 @@ import {
11868
12220
  Output,
11869
12221
  WebMOutputFormat
11870
12222
  } from "mediabunny";
11871
- var BOOLEAN_FLAGS9 = /* @__PURE__ */ new Set(["json", "help"]);
12223
+ var BOOLEAN_FLAGS10 = /* @__PURE__ */ new Set(["json", "help"]);
11872
12224
  async function probeVideoFile(path) {
11873
12225
  const input = new Input({
11874
12226
  source: new FilePathSource(path),
@@ -11959,7 +12311,7 @@ function parseViewport(v) {
11959
12311
  return { width: Number(m[1]), height: Number(m[2]) };
11960
12312
  }
11961
12313
  async function cmdIngest(argv) {
11962
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS9, MULTI_FLAGS2);
12314
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS10, MULTI_FLAGS2);
11963
12315
  const source = positionals[0];
11964
12316
  if (!source)
11965
12317
  throw new UsageError(
@@ -12165,6 +12517,7 @@ var RULES_BODY = `## Product videos (vosso)
12165
12517
  - Look before you cut: vos frames take --at-zooms; edit take/doc.json and re-render, never re-record
12166
12518
  - Hand back: vos open take serves the take into the studio at vos.so, doc.json intact
12167
12519
  - Local by default: vos push only when asked for a link, the shelf or a cloud render
12520
+ - 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
12168
12521
  - Credentials: VOS_API_KEY, then ~/.config/vos/credentials, then vos login; never print one
12169
12522
  - Reference: https://vos.so/llms-full.txt`;
12170
12523
  var RULES_BLOCK = `${RULES_BEGIN}
@@ -12313,7 +12666,7 @@ function bundledSkillsDir(from) {
12313
12666
  }
12314
12667
 
12315
12668
  // src/plugin/machineCmd.ts
12316
- var BOOLEAN_FLAGS10 = /* @__PURE__ */ new Set([
12669
+ var BOOLEAN_FLAGS11 = /* @__PURE__ */ new Set([
12317
12670
  "json",
12318
12671
  "help",
12319
12672
  "global",
@@ -12391,7 +12744,7 @@ async function doctorFacts(opts) {
12391
12744
  };
12392
12745
  }
12393
12746
  async function cmdSetup(argv) {
12394
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
12747
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
12395
12748
  const r = reporterFor(flags);
12396
12749
  const root = process.cwd();
12397
12750
  const global = flags.global === true;
@@ -12510,7 +12863,7 @@ Next: ${report.next_step}`
12510
12863
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
12511
12864
  }
12512
12865
  async function cmdDoctor(argv) {
12513
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
12866
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
12514
12867
  const r = reporterFor(flags);
12515
12868
  const root = process.cwd();
12516
12869
  const homes = detectAgents(root, { home: homedir2(), global: true });
@@ -12529,7 +12882,7 @@ Next: ${report.next_step}`
12529
12882
  return report.ok ? EXIT_OK : EXIT_NO_BROWSER;
12530
12883
  }
12531
12884
  async function cmdWhoami(argv) {
12532
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
12885
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
12533
12886
  const r = reporterFor(flags);
12534
12887
  const origin = platformOrigin({
12535
12888
  origin: strFlag(flags, "origin"),
@@ -12591,7 +12944,7 @@ async function cmdWhoami(argv) {
12591
12944
  return EXIT_OK;
12592
12945
  }
12593
12946
  function cmdLogout(argv) {
12594
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
12947
+ const { flags } = parseArgs(argv, BOOLEAN_FLAGS11);
12595
12948
  const r = reporterFor(flags);
12596
12949
  const had = existsSync23(CREDENTIALS_PATH);
12597
12950
  rmSync3(CREDENTIALS_PATH, { force: true });
@@ -12609,7 +12962,7 @@ function cmdLogout(argv) {
12609
12962
  }
12610
12963
 
12611
12964
  // src/plugin/run.ts
12612
- var BOOLEAN_FLAGS11 = /* @__PURE__ */ new Set([
12965
+ var BOOLEAN_FLAGS12 = /* @__PURE__ */ new Set([
12613
12966
  "json",
12614
12967
  "help",
12615
12968
  "wait",
@@ -12711,16 +13064,29 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
12711
13064
  --override consents to touching
12712
13065
  protected (human-edited) nodes, ONLY when the user asked.
12713
13066
  Without --folder a new vos lands unfiled, at the root of your
12714
- shelf. A document's local sound (doc.json audio keys) uploads
12715
- with the push. --wait stays until the version's still has
13067
+ shelf. A document's local files (doc.json: picture and video
13068
+ overlays, 3D props, sound; a take's mic and cam too) upload
13069
+ with the push, each once. --wait stays until the version's still has
12716
13070
  rendered and prints absolute still and preview links.
12717
13071
  --still <seconds> sets a PROGRAM's cover (doc.json "still", kept
12718
13072
  by every later push); without it the platform picks an early
12719
13073
  frame. A take's cover is its doc.json "still".
12720
- vos delete <vosId|watch-url|dir> [--yes] [--json]
12721
- take a vos off vos.so, every version of it, for good. Asks on a
12722
- terminal; headless it needs --yes. A dir that tracked it is
12723
- unlinked (its vos.json removed)
13074
+ vos delete <vosId|watch-url|dir> [--yes] [--dry-run] [--json]
13075
+ move a vos to Trash on vos.so. Nothing is erased: it stays
13076
+ restorable until the date the delete prints (vos restore <id>).
13077
+ Asks on a terminal; headless it needs --yes. --dry-run names
13078
+ what would move and changes nothing. A dir that tracked it is
13079
+ unlinked (its vos.json removed). Needs a key with the delete
13080
+ scope; no key can delete forever, only the person can, on the
13081
+ web
13082
+ vos trash [--json]
13083
+ what is in Trash on vos.so: what, moved by whom, restorable
13084
+ until when
13085
+ vos restore <id|watch-url> [...] [--json]
13086
+ bring those back from Trash, as they were
13087
+ vos trash restore --since <30m|1h|2d|ISO> [--by <key name>] [--json]
13088
+ undo everything this key (or the named one) moved to Trash
13089
+ since then
12724
13090
  vos pull [dir|take] [--vos <id>] [--since versionId] [--check] [--media] [--json]
12725
13091
  what changed on vos.so since your base; syncs config.json (backup
12726
13092
  kept) or the take's doc.json. ALWAYS pull before editing pushed work.
@@ -13066,7 +13432,7 @@ function wallGate(flags, r, opts) {
13066
13432
  async function cmdRecord(argv) {
13067
13433
  const { positionals, flags, multi } = parseArgs(
13068
13434
  argv,
13069
- BOOLEAN_FLAGS11,
13435
+ BOOLEAN_FLAGS12,
13070
13436
  MULTI_FLAGS2
13071
13437
  );
13072
13438
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
@@ -13187,10 +13553,10 @@ async function cmdRecord(argv) {
13187
13553
  const rec = await inSession(
13188
13554
  sessionName,
13189
13555
  actions,
13190
- (context) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
13556
+ (context2) => recordTake(browser2, url, actions, takePaths(outDir), r.log, {
13191
13557
  storageState,
13192
13558
  headers,
13193
- context,
13559
+ context: context2,
13194
13560
  dryRun: true,
13195
13561
  // A rehearsal is always strict, and it touches no directory.
13196
13562
  onArrival: wallGate(flags, r, { strict: true })
@@ -13249,11 +13615,11 @@ ${lines.join("\n")}
13249
13615
  const rec = await inSession(
13250
13616
  sessionName,
13251
13617
  actions,
13252
- (context) => recordTake(browser, url, actions, paths, r.log, {
13618
+ (context2) => recordTake(browser, url, actions, paths, r.log, {
13253
13619
  maxDurationSeconds,
13254
13620
  storageState,
13255
13621
  headers,
13256
- context,
13622
+ context: context2,
13257
13623
  onArrival: wallGate(flags, r, {
13258
13624
  strict: flags.strict === true,
13259
13625
  prepare
@@ -13308,7 +13674,7 @@ ${lines.join("\n")}
13308
13674
  async function cmdCreate2(argv) {
13309
13675
  const { positionals, flags, multi } = parseArgs(
13310
13676
  argv,
13311
- BOOLEAN_FLAGS11,
13677
+ BOOLEAN_FLAGS12,
13312
13678
  MULTI_FLAGS2
13313
13679
  );
13314
13680
  const actionsPath = strFlag(flags, "actions");
@@ -13353,11 +13719,11 @@ async function cmdCreate2(argv) {
13353
13719
  const rec = await inSession(
13354
13720
  sessionName,
13355
13721
  actions,
13356
- (context) => recordTake(browser, url, actions, paths, r.log, {
13722
+ (context2) => recordTake(browser, url, actions, paths, r.log, {
13357
13723
  maxDurationSeconds,
13358
13724
  storageState,
13359
13725
  headers,
13360
- context,
13726
+ context: context2,
13361
13727
  onArrival: wallGate(flags, r, {
13362
13728
  strict: flags.strict === true,
13363
13729
  prepare
@@ -13434,7 +13800,7 @@ async function cmdCreate2(argv) {
13434
13800
  async function cmdPlan(argv) {
13435
13801
  const { positionals, flags, multi } = parseArgs(
13436
13802
  argv,
13437
- BOOLEAN_FLAGS11,
13803
+ BOOLEAN_FLAGS12,
13438
13804
  /* @__PURE__ */ new Set(["with"])
13439
13805
  );
13440
13806
  const dir = positionals[0];
@@ -13535,7 +13901,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
13535
13901
  }
13536
13902
  const launch = await readLaunchBesideTake(dir, strFlag(flags, "launch"));
13537
13903
  const lines = (v) => v === null || v === void 0 ? null : v.replace(/\\n/g, "\n");
13538
- const words = {
13904
+ const words2 = {
13539
13905
  headline: lines(strFlag(flags, "headline") ?? launch?.roles.headline),
13540
13906
  kicker: lines(strFlag(flags, "kicker") ?? launch?.roles.kicker),
13541
13907
  brand: strFlag(flags, "brand-name") ?? lookPick.roles?.wordmark ?? null,
@@ -13620,7 +13986,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
13620
13986
  }
13621
13987
  }
13622
13988
  return {
13623
- words,
13989
+ words: words2,
13624
13990
  launchRoles,
13625
13991
  ink: endCardInk(lookPick.look, lookPick.roles),
13626
13992
  mark,
@@ -13632,7 +13998,7 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
13632
13998
  async function cmdRender(argv) {
13633
13999
  const { positionals, flags, multi } = parseArgs(
13634
14000
  argv,
13635
- BOOLEAN_FLAGS11,
14001
+ BOOLEAN_FLAGS12,
13636
14002
  MULTI_FLAGS2
13637
14003
  );
13638
14004
  const dir = positionals[0];
@@ -13700,7 +14066,7 @@ async function cmdRender(argv) {
13700
14066
  async function cmdFrames(argv) {
13701
14067
  const { positionals, flags, multi } = parseArgs(
13702
14068
  argv,
13703
- BOOLEAN_FLAGS11,
14069
+ BOOLEAN_FLAGS12,
13704
14070
  MULTI_FLAGS2
13705
14071
  );
13706
14072
  const dir = positionals[0];
@@ -13773,7 +14139,7 @@ async function cmdFrames(argv) {
13773
14139
  async function cmdDeliver(argv) {
13774
14140
  const { positionals, flags, multi } = parseArgs(
13775
14141
  argv,
13776
- BOOLEAN_FLAGS11,
14142
+ BOOLEAN_FLAGS12,
13777
14143
  MULTI_FLAGS2
13778
14144
  );
13779
14145
  const dir = positionals[0];
@@ -13939,7 +14305,7 @@ async function officialTemplate(origin, ref) {
13939
14305
  }
13940
14306
  }
13941
14307
  async function cmdDigest(argv) {
13942
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14308
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
13943
14309
  const dir = positionals[0];
13944
14310
  if (!dir)
13945
14311
  throw new UsageError(
@@ -14003,7 +14369,7 @@ async function cmdDigest(argv) {
14003
14369
  }
14004
14370
  }
14005
14371
  async function cmdOpen(argv) {
14006
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14372
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14007
14373
  const dir = positionals[0];
14008
14374
  if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
14009
14375
  const r = createReporter(flags.json === true);
@@ -14041,7 +14407,7 @@ async function cmdOpen(argv) {
14041
14407
  return EXIT_OK;
14042
14408
  }
14043
14409
  async function cmdJudge(argv) {
14044
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14410
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14045
14411
  const target = positionals[0];
14046
14412
  const against = strFlag(flags, "against");
14047
14413
  if (!target || !against)
@@ -14070,7 +14436,7 @@ Win rate so far: ${rate.wins} win(s), ${rate.ties} tie(s) of ${rate.judged} judg
14070
14436
  return EXIT_OK;
14071
14437
  }
14072
14438
  async function cmdCallout(argv) {
14073
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14439
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14074
14440
  const dir = positionals[0];
14075
14441
  const shape = positionals[1];
14076
14442
  if (!dir || !shape || !isCalloutShape(shape))
@@ -14095,19 +14461,19 @@ async function cmdCallout(argv) {
14095
14461
  );
14096
14462
  const codeFile = strFlag(flags, "code-file");
14097
14463
  const code = codeFile ? await readFile17(codeFile, "utf8") : strFlag(flags, "code");
14098
- const words = {
14464
+ const words2 = {
14099
14465
  kicker: strFlag(flags, "kicker"),
14100
14466
  title: strFlag(flags, "title"),
14101
14467
  body: strFlag(flags, "body"),
14102
14468
  code: code?.replace(/\\n/g, "\n")
14103
14469
  };
14104
- if (shape === "code" && !words.code)
14470
+ if (shape === "code" && !words2.code)
14105
14471
  throw new UsageError(
14106
14472
  'a code callout needs --code "\u2026" or --code-file <file>'
14107
14473
  );
14108
- if (shape === "note" && !words.title)
14474
+ if (shape === "note" && !words2.title)
14109
14475
  throw new UsageError('a note needs --title "\u2026"');
14110
- if (shape === "tag" && !words.kicker && !words.title)
14476
+ if (shape === "tag" && !words2.kicker && !words2.title)
14111
14477
  throw new UsageError('a tag needs --kicker "\u2026"');
14112
14478
  const stepRaw = strFlag(flags, "step");
14113
14479
  const atRaw = strFlag(flags, "at");
@@ -14118,7 +14484,7 @@ async function cmdCallout(argv) {
14118
14484
  try {
14119
14485
  composed = composeCallout(take.doc, register, {
14120
14486
  shape,
14121
- words,
14487
+ words: words2,
14122
14488
  id: strFlag(flags, "id"),
14123
14489
  step: stepRaw !== void 0 ? /^\d+$/.test(stepRaw) ? Number(stepRaw) : stepRaw : void 0,
14124
14490
  at: atRaw !== void 0 ? Number(atRaw) : void 0,
@@ -14169,7 +14535,7 @@ async function cmdCallout(argv) {
14169
14535
  return EXIT_OK;
14170
14536
  }
14171
14537
  async function cmdValidate(argv) {
14172
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14538
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14173
14539
  const target = positionals[0];
14174
14540
  if (!target) throw new UsageError("vos validate <actions.json|take>");
14175
14541
  const r = createReporter(flags.json === true);
@@ -14331,7 +14697,7 @@ async function cmdValidate(argv) {
14331
14697
  async function cmdPush3(argv) {
14332
14698
  const { positionals, flags, multi } = parseArgs(
14333
14699
  argv,
14334
- BOOLEAN_FLAGS11,
14700
+ BOOLEAN_FLAGS12,
14335
14701
  MULTI_FLAGS2
14336
14702
  );
14337
14703
  const dir = positionals[0];
@@ -14395,7 +14761,7 @@ ${landedLines(landed)}` : "")
14395
14761
  return EXIT_OK;
14396
14762
  }
14397
14763
  async function cmdPull2(argv) {
14398
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11, MULTI_FLAGS2);
14764
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12, MULTI_FLAGS2);
14399
14765
  const dir = positionals[0] ?? ".";
14400
14766
  const r = createReporter(flags.json === true);
14401
14767
  const result = await pullTake(
@@ -14508,6 +14874,10 @@ async function run(argv) {
14508
14874
  }
14509
14875
  case "delete":
14510
14876
  return await cmdDelete(rest);
14877
+ case "trash":
14878
+ return await cmdTrash(rest);
14879
+ case "restore":
14880
+ return await cmdRestore(rest);
14511
14881
  case "build":
14512
14882
  return await cmdBuild(rest);
14513
14883
  case "compare":
@@ -14560,7 +14930,7 @@ async function run(argv) {
14560
14930
  }
14561
14931
  }
14562
14932
  async function cmdSession(argv) {
14563
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS11);
14933
+ const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS12);
14564
14934
  const [sub, arg] = positionals;
14565
14935
  const r = createReporter(flags.json === true);
14566
14936
  const usage = "vos session open <url> --name <app> | check <name> [--url <url>] | list | rm <name>";
@@ -14700,4 +15070,4 @@ export {
14700
15070
  verbHelp,
14701
15071
  run
14702
15072
  };
14703
- //# sourceMappingURL=chunk-K6PE735V.js.map
15073
+ //# sourceMappingURL=chunk-67L7FPJR.js.map