@vosjs/cli 0.81.1 → 0.83.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
@@ -190,7 +190,7 @@ vos look https://app.acme.com/projects --session acme --click '[data-testid="new
190
190
 
191
191
  It writes `<out>/look.png` (the viewport; `--out look` by default) and lists every control in view (buttons, links, tabs, menu items, checkboxes, switches, options, fields, editable regions, and anything with a test id) with its tag, role, accessible name, test id, link path and query, rect in viewport px, whether it is disabled, inside an open dialog or covered by something on top of it, and ONE proposed selector: its test id; else its `aria-label`, inside the open dialog it sits in; else a link's own `href`, query kept (two tiles to `?vos=a` and `?vos=b` are two links); else role and name (`role=button[name="Continue"]`, what `page.locator` reads), narrowed by its dialog or landmark when that is what makes it one; else a field's own `name` or `placeholder`, a written id, or its words inside its dialog or landmark. Never a position in the tree. Each proposal is counted with the recorder's own selector engine and marked `UNIQUE` (it reaches this element and no other) or `AMBIGUOUS` (it reaches others too, and the recorder would take the first). A control whose words hold an address, a key or a card number is listed with those words withheld, and the frame's exposures are said the way a rehearsal says them. Looking never spends or destroys: a `--click` whose target's words begin with a stem that does (delet, remov, revok, eras, discard, publish, deploy, purchas, subscri, unsubscri, pay, buy, generat, submit, send, transfer, upgrad, confirm, leav, destroy, so `Allow deleting` and `Revoke` are caught with `Delete`), or say move to trash, empty trash or cancel subscription, is refused in words before it is pressed (exit 2), and `--type` fills a field without pressing Enter. A look cannot know every spend: `Make a video` or `Create` on a primary button may start paid work and passes, because refusing every Make would refuse half of every app. The list is a floor under looking; in a recorded flow, a step's `guard: "spend"` in `actions.json` is the fence. A sign-in in front of the page is the wall check's verdict, exit 4; the sign-in page is still looked at, since it is what a `setup` step scripts past. `--wait <ms>` adds a pause before the look for a page that animates in; `--json` prints the whole look as one done event (`url`, `screenshot`, `controls`, `exposures`, `wall`).
192
192
 
193
- **`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
193
+ **`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`, and the guard `if`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
194
194
 
195
195
  ```json
196
196
  "setup": [
@@ -203,9 +203,12 @@ It writes `<out>/look.png` (the viewport; `--out look` by default) and lists eve
203
203
 
204
204
  **`teardown`: the steps that run after it.** A flow that CREATES something in the account (an API key, a project, an invite) creates it on every rehearsal and every take, since `--dry-run` clicks for real. `teardown: [...]` beside `setup` removes it again: the same verbs and the same rules (a `{ "env": "NAME" }` text, no literal in a password field, http and https only), run off camera once the screencast has stopped, on the take's own page and session, so nothing of it is captured or lands in `meta.steps`. It runs after a rehearsal too, and when the take stops early (a failed `await`, `--strict` on a page that stopped answering, the cap) or throws once its first step ran; not when the take never began (a setup that failed, a wall). The steps together get 60 s. A teardown never fails the take or changes the exit code: it stops at the first step that fails, the done event carries `teardown: { ran, of, failed?: { step, do, selector?, error } }`, and the summary says `teardown: 3 step(s) ran` or `TEARDOWN FAILED at teardown[1] (click #confirm): <reason>. What the take created may still be in the product; remove it by hand.` A `{ env }` the shell does not hold refuses the take before it begins, since the take would create what its teardown cannot remove.
205
205
 
206
+ **`if`: a guard for either list.** `{ "do": "if", "selector": "…" }` looks for its selector (3 s, or its `ms`): on the page, the steps after it run; not on the page, they are skipped and the list ends there, which is never a failure. A take that deletes on camera what it created, and keeps a teardown as the net for a take that stops early, starts the undoing with one, so a good take says `teardown: nothing to undo, <selector> is not on the page` instead of a failure; the done event carries `skipped: { step, selector, rest }`. A setup that signs in guards its sign-in the same way (`if` the email field), so a session already signed in passes. A step that fails after a guard still fails.
207
+
206
208
  ```json
207
209
  "teardown": [
208
210
  { "do": "goto", "url": "http://localhost:3000/settings/keys" },
211
+ { "do": "if", "selector": "[data-testid=key-row]:first-child" },
209
212
  { "do": "click", "selector": "[data-testid=key-row]:first-child button[aria-label=Revoke]" },
210
213
  { "do": "click", "selector": "role=button[name=\"Revoke key\"]" }
211
214
  ]
@@ -452,6 +455,8 @@ Not sure of a selector? `vos look <url>` (with the take's `--session` or `--stor
452
455
 
453
456
  **Pinned layers.** A layer ABOUT something on the page names it, and the lowering keeps the two together: `"pin": { "step": "copy", "side": "auto", "mark": "ring", "leader": true }` on any overlay kind places the layer a gap off one side of that step's element and carries it with the element as the camera moves (the layer keeps its screen size; only its place follows). One of `step` (a recorder step's `id`, else its index), `press` (SOURCE seconds; the nearest press names the element, for a human recording) or `rect` (video fractions) names the referent. `side` is `auto` (the first of right, left, below, above whose box fits inside the frame through the layer's life) or a side by name; `gap` is design px (24); `mark` is `ring` or `underline`, a standing highlight on the referent for the layer's life; `leader` draws a hairline from the layer to the referent; `color` is their ink. `transform.x/y` stay the fallback for a pin that cannot resolve, and `vos validate` says why (an unknown step, a step that touched nothing, a scroll inside the layer's window). A layer that cannot name its referent is a caption: place it in the margin, never over the app.
454
457
 
458
+ **How long a caption stays.** The captions `vos plan --motion` and `--captions` land are about their steps: each lands as its step begins and leaves as the step ends, or once its words are read when that is later (17 characters a second, the subtitle standard, 1.2 to 3 s), so it never lingers over the steps after it. A caption whose words outlast its step is named in the done line with how long it stays past the step; shorter words, or more time on the step (its `ms`, then a re-record), let it leave with it. Cutting its duration instead only takes the time its words need. It gives way to the next caption 0.1 s before it and never outlives the clip it starts in; one with too little room to be read is dropped and named in the done line, with the room it needs.
459
+
455
460
  **Captions anchored to a step.** A layer's TIME can name a step the way a pin names its place: `"anchor": { "step": "share", "media": "m1", "at": "end", "offset": 0.2 }` starts the layer where that step plays in the cut. The lowering maps the step's moment (its `tStart`, or `tEnd` with `at: "end"`, in `meta.steps` of the media `media` names; absent = the primary) through the segments, speed spans and freezes exactly as a source-anchored span is mapped, then adds `offset` in OUTPUT seconds, so a trim, a speed change or a re-record moves the caption with its step and nobody recomputes a `start`. `start` is the fallback when the step is missing, skipped or cut out, and an anchored layer may leave it out: one whose step then resolves nowhere plays nowhere, which `vos validate` refuses, naming the step. `duration` is the layer's own; on a cut that replays the step's seconds the layer starts in the pass where the step plays the longest (the first on a tie), so a caption about a step lands in the main play rather than a short cold open. A text layer's `{elapsed}` fills with the anchored step's off-camera wait when the recorder kept one (the entry of `meta.skips` that names it, else `offCameraMs` on the step; never a step's own `ms`, which is its reading time), and a step that waited for nothing itself (the beat that lands after an await) reads the nearest earlier wait on its media, which validate notes, as a rounded duration: `"{elapsed} later"` renders `12 minutes later`. `vos validate` says when an anchor cannot resolve (and which fallback plays), when `{elapsed}` has no wait at or before its step to fill from, when two captions are on screen at the same time, naming both, and when a layer anchored to one media's step starts over another media's footage (a caption landed after its gesture, then the footage after that step trimmed away). A document without an anchor lowers exactly as before. Not the re-record `anchor` on spans, which only `vos plan --reuse` reads.
456
461
 
457
462
  **Image and video overlays.** Media kinds on the same lane: `{ "id": "m0", "kind": "image" | "video", "start": 2, "duration": 4, "key": "/logo.png", "width": 0.35, "radius": 12, "opacity": 1, "loop": false, "transform": { … } }`. `key` is a file inside the take directory (`"/logo.png"`) or a URL; `width` is a fraction of the frame width, height follows the media's aspect; corners in design px. Video time is clip-local and muted by design; soundtracks belong to `doc.audio`.
@@ -4150,6 +4150,7 @@ import {
4150
4150
  migrateMotion,
4151
4151
  momentOutputs as momentOutputs2,
4152
4152
  ratedSegments as ratedSegments4,
4153
+ resolveAnchor as resolveLayerAnchor,
4153
4154
  sameMedia,
4154
4155
  spanOutputExtent as spanOutputExtent3
4155
4156
  } from "@vosjs/studio-core";
@@ -4158,6 +4159,14 @@ import {
4158
4159
  var CAPTION_MAX = 3;
4159
4160
  var CAPTION_MIN = 1.2;
4160
4161
  var CAPTION_GAP = 0.1;
4162
+ var READ_CHARS_PER_SECOND = 17;
4163
+ function readingSeconds(text) {
4164
+ const chars = text.trim().replace(/\s+/g, " ").length;
4165
+ return Math.min(
4166
+ CAPTION_MAX,
4167
+ Math.max(CAPTION_MIN, chars / READ_CHARS_PER_SECOND)
4168
+ );
4169
+ }
4161
4170
  function fitCaptions(items, slotOf, opts = {}) {
4162
4171
  const max = opts.max ?? CAPTION_MAX;
4163
4172
  const min = opts.min ?? CAPTION_MIN;
@@ -4170,7 +4179,11 @@ function fitCaptions(items, slotOf, opts = {}) {
4170
4179
  const { item, slot } = ordered[i];
4171
4180
  const clipRoom = slot.end - slot.start;
4172
4181
  const nextRoom = nextStart - gap - slot.start;
4173
- const duration = Math.min(max, clipRoom, nextRoom);
4182
+ const ownRoom = slot.until === void 0 ? Infinity : Math.max(
4183
+ slot.until - slot.start,
4184
+ slot.text === void 0 ? min : readingSeconds(slot.text)
4185
+ );
4186
+ const duration = Math.min(max, clipRoom, nextRoom, ownRoom);
4174
4187
  if (duration < min - 1e-9) {
4175
4188
  dropped.unshift({
4176
4189
  item,
@@ -4178,11 +4191,19 @@ function fitCaptions(items, slotOf, opts = {}) {
4178
4191
  });
4179
4192
  continue;
4180
4193
  }
4181
- kept.unshift({ item, duration: +duration.toFixed(3) });
4194
+ const held = slot.until === void 0 ? 0 : duration - (slot.until - slot.start);
4195
+ kept.unshift({
4196
+ item,
4197
+ duration: +duration.toFixed(3),
4198
+ ...held > 0.05 ? { held: +held.toFixed(2) } : {}
4199
+ });
4182
4200
  nextStart = slot.start;
4183
4201
  }
4184
4202
  return { kept, dropped };
4185
4203
  }
4204
+ function heldLine(label, held) {
4205
+ return `${label} stays ${held.toFixed(2)}s past its step to be read; to let it leave with the step, shorten its words (17 characters read in a second) or give its step more time (its ms, then record again). Do not cut its duration: the words need that time`;
4206
+ }
4186
4207
 
4187
4208
  // src/plugin/moments.ts
4188
4209
  import {
@@ -4343,6 +4364,7 @@ function proposeMotion(input, opts) {
4343
4364
  const { words: words2, launch, catalog } = opts;
4344
4365
  const notes = [];
4345
4366
  const skipped = [];
4367
+ const held = [];
4346
4368
  const footage = outputLength(doc);
4347
4369
  const range2 = [0, footage];
4348
4370
  const entrance = launch.entrance;
@@ -4459,15 +4481,37 @@ function proposeMotion(input, opts) {
4459
4481
  (s) => c.id !== void 0 && s.id === c.id || s.step === c.step
4460
4482
  );
4461
4483
  if (!step || step.skipped) continue;
4462
- const t = stepOutputTime(rated, step, 0.2);
4484
+ const ref = step.id ?? step.step;
4485
+ const t = resolveLayerAnchor(doc, { step: ref, at: "start" }, rated).start;
4463
4486
  if (t === null || t < 0 || t >= footage) continue;
4464
- beats.push({ caption: c, t: +t.toFixed(3) });
4487
+ const until = resolveLayerAnchor(
4488
+ doc,
4489
+ { step: ref, at: "end" },
4490
+ rated
4491
+ ).start;
4492
+ beats.push({
4493
+ caption: c,
4494
+ t: +t.toFixed(3),
4495
+ ...until !== null ? { until } : {}
4496
+ });
4465
4497
  }
4466
- const fit = fitCaptions(beats, (b) => ({ start: b.t, end: footage }), {
4467
- max: CAPTION_BEAT_MAX
4468
- });
4498
+ const fit = fitCaptions(
4499
+ beats,
4500
+ (b) => ({
4501
+ start: b.t,
4502
+ end: footage,
4503
+ ...b.until !== void 0 ? { until: b.until } : {},
4504
+ text: b.caption.caption
4505
+ }),
4506
+ { max: CAPTION_BEAT_MAX }
4507
+ );
4469
4508
  for (const { item, why } of fit.dropped)
4470
4509
  skipped.push(`caption for step ${item.caption.step}: ${why}`);
4510
+ for (const k of fit.kept)
4511
+ if (k.held)
4512
+ held.push(
4513
+ heldLine(`the caption for step ${k.item.caption.step}`, k.held)
4514
+ );
4471
4515
  for (const { item, duration } of fit.kept)
4472
4516
  captionClips.push(
4473
4517
  houseCaption(
@@ -4531,7 +4575,7 @@ function proposeMotion(input, opts) {
4531
4575
  if (times.length) notes.push(`${times.length} click sound(s)`);
4532
4576
  }
4533
4577
  doc.audio = clips;
4534
- return { doc, notes, skipped };
4578
+ return { doc, notes, skipped, held };
4535
4579
  }
4536
4580
  function destinationMechanics(d, doc) {
4537
4581
  const set = [];
@@ -6754,7 +6798,7 @@ import {
6754
6798
  import {
6755
6799
  docFreezes,
6756
6800
  ratedSegments as ratedSegments6,
6757
- resolveAnchor as resolveLayerAnchor
6801
+ resolveAnchor as resolveLayerAnchor2
6758
6802
  } from "@vosjs/studio-core";
6759
6803
  var MIN_SPAN = 0.15;
6760
6804
  var AFTER_STEP = 1;
@@ -7050,7 +7094,7 @@ function retimeOverlays(prev, next, report) {
7050
7094
  };
7051
7095
  return overlays.map((o) => {
7052
7096
  if (o.anchor && typeof o.anchor === "object") {
7053
- const r = resolveLayerAnchor(next, o.anchor, newPieces);
7097
+ const r = resolveLayerAnchor2(next, o.anchor, newPieces);
7054
7098
  if (r.start === null) {
7055
7099
  report.flagged.push(
7056
7100
  o.start === void 0 ? `overlay ${o.id}: anchored to step ${String(o.anchor.step)}, which is missing, skipped or cut in the new recording \u2014 it has no start to fall back to, so it plays nowhere; re-anchor it or give it a start` : `overlay ${o.id}: anchored to step ${String(o.anchor.step)}, which is missing, skipped or cut in the new recording \u2014 it plays at its fallback ${o.start.toFixed(2)}s; re-anchor or re-place it`
@@ -7337,7 +7381,11 @@ async function planTake(dir, opts = {}) {
7337
7381
  if (opts.motion && (fresh || opts.motion.again)) {
7338
7382
  const proposed = proposeMotion(doc, opts.motion);
7339
7383
  doc = proposed.doc;
7340
- motion = { notes: proposed.notes, skipped: proposed.skipped };
7384
+ motion = {
7385
+ notes: proposed.notes,
7386
+ skipped: proposed.skipped,
7387
+ held: proposed.held
7388
+ };
7341
7389
  }
7342
7390
  if (opts.still != null) doc.still = opts.still;
7343
7391
  await writeJson(take.paths.doc, doc, true);
@@ -14659,6 +14707,7 @@ import {
14659
14707
  docMediaList as docMediaList2,
14660
14708
  findStep as findStep3,
14661
14709
  mediaKey,
14710
+ overlayPlainText,
14662
14711
  ratedSegments as ratedSegments8,
14663
14712
  resolveAnchor as resolveAnchor5,
14664
14713
  segmentOutputExtents as segmentOutputExtents2
@@ -14744,7 +14793,12 @@ function captionOf(step, actions, script, recorded = []) {
14744
14793
  }
14745
14794
  var safeId = (v) => String(v).replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "step";
14746
14795
  function landStepCaptions(doc, actions, opts = {}) {
14747
- const report = { landed: [], skipped: [], uncaptioned: [] };
14796
+ const report = {
14797
+ landed: [],
14798
+ skipped: [],
14799
+ held: [],
14800
+ uncaptioned: []
14801
+ };
14748
14802
  const mine = (o) => !opts.only || opts.only.has(mediaKey(o.anchor?.media));
14749
14803
  const kept = (doc.overlays ?? []).filter(
14750
14804
  (o) => o.from !== STEP_CAPTIONS_FROM || !mine(o)
@@ -14763,6 +14817,7 @@ function landStepCaptions(doc, actions, opts = {}) {
14763
14817
  };
14764
14818
  const ends = /* @__PURE__ */ new Map();
14765
14819
  const labels = /* @__PURE__ */ new Map();
14820
+ const untils = /* @__PURE__ */ new Map();
14766
14821
  const spoken = (media, step, steps) => kept.some((o) => {
14767
14822
  if (o.kind !== "text" || o.preset !== "caption") return false;
14768
14823
  if (o.anchor && mediaKey(o.anchor.media) === media)
@@ -14815,6 +14870,8 @@ function landStepCaptions(doc, actions, opts = {}) {
14815
14870
  from: STEP_CAPTIONS_FROM
14816
14871
  };
14817
14872
  ends.set(clip, end);
14873
+ const stepEnd = resolveAnchor5(doc, { ...anchor, at: "end" }, rated);
14874
+ if (stepEnd.start !== null) untils.set(clip, stepEnd.start);
14818
14875
  labels.set(clip, label);
14819
14876
  fresh.push(clip);
14820
14877
  report.landed.push({
@@ -14828,12 +14885,17 @@ function landStepCaptions(doc, actions, opts = {}) {
14828
14885
  }
14829
14886
  const fit = fitCaptions(fresh, (c) => ({
14830
14887
  start: c.start,
14831
- end: ends.get(c) ?? footageEnd
14888
+ end: ends.get(c) ?? footageEnd,
14889
+ ...untils.has(c) ? { until: untils.get(c) } : {},
14890
+ ...c.kind === "text" ? { text: overlayPlainText(c) } : {}
14832
14891
  }));
14833
14892
  for (const { item, why } of fit.dropped) {
14834
14893
  report.skipped.push(`${labels.get(item) ?? item.id}: ${why}`);
14835
14894
  report.landed = report.landed.filter((l) => l.id !== item.id);
14836
14895
  }
14896
+ for (const k of fit.kept)
14897
+ if (k.held)
14898
+ report.held.push(heldLine(labels.get(k.item) ?? k.item.id, k.held));
14837
14899
  const landedClips = fit.kept.map(({ item, duration }) => {
14838
14900
  item.duration = duration;
14839
14901
  return item;
@@ -16335,6 +16397,7 @@ async function cmdJoin(argv) {
16335
16397
  let captions = null;
16336
16398
  if (withCaptions) {
16337
16399
  captions = landStepCaptions(plan.doc, plan.actions, { only });
16400
+ for (const line of captions.held) plan.notes.push(line);
16338
16401
  for (const k of captions.uncaptioned)
16339
16402
  plan.notes.push(
16340
16403
  `no step in ${labelOf.get(k) ?? `media ${k}`} carries a caption`
@@ -16462,7 +16525,7 @@ import {
16462
16525
  freezeOutputExtents,
16463
16526
  mediaKey as mediaKey5,
16464
16527
  migrateMotion as migrateMotion4,
16465
- overlayPlainText,
16528
+ overlayPlainText as overlayPlainText2,
16466
16529
  longestOutputExtent as longestOutputExtent3,
16467
16530
  ratedSegments as ratedSegments11,
16468
16531
  resolveOverlayAnchors as resolveOverlayAnchors2,
@@ -16576,7 +16639,7 @@ function takeTimeline(rawDoc, actions = /* @__PURE__ */ new Map()) {
16576
16639
  const start = o.start;
16577
16640
  if (start === void 0) return [];
16578
16641
  const raw = rawById.get(o.id);
16579
- const text = o.kind === "text" ? overlayPlainText(o) : o.kind === "html" ? void 0 : o.key;
16642
+ const text = o.kind === "text" ? overlayPlainText2(o) : o.kind === "html" ? void 0 : o.key;
16580
16643
  return [
16581
16644
  {
16582
16645
  id: o.id,
@@ -19328,7 +19391,8 @@ async function cmdPlan(argvIn) {
19328
19391
  const zoomManual = s.doc.zoom.length - zoomAuto;
19329
19392
  const captionLines = captions ? `
19330
19393
  ${captions.landed.length} step caption${captions.landed.length === 1 ? "" : "s"} anchored to their steps` + (captions.skipped.length ? `
19331
- not landed: ${captions.skipped.join("; ")}` : "") + (captions.uncaptioned.length ? `
19394
+ not landed: ${captions.skipped.join("; ")}` : "") + (captions.held.length ? `
19395
+ ${captions.held.join("\n ")}` : "") + (captions.uncaptioned.length ? `
19332
19396
  no step in ${captions.uncaptioned.length === 1 && captions.uncaptioned[0] === "" ? "this take" : captions.uncaptioned.map((m) => m ? `media ${m}` : "the primary").join(", ")} carries a caption` : "") : "";
19333
19397
  const layoutLines = s.layout ? `
19334
19398
  layout from ${s.styleFrom}: ${[
@@ -19339,7 +19403,8 @@ async function cmdPlan(argvIn) {
19339
19403
  ${s.layout.notes.join("\n ")}` : "") : "";
19340
19404
  const motionLines = s.motion ? (s.motion.notes.length ? `
19341
19405
  proposed: ${s.motion.notes.join(", ")}` : "") + (s.motion.skipped.length ? `
19342
- not proposed: ${s.motion.skipped.join("; ")}` : "") : "";
19406
+ not proposed: ${s.motion.skipped.join("; ")}` : "") + (s.motion.held.length ? `
19407
+ ${s.motion.held.join("\n ")}` : "") : "";
19343
19408
  const reuseLines = s.reuse ? `
19344
19409
  reused ${s.reuse.from}: ${s.reuse.anchored} anchored + ${s.reuse.mapped} mapped span(s)` + (s.reuse.flagged.length ? `
19345
19410
  flagged:
@@ -20617,4 +20682,4 @@ export {
20617
20682
  verbHelp,
20618
20683
  run
20619
20684
  };
20620
- //# sourceMappingURL=chunk-S3RI2SHH.js.map
20685
+ //# sourceMappingURL=chunk-5VGIQKVT.js.map