@crouton-kit/capture 1.3.19 → 1.3.20

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
@@ -9154,6 +9154,9 @@ function validateCliInvocation(parsed) {
9154
9154
  throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
9155
9155
  }
9156
9156
  }
9157
+ if (leaf === "shot" && parsed.colorScheme !== void 0 && parsed.colorScheme !== "dark" && parsed.colorScheme !== "light") {
9158
+ throw invalidInput(`Invalid --color-scheme: ${parsed.colorScheme}; expected dark or light.`);
9159
+ }
9157
9160
  return;
9158
9161
  }
9159
9162
  if (parsed.command === "tab") {
@@ -9167,6 +9170,9 @@ function validateCliInvocation(parsed) {
9167
9170
  }
9168
9171
  if (parsed.command === "measure") {
9169
9172
  if (leaf === "snap" || leaf === "check" || leaf === "sweep") requireCount(values, 0, 1, `measure ${leaf}`);
9173
+ if (leaf === "snap" && parsed.colorScheme !== void 0 && parsed.colorScheme !== "dark" && parsed.colorScheme !== "light") {
9174
+ throw invalidInput(`Invalid --color-scheme: ${parsed.colorScheme}; expected dark or light.`);
9175
+ }
9170
9176
  if (leaf === "diff" || leaf === "census") requireCount(values, 0, 0, `measure ${leaf}`);
9171
9177
  if (leaf === "diff" && (!parsed.before || !parsed.after)) throw invalidInput("measure diff requires --before and --after.");
9172
9178
  if (leaf === "census" && !["color", "font", "spacing", "radius", "shadow", "animation", "geometry", "media", "queries"].includes(parsed.axis ?? "")) {
@@ -9272,6 +9278,9 @@ function parseCliSyntax(argv) {
9272
9278
  parsed.viewport = value;
9273
9279
  (parsed.viewports ??= []).push(value);
9274
9280
  i++;
9281
+ } else if (arg === "--color-scheme") {
9282
+ parsed.colorScheme = valueFor(arg, next);
9283
+ i++;
9275
9284
  } else if (arg === "--full-page") parsed.fullPage = true;
9276
9285
  else if (arg === "--all") parsed.all = true;
9277
9286
  else if (arg === "--session") {
@@ -9431,6 +9440,7 @@ var init_args = __esm({
9431
9440
  "--url",
9432
9441
  "--into",
9433
9442
  "--viewport",
9443
+ "--color-scheme",
9434
9444
  "--session",
9435
9445
  "--filter",
9436
9446
  "--name",
@@ -10221,7 +10231,7 @@ function validateHarAppendBatch(value, source) {
10221
10231
  return { entries, incompleteLifecycles };
10222
10232
  }
10223
10233
  function captureVersion() {
10224
- const version = "1.3.19";
10234
+ const version = "1.3.20";
10225
10235
  return typeof version === "string" && version.length > 0 ? version : "development";
10226
10236
  }
10227
10237
  function token() {
@@ -11980,7 +11990,7 @@ function wallTime(value) {
11980
11990
  }
11981
11991
  }
11982
11992
  function captureVersion2() {
11983
- const version = "1.3.19";
11993
+ const version = "1.3.20";
11984
11994
  return typeof version === "string" && version.length > 0 ? version : "development";
11985
11995
  }
11986
11996
  function deepFreeze(value) {
@@ -227597,6 +227607,49 @@ var init_viewport = __esm({
227597
227607
  }
227598
227608
  });
227599
227609
 
227610
+ // src/cdp/color-scheme.ts
227611
+ function parseColorScheme(value) {
227612
+ if (value === void 0) return void 0;
227613
+ if (value === "dark" || value === "light") return value;
227614
+ throw new Error("--color-scheme must be dark or light");
227615
+ }
227616
+ async function withAppliedColorScheme(client, colorScheme, fn) {
227617
+ if (!colorScheme) return fn();
227618
+ let ownsMediaOverride = false;
227619
+ let primaryFailed = false;
227620
+ let primaryError;
227621
+ try {
227622
+ ownsMediaOverride = true;
227623
+ await client.send("Emulation.setEmulatedMedia", {
227624
+ features: [{ name: "prefers-color-scheme", value: colorScheme }]
227625
+ });
227626
+ return await fn();
227627
+ } catch (error) {
227628
+ primaryFailed = true;
227629
+ primaryError = error;
227630
+ throw error;
227631
+ } finally {
227632
+ if (ownsMediaOverride) {
227633
+ try {
227634
+ await client.send("Emulation.setEmulatedMedia", { features: [] });
227635
+ } catch (cleanupError) {
227636
+ if (primaryFailed) {
227637
+ throw new AggregateError(
227638
+ [primaryError, cleanupError],
227639
+ "Color-scheme capture failed and media-emulation cleanup also failed.",
227640
+ { cause: primaryError }
227641
+ );
227642
+ }
227643
+ throw cleanupError;
227644
+ }
227645
+ }
227646
+ }
227647
+ }
227648
+ var init_color_scheme = __esm({
227649
+ "src/cdp/color-scheme.ts"() {
227650
+ }
227651
+ });
227652
+
227600
227653
  // src/cdp/commands/page/shot.ts
227601
227654
  function pngDimensions(png) {
227602
227655
  if (png.length < 24) return null;
@@ -227625,7 +227678,8 @@ function buildScreenshotResult(f) {
227625
227678
  const scale = f.dimensions && f.cssViewport ? { x: f.dimensions.width / f.cssViewport.width, y: f.dimensions.height / f.cssViewport.height } : void 0;
227626
227679
  const sections = [
227627
227680
  scale && f.cssViewport ? fact`CSS-to-image scale: ${scale.x.toFixed(6)} image px/CSS px horizontally and ${scale.y.toFixed(6)} image px/CSS px vertically (captured CSS region ${round6(f.cssViewport.width)}×${round6(f.cssViewport.height)} at page origin ${round6(f.cssViewport.x)},${round6(f.cssViewport.y)}).` : text`CSS-to-image scale unavailable: the browser did not return the CSS region this capture covers.`,
227628
- f.emulation === "none" ? text`emulation: none — the browser's actual current viewport was captured; no Emulation call was made.` : f.emulation === "viewport" ? fact`emulation: transient ${f.viewport.width}x${f.viewport.height} device-metrics override applied for the capture and cleared after — two page-observable resizes.` : f.viewport ? fact`emulation: transient full-page device-metrics override (from ${f.viewport.width}x${f.viewport.height}) applied for the capture and cleared after — two page-observable resizes.` : text`emulation: transient full-page device-metrics override applied for the capture and cleared after — two page-observable resizes.`
227681
+ f.emulation === "none" ? text`viewport emulation: none — the browser's actual current viewport was captured.` : f.emulation === "viewport" ? fact`emulation: transient ${f.viewport.width}x${f.viewport.height} device-metrics override applied for the capture and cleared after — two page-observable resizes.` : f.viewport ? fact`emulation: transient full-page device-metrics override (from ${f.viewport.width}x${f.viewport.height}) applied for the capture and cleared after — two page-observable resizes.` : text`emulation: transient full-page device-metrics override applied for the capture and cleared after — two page-observable resizes.`,
227682
+ f.colorScheme ? fact`color scheme: transient prefers-color-scheme=${f.colorScheme} override applied for the capture and cleared after.` : text`color scheme: browser default — no Emulation.setEmulatedMedia call was made.`
227629
227683
  ];
227630
227684
  return {
227631
227685
  tag: "screenshot",
@@ -227639,7 +227693,8 @@ function buildScreenshotResult(f) {
227639
227693
  "css-y": f.cssViewport === void 0 ? void 0 : round6(f.cssViewport.y),
227640
227694
  "css-to-image-x": scale?.x.toFixed(6),
227641
227695
  "css-to-image-y": scale?.y.toFixed(6),
227642
- emulation: f.emulation
227696
+ emulation: f.emulation,
227697
+ "color-scheme": f.colorScheme
227643
227698
  },
227644
227699
  summary: f.dimensions ? fact`saved ${f.path} — ${f.dimensions.width}x${f.dimensions.height}px, ${f.bytes} bytes.` : fact`saved ${f.path} — ${f.bytes} bytes.`,
227645
227700
  sections
@@ -227655,7 +227710,7 @@ async function cmdPageShot(parsed, _args) {
227655
227710
  {
227656
227711
  tag: "error",
227657
227712
  attrs: { command: "page shot", code: "invalid_input" },
227658
- summary: fact`received: ${parsed.positional.length} positional arguments; expected none — \`capture page shot [--viewport <WxH>] [--full-page] [--out <path>]\`.`
227713
+ summary: fact`received: ${parsed.positional.length} positional arguments; expected none — \`capture page shot [--viewport <WxH>] [--full-page] [--color-scheme <dark|light>] [--out <path>]\`.`
227659
227714
  },
227660
227715
  { json: parsed.json }
227661
227716
  );
@@ -227679,18 +227734,33 @@ async function cmdPageShot(parsed, _args) {
227679
227734
  return;
227680
227735
  }
227681
227736
  }
227737
+ let colorScheme;
227738
+ try {
227739
+ colorScheme = parseColorScheme(parsed.colorScheme);
227740
+ } catch {
227741
+ emitResult(
227742
+ {
227743
+ tag: "error",
227744
+ attrs: { command: "page shot", code: "invalid_color_scheme" },
227745
+ summary: fact`received: --color-scheme ${parsed.colorScheme}; expected dark or light.`
227746
+ },
227747
+ { json: parsed.json }
227748
+ );
227749
+ process.exitCode = 1;
227750
+ return;
227751
+ }
227682
227752
  const emulation = parsed.fullPage ? "full-page" : viewport2 ? "viewport" : "none";
227683
227753
  const result = await deps4.withConnection(
227684
227754
  { ...parsed, command: "shot" },
227685
- async (client) => {
227755
+ async (client) => withAppliedColorScheme(client, colorScheme, async () => {
227686
227756
  const screenshot = await captureScreenshotWithCssViewport(client, viewport2, { fullPage: parsed.fullPage });
227687
227757
  const outPath = await resolveOutPath(parsed);
227688
227758
  writeScreenshot(outPath, screenshot.png);
227689
227759
  return { path: outPath, bytes: screenshot.png.length, dimensions: pngDimensions(screenshot.png), cssViewport: screenshot.cssViewport };
227690
- },
227760
+ }),
227691
227761
  { settle: 0 }
227692
227762
  );
227693
- emitResult(buildScreenshotResult({ ...result, emulation, viewport: viewport2 }), { json: parsed.json });
227763
+ emitResult(buildScreenshotResult({ ...result, emulation, viewport: viewport2, colorScheme }), { json: parsed.json });
227694
227764
  }
227695
227765
  var fs15, path17, USAGE6, deps4, PNG_SIGNATURE;
227696
227766
  var init_shot = __esm({
@@ -227700,6 +227770,7 @@ var init_shot = __esm({
227700
227770
  init_connection();
227701
227771
  init_screenshot();
227702
227772
  init_viewport();
227773
+ init_color_scheme();
227703
227774
  init_session_context();
227704
227775
  init_commands();
227705
227776
  init_artifacts();
@@ -227709,13 +227780,14 @@ var init_shot = __esm({
227709
227780
  input:
227710
227781
  --viewport <WxH> transient device-metrics override for this capture; grammar: <positive-safe-int>x<positive-safe-int>, exact lowercase x with no whitespace \u2014 preset names are not accepted. Absent \u2192 no emulation, the browser's actual current viewport is captured
227711
227782
  --full-page transient override to the full scrollable content height for this capture
227783
+ --color-scheme <dark|light> transient prefers-color-scheme override for this capture; cleared afterward
227712
227784
  --out <path> destination file; default: the active session's shots/ sequence, or a fresh oneshot-*/page/ dir under the capture root when no session is active
227713
227785
  --target <tabId> | --url <pattern> | --port <n> tab targeting; defaults to the active session tab
227714
227786
  --json mirror the result as JSON
227715
227787
  output:
227716
- <screenshot path=\u2026 width=\u2026 height=\u2026 css-width=\u2026 css-height=\u2026 css-x=\u2026 css-y=\u2026 css-to-image-x=\u2026 css-to-image-y=\u2026 emulation=none|viewport|full-page> \u2014 saved path, PNG pixel dimensions, the CSS-pixel region the image covers (its page-coordinate origin and size) with the CSS-to-image scale derived from it, byte size, and the emulation fact (whether a transient override was applied, or its absence)
227788
+ <screenshot path=\u2026 width=\u2026 height=\u2026 css-width=\u2026 css-height=\u2026 css-x=\u2026 css-y=\u2026 css-to-image-x=\u2026 css-to-image-y=\u2026 emulation=none|viewport|full-page color-scheme=dark|light> \u2014 saved path, PNG pixel dimensions, the CSS-pixel region the image covers (its page-coordinate origin and size) with the CSS-to-image scale derived from it, byte size, and any requested transient emulation
227717
227789
  effects:
227718
- no flags: none \u2014 the capture reads the viewport as-is, with zero Emulation.* calls. With --viewport/--full-page: applies a transient Emulation.setDeviceMetricsOverride (~150ms re-layout wait) and clears it after the capture \u2014 two page-observable resizes (resize events fire; media queries flip and flip back)`;
227790
+ no flags: none \u2014 the capture reads the viewport as-is, with zero Emulation.* calls. --viewport/--full-page applies a transient Emulation.setDeviceMetricsOverride (~150ms re-layout wait) and clears it after the capture \u2014 two page-observable resizes. --color-scheme applies a transient Emulation.setEmulatedMedia prefers-color-scheme override and clears it after the capture.`;
227719
227791
  deps4 = { withConnection, nextStepPath, createOneshotSession };
227720
227792
  PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
227721
227793
  }
@@ -227856,7 +227928,7 @@ During a live composed recording every verb routes through the recorder.
227856
227928
  <subcommand name="scroll" args="<target> --to <top|bottom|px> [--no-screenshot]" whenToUse="scroll one resolved container to a position"/>
227857
227929
  <subcommand name="navigate" args="<url> [--settle <ms>]" whenToUse="navigate the tab and wait for load + settle"/>
227858
227930
  <subcommand name="exec" args="<code> | --file <path>" whenToUse="run arbitrary JS in the tab (expressions, return, await)"/>
227859
- <subcommand name="shot" args="[--viewport <WxH>] [--full-page] [--out <path>]" whenToUse="look at the page right now without acting"/>
227931
+ <subcommand name="shot" args="[--viewport <WxH>] [--full-page] [--color-scheme <dark|light>] [--out <path>]" whenToUse="look at the page right now without acting"/>
227860
227932
  <subcommand name="elements" args="[--all] [--limit <n>]" whenToUse="list what can be acted on \u2014 role, name, backend:<id> per element"/>
227861
227933
 
227862
227934
  capture page <leaf> -h Per-leaf usage`;
@@ -235901,6 +235973,7 @@ async function captureSnapshotSubstrate(options) {
235901
235973
  const pixels = options.pixels ?? false;
235902
235974
  const state = options.state ?? [];
235903
235975
  const viewport2 = options.viewport ?? null;
235976
+ const colorScheme = options.colorScheme;
235904
235977
  const url = options.url ? sanitizeString(options.url) : null;
235905
235978
  const artifacts = [];
235906
235979
  const phaseClient = (phase) => boundedClient(client, phase, dir);
@@ -236043,6 +236116,7 @@ async function captureSnapshotSubstrate(options) {
236043
236116
  captureUnsettled,
236044
236117
  pixels,
236045
236118
  states: state.map((spec) => sanitizeString(spec)),
236119
+ ...colorScheme ? { colorScheme } : {},
236046
236120
  unstableRegionCount: unstableRegions.length,
236047
236121
  ...freezeAnimations ? { animationsRestored: animationsRestored ?? false } : {},
236048
236122
  // #8: the browser-wide `Animation.setPlaybackRate({playbackRate:0})`
@@ -236568,7 +236642,7 @@ function settledNote(settled, captured, settleMs, settleTimeoutMs, unstableCount
236568
236642
  }
236569
236643
  return fact`Page did not settle within ${settleTimeoutMs}ms. No queryable substrate was written; evidence artifacts only (${unstableCount} unstable region(s)).`;
236570
236644
  }
236571
- async function captureMeasureSnap(parsed, targetRef = parsed.positional[0], viewportValue = parsed.viewport) {
236645
+ async function captureMeasureSnap(parsed, targetRef = parsed.positional[0], viewportValue = parsed.viewport, colorSchemeValue = parsed.colorScheme) {
236572
236646
  if (parsed.positional.length > 1) {
236573
236647
  throw invalidInput("measure snap accepts at most one positional URL or snapshot reference");
236574
236648
  }
@@ -236581,6 +236655,7 @@ async function captureMeasureSnap(parsed, targetRef = parsed.positional[0], view
236581
236655
  }
236582
236656
  }
236583
236657
  const viewport2 = viewportValue === void 0 ? void 0 : { label: viewportValue, ...parseViewport(viewportValue) };
236658
+ const colorScheme = parseColorScheme(colorSchemeValue);
236584
236659
  let base;
236585
236660
  let baseUrl = null;
236586
236661
  if (targetRef && !isUrlRef(targetRef)) {
@@ -236595,7 +236670,7 @@ async function captureMeasureSnap(parsed, targetRef = parsed.positional[0], view
236595
236670
  try {
236596
236671
  let artifacts = [];
236597
236672
  await withConnection(connectionArgs, async (client, tab) => {
236598
- await withAppliedViewport(client, viewport2, async () => {
236673
+ await withAppliedColorScheme(client, colorScheme, async () => withAppliedViewport(client, viewport2, async () => {
236599
236674
  const oneshot = active ? void 0 : createOneshotSession("measure");
236600
236675
  const destination = active ? path22.join(active.dir, "measure", "snaps") : oneshot.artifactsDir;
236601
236676
  snapDir = path22.join(destination, snapId);
@@ -236611,10 +236686,11 @@ async function captureMeasureSnap(parsed, targetRef = parsed.positional[0], view
236611
236686
  pixels: parsed.pixels,
236612
236687
  state: parsed.state ?? [],
236613
236688
  viewport: viewport2?.label ?? null,
236689
+ colorScheme,
236614
236690
  skipCollectors: parsed.skipCollector ?? []
236615
236691
  });
236616
236692
  artifacts = result.artifacts;
236617
- });
236693
+ }));
236618
236694
  }, { settle: 0 });
236619
236695
  if (!snapDir) throw new Error("measure snap did not allocate an artifact directory");
236620
236696
  return { id: snapId, dir: snapDir, artifacts, ...base ? { base } : {} };
@@ -236640,6 +236716,7 @@ function buildSnapshotResult(captured) {
236640
236716
  attrs: {
236641
236717
  url: meta.url ?? void 0,
236642
236718
  viewport: meta.viewport ?? void 0,
236719
+ "color-scheme": meta.colorScheme,
236643
236720
  elements,
236644
236721
  settled: meta.settled,
236645
236722
  "settle-ms": meta.settleMs,
@@ -236670,7 +236747,7 @@ function classifySnapError(err) {
236670
236747
  if (message.startsWith("--viewport cannot ")) {
236671
236748
  return { status: "viewport_unavailable", message };
236672
236749
  }
236673
- if (message.startsWith("measure snap accepts") || message.startsWith("--settle-timeout") || message.startsWith("--viewport")) {
236750
+ if (message.startsWith("measure snap accepts") || message.startsWith("--settle-timeout") || message.startsWith("--viewport") || message.startsWith("--color-scheme")) {
236674
236751
  return { status: "invalid_input", message };
236675
236752
  }
236676
236753
  if (message.includes("No tab found") || message.includes("Use --target") || message.includes("Tab has no WebSocket debugger URL")) {
@@ -236715,9 +236792,10 @@ async function cmdMeasureSnap(parsed, _args) {
236715
236792
  if (value === void 0) return void 0;
236716
236793
  return { label: value, ...parseViewport(value) };
236717
236794
  });
236795
+ const colorScheme = parseColorScheme(parsed.colorScheme);
236718
236796
  const results = [];
236719
236797
  for (const viewport2 of viewports) {
236720
- const capture = await captureMeasureSnap(parsed, parsed.positional[0], viewport2?.label);
236798
+ const capture = await captureMeasureSnap(parsed, parsed.positional[0], viewport2?.label, colorScheme);
236721
236799
  captures.push(capture);
236722
236800
  results.push(buildSnapshotResult(capture));
236723
236801
  }
@@ -236767,6 +236845,7 @@ var init_snap = __esm({
236767
236845
  init_connection();
236768
236846
  init_recorder_client();
236769
236847
  init_viewport();
236848
+ init_color_scheme();
236770
236849
  init_snapshot();
236771
236850
  init_redaction();
236772
236851
  init_settle();
@@ -236787,8 +236866,9 @@ input:
236787
236866
  --pixels also write per-element raster crops
236788
236867
  --state <state[:selector]> force a pseudo-state or real control state (repeatable)
236789
236868
  --viewport <WxH> temporarily capture at a CSS-pixel viewport (repeatable); exact <positive-safe-int>x<positive-safe-int> grammar with lowercase x and no whitespace
236869
+ --color-scheme <dark|light> temporarily capture under this prefers-color-scheme value; cleared afterward
236790
236870
  output: <snapshot id=\u2026 path=\u2026> \u2014 the settled artifact directory (geometry, styles, hit-test, text, forms, animation, ax, queries, focus, scroll, layers; per-element crops with --pixels) every other measure/motion query leaf reads instead of re-driving the browser; --json mirrors
236791
- effects: drives the target page to capture; writes one snapshot artifact directory under the active (or a private one-shot) session`;
236871
+ effects: drives the target page to capture; writes one snapshot artifact directory under the active (or a private one-shot) session. --color-scheme transiently applies Emulation.setEmulatedMedia and clears it after capture.`;
236792
236872
  }
236793
236873
  });
236794
236874
 
@@ -240154,7 +240234,7 @@ Every leaf defaults to rendered prose; --json mirrors the same result.
240154
240234
  Findings exit 0 \u2014 a report, not a failure.
240155
240235
  \`--gate\` (exit 2 on findings/changes) is accepted only by check and diff.
240156
240236
 
240157
- <subcommand name="snap" args="[url|snap] [--freeze-animations] [--settle-timeout <ms>] [--capture-unsettled] [--pixels] [--state <state[:selector]>]... [--viewport <WxH>]..." whenToUse="drive + write the settled snapshot substrate every other leaf reads"/>
240237
+ <subcommand name="snap" args="[url|snap] [--freeze-animations] [--settle-timeout <ms>] [--capture-unsettled] [--pixels] [--state <state[:selector]>]... [--viewport <WxH>]... [--color-scheme <dark|light>]" whenToUse="drive + write the settled snapshot substrate every other leaf reads"/>
240158
240238
  <subcommand name="check" args="[url|snap] [--for <checks>] [--gate]" whenToUse="read threshold/fact measurements from one snapshot"/>
240159
240239
  <subcommand name="diff" args="--before <snap> --after <snap> [--pixels] [--full] [--gate]" whenToUse="structured before/after delta between two snapshots"/>
240160
240240
  <subcommand name="census" args="[--snap <id>]... [--url <url>]... [--set-file <path>] --axis <axis>" whenToUse="value distributions across one or more snapshots"/>
@@ -242098,7 +242178,7 @@ CDP_PORT / CDP_TARGET pin the browser + tab for orchestrators \u2014 precedence:
242098
242178
  explicit flag > active session > env.`;
242099
242179
  }
242100
242180
  function printVersion() {
242101
- const declared = "1.3.19";
242181
+ const declared = "1.3.20";
242102
242182
  if (declared) {
242103
242183
  console.log(declared);
242104
242184
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/capture",
3
- "version": "1.3.19",
3
+ "version": "1.3.20",
4
4
  "description": "Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec",
5
5
  "bin": {
6
6
  "capture": "./bin/capture"
package/src/cdp/args.ts CHANGED
@@ -15,7 +15,7 @@ export function expandEqualsFlags(argv: string[]): string[] {
15
15
  }
16
16
 
17
17
  const VALUE_FLAGS = new Set([
18
- '--port', '--out', '--duration', '--settle', '--file', '--target', '--url', '--into', '--viewport', '--session',
18
+ '--port', '--out', '--duration', '--settle', '--file', '--target', '--url', '--into', '--viewport', '--color-scheme', '--session',
19
19
  '--filter', '--name', '--filter-url', '--filter-status', '--filter-method', '--limit', '--params', '--wait-event',
20
20
  '--timeout', '--socket', '--settle-timeout', '--state', '--for', '--before', '--after', '--snap', '--set-file',
21
21
  '--axis', '--from', '--to', '--viewport-height', '--rec-id', '--selector', '--do', '--element', '--prop', '--action', '--occurrence',
@@ -126,6 +126,9 @@ export function validateCliInvocation(parsed: ParsedArgs): void {
126
126
  throw invalidInput(`Invalid --viewport: ${parsed.viewport}.`);
127
127
  }
128
128
  }
129
+ if (leaf === 'shot' && parsed.colorScheme !== undefined && parsed.colorScheme !== 'dark' && parsed.colorScheme !== 'light') {
130
+ throw invalidInput(`Invalid --color-scheme: ${parsed.colorScheme}; expected dark or light.`);
131
+ }
129
132
  return;
130
133
  }
131
134
 
@@ -143,6 +146,9 @@ export function validateCliInvocation(parsed: ParsedArgs): void {
143
146
 
144
147
  if (parsed.command === 'measure') {
145
148
  if (leaf === 'snap' || leaf === 'check' || leaf === 'sweep') requireCount(values, 0, 1, `measure ${leaf}`);
149
+ if (leaf === 'snap' && parsed.colorScheme !== undefined && parsed.colorScheme !== 'dark' && parsed.colorScheme !== 'light') {
150
+ throw invalidInput(`Invalid --color-scheme: ${parsed.colorScheme}; expected dark or light.`);
151
+ }
146
152
  if (leaf === 'diff' || leaf === 'census') requireCount(values, 0, 0, `measure ${leaf}`);
147
153
  if (leaf === 'diff' && (!parsed.before || !parsed.after)) throw invalidInput('measure diff requires --before and --after.');
148
154
  if (leaf === 'census' && !['color', 'font', 'spacing', 'radius', 'shadow', 'animation', 'geometry', 'media', 'queries'].includes(parsed.axis ?? '')) {
@@ -233,6 +239,7 @@ export function parseCliSyntax(argv: string[]): ParsedArgs {
233
239
  else if (arg === '--into') { parsed.into = valueFor(arg, next); i++; }
234
240
  else if (arg === '--no-screenshot') parsed.noScreenshot = true;
235
241
  else if (arg === '--viewport') { const value = valueFor(arg, next); parsed.viewport = value; (parsed.viewports ??= []).push(value); i++; }
242
+ else if (arg === '--color-scheme') { parsed.colorScheme = valueFor(arg, next); i++; }
236
243
  else if (arg === '--full-page') parsed.fullPage = true;
237
244
  else if (arg === '--all') parsed.all = true;
238
245
  else if (arg === '--session') { parsed.session = valueFor(arg, next); i++; }
@@ -0,0 +1,47 @@
1
+ import type { CDPClient } from './client.js';
2
+
3
+ export type ColorScheme = 'dark' | 'light';
4
+
5
+ type MediaClient = Pick<CDPClient, 'send'>;
6
+
7
+ export function parseColorScheme(value: string | undefined): ColorScheme | undefined {
8
+ if (value === undefined) return undefined;
9
+ if (value === 'dark' || value === 'light') return value;
10
+ throw new Error('--color-scheme must be dark or light');
11
+ }
12
+
13
+ /** Applies one media-feature override only for the supplied capture operation. */
14
+ export async function withAppliedColorScheme<T>(client: MediaClient, colorScheme: ColorScheme | undefined, fn: () => Promise<T>): Promise<T> {
15
+ if (!colorScheme) return fn();
16
+ let ownsMediaOverride = false;
17
+ let primaryFailed = false;
18
+ let primaryError: unknown;
19
+ try {
20
+ // A rejected response does not prove Chrome rejected the request. Claim
21
+ // cleanup responsibility before awaiting it, just as viewport capture does.
22
+ ownsMediaOverride = true;
23
+ await client.send('Emulation.setEmulatedMedia', {
24
+ features: [{ name: 'prefers-color-scheme', value: colorScheme }],
25
+ });
26
+ return await fn();
27
+ } catch (error) {
28
+ primaryFailed = true;
29
+ primaryError = error;
30
+ throw error;
31
+ } finally {
32
+ if (ownsMediaOverride) {
33
+ try {
34
+ await client.send('Emulation.setEmulatedMedia', { features: [] });
35
+ } catch (cleanupError) {
36
+ if (primaryFailed) {
37
+ throw new AggregateError(
38
+ [primaryError, cleanupError],
39
+ 'Color-scheme capture failed and media-emulation cleanup also failed.',
40
+ { cause: primaryError },
41
+ );
42
+ }
43
+ throw cleanupError;
44
+ }
45
+ }
46
+ }
47
+ }
@@ -36,7 +36,7 @@ Every leaf defaults to rendered prose; --json mirrors the same result.
36
36
  Findings exit 0 — a report, not a failure.
37
37
  \`--gate\` (exit 2 on findings/changes) is accepted only by check and diff.
38
38
 
39
- <subcommand name="snap" args="[url|snap] [--freeze-animations] [--settle-timeout <ms>] [--capture-unsettled] [--pixels] [--state <state[:selector]>]... [--viewport <WxH>]..." whenToUse="drive + write the settled snapshot substrate every other leaf reads"/>
39
+ <subcommand name="snap" args="[url|snap] [--freeze-animations] [--settle-timeout <ms>] [--capture-unsettled] [--pixels] [--state <state[:selector]>]... [--viewport <WxH>]... [--color-scheme <dark|light>]" whenToUse="drive + write the settled snapshot substrate every other leaf reads"/>
40
40
  <subcommand name="check" args="[url|snap] [--for <checks>] [--gate]" whenToUse="read threshold/fact measurements from one snapshot"/>
41
41
  <subcommand name="diff" args="--before <snap> --after <snap> [--pixels] [--full] [--gate]" whenToUse="structured before/after delta between two snapshots"/>
42
42
  <subcommand name="census" args="[--snap <id>]... [--url <url>]... [--set-file <path>] --axis <axis>" whenToUse="value distributions across one or more snapshots"/>
@@ -6,6 +6,7 @@ import { type ParsedArgs } from '../../types.js';
6
6
  import { withConnection } from '../../connection.js';
7
7
  import { isRecorderHeldClient } from '../../recorder-client.js';
8
8
  import { parseViewport, type Viewport } from '../../viewport.js';
9
+ import { parseColorScheme, withAppliedColorScheme, type ColorScheme } from '../../color-scheme.js';
9
10
  import { captureSnapshotSubstrate, COLLECTOR_NAMES, SnapshotCaptureTimeout } from '../../measure/snapshot.js';
10
11
  import type { CollectorOutcome } from '../../measure/types.js';
11
12
  import { sanitizeString } from '../../measure/redaction.js';
@@ -36,8 +37,9 @@ input:
36
37
  --pixels also write per-element raster crops
37
38
  --state <state[:selector]> force a pseudo-state or real control state (repeatable)
38
39
  --viewport <WxH> temporarily capture at a CSS-pixel viewport (repeatable); exact <positive-safe-int>x<positive-safe-int> grammar with lowercase x and no whitespace
40
+ --color-scheme <dark|light> temporarily capture under this prefers-color-scheme value; cleared afterward
39
41
  output: <snapshot id=… path=…> — the settled artifact directory (geometry, styles, hit-test, text, forms, animation, ax, queries, focus, scroll, layers; per-element crops with --pixels) every other measure/motion query leaf reads instead of re-driving the browser; --json mirrors
40
- effects: drives the target page to capture; writes one snapshot artifact directory under the active (or a private one-shot) session`;
42
+ effects: drives the target page to capture; writes one snapshot artifact directory under the active (or a private one-shot) session. --color-scheme transiently applies Emulation.setEmulatedMedia and clears it after capture.`;
41
43
 
42
44
  interface SnapshotMetaForBase {
43
45
  readonly url?: string | null;
@@ -216,7 +218,7 @@ function settledNote(settled: boolean, captured: boolean, settleMs: number, sett
216
218
  * seam rather than importing the substrate directly, preserving snap as the
217
219
  * sole live-driving primitive.
218
220
  */
219
- export async function captureMeasureSnap(parsed: ParsedArgs, targetRef = parsed.positional[0], viewportValue = parsed.viewport): Promise<MeasureSnapCapture> {
221
+ export async function captureMeasureSnap(parsed: ParsedArgs, targetRef = parsed.positional[0], viewportValue = parsed.viewport, colorSchemeValue = parsed.colorScheme): Promise<MeasureSnapCapture> {
220
222
  if (parsed.positional.length > 1) {
221
223
  throw invalidInput('measure snap accepts at most one positional URL or snapshot reference');
222
224
  }
@@ -231,6 +233,7 @@ export async function captureMeasureSnap(parsed: ParsedArgs, targetRef = parsed.
231
233
  const viewport: ViewportSpec | undefined = viewportValue === undefined
232
234
  ? undefined
233
235
  : { label: viewportValue, ...parseViewport(viewportValue) };
236
+ const colorScheme = parseColorScheme(colorSchemeValue);
234
237
 
235
238
  let base: SnapRef | undefined;
236
239
  let baseUrl: string | null = null;
@@ -256,7 +259,7 @@ export async function captureMeasureSnap(parsed: ParsedArgs, targetRef = parsed.
256
259
  try {
257
260
  let artifacts: readonly string[] = [];
258
261
  await withConnection(connectionArgs, async (client, tab) => {
259
- await withAppliedViewport(client, viewport, async () => {
262
+ await withAppliedColorScheme(client, colorScheme, async () => withAppliedViewport(client, viewport, async () => {
260
263
  const oneshot = active ? undefined : createOneshotSession('measure');
261
264
  const destination = active ? path.join(active.dir, 'measure', 'snaps') : oneshot!.artifactsDir;
262
265
  snapDir = path.join(destination, snapId);
@@ -272,10 +275,11 @@ export async function captureMeasureSnap(parsed: ParsedArgs, targetRef = parsed.
272
275
  pixels: parsed.pixels,
273
276
  state: parsed.state ?? [],
274
277
  viewport: viewport?.label ?? null,
278
+ colorScheme,
275
279
  skipCollectors: parsed.skipCollector ?? [],
276
280
  });
277
281
  artifacts = result.artifacts;
278
- });
282
+ }));
279
283
  }, { settle: 0 });
280
284
 
281
285
  if (!snapDir) throw new Error('measure snap did not allocate an artifact directory');
@@ -290,6 +294,7 @@ function buildSnapshotResult(captured: MeasureSnapCapture): RenderableResult {
290
294
  const meta = readMeta<{
291
295
  url: string | null;
292
296
  viewport: string | null;
297
+ colorScheme?: ColorScheme;
293
298
  settled: boolean;
294
299
  settleMs: number;
295
300
  settleTimeoutMs: number;
@@ -313,6 +318,7 @@ function buildSnapshotResult(captured: MeasureSnapCapture): RenderableResult {
313
318
  attrs: {
314
319
  url: meta.url ?? undefined,
315
320
  viewport: meta.viewport ?? undefined,
321
+ 'color-scheme': meta.colorScheme,
316
322
  elements,
317
323
  settled: meta.settled,
318
324
  'settle-ms': meta.settleMs,
@@ -358,7 +364,7 @@ export function classifySnapError(err: unknown): SnapErrorDetails {
358
364
  if (message.startsWith('--viewport cannot ')) {
359
365
  return { status: 'viewport_unavailable', message };
360
366
  }
361
- if (message.startsWith('measure snap accepts') || message.startsWith('--settle-timeout') || message.startsWith('--viewport')) {
367
+ if (message.startsWith('measure snap accepts') || message.startsWith('--settle-timeout') || message.startsWith('--viewport') || message.startsWith('--color-scheme')) {
362
368
  return { status: 'invalid_input', message };
363
369
  }
364
370
  if (message.includes('No tab found') || message.includes('Use --target') || message.includes('Tab has no WebSocket debugger URL')) {
@@ -408,9 +414,10 @@ export async function cmdMeasureSnap(parsed: ParsedArgs, _args: string[]): Promi
408
414
  if (value === undefined) return undefined;
409
415
  return { label: value, ...parseViewport(value) };
410
416
  });
417
+ const colorScheme = parseColorScheme(parsed.colorScheme);
411
418
  const results = [] as RenderableResult[];
412
419
  for (const viewport of viewports) {
413
- const capture = await captureMeasureSnap(parsed, parsed.positional[0], viewport?.label);
420
+ const capture = await captureMeasureSnap(parsed, parsed.positional[0], viewport?.label, colorScheme);
414
421
  captures.push(capture);
415
422
  results.push(buildSnapshotResult(capture));
416
423
  }
@@ -39,7 +39,7 @@ During a live composed recording every verb routes through the recorder.
39
39
  <subcommand name="scroll" args="<target> --to <top|bottom|px> [--no-screenshot]" whenToUse="scroll one resolved container to a position"/>
40
40
  <subcommand name="navigate" args="<url> [--settle <ms>]" whenToUse="navigate the tab and wait for load + settle"/>
41
41
  <subcommand name="exec" args="<code> | --file <path>" whenToUse="run arbitrary JS in the tab (expressions, return, await)"/>
42
- <subcommand name="shot" args="[--viewport <WxH>] [--full-page] [--out <path>]" whenToUse="look at the page right now without acting"/>
42
+ <subcommand name="shot" args="[--viewport <WxH>] [--full-page] [--color-scheme <dark|light>] [--out <path>]" whenToUse="look at the page right now without acting"/>
43
43
  <subcommand name="elements" args="[--all] [--limit <n>]" whenToUse="list what can be acted on — role, name, backend:<id> per element"/>
44
44
 
45
45
  capture page <leaf> -h Per-leaf usage`;
@@ -4,8 +4,9 @@
4
4
  * The navigational look: vision as navigator, never inspector — cheap, no
5
5
  * settling, no collectors. With no flags it captures the browser's ACTUAL
6
6
  * current viewport and performs zero `Emulation.*` calls; emulation exists
7
- * only when explicitly asked (`--viewport <WxH>` / `--full-page`), applied
8
- * as a transient `Emulation.setDeviceMetricsOverride` (+ ~150ms re-layout)
7
+ * only when explicitly asked (`--viewport <WxH>` / `--full-page` /
8
+ * `--color-scheme dark|light`), applied transiently; viewport emulation uses
9
+ * `Emulation.setDeviceMetricsOverride` (+ ~150ms re-layout)
9
10
  * and cleared after — two page-observable resizes, declared as the leaf's
10
11
  * effect in `-h` (I-1/I-2 posture). Explicit viewport emulation uses the
11
12
  * single `WxH` grammar (D10).
@@ -20,6 +21,7 @@ import { type ParsedArgs } from '../../types.js';
20
21
  import { withConnection } from '../../connection.js';
21
22
  import { captureScreenshotWithCssViewport, type CapturedRegion } from '../../screenshot.js';
22
23
  import { parseViewport, type Viewport } from '../../viewport.js';
24
+ import { parseColorScheme, withAppliedColorScheme, type ColorScheme } from '../../color-scheme.js';
23
25
  import { nextStepPath } from '../../../session-context.js';
24
26
  import { createOneshotSession } from '../../../session/commands.js';
25
27
  import { assertUnderCaptureRoot, writeBinaryPrivate } from '../../../session/artifacts.js';
@@ -36,13 +38,14 @@ const USAGE = `capture page shot — capture a PNG of the tab as it is right now
36
38
  input:
37
39
  --viewport <WxH> transient device-metrics override for this capture; grammar: <positive-safe-int>x<positive-safe-int>, exact lowercase x with no whitespace — preset names are not accepted. Absent → no emulation, the browser's actual current viewport is captured
38
40
  --full-page transient override to the full scrollable content height for this capture
41
+ --color-scheme <dark|light> transient prefers-color-scheme override for this capture; cleared afterward
39
42
  --out <path> destination file; default: the active session's shots/ sequence, or a fresh oneshot-*/page/ dir under the capture root when no session is active
40
43
  --target <tabId> | --url <pattern> | --port <n> tab targeting; defaults to the active session tab
41
44
  --json mirror the result as JSON
42
45
  output:
43
- <screenshot path=… width=… height=… css-width=… css-height=… css-x=… css-y=… css-to-image-x=… css-to-image-y=… emulation=none|viewport|full-page> — saved path, PNG pixel dimensions, the CSS-pixel region the image covers (its page-coordinate origin and size) with the CSS-to-image scale derived from it, byte size, and the emulation fact (whether a transient override was applied, or its absence)
46
+ <screenshot path=… width=… height=… css-width=… css-height=… css-x=… css-y=… css-to-image-x=… css-to-image-y=… emulation=none|viewport|full-page color-scheme=dark|light> — saved path, PNG pixel dimensions, the CSS-pixel region the image covers (its page-coordinate origin and size) with the CSS-to-image scale derived from it, byte size, and any requested transient emulation
44
47
  effects:
45
- no flags: none — the capture reads the viewport as-is, with zero Emulation.* calls. With --viewport/--full-page: applies a transient Emulation.setDeviceMetricsOverride (~150ms re-layout wait) and clears it after the capture — two page-observable resizes (resize events fire; media queries flip and flip back)`;
48
+ no flags: none — the capture reads the viewport as-is, with zero Emulation.* calls. --viewport/--full-page applies a transient Emulation.setDeviceMetricsOverride (~150ms re-layout wait) and clears it after the capture — two page-observable resizes. --color-scheme applies a transient Emulation.setEmulatedMedia prefers-color-scheme override and clears it after the capture.`;
46
49
 
47
50
  // ---------------------------------------------------------------------------
48
51
  // Test-injectable dependency seam (the CDP-stub test pattern; the capture
@@ -128,6 +131,7 @@ export function buildScreenshotResult(f: {
128
131
  cssViewport?: CapturedRegion;
129
132
  emulation: EmulationMode;
130
133
  viewport?: { width: number; height: number };
134
+ colorScheme?: ColorScheme;
131
135
  }): RenderableResult {
132
136
  // The denominator is the CSS region the image was actually cut from, so the
133
137
  // ratio stays true under page zoom, device pixel ratio, and the local
@@ -140,12 +144,15 @@ export function buildScreenshotResult(f: {
140
144
  ? fact`CSS-to-image scale: ${scale.x.toFixed(6)} image px/CSS px horizontally and ${scale.y.toFixed(6)} image px/CSS px vertically (captured CSS region ${round6(f.cssViewport.width)}×${round6(f.cssViewport.height)} at page origin ${round6(f.cssViewport.x)},${round6(f.cssViewport.y)}).`
141
145
  : text`CSS-to-image scale unavailable: the browser did not return the CSS region this capture covers.`,
142
146
  f.emulation === 'none'
143
- ? text`emulation: none — the browser's actual current viewport was captured; no Emulation call was made.`
147
+ ? text`viewport emulation: none — the browser's actual current viewport was captured.`
144
148
  : f.emulation === 'viewport'
145
149
  ? fact`emulation: transient ${f.viewport!.width}x${f.viewport!.height} device-metrics override applied for the capture and cleared after — two page-observable resizes.`
146
150
  : f.viewport
147
151
  ? fact`emulation: transient full-page device-metrics override (from ${f.viewport.width}x${f.viewport.height}) applied for the capture and cleared after — two page-observable resizes.`
148
152
  : text`emulation: transient full-page device-metrics override applied for the capture and cleared after — two page-observable resizes.`,
153
+ f.colorScheme
154
+ ? fact`color scheme: transient prefers-color-scheme=${f.colorScheme} override applied for the capture and cleared after.`
155
+ : text`color scheme: browser default — no Emulation.setEmulatedMedia call was made.`,
149
156
  ];
150
157
  return {
151
158
  tag: 'screenshot',
@@ -160,6 +167,7 @@ export function buildScreenshotResult(f: {
160
167
  'css-to-image-x': scale?.x.toFixed(6),
161
168
  'css-to-image-y': scale?.y.toFixed(6),
162
169
  emulation: f.emulation,
170
+ 'color-scheme': f.colorScheme,
163
171
  },
164
172
  summary: f.dimensions
165
173
  ? fact`saved ${f.path} — ${f.dimensions.width}x${f.dimensions.height}px, ${f.bytes} bytes.`
@@ -183,7 +191,7 @@ export async function cmdPageShot(parsed: ParsedArgs, _args: string[]): Promise<
183
191
  {
184
192
  tag: 'error',
185
193
  attrs: { command: 'page shot', code: 'invalid_input' },
186
- summary: fact`received: ${parsed.positional.length} positional arguments; expected none — \`capture page shot [--viewport <WxH>] [--full-page] [--out <path>]\`.`,
194
+ summary: fact`received: ${parsed.positional.length} positional arguments; expected none — \`capture page shot [--viewport <WxH>] [--full-page] [--color-scheme <dark|light>] [--out <path>]\`.`,
187
195
  },
188
196
  { json: parsed.json },
189
197
  );
@@ -209,6 +217,22 @@ export async function cmdPageShot(parsed: ParsedArgs, _args: string[]): Promise<
209
217
  }
210
218
  }
211
219
 
220
+ let colorScheme: ColorScheme | undefined;
221
+ try {
222
+ colorScheme = parseColorScheme(parsed.colorScheme);
223
+ } catch {
224
+ emitResult(
225
+ {
226
+ tag: 'error',
227
+ attrs: { command: 'page shot', code: 'invalid_color_scheme' },
228
+ summary: fact`received: --color-scheme ${parsed.colorScheme}; expected dark or light.`,
229
+ },
230
+ { json: parsed.json },
231
+ );
232
+ process.exitCode = 1;
233
+ return;
234
+ }
235
+
212
236
  const emulation: EmulationMode = parsed.fullPage ? 'full-page' : viewport ? 'viewport' : 'none';
213
237
 
214
238
  // connection.ts derives the recorder-routed action label from
@@ -216,14 +240,14 @@ export async function cmdPageShot(parsed: ParsedArgs, _args: string[]): Promise<
216
240
  // restore the verb so stderr diagnostics identify this leaf.
217
241
  const result = await deps.withConnection(
218
242
  { ...parsed, command: 'shot' },
219
- async (client) => {
243
+ async (client) => withAppliedColorScheme(client, colorScheme, async () => {
220
244
  const screenshot = await captureScreenshotWithCssViewport(client, viewport, { fullPage: parsed.fullPage });
221
245
  const outPath = await resolveOutPath(parsed);
222
246
  writeScreenshot(outPath, screenshot.png);
223
247
  return { path: outPath, bytes: screenshot.png.length, dimensions: pngDimensions(screenshot.png), cssViewport: screenshot.cssViewport };
224
- },
248
+ }),
225
249
  { settle: 0 },
226
250
  );
227
251
 
228
- emitResult(buildScreenshotResult({ ...result, emulation, viewport }), { json: parsed.json });
252
+ emitResult(buildScreenshotResult({ ...result, emulation, viewport, colorScheme }), { json: parsed.json });
229
253
  }
@@ -250,6 +250,7 @@ export async function captureSnapshotSubstrate(options: CaptureSnapshotOptions):
250
250
  const pixels = options.pixels ?? false;
251
251
  const state = options.state ?? [];
252
252
  const viewport = options.viewport ?? null;
253
+ const colorScheme = options.colorScheme;
253
254
  // Apply the shared artifact-string cap once at the source. URL contents
254
255
  // otherwise survive unchanged in meta.json, the result, and collector context.
255
256
  const url = options.url ? sanitizeString(options.url) : null;
@@ -483,6 +484,7 @@ export async function captureSnapshotSubstrate(options: CaptureSnapshotOptions):
483
484
  captureUnsettled,
484
485
  pixels,
485
486
  states: state.map((spec) => sanitizeString(spec)),
487
+ ...(colorScheme ? { colorScheme } : {}),
486
488
  unstableRegionCount: unstableRegions.length,
487
489
  ...(freezeAnimations ? { animationsRestored: animationsRestored ?? false } : {}),
488
490
  // #8: the browser-wide `Animation.setPlaybackRate({playbackRate:0})`
@@ -53,6 +53,8 @@ export interface CaptureSnapshotOptions {
53
53
  /** `--state <state[:selector]>` (repeatable); default `[]`. */
54
54
  readonly state?: readonly string[];
55
55
  readonly viewport?: string | null;
56
+ /** Transient `prefers-color-scheme` media override used while capturing. */
57
+ readonly colorScheme?: 'dark' | 'light';
56
58
  /**
57
59
  * Poll interval for the settledness loop; default {@link DEFAULT_POLL_INTERVAL_MS}
58
60
  * (100ms). Not part of the CLI surface — a test-only override so the
@@ -109,6 +111,8 @@ export interface SnapshotMeta extends SnapMeta {
109
111
  readonly captureUnsettled: boolean;
110
112
  readonly pixels: boolean;
111
113
  readonly states: readonly string[];
114
+ /** Present only when this snapshot applied a transient color-scheme override. */
115
+ readonly colorScheme?: 'dark' | 'light';
112
116
  readonly unstableRegionCount: number;
113
117
  /** Present only when `freezeAnimations` is true — whether every animation `freezeAnimationsBeforeCapture` paused was confirmed resumed to its pre-freeze `playState` (I-6). `false` means restoration could not be guaranteed (origin capture failed, or the restore call itself threw) — never omitted to imply a clean restore. */
114
118
  readonly animationsRestored?: boolean;
package/src/cdp/types.ts CHANGED
@@ -38,6 +38,8 @@ export interface ParsedArgs {
38
38
  into?: string;
39
39
  noScreenshot?: boolean;
40
40
  viewport?: string;
41
+ /** `page shot|measure snap --color-scheme dark|light` — transient media override. */
42
+ colorScheme?: string;
41
43
  fullPage?: boolean;
42
44
  help?: boolean;
43
45
  /** `--all` — boolean scope-widener (per-leaf semantics). */