@crouton-kit/capture 1.3.13 → 1.3.15

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/bin/capture CHANGED
@@ -7797,6 +7797,7 @@ var init_render = __esm({
7797
7797
  "screenshot",
7798
7798
  "tabs",
7799
7799
  "tab-opened",
7800
+ "tab-closed",
7800
7801
  "tab-reset",
7801
7802
  "network",
7802
7803
  "cdp-result",
@@ -8715,7 +8716,7 @@ function validateKnownLeaf(command, positional) {
8715
8716
  const branches = {
8716
8717
  session: ["start", "stop", "list", "view", "har", "log"],
8717
8718
  page: ["click", "type", "scroll", "navigate", "exec", "shot", "elements"],
8718
- tab: ["list", "open", "reset", "network"],
8719
+ tab: ["list", "open", "close", "reset", "network"],
8719
8720
  measure: ["snap", "check", "diff", "census", "explain", "sweep", "map"],
8720
8721
  motion: ["rec", "mask", "timeline", "jank", "response"],
8721
8722
  lib: ["list", "search", "show", "read"]
@@ -8862,6 +8863,7 @@ function parseCliSyntax(argv) {
8862
8863
  if (VALUE_FLAGS.has(arg)) valueFor(arg, next);
8863
8864
  if (arg === "--port") {
8864
8865
  parsed.port = integer(valueFor(arg, next), "--port", 1, 65535);
8866
+ parsed.portSource = "flag";
8865
8867
  i++;
8866
8868
  } else if (arg === "--out") {
8867
8869
  parsed.out = valueFor(arg, next);
@@ -9018,10 +9020,14 @@ function resolveCliContext(parsed) {
9018
9020
  parsed.target = session.targetId;
9019
9021
  parsed.targetSource = "session";
9020
9022
  }
9021
- if (parsed.port === void 0 && session.port !== void 0 && session.port !== null) parsed.port = session.port;
9023
+ if (parsed.port === void 0 && session.port !== void 0 && session.port !== null) {
9024
+ parsed.port = session.port;
9025
+ parsed.portSource = "session";
9026
+ }
9022
9027
  }
9023
9028
  if (parsed.port === void 0 && process.env.CDP_PORT !== void 0) {
9024
9029
  parsed.port = integer(process.env.CDP_PORT, "CDP_PORT", 1, 65535);
9030
+ parsed.portSource = "env";
9025
9031
  }
9026
9032
  if (!session) getActiveSession();
9027
9033
  if (!parsed.target && !parsed.url && process.env.CDP_TARGET) {
@@ -9099,6 +9105,20 @@ var init_gate_guard = __esm({
9099
9105
  });
9100
9106
 
9101
9107
  // src/session/coordinator.ts
9108
+ function scopeLifecycleLockPath() {
9109
+ return path5.join(CAPTURE_ROOT, `.session-lifecycle-${process.env.CRTR_NODE_ID ?? "default"}`);
9110
+ }
9111
+ async function withSessionScopeLifecycle(action) {
9112
+ const lock = await acquirePrivateLock(scopeLifecycleLockPath(), {
9113
+ acquireTimeoutMs: 12e4,
9114
+ leaseMs: 1e3
9115
+ });
9116
+ try {
9117
+ return await action();
9118
+ } finally {
9119
+ lock.release();
9120
+ }
9121
+ }
9102
9122
  function statePath(sessionDir2) {
9103
9123
  return path5.join(assertUnderCaptureRoot(sessionDir2), ".operations.json");
9104
9124
  }
@@ -9822,7 +9842,7 @@ function validateHarAppendBatch(value, source) {
9822
9842
  return { entries, incompleteLifecycles };
9823
9843
  }
9824
9844
  function captureVersion() {
9825
- const version = "1.3.13";
9845
+ const version = "1.3.15";
9826
9846
  return typeof version === "string" && version.length > 0 ? version : "development";
9827
9847
  }
9828
9848
  function token() {
@@ -11467,8 +11487,8 @@ function __domPathOf(el) {
11467
11487
  const elements = [];
11468
11488
  if (raw.length > 0) {
11469
11489
  const resolved = await Promise.all(
11470
- raw.map(async (fact3) => {
11471
- const objectId = objectIds[fact3.idx];
11490
+ raw.map(async (fact4) => {
11491
+ const objectId = objectIds[fact4.idx];
11472
11492
  let backendNodeId2;
11473
11493
  let quads = [];
11474
11494
  let boxModel = null;
@@ -11495,33 +11515,33 @@ function __domPathOf(el) {
11495
11515
  }
11496
11516
  const geometryUnavailable = geometryUnavailableReason !== void 0;
11497
11517
  const rect3 = quads.length > 0 ? unionRect(quads) : { x: 0, y: 0, width: 0, height: 0 };
11498
- const zeroSize = quadsUnavailable ? fact3.visibility.zeroSize : fact3.visibility.zeroSize || quads.length === 0;
11499
- const visible = quadsUnavailable ? fact3.visibility.visible : fact3.visibility.visible && quads.length > 0;
11518
+ const zeroSize = quadsUnavailable ? fact4.visibility.zeroSize : fact4.visibility.zeroSize || quads.length === 0;
11519
+ const visible = quadsUnavailable ? fact4.visibility.visible : fact4.visibility.visible && quads.length > 0;
11500
11520
  return {
11501
- id: `el-${fact3.idx}`,
11502
- tag: sanitizeString(fact3.tag, { max: 64 }),
11503
- selector: fact3.selector !== void 0 ? sanitizeString(fact3.selector, { max: 300 }) : void 0,
11504
- domPath: sanitizeString(fact3.domPath, { max: 500 }),
11521
+ id: `el-${fact4.idx}`,
11522
+ tag: sanitizeString(fact4.tag, { max: 64 }),
11523
+ selector: fact4.selector !== void 0 ? sanitizeString(fact4.selector, { max: 300 }) : void 0,
11524
+ domPath: sanitizeString(fact4.domPath, { max: 500 }),
11505
11525
  ...resolvedIdentity(backendNodeId2),
11506
- text: fact3.text !== void 0 ? sanitizeString(fact3.text, { max: 200 }) : void 0,
11507
- frame: fact3.frame,
11508
- shadow: fact3.shadow ? {
11509
- ...fact3.shadow,
11510
- hostSelector: fact3.shadow.hostSelector !== void 0 ? sanitizeString(fact3.shadow.hostSelector, { max: 300 }) : void 0
11526
+ text: fact4.text !== void 0 ? sanitizeString(fact4.text, { max: 200 }) : void 0,
11527
+ frame: fact4.frame,
11528
+ shadow: fact4.shadow ? {
11529
+ ...fact4.shadow,
11530
+ hostSelector: fact4.shadow.hostSelector !== void 0 ? sanitizeString(fact4.shadow.hostSelector, { max: 300 }) : void 0
11511
11531
  } : null,
11512
11532
  rect: rect3,
11513
11533
  quads,
11514
11534
  boxModel,
11515
- zIndex: sanitizeString(fact3.zIndex, { max: 20 }),
11516
- stackingContext: fact3.stackingContext,
11517
- visibility: { ...fact3.visibility, visible, zeroSize },
11518
- clipping: fact3.clipping ? {
11519
- clippedBy: fact3.clipping.clippedBy !== void 0 ? sanitizeString(fact3.clipping.clippedBy, { max: 300 }) : void 0,
11520
- clippedFraction: fact3.clipping.clippedFraction
11535
+ zIndex: sanitizeString(fact4.zIndex, { max: 20 }),
11536
+ stackingContext: fact4.stackingContext,
11537
+ visibility: { ...fact4.visibility, visible, zeroSize },
11538
+ clipping: fact4.clipping ? {
11539
+ clippedBy: fact4.clipping.clippedBy !== void 0 ? sanitizeString(fact4.clipping.clippedBy, { max: 300 }) : void 0,
11540
+ clippedFraction: fact4.clipping.clippedFraction
11521
11541
  } : null,
11522
- layout: capLayoutFact(fact3.layout),
11542
+ layout: capLayoutFact(fact4.layout),
11523
11543
  ...geometryUnavailable ? { geometryUnavailable: true, geometryUnavailableReason } : {},
11524
- ...fact3.iframeContentUnavailable ? { iframeContentUnavailable: true, iframeContentUnavailableReason: fact3.iframeContentUnavailableReason } : {}
11544
+ ...fact4.iframeContentUnavailable ? { iframeContentUnavailable: true, iframeContentUnavailableReason: fact4.iframeContentUnavailableReason } : {}
11525
11545
  };
11526
11546
  })
11527
11547
  );
@@ -11581,7 +11601,7 @@ function wallTime(value) {
11581
11601
  }
11582
11602
  }
11583
11603
  function captureVersion2() {
11584
- const version = "1.3.13";
11604
+ const version = "1.3.15";
11585
11605
  return typeof version === "string" && version.length > 0 ? version : "development";
11586
11606
  }
11587
11607
  function deepFreeze(value) {
@@ -13759,20 +13779,6 @@ function readBundleManifest(bundlePath) {
13759
13779
  function sessionDir(id) {
13760
13780
  return path12.join(CAPTURE_ROOT, id);
13761
13781
  }
13762
- function lifecycleScopeLockPath() {
13763
- return path12.join(CAPTURE_ROOT, `.session-lifecycle-${process.env.CRTR_NODE_ID ?? "default"}`);
13764
- }
13765
- async function withLifecycleCoordinator(action) {
13766
- const handle = await acquirePrivateLock(lifecycleScopeLockPath(), {
13767
- acquireTimeoutMs: 12e4,
13768
- leaseMs: 1e3
13769
- });
13770
- try {
13771
- return await action();
13772
- } finally {
13773
- handle.release();
13774
- }
13775
- }
13776
13782
  function isSessionStopped(session) {
13777
13783
  return Boolean(session.stoppedAt) || fs9.existsSync(path12.join(session.dir, "bundle.json"));
13778
13784
  }
@@ -13896,7 +13902,7 @@ async function start(parsed) {
13896
13902
  return;
13897
13903
  }
13898
13904
  if (rejectSurplusPositionals(parsed, "start", 0, "capture session start [--url <url>] [--hold]")) return;
13899
- return withLifecycleCoordinator(async () => {
13905
+ return withSessionScopeLifecycle(async () => {
13900
13906
  const active = getActiveSession();
13901
13907
  if (active && !isSessionStopped(active)) {
13902
13908
  emitResult({
@@ -14306,7 +14312,7 @@ async function stop(parsed) {
14306
14312
  process.exitCode = 1;
14307
14313
  return;
14308
14314
  }
14309
- return withLifecycleCoordinator(async () => {
14315
+ return withSessionScopeLifecycle(async () => {
14310
14316
  let session;
14311
14317
  try {
14312
14318
  session = readSession(id);
@@ -14626,6 +14632,8 @@ input:
14626
14632
  --hold Hold one CDP browser connection open for the session's
14627
14633
  lifetime, so browser-level state (permission grants,
14628
14634
  ServiceWorker enablement) survives across separate commands.
14635
+ With no --url, later --target <existing-id> commands adopt
14636
+ an already-open tab without creating a new one.
14629
14637
  --port <n> CDP port to attach to; default is auto-detected across localhost.
14630
14638
 
14631
14639
  Output:
@@ -14870,7 +14878,12 @@ async function connectForCommand(parsed) {
14870
14878
  }
14871
14879
  const port = resolved?.port ?? getPortFromWebSocketDebuggerUrl(tab.webSocketDebuggerUrl);
14872
14880
  if (activeSession && !activeSession.targetId) {
14873
- await seams.updateActiveSession({ targetId: tab.id, port: port ?? null });
14881
+ await seams.withSessionScopeLifecycle(async () => {
14882
+ const current = seams.getActiveSession();
14883
+ if (current?.sessionId === activeSession.sessionId && !current.targetId) {
14884
+ await seams.updateActiveSession({ targetId: tab.id, port: port ?? null });
14885
+ }
14886
+ });
14874
14887
  }
14875
14888
  console.error(
14876
14889
  `Using target ${tab.id.slice(0, 8)}${port ? ` on port ${port}` : ""}${tab.url ? ` (${tab.url})` : ""}`
@@ -14988,12 +15001,14 @@ var init_connection = __esm({
14988
15001
  init_har_recorder();
14989
15002
  init_har_manager();
14990
15003
  init_session_context();
15004
+ init_coordinator();
14991
15005
  init_recorder_client();
14992
15006
  init_recorder();
14993
15007
  init_errors();
14994
15008
  seams = {
14995
15009
  getActiveSession: () => getActiveSession(),
14996
15010
  updateActiveSession,
15011
+ withSessionScopeLifecycle,
14997
15012
  resolveTab: defaultResolveTab,
14998
15013
  createClient: (wsUrl) => new CDPClient(wsUrl),
14999
15014
  appendHar: appendToHarRecording,
@@ -227405,6 +227420,94 @@ effects: opens (or reuses) a background browser tab and waits up to 10s for its
227405
227420
  }
227406
227421
  });
227407
227422
 
227423
+ // src/cdp/commands/tab/close.ts
227424
+ function buildTabClosedResult(tab, port) {
227425
+ return {
227426
+ tag: "tab-closed",
227427
+ attrs: { port, target: tab.id },
227428
+ summary: line(text`tab `, data(tab.id.slice(0, 8)), text` closed: `, data(tab.url, 300))
227429
+ };
227430
+ }
227431
+ async function cmdTabClose(parsed, _args) {
227432
+ if (parsed.help) {
227433
+ console.log(USAGE10);
227434
+ return;
227435
+ }
227436
+ const target = parsed.positional[0];
227437
+ if (!target) {
227438
+ throw invalidInput("received: no target; expected: capture tab close <target> [--port <port>].", "missing_argument");
227439
+ }
227440
+ if (target.length < 8) {
227441
+ throw invalidInput(`received: target ${target}; expected: a full target id or an unambiguous prefix of at least 8 characters.`, "target_prefix_too_short");
227442
+ }
227443
+ let port;
227444
+ try {
227445
+ port = parsed.port ?? await detectCdpPort();
227446
+ } catch {
227447
+ throw captureError(
227448
+ "world",
227449
+ "no_cdp_endpoint",
227450
+ "received: no --port, and no CDP endpoint was discovered on localhost; expected: a running CDP-enabled browser (or an explicit --port <port>). capture tab list probes every localhost CDP endpoint."
227451
+ );
227452
+ }
227453
+ const tab = await withSessionScopeLifecycle(async () => {
227454
+ let resolved;
227455
+ try {
227456
+ resolved = await findTabById(port, target);
227457
+ } catch (error) {
227458
+ throw captureError("precondition", "target_unavailable", `received: target ${target} on port ${port}; expected: one unambiguous page-tab target id. ${error instanceof Error ? error.message : String(error)}`, error);
227459
+ }
227460
+ if (!resolved || resolved.type !== "page") {
227461
+ throw captureError(
227462
+ "precondition",
227463
+ "target_unavailable",
227464
+ `received: target ${target} on port ${port}; expected: one existing page-tab target id (8-character prefix accepted). capture tab list shows available tabs.`
227465
+ );
227466
+ }
227467
+ const active = getActiveSession();
227468
+ if (active && !active.targetId) {
227469
+ throw captureError(
227470
+ "precondition",
227471
+ "active_session_target_unknown",
227472
+ `session ${active.sessionId} has not established its target yet, so capture tab close cannot prove ${resolved.id} is a background tab. Target the session with a page command first, or stop the session with capture session stop ${active.sessionId}.`
227473
+ );
227474
+ }
227475
+ if (active?.targetId === resolved.id) {
227476
+ throw captureError(
227477
+ "precondition",
227478
+ "active_session_target",
227479
+ `target ${resolved.id} is the active session ${active.sessionId}'s tab; capture tab close never leaves an active session pointing at a closed target. Use capture tab reset <url> to replace it or capture session stop ${active.sessionId} first.`
227480
+ );
227481
+ }
227482
+ try {
227483
+ await closeTarget(port, resolved.id);
227484
+ } catch (error) {
227485
+ throw captureError("world", "close_failed", `closing target ${resolved.id} on port ${port} failed: ${error instanceof Error ? error.message : String(error)}`, error);
227486
+ }
227487
+ return resolved;
227488
+ });
227489
+ emitResult(buildTabClosedResult(tab, port), { json: parsed.json });
227490
+ }
227491
+ var USAGE10;
227492
+ var init_close = __esm({
227493
+ "src/cdp/commands/tab/close.ts"() {
227494
+ init_errors();
227495
+ init_detect();
227496
+ init_targets();
227497
+ init_session_context();
227498
+ init_coordinator();
227499
+ init_render();
227500
+ USAGE10 = `capture tab close \u2014 close one explicitly identified background browser tab.
227501
+
227502
+ input:
227503
+ <target> required \u2014 full tab target id or an unambiguous 8-character prefix
227504
+ --port <port> CDP endpoint (default: the auto-discovered preferred endpoint)
227505
+
227506
+ output: <tab-closed port=\u2026 target=\u2026> \u2014 the closed tab's full target id and last observed URL.
227507
+ effects: closes exactly one page tab. Refuses unless any active session's background-tab ownership is known, and never closes that session's tab; reset or stop the session first.`;
227508
+ }
227509
+ });
227510
+
227408
227511
  // src/cdp/commands/tab/reset.ts
227409
227512
  function buildTabResetResult(tab, port, sessionUpdated) {
227410
227513
  return {
@@ -227445,46 +227548,48 @@ async function boundedLoadWait(tab) {
227445
227548
  }
227446
227549
  async function cmdTabReset(parsed, _args) {
227447
227550
  if (parsed.help) {
227448
- console.log(USAGE10);
227551
+ console.log(USAGE11);
227449
227552
  return;
227450
227553
  }
227451
227554
  const url = parsed.positional[0];
227452
227555
  if (!url) throw invalidInput("received: no URL; expected: capture tab reset <url> [--port <port>].", "missing_argument");
227453
- const session = getActiveSession();
227454
- if (!session) {
227455
- let port;
227456
- try {
227457
- port = parsed.port ?? await deps5.detectCdpPort();
227458
- } catch {
227459
- throw captureError("world", "no_cdp_endpoint", "received: no --port, and no CDP endpoint was discovered on localhost; expected: a running CDP-enabled browser (or an explicit --port <port>).");
227460
- }
227461
- const tab = await openTabOrThrow(port, url);
227462
- await boundedLoadWait(tab);
227463
- emitResult(buildTabResetResult(tab, port, false), { json: parsed.json });
227464
- return;
227465
- }
227466
- const op = await admitSessionOperation(session.dir);
227467
- try {
227468
- const result = await withSessionLifecycle(session.dir, async () => {
227469
- const fresh = getActiveSession();
227470
- if (!fresh || fresh.dir !== session.dir) throw captureError("precondition", "session_unavailable", "The active capture session is no longer available.");
227471
- const scan = scanRecorderHandles(session.dir, deps5.lifecycle);
227472
- if (scan.some((h) => h.classification === "unknown")) throw captureError("world", "recorder_liveness_unknown", "Cannot determine recorder liveness; refusing to reset.");
227473
- if (scan.some((h) => h.classification === "malformed")) throw captureError("precondition", "recorder_unavailable", "A malformed recorder handle exists on this session; resolve it before resetting.");
227474
- if (scan.some((h) => h.classification === "live")) throw captureError("precondition", "recorder_active", "A recording is active on this session; stop it first: `capture motion rec --stop`. Reset never rebinds a live recorder.");
227475
- await clearDanglingRecorderPointer(session.dir, scan);
227476
- const port = fresh.port ?? parsed.port ?? await deps5.detectCdpPort();
227556
+ return withSessionScopeLifecycle(async () => {
227557
+ const session = getActiveSession();
227558
+ if (!session) {
227559
+ let port;
227560
+ try {
227561
+ port = parsed.port ?? await deps5.detectCdpPort();
227562
+ } catch {
227563
+ throw captureError("world", "no_cdp_endpoint", "received: no --port, and no CDP endpoint was discovered on localhost; expected: a running CDP-enabled browser (or an explicit --port <port>).");
227564
+ }
227477
227565
  const tab = await openTabOrThrow(port, url);
227478
227566
  await boundedLoadWait(tab);
227479
- await updateSessionState(session.dir, { targetId: tab.id, port });
227480
- return buildTabResetResult(tab, port, true);
227481
- }, deps5.lifecycle);
227482
- emitResult(result, { json: parsed.json });
227483
- } finally {
227484
- await op.release();
227485
- }
227567
+ emitResult(buildTabResetResult(tab, port, false), { json: parsed.json });
227568
+ return;
227569
+ }
227570
+ const op = await admitSessionOperation(session.dir);
227571
+ try {
227572
+ const result = await withSessionLifecycle(session.dir, async () => {
227573
+ const fresh = getActiveSession();
227574
+ if (!fresh || fresh.dir !== session.dir) throw captureError("precondition", "session_unavailable", "The active capture session is no longer available.");
227575
+ const scan = scanRecorderHandles(session.dir, deps5.lifecycle);
227576
+ if (scan.some((h) => h.classification === "unknown")) throw captureError("world", "recorder_liveness_unknown", "Cannot determine recorder liveness; refusing to reset.");
227577
+ if (scan.some((h) => h.classification === "malformed")) throw captureError("precondition", "recorder_unavailable", "A malformed recorder handle exists on this session; resolve it before resetting.");
227578
+ if (scan.some((h) => h.classification === "live")) throw captureError("precondition", "recorder_active", "A recording is active on this session; stop it first: `capture motion rec --stop`. Reset never rebinds a live recorder.");
227579
+ await clearDanglingRecorderPointer(session.dir, scan);
227580
+ const port = fresh.port ?? parsed.port ?? await deps5.detectCdpPort();
227581
+ const tab = await openTabOrThrow(port, url);
227582
+ await boundedLoadWait(tab);
227583
+ await updateSessionState(session.dir, { targetId: tab.id, port });
227584
+ return buildTabResetResult(tab, port, true);
227585
+ }, deps5.lifecycle);
227586
+ emitResult(result, { json: parsed.json });
227587
+ } finally {
227588
+ await op.release();
227589
+ }
227590
+ });
227486
227591
  }
227487
- var USAGE10, deps5;
227592
+ var USAGE11, deps5;
227488
227593
  var init_reset = __esm({
227489
227594
  "src/cdp/commands/tab/reset.ts"() {
227490
227595
  init_detect();
@@ -227494,7 +227599,7 @@ var init_reset = __esm({
227494
227599
  init_coordinator();
227495
227600
  init_errors();
227496
227601
  init_render();
227497
- USAGE10 = `capture tab reset \u2014 abandon a stuck tab and open a fresh one at the URL.
227602
+ USAGE11 = `capture tab reset \u2014 abandon a stuck tab and open a fresh one at the URL.
227498
227603
 
227499
227604
  input:
227500
227605
  <url> required \u2014 the URL the fresh tab opens at
@@ -227566,7 +227671,7 @@ function buildNetworkResult(mode, target) {
227566
227671
  }
227567
227672
  async function cmdTabNetwork(parsed, _args) {
227568
227673
  if (parsed.help) {
227569
- console.log(USAGE11);
227674
+ console.log(USAGE12);
227570
227675
  return;
227571
227676
  }
227572
227677
  const mode = parsed.positional[0];
@@ -227656,7 +227761,7 @@ async function cmdTabNetwork(parsed, _args) {
227656
227761
  }
227657
227762
  emitResult(buildNetworkResult(mode, targetId), { json: parsed.json });
227658
227763
  }
227659
- var fs15, USAGE11;
227764
+ var fs15, USAGE12;
227660
227765
  var init_network = __esm({
227661
227766
  "src/cdp/commands/tab/network.ts"() {
227662
227767
  fs15 = __toESM(require("node:fs"));
@@ -227665,7 +227770,7 @@ var init_network = __esm({
227665
227770
  init_coordinator();
227666
227771
  init_client2();
227667
227772
  init_render();
227668
- USAGE11 = `capture tab network \u2014 toggle connection-level network emulation for the active session's held tab.
227773
+ USAGE12 = `capture tab network \u2014 toggle connection-level network emulation for the active session's held tab.
227669
227774
 
227670
227775
  input:
227671
227776
  <offline|online> required \u2014 offline blocks HTTP requests and drops WebSocket connections; online restores connectivity
@@ -227687,6 +227792,8 @@ async function tabMain(parsed, args) {
227687
227792
  return cmdTabList(rest, args);
227688
227793
  case "open":
227689
227794
  return cmdTabOpen(rest, args);
227795
+ case "close":
227796
+ return cmdTabClose(rest, args);
227690
227797
  case "reset":
227691
227798
  return cmdTabReset(rest, args);
227692
227799
  case "network":
@@ -227704,12 +227811,13 @@ var init_tab = __esm({
227704
227811
  init_errors();
227705
227812
  init_list();
227706
227813
  init_open();
227814
+ init_close();
227707
227815
  init_reset();
227708
227816
  init_network();
227709
227817
  COMMAND_BLOCK3 = `<command name="tab">
227710
- tab and connection plumbing \u2014 endpoint/tab discovery, open/reset tabs, connection-level network emulation
227711
- use when finding a CDP endpoint or tab, opening/replacing a tab, or toggling connectivity; \`tab list\` is the probe for a running browser
227712
- list \xB7 open \xB7 reset \xB7 network \u2014 \`capture tab -h\`
227818
+ tab and connection plumbing \u2014 endpoint/tab discovery, lifecycle, connection-level network emulation
227819
+ use when finding a CDP endpoint or tab, opening/closing/replacing a tab, or toggling connectivity; \`tab list\` is the probe for a running browser
227820
+ list \xB7 open \xB7 close \xB7 reset \xB7 network \u2014 \`capture tab -h\`
227713
227821
  </command>`;
227714
227822
  TAB_USAGE = `capture tab \u2014 tab and connection plumbing: discovery, lifecycle, network emulation.
227715
227823
 
@@ -227721,6 +227829,7 @@ the session's {target, port} pair together.
227721
227829
 
227722
227830
  <subcommand name="list" args="[--port <port>]" whenToUse="discover CDP endpoints and the tabs open on them"/>
227723
227831
  <subcommand name="open" args="<url> [--new] [--port <port>]" whenToUse="open a URL and get its tab id"/>
227832
+ <subcommand name="close" args="<target> [--port <port>]" whenToUse="close one explicitly identified background tab"/>
227724
227833
  <subcommand name="reset" args="<url> [--port <port>]" whenToUse="abandon a stuck tab and open a fresh one (refuses while a recording is active; reaps dead recorder handles; updates the active session's {target, port} together)"/>
227725
227834
  <subcommand name="network" args="<offline|online>" whenToUse="toggle connection-level network emulation for a tab"/>
227726
227835
 
@@ -228279,8 +228388,10 @@ function invocationLabel(method, waitEvent) {
228279
228388
  async function runBrowserScope(method, params, parsed, timeoutMs, deps7 = DEFAULT_BROWSER_DEPS) {
228280
228389
  const flagTarget = parsed.targetSource === "flag" ? parsed.target : void 0;
228281
228390
  const session = getActiveSession();
228282
- if (session?.bridgeSocket && fs17.existsSync(session.bridgeSocket)) {
228283
- const resp = await deps7.sendBridgeRequest(session.bridgeSocket, {
228391
+ const bridgeSocket = session?.bridgeSocket;
228392
+ const heldBridgeActive = Boolean(bridgeSocket && fs17.existsSync(bridgeSocket));
228393
+ if (parsed.portSource !== "flag" && bridgeSocket && heldBridgeActive) {
228394
+ const resp = await deps7.sendBridgeRequest(bridgeSocket, {
228284
228395
  method,
228285
228396
  params,
228286
228397
  targetId: flagTarget,
@@ -228304,8 +228415,9 @@ async function runBrowserScope(method, params, parsed, timeoutMs, deps7 = DEFAUL
228304
228415
  });
228305
228416
  return;
228306
228417
  }
228418
+ const connectionReason = parsed.portSource === "flag" ? heldBridgeActive ? `Using explicitly selected CDP port ${parsed.port} instead of the active session bridge` : `Using explicitly selected CDP port ${parsed.port}` : "No held session bridge active";
228307
228419
  console.error(
228308
- "No held session bridge active \u2014 this one-shot connection closes when the command exits, so any browser-level grant or target enablement made here reverts immediately after."
228420
+ `${connectionReason} \u2014 this one-shot connection closes when the command exits, so any browser-level grant or target enablement made here reverts immediately after.`
228309
228421
  );
228310
228422
  const port = parsed.port ?? await deps7.detectCdpPort();
228311
228423
  const { client } = await deps7.getBrowserClient(port);
@@ -228418,7 +228530,8 @@ Input:
228418
228530
  --params <json> JSON-encoded params object for the method. Spec deviation: params stay inline JSON (no file indirection) \u2014 raw CDP params are small protocol-defined objects.
228419
228531
  --wait-event <Domain.event> wait for (and return) the next occurrence of a protocol event; combinable with a method (the method is sent first) or usable alone.
228420
228532
  --browser route through the held connection (session start --hold) instead of a one-shot page websocket. Connection-scoped state (permission grants, domain enables) reverts the instant its connection closes \u2014 it survives across commands only inside a held session.
228421
- --target <id> with --browser: attach a flattened CDP session on the held connection to this target (for target-scoped domains); without --browser: the page target to run against. 8-char id prefix accepted.
228533
+ --port <port> CDP endpoint. An explicit flag selects that endpoint even when an active session holds another browser connection.
228534
+ --target <id> with --browser: attach a flattened CDP session on the held connection to this target (for target-scoped domains), including an already-open tab adopted by \`session start --hold\` without --url; without --browser: the page target to run against. 8-char id prefix accepted.
228422
228535
  --timeout <ms> event-wait timeout (default ${DEFAULT_TIMEOUT_MS}ms).
228423
228536
 
228424
228537
  Output:
@@ -230035,9 +230148,9 @@ async function collectStylesInner(ctx, styleSheetUrls, styleSheetHeaders) {
230035
230148
  }
230036
230149
  const sourceCache = /* @__PURE__ */ new Map();
230037
230150
  const elements = await Promise.all(
230038
- facts.map(async (fact3, index) => {
230151
+ facts.map(async (fact4, index) => {
230039
230152
  const ref = resolved[index];
230040
- const computed2 = normalizeComputed(fact3.computed);
230153
+ const computed2 = normalizeComputed(fact4.computed);
230041
230154
  let winningDeclarations;
230042
230155
  let provenanceUnavailable;
230043
230156
  if (ref?.nodeId !== void 0) {
@@ -230063,7 +230176,7 @@ async function collectStylesInner(ctx, styleSheetUrls, styleSheetHeaders) {
230063
230176
  }
230064
230177
  return {
230065
230178
  id: `s-${index}`,
230066
- selector: sanitizeString(fact3.cssPath),
230179
+ selector: sanitizeString(fact4.cssPath),
230067
230180
  ...resolvedIdentity3(ref?.backendNodeId),
230068
230181
  computed: computed2,
230069
230182
  winningDeclarations,
@@ -231015,11 +231128,11 @@ var init_text = __esm({
231015
231128
  } else {
231016
231129
  rawPlatformFontFamilies = fontsResult.fonts.map((font) => font.familyName ?? "");
231017
231130
  platformFonts = fontsResult.fonts.map((font) => {
231018
- const fact3 = {
231131
+ const fact4 = {
231019
231132
  familyName: sanitizeString(font.familyName),
231020
231133
  isCustomFont: !!font.isCustomFont
231021
231134
  };
231022
- return typeof font.glyphCount === "number" ? { ...fact3, glyphCount: font.glyphCount } : fact3;
231135
+ return typeof font.glyphCount === "number" ? { ...fact4, glyphCount: font.glyphCount } : fact4;
231023
231136
  });
231024
231137
  platformFontsAvailable = true;
231025
231138
  platformFontsUnavailableReason = void 0;
@@ -234869,36 +234982,36 @@ document.querySelectorAll('img, video, canvas, svg, iframe').length;`;
234869
234982
  resolved = facts.map(() => void 0);
234870
234983
  }
234871
234984
  }
234872
- const elements = facts.map((fact3, index) => {
234873
- const natural = fact3.naturalWidth !== null && fact3.naturalHeight !== null ? { w: fact3.naturalWidth, h: fact3.naturalHeight } : null;
234985
+ const elements = facts.map((fact4, index) => {
234986
+ const natural = fact4.naturalWidth !== null && fact4.naturalHeight !== null ? { w: fact4.naturalWidth, h: fact4.naturalHeight } : null;
234874
234987
  const intrinsicAspectRatio = natural && natural.w > 0 && natural.h > 0 ? natural.w / natural.h : null;
234875
- const crop = fact3.styleUnavailable ? null : computeObjectFitCrop(natural, { w: fact3.rect.width, h: fact3.rect.height }, fact3.objectFit, fact3.objectPosition);
234988
+ const crop = fact4.styleUnavailable ? null : computeObjectFitCrop(natural, { w: fact4.rect.width, h: fact4.rect.height }, fact4.objectFit, fact4.objectPosition);
234876
234989
  return {
234877
234990
  id: `m-${index}`,
234878
- selector: fact3.cssPath ? sanitizeString(fact3.cssPath) : "",
234991
+ selector: fact4.cssPath ? sanitizeString(fact4.cssPath) : "",
234879
234992
  ...resolvedIdentity8(resolved[index]?.backendNodeId),
234880
- tag: fact3.tag,
234881
- rect: fact3.rect,
234882
- visible: fact3.visible,
234883
- naturalWidth: fact3.naturalWidth,
234884
- naturalHeight: fact3.naturalHeight,
234885
- renderedWidth: fact3.rect.width,
234886
- renderedHeight: fact3.rect.height,
234887
- devicePixelRatio: fact3.dpr,
234888
- currentSrc: sanitizeOptional2(fact3.currentSrc),
234889
- decodeState: fact3.decodeState,
234993
+ tag: fact4.tag,
234994
+ rect: fact4.rect,
234995
+ visible: fact4.visible,
234996
+ naturalWidth: fact4.naturalWidth,
234997
+ naturalHeight: fact4.naturalHeight,
234998
+ renderedWidth: fact4.rect.width,
234999
+ renderedHeight: fact4.rect.height,
235000
+ devicePixelRatio: fact4.dpr,
235001
+ currentSrc: sanitizeOptional2(fact4.currentSrc),
235002
+ decodeState: fact4.decodeState,
234890
235003
  intrinsicAspectRatio,
234891
- objectFit: sanitizeOptional2(fact3.objectFit),
234892
- objectPosition: sanitizeOptional2(fact3.objectPosition),
235004
+ objectFit: sanitizeOptional2(fact4.objectFit),
235005
+ objectPosition: sanitizeOptional2(fact4.objectPosition),
234893
235006
  crop,
234894
- styleUnavailable: fact3.styleUnavailable || void 0,
234895
- intrinsicDimsUnavailable: fact3.intrinsicDimsUnavailable || void 0,
234896
- contextType: fact3.contextType,
234897
- backingWidth: fact3.backingWidth,
234898
- backingHeight: fact3.backingHeight,
234899
- poster: fact3.tag === "video" ? sanitizeOptional2(fact3.poster) : void 0,
234900
- src: fact3.tag === "iframe" ? sanitizeOptional2(fact3.src) : void 0,
234901
- crossOrigin: fact3.tag === "iframe" ? fact3.crossOrigin : void 0
235007
+ styleUnavailable: fact4.styleUnavailable || void 0,
235008
+ intrinsicDimsUnavailable: fact4.intrinsicDimsUnavailable || void 0,
235009
+ contextType: fact4.contextType,
235010
+ backingWidth: fact4.backingWidth,
235011
+ backingHeight: fact4.backingHeight,
235012
+ poster: fact4.tag === "video" ? sanitizeOptional2(fact4.poster) : void 0,
235013
+ src: fact4.tag === "iframe" ? sanitizeOptional2(fact4.src) : void 0,
235014
+ crossOrigin: fact4.tag === "iframe" ? fact4.crossOrigin : void 0
234902
235015
  };
234903
235016
  });
234904
235017
  ctx.write.json("media.json", {
@@ -235381,16 +235494,16 @@ function rectsOverlap(a, b) {
235381
235494
  return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
235382
235495
  }
235383
235496
  function annotateUnstableFacts(facts, regions) {
235384
- return facts.map((fact3) => {
235497
+ return facts.map((fact4) => {
235385
235498
  const caveats = [];
235386
235499
  for (const region of regions) {
235387
- const byElement = fact3.elementId !== void 0 && (region.elementIds?.includes(fact3.elementId) ?? false);
235388
- const byRect = fact3.rect !== void 0 && region.rect !== void 0 && rectsOverlap(fact3.rect, region.rect);
235500
+ const byElement = fact4.elementId !== void 0 && (region.elementIds?.includes(fact4.elementId) ?? false);
235501
+ const byRect = fact4.rect !== void 0 && region.rect !== void 0 && rectsOverlap(fact4.rect, region.rect);
235389
235502
  if (byElement || byRect) {
235390
235503
  caveats.push({ regionId: region.id, selector: region.selector, reason: region.reason });
235391
235504
  }
235392
235505
  }
235393
- return { fact: fact3, caveats };
235506
+ return { fact: fact4, caveats };
235394
235507
  });
235395
235508
  }
235396
235509
  var fs18, path20, ArtifactResolutionError, SNAP_CONFIG, REC_CONFIG, CREATING_COMMAND_BY_FILE;
@@ -235675,7 +235788,7 @@ function errorSections(details) {
235675
235788
  }
235676
235789
  async function cmdMeasureSnap(parsed, _args) {
235677
235790
  if (parsed.help) {
235678
- console.log(USAGE12);
235791
+ console.log(USAGE13);
235679
235792
  return;
235680
235793
  }
235681
235794
  const captures = [];
@@ -235725,7 +235838,7 @@ async function cmdMeasureSnap(parsed, _args) {
235725
235838
  process.exitCode = 1;
235726
235839
  }
235727
235840
  }
235728
- var fs19, path21, import_crypto2, USAGE12;
235841
+ var fs19, path21, import_crypto2, USAGE13;
235729
235842
  var init_snap = __esm({
235730
235843
  "src/cdp/commands/measure/snap.ts"() {
235731
235844
  fs19 = __toESM(require("fs"));
@@ -235743,7 +235856,7 @@ var init_snap = __esm({
235743
235856
  init_artifact();
235744
235857
  init_errors();
235745
235858
  init_render();
235746
- USAGE12 = `capture measure snap [url|snap] \u2014 drive the page and write one settled snapshot substrate directory
235859
+ USAGE13 = `capture measure snap [url|snap] \u2014 drive the page and write one settled snapshot substrate directory
235747
235860
 
235748
235861
  input:
235749
235862
  [url|snap] a URL drives the page fresh; a snap id or absolute path re-captures states over that base snapshot. Outside a session a URL writes under a private one-shot session; with an active session, snapshots are written under it and later resolve by snap id
@@ -235986,7 +236099,7 @@ function checkSnapshot(ref, requested) {
235986
236099
  }
235987
236100
  const regions = unstableRegionsFor(ref);
235988
236101
  const annotated = annotateUnstableFacts(findings.map((f) => ({ ...f, elementId: f.elementId, rect: f.rect })), regions);
235989
- return { findings: annotated.map(({ fact: fact3, caveats }) => ({ ...fact3, caveats })), elementCount: elements.length, settled: meta.settled, viewport: vp };
236102
+ return { findings: annotated.map(({ fact: fact4, caveats }) => ({ ...fact4, caveats })), elementCount: elements.length, settled: meta.settled, viewport: vp };
235990
236103
  }
235991
236104
  function writeFindingCrop(ref, finding2, index) {
235992
236105
  if (!finding2.rect || !artifactExists(ref, "screenshot.png")) return void 0;
@@ -236047,7 +236160,7 @@ function caveatLine(caveats) {
236047
236160
  }
236048
236161
  async function cmdMeasureCheck(parsed, _args) {
236049
236162
  if (parsed.help) {
236050
- console.log(USAGE13);
236163
+ console.log(USAGE14);
236051
236164
  return;
236052
236165
  }
236053
236166
  if (parsed.positional.length > 1) {
@@ -236104,14 +236217,14 @@ async function cmdMeasureCheck(parsed, _args) {
236104
236217
  process.exitCode = 1;
236105
236218
  }
236106
236219
  }
236107
- var USAGE13, DEFAULT_FINDING_LIMIT;
236220
+ var USAGE14, DEFAULT_FINDING_LIMIT;
236108
236221
  var init_check2 = __esm({
236109
236222
  "src/cdp/commands/measure/check.ts"() {
236110
236223
  init_artifact();
236111
236224
  init_render();
236112
236225
  init_snap();
236113
236226
  init_check();
236114
- USAGE13 = `capture measure check [url|snap] \u2014 read threshold/fact measurements from a settled snapshot
236227
+ USAGE14 = `capture measure check [url|snap] \u2014 read threshold/fact measurements from a settled snapshot
236115
236228
 
236116
236229
  input:
236117
236230
  [url|snap] a URL creates one one-shot snapshot first; a snap id or absolute path is read without re-driving the browser
@@ -236695,7 +236808,7 @@ function provenanceLines(records) {
236695
236808
  }
236696
236809
  async function cmdMeasureDiff(parsed, _args) {
236697
236810
  if (parsed.help) {
236698
- console.log(USAGE14);
236811
+ console.log(USAGE15);
236699
236812
  return;
236700
236813
  }
236701
236814
  if (parsed.positional.length > 0 || !parsed.before || !parsed.after) {
@@ -236760,13 +236873,13 @@ async function cmdMeasureDiff(parsed, _args) {
236760
236873
  process.exitCode = 1;
236761
236874
  }
236762
236875
  }
236763
- var USAGE14;
236876
+ var USAGE15;
236764
236877
  var init_diff3 = __esm({
236765
236878
  "src/cdp/commands/measure/diff.ts"() {
236766
236879
  init_artifact();
236767
236880
  init_diff2();
236768
236881
  init_render();
236769
- USAGE14 = `capture measure diff \u2014 structured before/after diff over two settled snapshots
236882
+ USAGE15 = `capture measure diff \u2014 structured before/after diff over two settled snapshots
236770
236883
 
236771
236884
  input:
236772
236885
  --before <snap> earlier snapshot id or absolute path (required; no positional target \u2014 both snapshots must already exist)
@@ -236969,7 +237082,7 @@ function errorResult(message, status) {
236969
237082
  }
236970
237083
  async function cmdMeasureCensus(parsed, _args) {
236971
237084
  if (parsed.help) {
236972
- console.log(USAGE15);
237085
+ console.log(USAGE16);
236973
237086
  return;
236974
237087
  }
236975
237088
  if (parsed.positional.length) {
@@ -237000,7 +237113,7 @@ async function cmdMeasureCensus(parsed, _args) {
237000
237113
  process.exitCode = 1;
237001
237114
  }
237002
237115
  }
237003
- var fs23, USAGE15;
237116
+ var fs23, USAGE16;
237004
237117
  var init_census2 = __esm({
237005
237118
  "src/cdp/commands/measure/census.ts"() {
237006
237119
  fs23 = __toESM(require("fs"));
@@ -237008,7 +237121,7 @@ var init_census2 = __esm({
237008
237121
  init_artifact();
237009
237122
  init_render();
237010
237123
  init_census();
237011
- USAGE15 = `capture measure census --axis <axis> \u2014 value distributions and token-audit facts across one or more settled snapshots
237124
+ USAGE16 = `capture measure census --axis <axis> \u2014 value distributions and token-audit facts across one or more settled snapshots
237012
237125
 
237013
237126
  input:
237014
237127
  --axis <axis> color|font|spacing|radius|shadow|animation|geometry|media|queries (required)
@@ -237414,7 +237527,7 @@ function invalidInput2(message) {
237414
237527
  }
237415
237528
  async function cmdMeasureExplain(parsed, _args) {
237416
237529
  if (parsed.help) {
237417
- console.log(USAGE16);
237530
+ console.log(USAGE17);
237418
237531
  return;
237419
237532
  }
237420
237533
  if (parsed.positional.length !== 1) {
@@ -237474,13 +237587,13 @@ async function cmdMeasureExplain(parsed, _args) {
237474
237587
  process.exitCode = 1;
237475
237588
  }
237476
237589
  }
237477
- var USAGE16;
237590
+ var USAGE17;
237478
237591
  var init_explain2 = __esm({
237479
237592
  "src/cdp/commands/measure/explain.ts"() {
237480
237593
  init_explain();
237481
237594
  init_artifact();
237482
237595
  init_render();
237483
- USAGE16 = `capture measure explain <snap> --selector <sel> \u2014 per-element cascade/stacking/clipping explanation over one snapshot
237596
+ USAGE17 = `capture measure explain <snap> --selector <sel> \u2014 per-element cascade/stacking/clipping explanation over one snapshot
237484
237597
 
237485
237598
  input:
237486
237599
  <snap> snapshot id in the active session or absolute artifact path (required)
@@ -237744,7 +237857,7 @@ async function cmdMeasureSweep(parsed, args) {
237744
237857
  async function runMeasureSweep(parsed, _args, overrides = {}) {
237745
237858
  const dependencies = { ...defaultDependencies, ...overrides };
237746
237859
  if (parsed.help) {
237747
- console.log(USAGE17);
237860
+ console.log(USAGE18);
237748
237861
  return;
237749
237862
  }
237750
237863
  if (parsed.positional.length > 1) {
@@ -237855,7 +237968,7 @@ async function runMeasureSweep(parsed, _args, overrides = {}) {
237855
237968
  process.exitCode = 1;
237856
237969
  }
237857
237970
  }
237858
- var fs25, path26, import_crypto3, USAGE17, SAMPLE_LIMIT, defaultDependencies, parsePositive;
237971
+ var fs25, path26, import_crypto3, USAGE18, SAMPLE_LIMIT, defaultDependencies, parsePositive;
237859
237972
  var init_sweep2 = __esm({
237860
237973
  "src/cdp/commands/measure/sweep.ts"() {
237861
237974
  fs25 = __toESM(require("fs"));
@@ -237868,7 +237981,7 @@ var init_sweep2 = __esm({
237868
237981
  init_render();
237869
237982
  init_leaf_grammar();
237870
237983
  init_sweep();
237871
- USAGE17 = `capture measure sweep [url] --axis <axis> \u2014 responsive/environment sampling with recursive bracketing of observed state changes
237984
+ USAGE18 = `capture measure sweep [url] --axis <axis> \u2014 responsive/environment sampling with recursive bracketing of observed state changes
237872
237985
 
237873
237986
  input:
237874
237987
  [url] page to sweep; outside a session, snapshots and sweep.json are written under one private one-shot measure directory
@@ -238008,7 +238121,7 @@ function recoveryResult(err) {
238008
238121
  }
238009
238122
  async function cmdMeasureMapFocus(parsed, _args) {
238010
238123
  if (parsed.help) {
238011
- console.log(USAGE18);
238124
+ console.log(USAGE19);
238012
238125
  return;
238013
238126
  }
238014
238127
  if (parsed.positional.length !== 1) {
@@ -238032,14 +238145,14 @@ async function cmdMeasureMapFocus(parsed, _args) {
238032
238145
  process.exitCode = 1;
238033
238146
  }
238034
238147
  }
238035
- var USAGE18;
238148
+ var USAGE19;
238036
238149
  var init_map_focus2 = __esm({
238037
238150
  "src/cdp/commands/measure/map-focus.ts"() {
238038
238151
  init_snap();
238039
238152
  init_map_focus();
238040
238153
  init_artifact();
238041
238154
  init_render();
238042
- USAGE18 = `capture measure map focus [url|snap] \u2014 keyboard traversal facts recorded in a snapshot's focus.json
238155
+ USAGE19 = `capture measure map focus [url|snap] \u2014 keyboard traversal facts recorded in a snapshot's focus.json
238043
238156
 
238044
238157
  input:
238045
238158
  [url|snap] required target: a URL creates a settled snapshot first; a snapshot id or absolute path is read without re-driving the browser
@@ -238255,7 +238368,7 @@ async function runMeasureMapScroll(parsed, deps7 = {}) {
238255
238368
  const captureSnap = deps7.captureSnap ?? captureMeasureSnap;
238256
238369
  const emit = deps7.emit ?? emitResult;
238257
238370
  if (parsed.help) {
238258
- console.log(USAGE19);
238371
+ console.log(USAGE20);
238259
238372
  return;
238260
238373
  }
238261
238374
  if (parsed.positional.length > 1) {
@@ -238309,14 +238422,14 @@ async function runMeasureMapScroll(parsed, deps7 = {}) {
238309
238422
  async function cmdMeasureMapScroll(parsed, _args) {
238310
238423
  await runMeasureMapScroll(parsed);
238311
238424
  }
238312
- var USAGE19;
238425
+ var USAGE20;
238313
238426
  var init_map_scroll2 = __esm({
238314
238427
  "src/cdp/commands/measure/map-scroll.ts"() {
238315
238428
  init_snap();
238316
238429
  init_artifact();
238317
238430
  init_render();
238318
238431
  init_map_scroll();
238319
- USAGE19 = `capture measure map scroll [url|snap] \u2014 scroll-container topology recorded in a snapshot's scroll.json
238432
+ USAGE20 = `capture measure map scroll [url|snap] \u2014 scroll-container topology recorded in a snapshot's scroll.json
238320
238433
 
238321
238434
  input:
238322
238435
  [url|snap] required target: a URL creates a settled snapshot first; a snapshot id from the active session or an absolute snapshot artifact path is read without re-driving the browser
@@ -238483,7 +238596,7 @@ function errorResult2(err) {
238483
238596
  }
238484
238597
  async function cmdMeasureMapLayers(parsed, _args) {
238485
238598
  if (parsed.help) {
238486
- console.log(USAGE20);
238599
+ console.log(USAGE21);
238487
238600
  return;
238488
238601
  }
238489
238602
  if (parsed.positional.length > 1) {
@@ -238516,14 +238629,14 @@ async function cmdMeasureMapLayers(parsed, _args) {
238516
238629
  process.exitCode = 1;
238517
238630
  }
238518
238631
  }
238519
- var USAGE20;
238632
+ var USAGE21;
238520
238633
  var init_map_layers2 = __esm({
238521
238634
  "src/cdp/commands/measure/map-layers.ts"() {
238522
238635
  init_snap();
238523
238636
  init_map_layers();
238524
238637
  init_artifact();
238525
238638
  init_render();
238526
- USAGE20 = `capture measure map layers [url|snap] \u2014 paint/compositor facts recorded in a snapshot's layers.json
238639
+ USAGE21 = `capture measure map layers [url|snap] \u2014 paint/compositor facts recorded in a snapshot's layers.json
238527
238640
 
238528
238641
  input:
238529
238642
  [url|snap] required target: a URL creates a settled snapshot first; a snapshot id or absolute path is read without re-driving the browser
@@ -239007,7 +239120,7 @@ function recoveryResult2(err) {
239007
239120
  }
239008
239121
  async function cmdMeasureMapAx(parsed, _args) {
239009
239122
  if (parsed.help) {
239010
- console.log(USAGE21);
239123
+ console.log(USAGE22);
239011
239124
  return;
239012
239125
  }
239013
239126
  if (parsed.positional.length !== 1) {
@@ -239031,14 +239144,14 @@ async function cmdMeasureMapAx(parsed, _args) {
239031
239144
  process.exitCode = 1;
239032
239145
  }
239033
239146
  }
239034
- var USAGE21;
239147
+ var USAGE22;
239035
239148
  var init_map_ax2 = __esm({
239036
239149
  "src/cdp/commands/measure/map-ax.ts"() {
239037
239150
  init_snap();
239038
239151
  init_map_ax();
239039
239152
  init_artifact();
239040
239153
  init_render();
239041
- USAGE21 = `capture measure map ax [url|snap] \u2014 accessibility-tree facts recorded in a snapshot's ax.json joined against geometry.json
239154
+ USAGE22 = `capture measure map ax [url|snap] \u2014 accessibility-tree facts recorded in a snapshot's ax.json joined against geometry.json
239042
239155
 
239043
239156
  input:
239044
239157
  [url|snap] required target: a URL creates a settled snapshot first; a snapshot id or absolute path is read without re-driving the browser
@@ -239145,7 +239258,7 @@ capture measure map <leaf> -h Per-leaf usage`;
239145
239258
  // src/cdp/commands/motion/rec.ts
239146
239259
  async function cmdMotionRec(parsed, _args) {
239147
239260
  if (parsed.help) {
239148
- console.log(USAGE22);
239261
+ console.log(USAGE23);
239149
239262
  return;
239150
239263
  }
239151
239264
  const lifecycleError = validateLifecycleInputs(parsed);
@@ -239509,7 +239622,7 @@ function listRecordingArtifacts(recDir) {
239509
239622
  }
239510
239623
  return names.sort().map((name) => ({ name }));
239511
239624
  }
239512
- var fs26, path27, crypto9, import_child_process3, deps6, USAGE22, DoActionError;
239625
+ var fs26, path27, crypto9, import_child_process3, deps6, USAGE23, DoActionError;
239513
239626
  var init_rec = __esm({
239514
239627
  "src/cdp/commands/motion/rec.ts"() {
239515
239628
  fs26 = __toESM(require("fs"));
@@ -239541,7 +239654,7 @@ var init_rec = __esm({
239541
239654
  stopComposedRecorder,
239542
239655
  encodeVideo: encodeVideoIfAvailable
239543
239656
  };
239544
- USAGE22 = `capture motion rec \u2014 record the page over time: composed (whatever the active session does between --start and --stop) or one-shot (one scripted action on a URL or the active session tab).
239657
+ USAGE23 = `capture motion rec \u2014 record the page over time: composed (whatever the active session does between --start and --stop) or one-shot (one scripted action on a URL or the active session tab).
239545
239658
 
239546
239659
  input:
239547
239660
  [url] --do <action> one-shot: open <url> (or record the active session tab when <url> is omitted), record one action, finalize
@@ -239854,7 +239967,7 @@ var init_mask = __esm({
239854
239967
  // src/cdp/commands/motion/mask.ts
239855
239968
  async function cmdMotionMask(parsed, _args) {
239856
239969
  if (parsed.help) {
239857
- console.log(USAGE23);
239970
+ console.log(USAGE24);
239858
239971
  return;
239859
239972
  }
239860
239973
  if (parsed.positional.length !== 1) {
@@ -239916,14 +240029,14 @@ function emitCommandError2(parsed, status, message) {
239916
240029
  }, { json: parsed.json });
239917
240030
  process.exitCode = 1;
239918
240031
  }
239919
- var DEFAULT_REGION_LIMIT, USAGE23;
240032
+ var DEFAULT_REGION_LIMIT, USAGE24;
239920
240033
  var init_mask2 = __esm({
239921
240034
  "src/cdp/commands/motion/mask.ts"() {
239922
240035
  init_mask();
239923
240036
  init_artifact();
239924
240037
  init_render();
239925
240038
  DEFAULT_REGION_LIMIT = 20;
239926
- USAGE23 = `capture motion mask <rec> \u2014 motion-diff composite image plus per-region facts over a finalized recording
240039
+ USAGE24 = `capture motion mask <rec> \u2014 motion-diff composite image plus per-region facts over a finalized recording
239927
240040
 
239928
240041
  input:
239929
240042
  <rec> recording id in the active session or an absolute recording path (required; the recording must be finalized)
@@ -240061,7 +240174,7 @@ var init_timeline = __esm({
240061
240174
  // src/cdp/commands/motion/timeline.ts
240062
240175
  async function cmdMotionTimeline(parsed, _args) {
240063
240176
  if (parsed.help) {
240064
- console.log(USAGE24);
240177
+ console.log(USAGE25);
240065
240178
  return;
240066
240179
  }
240067
240180
  const recArg = parsed.positional[0];
@@ -240159,13 +240272,13 @@ function emitCommandError3(parsed, status, message, recovery, ref, state) {
240159
240272
  emitResult(result, { json: parsed.json });
240160
240273
  process.exitCode = 1;
240161
240274
  }
240162
- var USAGE24;
240275
+ var USAGE25;
240163
240276
  var init_timeline2 = __esm({
240164
240277
  "src/cdp/commands/motion/timeline.ts"() {
240165
240278
  init_artifact();
240166
240279
  init_timeline();
240167
240280
  init_render();
240168
- USAGE24 = `capture motion timeline <rec> --element <sel> \u2014 per-frame geometry/scroll/property timeline for one element across a finalized recording
240281
+ USAGE25 = `capture motion timeline <rec> --element <sel> \u2014 per-frame geometry/scroll/property timeline for one element across a finalized recording
240169
240282
 
240170
240283
  input:
240171
240284
  <rec> recording id in the active session or an absolute recording path (required; the recording must be finalized)
@@ -240457,7 +240570,7 @@ var init_jank = __esm({
240457
240570
  // src/cdp/commands/motion/jank.ts
240458
240571
  async function cmdMotionJank(parsed, _args) {
240459
240572
  if (parsed.help) {
240460
- console.log(USAGE25);
240573
+ console.log(USAGE26);
240461
240574
  return;
240462
240575
  }
240463
240576
  if (parsed.positional.length !== 1 || !parsed.positional[0]) {
@@ -240550,13 +240663,13 @@ function emitCommandError4(parsed, status, message, recovery) {
240550
240663
  emitResult(result, { json: parsed.json });
240551
240664
  process.exitCode = 1;
240552
240665
  }
240553
- var USAGE25;
240666
+ var USAGE26;
240554
240667
  var init_jank2 = __esm({
240555
240668
  "src/cdp/commands/motion/jank.ts"() {
240556
240669
  init_artifact();
240557
240670
  init_render();
240558
240671
  init_jank();
240559
- USAGE25 = `capture motion jank <rec> \u2014 dropped-frame, long-task-record, and layout-shift facts over a finalized recording
240672
+ USAGE26 = `capture motion jank <rec> \u2014 dropped-frame, long-task-record, and layout-shift facts over a finalized recording
240560
240673
 
240561
240674
  input:
240562
240675
  <rec> recording id in the active session or an absolute recording path (required; the recording must be finalized)
@@ -240811,7 +240924,7 @@ function emitError(parsed, status, message, sections) {
240811
240924
  }
240812
240925
  async function cmdMotionResponse(parsed, _args) {
240813
240926
  if (parsed.help) {
240814
- console.log(USAGE26);
240927
+ console.log(USAGE27);
240815
240928
  return;
240816
240929
  }
240817
240930
  if (parsed.positional.length !== 1) {
@@ -240852,13 +240965,13 @@ async function cmdMotionResponse(parsed, _args) {
240852
240965
  return emitError(parsed, "response_unavailable", err instanceof Error ? err.message : String(err));
240853
240966
  }
240854
240967
  }
240855
- var USAGE26;
240968
+ var USAGE27;
240856
240969
  var init_response2 = __esm({
240857
240970
  "src/cdp/commands/motion/response.ts"() {
240858
240971
  init_render();
240859
240972
  init_artifact();
240860
240973
  init_response();
240861
- USAGE26 = `capture motion response <rec> \u2014 input-to-settled response timeline over a finalized recording
240974
+ USAGE27 = `capture motion response <rec> \u2014 input-to-settled response timeline over a finalized recording
240862
240975
 
240863
240976
  input:
240864
240977
  <rec> recording id in the active session or an absolute recording path (required; the recording must be finalized)
@@ -241033,6 +241146,9 @@ init_motion();
241033
241146
  init_cdp();
241034
241147
  init_lib();
241035
241148
  var ROOTS = ["session", "page", "tab", "measure", "motion", "cdp", "lib"];
241149
+ var GUESSABLE_HINTS = {
241150
+ screenshot: "page shot"
241151
+ };
241036
241152
  var ROOT_BLOCKS = [
241037
241153
  COMMAND_BLOCK,
241038
241154
  COMMAND_BLOCK2,
@@ -241056,7 +241172,7 @@ CDP_PORT / CDP_TARGET pin the browser + tab for orchestrators \u2014 precedence:
241056
241172
  explicit flag > active session > env.`;
241057
241173
  }
241058
241174
  function printVersion() {
241059
- const declared = "1.3.13";
241175
+ const declared = "1.3.15";
241060
241176
  if (declared) {
241061
241177
  console.log(declared);
241062
241178
  return;
@@ -241086,10 +241202,11 @@ async function main() {
241086
241202
  if (ROOTS.includes(command) || command === "__bridge-serve") {
241087
241203
  return cdpMain();
241088
241204
  }
241205
+ const hint = GUESSABLE_HINTS[command];
241089
241206
  throw captureError(
241090
241207
  "invocation",
241091
241208
  "unknown_command",
241092
- `Unknown command ${command}; expected one of the seven roots: session, page, tab, measure, motion, cdp, lib.`
241209
+ hint ? `Unknown command ${command}; did you mean \`capture ${hint}\`? Expected one of the seven roots: session, page, tab, measure, motion, cdp, lib.` : `Unknown command ${command}; expected one of the seven roots: session, page, tab, measure, motion, cdp, lib.`
241093
241210
  );
241094
241211
  }
241095
241212
  main().catch((error) => {