@vosjs/cli 0.37.0 → 0.39.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.
@@ -2,8 +2,10 @@
2
2
  import {
3
3
  BrowserUnavailableError,
4
4
  directoryKind,
5
- launchBrowser
6
- } from "./chunk-NHKHTIDR.js";
5
+ launchBrowser,
6
+ noteGivenFlag,
7
+ trackFlags
8
+ } from "./chunk-FACYY7VV.js";
7
9
 
8
10
  // src/plugin/run.ts
9
11
  import { mkdir as mkdir10, readFile as readFile15, rm as rm5 } from "fs/promises";
@@ -52,7 +54,9 @@ function parseArgs(argv, booleanFlags, multiFlags = /* @__PURE__ */ new Set()) {
52
54
  }
53
55
  positionals.push(arg);
54
56
  }
55
- return { positionals, flags, multi };
57
+ for (const name of Object.keys(flags)) noteGivenFlag(name);
58
+ for (const name of Object.keys(multi)) noteGivenFlag(name);
59
+ return { positionals, flags: trackFlags(flags), multi: trackFlags(multi) };
56
60
  }
57
61
  function numFlag(flags, name, fallback) {
58
62
  const v = flags[name];
@@ -943,6 +947,17 @@ function lintDoc(docIn) {
943
947
  }
944
948
  const TRANSITIONS = ["none", "fade", "rise"];
945
949
  const PRESETS = ["title", "caption", "label"];
950
+ let footageEnd = null;
951
+ if (recording && Array.isArray(doc.segments)) {
952
+ try {
953
+ footageEnd = ratedSegments(doc).reduce(
954
+ (a, s) => a + (s.out - s.in) / (s.rate ?? 1),
955
+ 0
956
+ );
957
+ } catch {
958
+ footageEnd = null;
959
+ }
960
+ }
946
961
  for (let i = 0; i < (overlays?.length ?? 0); i++) {
947
962
  const o = overlays[i];
948
963
  const name = `overlays[${i}]`;
@@ -960,6 +975,14 @@ function lintDoc(docIn) {
960
975
  if (!isNum(o.duration) || o.duration <= 0) {
961
976
  problems.push(`${name}.duration must be > 0 (seconds)`);
962
977
  }
978
+ if (footageEnd !== null && footageEnd > 0 && isNum(o.start) && isNum(o.duration)) {
979
+ const over = o.start + o.duration - footageEnd;
980
+ if (over > 0.05) {
981
+ warnings.push(
982
+ 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`
983
+ );
984
+ }
985
+ }
963
986
  checkPin(o, name, docIn, recording, problems, warnings);
964
987
  if (o.kind === "text") {
965
988
  if (typeof o.text !== "string")
@@ -1635,7 +1658,7 @@ function framingWarnings(docIn, doc, zoom, overlays, warnings) {
1635
1658
  if (pinned) {
1636
1659
  if (pinned.clamped > 4) {
1637
1660
  warnings.push(
1638
- `overlays[${i}] is pinned ${pinned.side} of its referent but the frame has no room there \u2014 it sits ${Math.round(pinned.clamped)} design px off; a smaller box, another side, or a lower zoom level keeps it beside what it names`
1661
+ `overlays[${i}] is pinned ${pinned.side} of its referent but the frame has no room there \u2014 it sits ${Math.round(pinned.clamped)} design px off; a smaller box, another side, or a lower zoom level keeps it beside what it names. A pin resolves THROUGH the camera, so author the zoom first: a callout composed before it is measured against a frame the viewer never sees`
1639
1662
  );
1640
1663
  }
1641
1664
  return;
@@ -1798,7 +1821,7 @@ import { pipeline } from "stream/promises";
1798
1821
 
1799
1822
  // src/plugin/take.ts
1800
1823
  import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
1801
- import { existsSync } from "fs";
1824
+ import { existsSync, readdirSync, statSync } from "fs";
1802
1825
  import { basename, join } from "path";
1803
1826
  var RECORDING_NAME = "recording.webm";
1804
1827
  function takeMediaFiles(dir, stem) {
@@ -1829,6 +1852,20 @@ async function ensureTakeDir(dir) {
1829
1852
  await mkdir(p.framesDir, { recursive: true });
1830
1853
  return p;
1831
1854
  }
1855
+ async function dropScreencastFrames(p) {
1856
+ if (!existsSync(p.recording) || statSync(p.recording).size === 0) return 0;
1857
+ if (!existsSync(p.framesDir)) return 0;
1858
+ let freed = 0;
1859
+ for (const name of readdirSync(p.framesDir)) {
1860
+ try {
1861
+ freed += statSync(join(p.framesDir, name)).size;
1862
+ } catch {
1863
+ }
1864
+ }
1865
+ await rm(p.framesDir, { recursive: true, force: true });
1866
+ await rm(p.framesIndex, { force: true });
1867
+ return freed;
1868
+ }
1832
1869
  async function prepareReRecord(dir) {
1833
1870
  const p = takePaths(dir);
1834
1871
  const kept = [];
@@ -2154,7 +2191,7 @@ import {
2154
2191
 
2155
2192
  // src/plugin/server.ts
2156
2193
  import { createServer } from "http";
2157
- import { existsSync as existsSync3, readFileSync, statSync } from "fs";
2194
+ import { existsSync as existsSync3, readFileSync, statSync as statSync2 } from "fs";
2158
2195
  import { writeFile as writeFile3 } from "fs/promises";
2159
2196
  import { extname, join as join3 } from "path";
2160
2197
  var MIME = {
@@ -2203,8 +2240,8 @@ function startTakeServer(rootDir, pages) {
2203
2240
  return;
2204
2241
  }
2205
2242
  const file = join3(rootDir, decodeURIComponent(url.pathname));
2206
- if (existsSync3(file) && statSync(file).isFile()) {
2207
- const size = statSync(file).size;
2243
+ if (existsSync3(file) && statSync2(file).isFile()) {
2244
+ const size = statSync2(file).size;
2208
2245
  const buf = readFileSync(file);
2209
2246
  const type = sniffMediaType(buf.subarray(0, MEDIA_HEAD_BYTES)) ?? MIME[extname(file)] ?? "application/octet-stream";
2210
2247
  const range = req.headers.range;
@@ -5659,6 +5696,19 @@ function winRate(verdicts) {
5659
5696
  };
5660
5697
  }
5661
5698
 
5699
+ // src/plugin/fontName.ts
5700
+ import { findFontFamily as findFontFamily2 } from "@vosjs/shared";
5701
+ function catalogFamily(family) {
5702
+ const name = family.trim();
5703
+ if (findFontFamily2(name)) return name;
5704
+ const base = name.replace(/\s+Variable$/i, "");
5705
+ if (base !== name) {
5706
+ const hit = findFontFamily2(base);
5707
+ if (hit) return hit.family;
5708
+ }
5709
+ return family;
5710
+ }
5711
+
5662
5712
  // src/plugin/callout.ts
5663
5713
  import { existsSync as existsSync10 } from "fs";
5664
5714
  import { readFile as readFile9 } from "fs/promises";
@@ -5668,6 +5718,7 @@ import {
5668
5718
  CALLOUT_DEFAULT_BODY_PX,
5669
5719
  CALLOUT_SHAPES,
5670
5720
  calloutClip,
5721
+ calloutLook,
5671
5722
  calloutGroundOf,
5672
5723
  cameraModel as cameraModel2,
5673
5724
  deltaE,
@@ -5686,10 +5737,11 @@ var CALLOUT_DELTA_E_PROBLEM = 8;
5686
5737
  var CALLOUT_DELTA_E_WARNING = 16;
5687
5738
  function registerFrom(roles, flags) {
5688
5739
  const ground = flags.ground ?? roles?.bgA;
5689
- const accent = flags.accent ?? roles?.accent;
5740
+ const accent = flags.accent ?? roles?.callout ?? roles?.accent;
5690
5741
  if (!ground || !accent) return null;
5691
5742
  if (!hexToRgb(ground) || !hexToRgb(accent)) return null;
5692
- const face = flags.face ?? roles?.fontBody;
5743
+ const named = flags.face ?? roles?.fontBody;
5744
+ const face = named ? catalogFamily(named) : named;
5693
5745
  return {
5694
5746
  ground,
5695
5747
  accent,
@@ -5755,16 +5807,28 @@ function composeCallout(doc, register, ask) {
5755
5807
  let n = 2;
5756
5808
  while (taken.has(id) && !ask.id)
5757
5809
  id = `${ask.shape}-${ask.step ?? Math.round(window2.start * 10)}-${n++}`;
5810
+ const seen = appScaleAt(doc, window2.start + 0.35);
5811
+ const scale = ask.bodyPxGiven ? 1 : seen;
5812
+ const look = calloutLook(register, scale);
5758
5813
  const clip = calloutClip(ask.shape, register, ask.words, {
5759
5814
  id,
5760
5815
  start: window2.start,
5761
5816
  duration: window2.duration,
5762
- scale: appScaleAt(doc, window2.start + 0.35),
5817
+ scale,
5763
5818
  pin,
5764
5819
  // The fallback place, in the margin: the lower third.
5765
5820
  at: { x: 0.5, y: 0.82 }
5766
5821
  });
5767
- return { clip, window: window2 };
5822
+ return {
5823
+ clip,
5824
+ window: window2,
5825
+ sizes: {
5826
+ scale: +seen.toFixed(3),
5827
+ kickerPx: look.kickerPx,
5828
+ titlePx: look.titlePx,
5829
+ bodyPx: look.bodyPx
5830
+ }
5831
+ };
5768
5832
  }
5769
5833
  function isCalloutShape(s) {
5770
5834
  return CALLOUT_SHAPES.includes(s);
@@ -6565,7 +6629,8 @@ function paceLine(r) {
6565
6629
  }
6566
6630
 
6567
6631
  // src/plugin/recorder.ts
6568
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6632
+ var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
6633
+ var DRY_PAUSE_MS = 120;
6569
6634
  function jpegDims(buf) {
6570
6635
  let i = 2;
6571
6636
  while (i < buf.length - 9) {
@@ -6580,12 +6645,15 @@ function jpegDims(buf) {
6580
6645
  return null;
6581
6646
  }
6582
6647
  async function recordTake(browser, url, actions, paths, log, opts = {}) {
6583
- const maxSeconds = opts.maxDurationSeconds ?? Infinity;
6648
+ const dry = opts.dryRun === true;
6649
+ const sleep = dry ? (ms) => realSleep(Math.min(ms, DRY_PAUSE_MS)) : realSleep;
6650
+ const maxSeconds = dry ? Infinity : opts.maxDurationSeconds ?? Infinity;
6584
6651
  const vw = actions.viewport?.width ?? 1280;
6585
6652
  const vh = actions.viewport?.height ?? 720;
6586
6653
  const context = await browser.newContext({
6587
6654
  viewport: { width: vw, height: vh },
6588
- deviceScaleFactor: 1
6655
+ deviceScaleFactor: 1,
6656
+ ...opts.storageState ? { storageState: opts.storageState } : {}
6589
6657
  });
6590
6658
  const page = await context.newPage();
6591
6659
  page.on("console", (m) => {
@@ -6602,22 +6670,24 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6602
6670
  const frames = [];
6603
6671
  let frameIdx = 0;
6604
6672
  let t0 = 0;
6605
- cdp.on("Page.screencastFrame", (ev) => {
6606
- const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
6607
- const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
6608
- writeFileSync2(join13(paths.framesDir, file), Buffer.from(ev.data, "base64"));
6609
- frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
6610
- cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
6673
+ if (!dry)
6674
+ cdp.on("Page.screencastFrame", (ev) => {
6675
+ const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
6676
+ const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
6677
+ writeFileSync2(join13(paths.framesDir, file), Buffer.from(ev.data, "base64"));
6678
+ frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
6679
+ cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
6680
+ });
6611
6681
  });
6612
- });
6613
6682
  t0 = Date.now();
6614
- await cdp.send("Page.startScreencast", {
6615
- format: "jpeg",
6616
- quality: 90,
6617
- maxWidth: vw,
6618
- maxHeight: vh,
6619
- everyNthFrame: 1
6620
- });
6683
+ if (!dry)
6684
+ await cdp.send("Page.startScreencast", {
6685
+ format: "jpeg",
6686
+ quality: 90,
6687
+ maxWidth: vw,
6688
+ maxHeight: vh,
6689
+ everyNthFrame: 1
6690
+ });
6621
6691
  const events = [];
6622
6692
  const cur = { x: 48, y: 48 };
6623
6693
  const now = () => Date.now() - t0;
@@ -6629,7 +6699,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6629
6699
  const dist = Math.hypot(tx - cur.x, ty - cur.y);
6630
6700
  const from = { ...cur };
6631
6701
  await clockMotion(
6632
- pointerTravelMs(dist),
6702
+ dry ? 0 : pointerTravelMs(dist),
6633
6703
  async (u) => {
6634
6704
  cur.x = from.x + (tx - from.x) * u;
6635
6705
  cur.y = from.y + (ty - from.y) * u;
@@ -6904,12 +6974,14 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6904
6974
  ` WARNING: ${freezePct}% of the take is visually frozen (${freezes.length} freezes, longest ${Math.max(...freezes.map((f) => f.ms))}ms) \u2014 keep motion in frame or trim these spans in doc.json`
6905
6975
  );
6906
6976
  }
6907
- await writeJson(paths.cursor, events);
6908
- await writeJson(paths.meta, meta, true);
6909
- await writeJson(paths.framesIndex, frames);
6910
- await writeJson(paths.actions, { ...actions, url }, true);
6911
6977
  const pace = paceReport(paces);
6912
- log(` ${paceLine(pace)}`);
6978
+ if (!dry) {
6979
+ await writeJson(paths.cursor, events);
6980
+ await writeJson(paths.meta, meta, true);
6981
+ await writeJson(paths.framesIndex, frames);
6982
+ await writeJson(paths.actions, { ...actions, url }, true);
6983
+ log(` ${paceLine(pace)}`);
6984
+ }
6913
6985
  return {
6914
6986
  events,
6915
6987
  frames,
@@ -7890,6 +7962,15 @@ function printChanges(r, changes, protectedIds) {
7890
7962
  r.event({ event: "protected", nodes: protectedIds });
7891
7963
  }
7892
7964
  }
7965
+ function takePushRefusal(ask) {
7966
+ if (ask.programOnly.length) {
7967
+ const list = ask.programOnly.map((f) => `--${f}`).join(", ");
7968
+ return `${list} ${ask.programOnly.length > 1 ? "belong" : "belongs"} to a PROGRAM push (a config.json); a take push reads --vos, --title, --label, --note, --folder, --override and --yes`;
7969
+ }
7970
+ if (ask.vos !== void 0 && ask.trackedVosId !== null && ask.trackedVosId !== ask.vos)
7971
+ return `this take tracks vos ${ask.trackedVosId} (its vos.json), not ${ask.vos}. Drop --vos to push there, or remove vos.json to adopt ${ask.vos} instead`;
7972
+ return null;
7973
+ }
7893
7974
  async function pushTake(dir, flags, r) {
7894
7975
  const take = await loadTake(dir);
7895
7976
  if (!take.doc) {
@@ -7906,7 +7987,22 @@ async function pushTake(dir, flags, r) {
7906
7987
  );
7907
7988
  }
7908
7989
  const ctx = apiContext(flags);
7909
- const state = readSyncState(dir);
7990
+ const tracked = readSyncState(dir);
7991
+ const firstPush = tracked === null;
7992
+ let state = tracked;
7993
+ if (!state && flags.vos) {
7994
+ const meta = await api(ctx, `/vos/${flags.vos}`);
7995
+ const head = meta.json.vos?.currentVersionId;
7996
+ if (meta.status !== 200 || !head) {
7997
+ throw new Error(
7998
+ `--vos ${flags.vos}: cannot adopt it (${meta.status}${meta.json.error ? `: ${String(meta.json.error)}` : ""}) \u2014 it must be a vos this credential can read, with a version to build on`
7999
+ );
8000
+ }
8001
+ r.log(
8002
+ `adopting vos ${flags.vos}: this take becomes the next version after its head (${head})`
8003
+ );
8004
+ state = { vosId: flags.vos, versionId: head };
8005
+ }
7910
8006
  let folderId;
7911
8007
  if (flags.folder) {
7912
8008
  if (state) {
@@ -7919,7 +8015,7 @@ async function pushTake(dir, flags, r) {
7919
8015
  flags.folder
7920
8016
  ).id;
7921
8017
  }
7922
- if (!state && !flags.yes) {
8018
+ if (firstPush && !flags.yes) {
7923
8019
  if (!process.stdin.isTTY) {
7924
8020
  throw new Error(
7925
8021
  "first push of this take uploads its recording \u2014 pass --yes to consent headlessly"
@@ -8256,12 +8352,12 @@ async function browserLogin(origin, r, opts) {
8256
8352
  r.log(`Sign in at ${verifyUrl}`);
8257
8353
  if (opts.openBrowser) void launchDefaultBrowser(verifyUrl);
8258
8354
  r.log("Waiting for approval\u2026 (Ctrl-C to cancel)");
8259
- const sleep2 = opts.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
8355
+ const sleep = opts.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
8260
8356
  let intervalMs = baseIntervalMs;
8261
8357
  let failures = 0;
8262
8358
  let capNoted = false;
8263
8359
  while (Date.now() < expiresAt) {
8264
- await sleep2(intervalMs);
8360
+ await sleep(intervalMs);
8265
8361
  let res;
8266
8362
  try {
8267
8363
  res = await apiJson(origin, "/api/cli/login/poll", {
@@ -9311,10 +9407,12 @@ function composeBrand(input) {
9311
9407
  const bgC = mixHex(bgA, accent, 0.14);
9312
9408
  p.bgC = `bgA tinted 14% toward the accent (a highlight ground)`;
9313
9409
  p.ink = w.h1 ? `the h1's colour` : `the body's colour`;
9314
- const fontDisplay = design?.fonts[0] ?? (w.h1 ? firstFamily(w.h1.fontFamily) : firstFamily(w.body.fontFamily));
9410
+ const fontDisplay = catalogFamily(
9411
+ design?.fonts[0] ?? (w.h1 ? firstFamily(w.h1.fontFamily) : firstFamily(w.body.fontFamily))
9412
+ );
9315
9413
  p.fontDisplay = design?.fonts[0] ? `named in design.md` : w.h1 ? `the h1's computed face` : `the body's computed face (no h1)`;
9316
9414
  const designBody = design?.fonts.slice(1).find((f) => !/\bmono\b/i.test(f));
9317
- const fontBody = designBody ?? firstFamily(w.body.fontFamily);
9415
+ const fontBody = catalogFamily(designBody ?? firstFamily(w.body.fontFamily));
9318
9416
  p.fontBody = designBody ? `named in design.md` : `the body's computed face`;
9319
9417
  const isIcon = (u) => /favicon|apple-touch|icon/i.test(u);
9320
9418
  const onDark = (u) => /on-dark|white/i.test(u);
@@ -10016,14 +10114,16 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
10016
10114
  "composed",
10017
10115
  "check",
10018
10116
  "motion",
10019
- "leader"
10117
+ "leader",
10118
+ "keep-frames",
10119
+ "dry-run"
10020
10120
  ]);
10021
- var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override"]);
10121
+ var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg"]);
10022
10122
  var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
10023
10123
 
10024
10124
  Take pipeline
10025
- vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--max-duration <s>] [--background <slug|url|none>] [render flags] [--json]
10026
- vos record --actions actions.json [--url <url>] [--out take] [--strict] [--max-duration <s>] [--background <slug|url|none>] [--json]
10125
+ vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [render flags] [--json]
10126
+ vos record --actions actions.json [--url <url>] [--out take] [--strict] [--dry-run] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
10027
10127
  vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<s>]]... [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|fade|slide|none] [--transitions slide|fade|scale|none] [--end-card on|none|<doc.json|vosId>] [--captions none] [--clicks none] [--still <t>] [--release v2.1] [--json]
10028
10128
  vos render <take> [out.webm] [--width] [--height] [--fps] [--format webm|mp4] [--parallel N] [--range a..b] [--draft] [--frame <kind>] [--background <url|slug>] [--set <path=value>]... [--json]
10029
10129
  vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--at-still] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
@@ -10031,7 +10131,7 @@ Take pipeline
10031
10131
  vos digest <take> [--out dir] [--full 960] [--crop 640] [--no-frames] [--transcript <file.json>] [--style <doc.json|vosId>] [--json]
10032
10132
  vos brand <url> [--out BRAND.md] [--json]
10033
10133
  vos open <take> [--studio <url>] [--print]
10034
- vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side auto|right|left|below|above] [--mark ring|underline|none] [--leader] [--color #hex] [--brand BRAND.md] [--ground #hex] [--accent #hex] [--font "\u2026"] [--body-px 14] [--id x] [--print] [--json]
10134
+ vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side auto|right|left|below|above] [--mark ring|underline|none] [--leader] [--color #hex (the mark and leader; the kicker is --accent)] [--brand BRAND.md] [--ground #hex] [--accent #hex] [--font "\u2026"] [--body-px <n> (the body size on the delivered frame)] [--id x] [--print] [--json]
10035
10135
  vos validate <actions.json|take|kit.json> [--picture] [--json]
10036
10136
  vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]
10037
10137
  vos actions from-agent-browser <steps.jsonl> [--out actions.json] [--url <url>] [--viewport WxH] [--json]
@@ -10051,7 +10151,10 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
10051
10151
  a take DIRECTORY (doc.json) pushes recording + doc; a config.json
10052
10152
  pushes the program. No --vos: create a PRIVATE vos; with --vos:
10053
10153
  add a version against the tracked base \u2014 a stale push 409s WITH
10054
- the platform's typed changelog. --claimable (programs only, NO
10154
+ the platform's typed changelog. A TAKE pushes to the vos in its
10155
+ vos.json; --vos on a take with none ADOPTS that vos (the take
10156
+ becomes its next version, built on its head), and --vos that
10157
+ disagrees with vos.json is refused before anything uploads. --claimable (programs only, NO
10055
10158
  credential): creates a 72h claim link instead \u2014 hand it to the
10056
10159
  user and nowhere else; unclaimed work is deleted after 72h.
10057
10160
  --override consents to touching
@@ -10298,6 +10401,18 @@ function strictReason(rec) {
10298
10401
  if (rec.capped) parts.push("stopped at --max-duration");
10299
10402
  return parts.join(" + ");
10300
10403
  }
10404
+ function takeBrowserArgs(multi) {
10405
+ const given = multi["browser-arg"];
10406
+ return (given ?? []).filter(Boolean);
10407
+ }
10408
+ function takeStorageState(flags) {
10409
+ const raw = strFlag(flags, "storage-state");
10410
+ if (!raw) return void 0;
10411
+ const path = resolve9(raw);
10412
+ if (!existsSync15(path))
10413
+ throw new UsageError(`--storage-state: no such file: ${path}`);
10414
+ return path;
10415
+ }
10301
10416
  async function takeBackdrop(flags, r) {
10302
10417
  const { backdrop, note } = await openingBackdrop(
10303
10418
  strFlag(flags, "background"),
@@ -10309,8 +10424,20 @@ async function takeBackdrop(flags, r) {
10309
10424
  if (note) r.log(`note: ${note}`);
10310
10425
  return backdrop;
10311
10426
  }
10427
+ async function releaseFrames(paths, flags, log) {
10428
+ if (flags["keep-frames"] === true) return;
10429
+ const freed = await dropScreencastFrames(paths);
10430
+ if (freed >= 1e6)
10431
+ log(
10432
+ `released ${Math.round(freed / 1e6)} MB of screencast frames (the recording holds them now; --keep-frames keeps them)`
10433
+ );
10434
+ }
10312
10435
  async function cmdRecord(argv) {
10313
- const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
10436
+ const { positionals, flags, multi } = parseArgs(
10437
+ argv,
10438
+ BOOLEAN_FLAGS5,
10439
+ MULTI_FLAGS2
10440
+ );
10314
10441
  const actionsPath = strFlag(flags, "actions") ?? positionals[0];
10315
10442
  if (!actionsPath)
10316
10443
  throw new UsageError(
@@ -10324,6 +10451,49 @@ async function cmdRecord(argv) {
10324
10451
  const outDir = resolve9(strFlag(flags, "out") ?? "take");
10325
10452
  const backdrop = await takeBackdrop(flags, r);
10326
10453
  const maxDurationSeconds = await maxDuration(flags, r);
10454
+ const storageState = takeStorageState(flags);
10455
+ const browserArgs = takeBrowserArgs(multi);
10456
+ if (flags["dry-run"] === true) {
10457
+ void flags.strict;
10458
+ void flags["keep-frames"];
10459
+ const browser2 = await launchBrowser(browserArgs);
10460
+ try {
10461
+ r.log("rehearsing (nothing is captured or written)\u2026");
10462
+ r.event({ event: "phase", phase: "rehearse" });
10463
+ const started = Date.now();
10464
+ const rec = await recordTake(
10465
+ browser2,
10466
+ url,
10467
+ actions,
10468
+ takePaths(outDir),
10469
+ r.log,
10470
+ { storageState, dryRun: true }
10471
+ );
10472
+ const steps = rec.meta.steps ?? [];
10473
+ const lines = steps.map((s) => {
10474
+ const rect = s.rect ? ` rect ${s.rect.x},${s.rect.y} ${s.rect.w}\xD7${s.rect.h}` : "";
10475
+ const what = s.selector ? ` ${s.selector}` : "";
10476
+ return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
10477
+ });
10478
+ const failed2 = rec.skipped.length > 0 || rec.navTimeout;
10479
+ r.done(
10480
+ {
10481
+ dryRun: true,
10482
+ ok: !failed2,
10483
+ seconds: +((Date.now() - started) / 1e3).toFixed(1),
10484
+ steps,
10485
+ skipped: rec.skipped,
10486
+ navTimeout: rec.navTimeout
10487
+ },
10488
+ `${failed2 ? "REHEARSAL FAILED" : "Rehearsal passed"}: ${steps.length - rec.skipped.length}/${steps.length} steps resolved in ${((Date.now() - started) / 1e3).toFixed(1)}s${rec.navTimeout ? " (the first load never reached networkidle)" : ""}
10489
+ ${lines.join("\n")}
10490
+ ${failed2 ? "Fix the script, rehearse again, then record." : `Rects are capture px (the step rects a pin or a callout reads). Next: vos record --actions ${actionsPath} --out ${outDir} --strict`}`
10491
+ );
10492
+ return failed2 ? EXIT_USAGE : EXIT_OK;
10493
+ } finally {
10494
+ await browser2.close();
10495
+ }
10496
+ }
10327
10497
  if (existsSync15(join18(outDir, "meta.json"))) {
10328
10498
  const { prevDoc, kept } = await prepareReRecord(outDir);
10329
10499
  r.log(
@@ -10332,12 +10502,13 @@ async function cmdRecord(argv) {
10332
10502
  }
10333
10503
  await mkdir10(outDir, { recursive: true });
10334
10504
  const paths = await ensureTakeDir(outDir);
10335
- const browser = await launchBrowser();
10505
+ const browser = await launchBrowser(browserArgs);
10336
10506
  try {
10337
10507
  r.log("recording\u2026");
10338
10508
  r.event({ event: "phase", phase: "record" });
10339
10509
  const rec = await recordTake(browser, url, actions, paths, r.log, {
10340
- maxDurationSeconds
10510
+ maxDurationSeconds,
10511
+ storageState
10341
10512
  });
10342
10513
  r.event({ event: "phase", phase: "encode" });
10343
10514
  r.log("encoding\u2026");
@@ -10346,6 +10517,7 @@ async function cmdRecord(argv) {
10346
10517
  outDir,
10347
10518
  (p) => r.event({ event: "progress", phase: "encode", fraction: p })
10348
10519
  );
10520
+ await releaseFrames(paths, flags, r.log);
10349
10521
  r.event({ event: "phase", phase: "plan" });
10350
10522
  const plan = await planTake(outDir, { backdrop });
10351
10523
  const clicks = rec.events.filter((e) => e.type === "down").length;
@@ -10408,6 +10580,8 @@ async function cmdCreate2(argv) {
10408
10580
  }
10409
10581
  const backdrop = await takeBackdrop(flags, r);
10410
10582
  const maxDurationSeconds = await maxDuration(flags, r);
10583
+ const storageState = takeStorageState(flags);
10584
+ const browserArgs = takeBrowserArgs(multi);
10411
10585
  if (existsSync15(join18(outDir, "meta.json"))) {
10412
10586
  const { prevDoc, kept } = await prepareReRecord(outDir);
10413
10587
  r.log(
@@ -10416,12 +10590,13 @@ async function cmdCreate2(argv) {
10416
10590
  }
10417
10591
  await mkdir10(outDir, { recursive: true });
10418
10592
  const paths = await ensureTakeDir(outDir);
10419
- const browser = await launchBrowser();
10593
+ const browser = await launchBrowser(browserArgs);
10420
10594
  try {
10421
10595
  r.log("recording\u2026");
10422
10596
  r.event({ event: "phase", phase: "record" });
10423
10597
  const rec = await recordTake(browser, url, actions, paths, r.log, {
10424
- maxDurationSeconds
10598
+ maxDurationSeconds,
10599
+ storageState
10425
10600
  });
10426
10601
  r.event({ event: "phase", phase: "encode" });
10427
10602
  r.log("encoding\u2026");
@@ -10430,6 +10605,7 @@ async function cmdCreate2(argv) {
10430
10605
  outDir,
10431
10606
  (p) => r.event({ event: "progress", phase: "encode", fraction: p })
10432
10607
  );
10608
+ await releaseFrames(paths, flags, r.log);
10433
10609
  r.event({ event: "phase", phase: "plan" });
10434
10610
  await planTake(outDir, { backdrop });
10435
10611
  if (flags.strict === true && (rec.skipped.length > 0 || rec.navTimeout || rec.capped)) {
@@ -11129,7 +11305,7 @@ async function cmdCallout(argv) {
11129
11305
  const shape = positionals[1];
11130
11306
  if (!dir || !shape || !isCalloutShape(shape))
11131
11307
  throw new UsageError(
11132
- 'vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side \u2026] [--mark ring|underline|none] [--leader] [--color #hex] [--brand BRAND.md] [--ground #hex --accent #hex] [--font "\u2026"] [--body-px 14] [--id x] [--print] [--json]'
11308
+ 'vos callout <take> <note|tag|code> --step <id> [--title "\u2026"] [--body "\u2026"] [--kicker "\u2026"] [--code "\u2026"|--code-file f] [--seconds 3] [--side \u2026] [--mark ring|underline|none] [--leader] [--color #hex (the mark and leader; the kicker is --accent)] [--brand BRAND.md] [--ground #hex --accent #hex] [--font "\u2026"] [--body-px <n> (the body size on the delivered frame)] [--id x] [--print] [--json]'
11133
11309
  );
11134
11310
  const r = createReporter(flags.json === true);
11135
11311
  const take = await loadTake(dir);
@@ -11180,15 +11356,16 @@ async function cmdCallout(argv) {
11180
11356
  side,
11181
11357
  mark,
11182
11358
  leader: flags.leader === true,
11183
- color: strFlag(flags, "color")
11359
+ color: strFlag(flags, "color"),
11360
+ bodyPxGiven: bodyPx !== void 0
11184
11361
  });
11185
11362
  } catch (e) {
11186
11363
  throw new UsageError(e instanceof Error ? e.message : String(e));
11187
11364
  }
11188
- const { clip, window: window2 } = composed;
11365
+ const { clip, window: window2, sizes } = composed;
11189
11366
  if (flags.print === true) {
11190
11367
  r.done(
11191
- { clip, window: window2, register, brand: brand?.file ?? null },
11368
+ { clip, window: window2, sizes, register, brand: brand?.file ?? null },
11192
11369
  JSON.stringify(clip, null, 2)
11193
11370
  );
11194
11371
  return EXIT_OK;
@@ -11210,11 +11387,12 @@ async function cmdCallout(argv) {
11210
11387
  {
11211
11388
  clip,
11212
11389
  window: window2,
11390
+ sizes,
11213
11391
  register,
11214
11392
  brand: brand?.file ?? null,
11215
11393
  warnings: lint.warnings
11216
11394
  },
11217
- `${shape} "${clip.id}" at ${window2.start.toFixed(2)}s for ${window2.duration.toFixed(2)}s${clip.pin ? `, pinned to ${clip.pin.step !== void 0 ? `step ${String(clip.pin.step)}` : `the press at ${String(clip.pin.press)}s`}` : ""}, ground ${register.ground} \u2192 card in the product's hue, written to ${take.paths.doc}${lint.warnings.length ? `
11395
+ `${shape} "${clip.id}" at ${window2.start.toFixed(2)}s for ${window2.duration.toFixed(2)}s${clip.pin ? `, pinned to ${clip.pin.step !== void 0 ? `step ${String(clip.pin.step)}` : `the press at ${String(clip.pin.press)}s`}` : ""}, ground ${register.ground} \u2192 card in the product's hue, type ${sizes.kickerPx} / ${sizes.titlePx} / ${sizes.bodyPx} px (kicker / title / body; ${bodyPx !== void 0 ? `--body-px ${bodyPx} as delivered` : `the app's body as seen, footage scale ${sizes.scale}; --body-px <n> sets the delivered size`}), written to ${take.paths.doc}${lint.warnings.length ? `
11218
11396
  warnings:
11219
11397
  ${lint.warnings.join("\n ")}` : ""}`
11220
11398
  );
@@ -11377,8 +11555,16 @@ async function cmdPush3(argv) {
11377
11555
  const dir = positionals[0];
11378
11556
  if (!dir)
11379
11557
  throw new UsageError(
11380
- "vos push <take> [--title|--label|--note|--folder|--override|--yes|--key|--api]"
11558
+ "vos push <take> [--vos|--title|--label|--note|--folder|--override|--yes|--key|--api]"
11381
11559
  );
11560
+ const refusal = takePushRefusal({
11561
+ vos: strFlag(flags, "vos"),
11562
+ trackedVosId: readSyncState(resolve9(dir))?.vosId ?? null,
11563
+ programOnly: ["slug", "desc", "tags", "base", "remix-of"].filter(
11564
+ (name) => hasFlag(flags, name)
11565
+ )
11566
+ });
11567
+ if (refusal) throw new UsageError(refusal);
11382
11568
  const r = createReporter(flags.json === true);
11383
11569
  const overrides = multi.override;
11384
11570
  const result = await pushTake(
@@ -11388,6 +11574,7 @@ async function cmdPush3(argv) {
11388
11574
  api: strFlag(flags, "api"),
11389
11575
  origin: strFlag(flags, "origin"),
11390
11576
  yes: flags.yes === true,
11577
+ vos: strFlag(flags, "vos"),
11391
11578
  title: strFlag(flags, "title"),
11392
11579
  label: strFlag(flags, "label"),
11393
11580
  note: strFlag(flags, "note"),
@@ -11548,6 +11735,9 @@ export {
11548
11735
  parseAgentBrowserLog,
11549
11736
  splitCommand,
11550
11737
  convertAgentBrowser,
11738
+ MULTI_FLAGS2 as MULTI_FLAGS,
11739
+ HELP,
11740
+ takeBrowserArgs,
11551
11741
  run
11552
11742
  };
11553
- //# sourceMappingURL=chunk-DD6OXVFJ.js.map
11743
+ //# sourceMappingURL=chunk-RECQEBXN.js.map