@vosjs/cli 0.38.0 → 0.40.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.
package/README.md CHANGED
@@ -60,6 +60,7 @@ A **take** is a directory: the recording of a scripted browser flow, its exact c
60
60
 
61
61
  ```bash
62
62
  vos create --actions actions.json out.webm --strict # one shot: record, auto-plan, render
63
+ vos record --actions actions.json --out take --dry-run # rehearse the script first: which selectors resolve, their rects, in seconds
63
64
  vos record --actions actions.json --out take --strict # drive the page, record it with an exact cursor track, plan the cut
64
65
  vos digest take # SEE the recording before cutting: moments, frames, crops (an agent's eyes)
65
66
  # … edit take/doc.json (zoom spans, trims, speed, overlays) by hand or by agent …
@@ -79,7 +80,7 @@ vos plan take --reuse # re-time that cut onto
79
80
 
80
81
  | Verb | Flags |
81
82
  | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
82
- | `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--max-duration <s>` `--background <slug\|url\|none>` |
83
+ | `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--dry-run` `--keep-frames` `--storage-state <file>` `--browser-arg=<switch>`... `--max-duration <s>` `--background <slug\|url\|none>` |
83
84
  | `create` | The `record` flags plus the render flags (`--width` `--height` `--fps` `--format` `--parallel` `--draft` `--frame` `--set`), no `--range`. With `--strict` an incomplete recording exits 2 before anything is rendered |
84
85
  | `plan` | `--fresh` (discard the current plan) `--reuse` `--from <doc.json>` (defaults to `<take>/doc.prev.json`) `--style <doc.json\|take\|vosId>` `--with <doc.json\|take\|vosId>[@end\|@start\|@step:<id>\|@<seconds>]` (a template, repeatable) `--background` `--motion` (re-propose the motion) `--headline` `--kicker` `--launch` `--brand` `--music` `--entrance` `--transitions slide\|fade\|scale\|none` `--end-card on\|none\|<ref>` `--captions` `--clicks` `--release` |
85
86
  | `digest` | `--out <take>/digest` `--full 960` `--crop 640` (image long edges, the token budget) `--no-frames` `--transcript <file>` (Whisper-shaped segments merged as `said`) `--style <ref>` (report a reference document's style fields) |
@@ -87,6 +88,8 @@ vos plan take --reuse # re-time that cut onto
87
88
  | `render` | `--width` `--height` `--fps` `--format webm\|mp4` `--parallel N` (1..16 sessions) `--range a..b` (output seconds; keeps its audio) `--draft` `--frame <kind>` `--background` `--set …`; `out` defaults to `<take>/out.<format>` |
88
89
  | `open` | `--studio http://localhost:6060` `--print` (print the URL, do not launch a browser) |
89
90
 
91
+ **Rehearse before you record.** `vos record … --dry-run` runs every step against the real page, in order, because a later selector usually exists only after an earlier click. Nothing is captured and nothing is written: the pointer lands instead of travelling, every pause is cut to a beat, and the take directory beside it keeps its footage, its cut and its script exactly as they were (a real re-record moves `doc.json` aside; a rehearsal does not). Selector lookups keep their whole timeout, so a miss here is a miss in the take. It prints each step with the rect it resolved, in capture px, which are the rects a pin or `vos callout --step` reads, and exits 2 on any miss or a first load that never settled. Add `--dry-run` to the exact command you were about to run; `--storage-state` and `--browser-arg=` apply to it too.
92
+
90
93
  **Digest first.** `vos digest <take>` is how an agent sees a recording without reading the video. It writes `digest/digest.json`: one moment per thing the cursor track says mattered (click clusters, typing sessions, scroll runs, dwells, idle gaps, head, tail, and frame-diff scene changes), each with source and output extents, a normalized `focus` and `rect` you can copy into a zoom span, per-second `activity`, and the planners' `proposed` span ids; plus one footage frame and a crop around the target per moment, and `sheet.png`, the contact sheet. Read the JSON, then the sheet, then a crop only where you must decide. `vos validate` then warns when a zoom does not contain what was clicked under it, and `vos frames --at-moments` renders the composed output at every moment so a still and its footage crop share an id.
91
94
 
92
95
  **Many media in one take (concat).** `doc.json` may carry `media: [{ id, videoKey, cursor, meta, … }]`, the take's OTHER recordings or uploads, each the `source` shape with an `id`; a segment on one (`{ in, out, media: "m1" }`) plays it, and a zoom, tilt, speed, freeze or cam-move span names the media its source seconds belong to (absent = the primary, `source`). The Video row shows the clips in order, a span is drawn where its media plays, `vos plan` proposes zoom and speed spans on every media from its own cursor track, `vos validate` measures each span against its own media's length, and `vos push` and `vos pull --media` carry every media through the recording door. A media wears its OWN card: `media[].frame` carries the card-owned fields (its placement and size as `inset`, the browser bar, the corner, the shadow, the border, the cover fit and its focus) over the take's frame while it plays; the bar names that media's recorded page, an upload with no page wears none, and the frame-wide fields (the aspect, the padding, the ground, the backdrop, the card's animation) stay the take's. Sound: the primary's tracks play at the primary's moments; another media's own audio is not spliced into the cut yet. **Many cards**: an image or video overlay may show a document media by reference (`key: "media:<id>"`, `media:` alone the primary; its cursor track and recorded page come with it) and wear a card (`frame`: `browserBar`, a `lean` `{rx, ry}` in degrees, `shadow`, `shadowContact`, `shadowColor`, `cursor`), drawn by the card painter on its own plane above the primary card with the media's cursor dot and click rings inside it; a layer without `frame` stays the flat picture. The primary card stays primary: the sequence, the camera and the cut are its.
@@ -214,8 +217,9 @@ Everything that runs on your machine is open source; [vos.so](https://vos.so) is
214
217
  vos login [--key <k>] [--label <name>] [--no-browser] # browser sign-in: a code and a vos.so/cli/auth URL, a human approves, the key stores itself
215
218
  vos fetch <vosId|url> [--out <slug>] [--media] # a program writes config.json + vos.json; a take writes doc.json (+ the recording with --media)
216
219
  vos check bright-loop/config.json # full local validation
217
- vos push bright-loop/config.json # create a PRIVATE vos (lineage from vos.json or --remix-of)
218
- vos push bright-loop/config.json --vos <id> # add a version against your tracked base
220
+ vos push bright-loop/config.json # untracked: create a PRIVATE vos. Tracked (vos.json): add a version to it
221
+ vos push bright-loop/config.json --vos <id> # add a version to a vos this directory does not track
222
+ vos push bright-loop/config.json --remix-of <id> # create a SEPARATE vos from this config, with that lineage
219
223
  vos push bright-loop/config.json --claimable # no credential: a 72 h claim link instead (programs only)
220
224
  vos push take --yes --label "first pass" --note "…" # host a take: private vos + version history (the recording uploads once)
221
225
  vos pull bright-loop [--since <versionId>] [--check] # what changed on vos.so since your base; syncs config.json (backup kept), or doc.json for a take
@@ -226,7 +230,7 @@ vos asset push <file…> [--folder <slug>] | rename <id> <name.ext>
226
230
  vos recipe push <FILE.md> --folder <slug> | --asset <id> # the one recipe write: create, or replace in place
227
231
  ```
228
232
 
229
- `push` is polymorphic by a deterministic sniff, never a flag: a take directory (a `doc.json` carrying `source`) pushes recording and document through the take pipeline; a `config.json` (or a directory holding one) pushes the program, and a `doc.json` beside it that carries `program` (a program document: overlays, objects, audio, speed, tween edits, its own length; `program.config` omitted on disk) rides along, lint-gated. `fetch` and `pull` write a program document back the same way. `pull` takes `--since <versionId>` (walk the changelog from that base instead of the tracked one) and `--check` (print what changed and stop: nothing on disk moves) on both paths. Program pushes take `--vos`, `--title`, `--slug`, `--desc`, `--tags`, `--folder`, `--remix-of`, `--base`, `--label`, `--note`, `--override <id>` (repeatable) and `--claimable`; take pushes take `--title`, `--label`, `--note`, `--folder`, `--override` and `--yes`.
233
+ `push` is polymorphic by a deterministic sniff, never a flag: a take directory (a `doc.json` carrying `source`) pushes recording and document through the take pipeline; a `config.json` (or a directory holding one) pushes the program, and a `doc.json` beside it that carries `program` (a program document: overlays, objects, audio, speed, tween edits, its own length; `program.config` omitted on disk) rides along, lint-gated. `fetch` and `pull` write a program document back the same way. `pull` takes `--since <versionId>` (walk the changelog from that base instead of the tracked one) and `--check` (print what changed and stop: nothing on disk moves) on both paths. A program push LANDS where `vos.json` says: a directory that tracks a vos iterates it, so the fetch, edit, check, push, pull loop needs no flags, and making something separate from the same config is the explicit `--remix-of` door. Program pushes take `--vos`, `--title`, `--slug`, `--desc`, `--tags`, `--folder`, `--remix-of`, `--base`, `--label`, `--note`, `--override <id>` (repeatable) and `--claimable`; take pushes take `--title`, `--label`, `--note`, `--folder`, `--override` and `--yes`.
230
234
 
231
235
  Both paths share the same base tracking and the same two 409 shapes. `stale_base` replays the platform's typed changelog: run `vos pull`, re-apply, push again. `protected_conflict` lists nodes a human edited in the studio: keep their values, or re-push with `--override <id>` only when the user asked for that exact change. The first push of a take asks before uploading (`--yes` for headless); agents never upload unprompted. The take's duration rides the upload, and the platform refuses a take over the hosted recording cap. Every push should carry `--label` (what changed, one line) and `--note` (why: the user's ask); the version history reads as a conversation, and an unlabelled push is a turn the human cannot read.
232
236
 
@@ -6629,7 +6629,8 @@ function paceLine(r) {
6629
6629
  }
6630
6630
 
6631
6631
  // src/plugin/recorder.ts
6632
- 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;
6633
6634
  function jpegDims(buf) {
6634
6635
  let i = 2;
6635
6636
  while (i < buf.length - 9) {
@@ -6644,7 +6645,9 @@ function jpegDims(buf) {
6644
6645
  return null;
6645
6646
  }
6646
6647
  async function recordTake(browser, url, actions, paths, log, opts = {}) {
6647
- 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;
6648
6651
  const vw = actions.viewport?.width ?? 1280;
6649
6652
  const vh = actions.viewport?.height ?? 720;
6650
6653
  const context = await browser.newContext({
@@ -6667,22 +6670,24 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6667
6670
  const frames = [];
6668
6671
  let frameIdx = 0;
6669
6672
  let t0 = 0;
6670
- cdp.on("Page.screencastFrame", (ev) => {
6671
- const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
6672
- const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
6673
- writeFileSync2(join13(paths.framesDir, file), Buffer.from(ev.data, "base64"));
6674
- frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
6675
- 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
+ });
6676
6681
  });
6677
- });
6678
6682
  t0 = Date.now();
6679
- await cdp.send("Page.startScreencast", {
6680
- format: "jpeg",
6681
- quality: 90,
6682
- maxWidth: vw,
6683
- maxHeight: vh,
6684
- everyNthFrame: 1
6685
- });
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
+ });
6686
6691
  const events = [];
6687
6692
  const cur = { x: 48, y: 48 };
6688
6693
  const now = () => Date.now() - t0;
@@ -6694,7 +6699,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6694
6699
  const dist = Math.hypot(tx - cur.x, ty - cur.y);
6695
6700
  const from = { ...cur };
6696
6701
  await clockMotion(
6697
- pointerTravelMs(dist),
6702
+ dry ? 0 : pointerTravelMs(dist),
6698
6703
  async (u) => {
6699
6704
  cur.x = from.x + (tx - from.x) * u;
6700
6705
  cur.y = from.y + (ty - from.y) * u;
@@ -6969,12 +6974,14 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
6969
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`
6970
6975
  );
6971
6976
  }
6972
- await writeJson(paths.cursor, events);
6973
- await writeJson(paths.meta, meta, true);
6974
- await writeJson(paths.framesIndex, frames);
6975
- await writeJson(paths.actions, { ...actions, url }, true);
6976
6977
  const pace = paceReport(paces);
6977
- 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
+ }
6978
6985
  return {
6979
6986
  events,
6980
6987
  frames,
@@ -7964,6 +7971,21 @@ function takePushRefusal(ask) {
7964
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`;
7965
7972
  return null;
7966
7973
  }
7974
+ function programPushTarget(ask) {
7975
+ if (ask.vos) return { kind: "version", vosId: ask.vos, tracked: false };
7976
+ if (ask.remixOf) return { kind: "create", remixOfId: ask.remixOf };
7977
+ const tracked = ask.trackedVosId;
7978
+ if (!tracked) return { kind: "create" };
7979
+ const extra = ask.createOnly ?? [];
7980
+ if (extra.length) {
7981
+ const list = extra.map((f) => `--${f}`).join(", ");
7982
+ return {
7983
+ kind: "refuse",
7984
+ why: `this directory tracks vos ${tracked} (vos.json), so a bare push adds a VERSION to it, and ${list} ${extra.length > 1 ? "apply" : "applies"} only when creating. To make a separate vos from this config: vos push <config.json> --remix-of ${tracked} ${list}. To change the tracked vos's title or slug, edit it on vos.so`
7985
+ };
7986
+ }
7987
+ return { kind: "version", vosId: tracked, tracked: true };
7988
+ }
7967
7989
  async function pushTake(dir, flags, r) {
7968
7990
  const take = await loadTake(dir);
7969
7991
  if (!take.doc) {
@@ -8345,12 +8367,12 @@ async function browserLogin(origin, r, opts) {
8345
8367
  r.log(`Sign in at ${verifyUrl}`);
8346
8368
  if (opts.openBrowser) void launchDefaultBrowser(verifyUrl);
8347
8369
  r.log("Waiting for approval\u2026 (Ctrl-C to cancel)");
8348
- const sleep2 = opts.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
8370
+ const sleep = opts.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
8349
8371
  let intervalMs = baseIntervalMs;
8350
8372
  let failures = 0;
8351
8373
  let capNoted = false;
8352
8374
  while (Date.now() < expiresAt) {
8353
- await sleep2(intervalMs);
8375
+ await sleep(intervalMs);
8354
8376
  let res;
8355
8377
  try {
8356
8378
  res = await apiJson(origin, "/api/cli/login/poll", {
@@ -8652,7 +8674,7 @@ async function cmdPushProgram(argv) {
8652
8674
  const config = pre.config;
8653
8675
  const state = readSyncState(dir);
8654
8676
  const vosFlag = strFlag(flags, "vos");
8655
- const vosId = vosFlag ? parseVosId(vosFlag) : null;
8677
+ const explicitVosId = vosFlag ? parseVosId(vosFlag) : null;
8656
8678
  const programDoc = await readProgramDoc(dir, config);
8657
8679
  if (programDoc) {
8658
8680
  const lint = lintDoc(programDoc);
@@ -8663,7 +8685,7 @@ async function cmdPushProgram(argv) {
8663
8685
  for (const w of lint.warnings) r.log(`warning doc.json: ${w}`);
8664
8686
  }
8665
8687
  if (flags.claimable === true) {
8666
- if (vosId) {
8688
+ if (explicitVosId) {
8667
8689
  throw new UsageError(
8668
8690
  "--claimable creates a NEW claimable vos and cannot iterate (--vos). After the human claims it, iterate with their key: vos push --vos <id>"
8669
8691
  );
@@ -8697,6 +8719,15 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8697
8719
  const desc = strFlag(flags, "desc");
8698
8720
  const tagsFlag = strFlag(flags, "tags");
8699
8721
  const folderRef = strFlag(flags, "folder");
8722
+ const remixOfFlag = strFlag(flags, "remix-of");
8723
+ const pushTarget = programPushTarget({
8724
+ vos: explicitVosId,
8725
+ remixOf: remixOfFlag,
8726
+ trackedVosId: state?.vosId ?? null,
8727
+ createOnly: ["title", "slug"].filter((f) => strFlag(flags, f))
8728
+ });
8729
+ if (pushTarget.kind === "refuse") throw new UsageError(pushTarget.why);
8730
+ const vosId = pushTarget.kind === "version" ? pushTarget.vosId : null;
8700
8731
  if (vosId && (desc || tagsFlag || folderRef)) {
8701
8732
  throw new UsageError(
8702
8733
  "--desc/--tags/--folder apply when CREATING a vos. On an existing one: vos folder move <id> --to <folder> for filing; edit title/description/tags on vos.so"
@@ -8765,7 +8796,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8765
8796
  );
8766
8797
  return EXIT_OK;
8767
8798
  }
8768
- const remixOfId = strFlag(flags, "remix-of") ?? state?.vosId;
8799
+ const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
8769
8800
  const fallbackTitle = state?.title ? `${state.title} remix` : basename4(source).replace(/\.json$/i, "") || "vos remix";
8770
8801
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
8771
8802
  const folderId = folderRef ? resolveFolder(await listFolders(origin, key), folderRef).id : void 0;
@@ -10108,14 +10139,15 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
10108
10139
  "check",
10109
10140
  "motion",
10110
10141
  "leader",
10111
- "keep-frames"
10142
+ "keep-frames",
10143
+ "dry-run"
10112
10144
  ]);
10113
10145
  var MULTI_FLAGS2 = /* @__PURE__ */ new Set(["set", "override", "browser-arg"]);
10114
10146
  var HELP = `vos \u2014 record a browser flow, plan effects, render a product video; sync with vos.so
10115
10147
 
10116
10148
  Take pipeline
10117
10149
  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]
10118
- vos record --actions actions.json [--url <url>] [--out take] [--strict] [--keep-frames] [--max-duration <s>] [--storage-state <file>] [--browser-arg=<switch>]... [--background <slug|url|none>] [--json]
10150
+ 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]
10119
10151
  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]
10120
10152
  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]
10121
10153
  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]
@@ -10445,6 +10477,47 @@ async function cmdRecord(argv) {
10445
10477
  const maxDurationSeconds = await maxDuration(flags, r);
10446
10478
  const storageState = takeStorageState(flags);
10447
10479
  const browserArgs = takeBrowserArgs(multi);
10480
+ if (flags["dry-run"] === true) {
10481
+ void flags.strict;
10482
+ void flags["keep-frames"];
10483
+ const browser2 = await launchBrowser(browserArgs);
10484
+ try {
10485
+ r.log("rehearsing (nothing is captured or written)\u2026");
10486
+ r.event({ event: "phase", phase: "rehearse" });
10487
+ const started = Date.now();
10488
+ const rec = await recordTake(
10489
+ browser2,
10490
+ url,
10491
+ actions,
10492
+ takePaths(outDir),
10493
+ r.log,
10494
+ { storageState, dryRun: true }
10495
+ );
10496
+ const steps = rec.meta.steps ?? [];
10497
+ const lines = steps.map((s) => {
10498
+ const rect = s.rect ? ` rect ${s.rect.x},${s.rect.y} ${s.rect.w}\xD7${s.rect.h}` : "";
10499
+ const what = s.selector ? ` ${s.selector}` : "";
10500
+ return ` ${s.skipped ? "\u2717" : "\u2713"} #${s.step}${s.id ? ` (${s.id})` : ""} ${s.do}${what}${s.skipped ? " NOT FOUND" : rect}${s.navigated ? " \u2192 navigated" : ""}`;
10501
+ });
10502
+ const failed2 = rec.skipped.length > 0 || rec.navTimeout;
10503
+ r.done(
10504
+ {
10505
+ dryRun: true,
10506
+ ok: !failed2,
10507
+ seconds: +((Date.now() - started) / 1e3).toFixed(1),
10508
+ steps,
10509
+ skipped: rec.skipped,
10510
+ navTimeout: rec.navTimeout
10511
+ },
10512
+ `${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)" : ""}
10513
+ ${lines.join("\n")}
10514
+ ${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`}`
10515
+ );
10516
+ return failed2 ? EXIT_USAGE : EXIT_OK;
10517
+ } finally {
10518
+ await browser2.close();
10519
+ }
10520
+ }
10448
10521
  if (existsSync15(join18(outDir, "meta.json"))) {
10449
10522
  const { prevDoc, kept } = await prepareReRecord(outDir);
10450
10523
  r.log(
@@ -11691,4 +11764,4 @@ export {
11691
11764
  takeBrowserArgs,
11692
11765
  run
11693
11766
  };
11694
- //# sourceMappingURL=chunk-LGRNHJ2U.js.map
11767
+ //# sourceMappingURL=chunk-7TDL3N24.js.map