cbrowser 18.70.0 → 18.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/browser.d.ts +32 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +46 -2
  4. package/dist/browser.js.map +1 -1
  5. package/dist/cli.js +1475 -24
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cognitive/index.d.ts +10 -0
  8. package/dist/cognitive/index.d.ts.map +1 -1
  9. package/dist/cognitive/index.js +8 -0
  10. package/dist/cognitive/index.js.map +1 -1
  11. package/dist/daemon.d.ts.map +1 -1
  12. package/dist/daemon.js +151 -3
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/mcp-server.d.ts.map +1 -1
  15. package/dist/mcp-server.js +67 -0
  16. package/dist/mcp-server.js.map +1 -1
  17. package/dist/mcp-tools/base/capture-tools.d.ts +189 -0
  18. package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -0
  19. package/dist/mcp-tools/base/capture-tools.js +667 -0
  20. package/dist/mcp-tools/base/capture-tools.js.map +1 -0
  21. package/dist/mcp-tools/base/gif-tools.d.ts +19 -0
  22. package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
  23. package/dist/mcp-tools/base/gif-tools.js +45 -27
  24. package/dist/mcp-tools/base/gif-tools.js.map +1 -1
  25. package/dist/mcp-tools/base/index.d.ts +2 -1
  26. package/dist/mcp-tools/base/index.d.ts.map +1 -1
  27. package/dist/mcp-tools/base/index.js +5 -1
  28. package/dist/mcp-tools/base/index.js.map +1 -1
  29. package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
  30. package/dist/mcp-tools/tool-categories.js +11 -0
  31. package/dist/mcp-tools/tool-categories.js.map +1 -1
  32. package/dist/recording/auto-capture.d.ts +72 -0
  33. package/dist/recording/auto-capture.d.ts.map +1 -0
  34. package/dist/recording/auto-capture.js +155 -0
  35. package/dist/recording/auto-capture.js.map +1 -0
  36. package/dist/recording/contact-sheet.d.ts +65 -0
  37. package/dist/recording/contact-sheet.d.ts.map +1 -0
  38. package/dist/recording/contact-sheet.js +204 -0
  39. package/dist/recording/contact-sheet.js.map +1 -0
  40. package/dist/recording/encoders.d.ts +101 -0
  41. package/dist/recording/encoders.d.ts.map +1 -0
  42. package/dist/recording/encoders.js +304 -0
  43. package/dist/recording/encoders.js.map +1 -0
  44. package/dist/recording/engine.d.ts +263 -0
  45. package/dist/recording/engine.d.ts.map +1 -0
  46. package/dist/recording/engine.js +896 -0
  47. package/dist/recording/engine.js.map +1 -0
  48. package/dist/recording/ssim.d.ts +78 -0
  49. package/dist/recording/ssim.d.ts.map +1 -0
  50. package/dist/recording/ssim.js +209 -0
  51. package/dist/recording/ssim.js.map +1 -0
  52. package/dist/recording/timing.d.ts +124 -0
  53. package/dist/recording/timing.d.ts.map +1 -0
  54. package/dist/recording/timing.js +251 -0
  55. package/dist/recording/timing.js.map +1 -0
  56. package/dist/recording/types.d.ts +316 -0
  57. package/dist/recording/types.d.ts.map +1 -0
  58. package/dist/recording/types.js +337 -0
  59. package/dist/recording/types.js.map +1 -0
  60. package/dist/testing/nl-test-suite.d.ts +10 -0
  61. package/dist/testing/nl-test-suite.d.ts.map +1 -1
  62. package/dist/testing/nl-test-suite.js +13 -1
  63. package/dist/testing/nl-test-suite.js.map +1 -1
  64. package/dist/types.d.ts +22 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js.map +1 -1
  67. package/package.json +1 -1
@@ -0,0 +1,251 @@
1
+ /**
2
+ * CBrowser - Cognitive Browser Automation
3
+ * Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
4
+ * Learn more at https://cbrowser.ai - MIT License
5
+ */
6
+ /**
7
+ * Minimum per-frame delay. GIF and WebP treat sub-20ms delays inconsistently
8
+ * (many decoders silently substitute 100ms), so delays are floored here.
9
+ */
10
+ export const MIN_DELAY_MS = 20;
11
+ /**
12
+ * Maximum per-frame delay.
13
+ *
14
+ Sharp validates each delay against 0..65535 and rejects the whole encode
15
+ * otherwise: "Expected integer or an array of integers between 0 and 65535 for
16
+ * delay". A capture left running while the page goes quiet produces exactly
17
+ * that: the last frame's delay stretches to cover the silent tail, and one
18
+ * out-of-range value destroys the artifact.
19
+ *
20
+ * Note this is sharp's API limit, not the format's. GIF stores delay as a
21
+ * uint16 of CENTIseconds, so a single frame can legally be held for up to
22
+ * 655_350ms — ten times what sharp will accept in one value. That gap is what
23
+ * {@link expandForPlayback} exploits: libvips merges identical consecutive
24
+ * frames and SUMS their delays, so N legal repeats collapse back into one page
25
+ * holding for their combined duration. Verified end-to-end in
26
+ * tests/recording-encoder-delays.test.ts.
27
+ *
28
+ * Emitting an illegal delay is therefore not a caller error to validate — it is
29
+ * this function's job to make impossible. See {@link expandForPlayback} for
30
+ * holding a frame longer than this ceiling without lying about the timing.
31
+ */
32
+ export const MAX_DELAY_MS = 65535;
33
+ /** Tolerance for float comparisons on millisecond sums. */
34
+ const EPS = 1e-9;
35
+ /**
36
+ * Per-frame display delay in milliseconds, derived from consecutive timestamps.
37
+ *
38
+ * Frame `i` is displayed for `tMs[i + 1] - tMs[i]`; the last frame is displayed
39
+ * until `totalDurationMs`. Any lead-in before the first frame (`tMs[0] > 0`) is
40
+ * folded into frame 0's delay, so the delays describe the whole recording.
41
+ *
42
+ * Clamp behaviour, applied in this order:
43
+ *
44
+ * 1. FLOOR at {@link MIN_DELAY_MS}. When the floor inflates the total, the
45
+ * surplus is reclaimed from the frames that have headroom above it,
46
+ * proportionally to how much headroom each has, so the sum stays exactly
47
+ * `totalDurationMs` and no frame is singled out. If the recording is too
48
+ * short for every frame to hold the floor
49
+ * (`frames * MIN_DELAY_MS > totalDurationMs`), the floor wins and the sum is
50
+ * `frames * MIN_DELAY_MS` — the animation is then longer than the capture,
51
+ * which is the only way to keep all frames visible.
52
+ * 2. CEILING at {@link MAX_DELAY_MS}, last, so the reclaim cannot push a delay
53
+ * back over it. A capped delay means the returned timeline is SHORTER than
54
+ * `totalDurationMs`: a frame held past the ceiling cannot be expressed as a
55
+ * single delay. That truncation is the safe failure — the alternative is an
56
+ * encode that dies and takes the whole artifact with it. To keep real-time
57
+ * playback across a long hold, use {@link expandForPlayback}, which repeats
58
+ * the held frame instead of truncating it.
59
+ *
60
+ * Resulting guarantee: every returned delay is within
61
+ * `[MIN_DELAY_MS, MAX_DELAY_MS]`, for every input this function accepts.
62
+ *
63
+ * @throws if `tMs` is empty, contains negative or decreasing values, or if
64
+ * `totalDurationMs` is shorter than the last timestamp.
65
+ */
66
+ export function delaysFromTimestamps(tMs, totalDurationMs, options = {}) {
67
+ if (tMs.length === 0) {
68
+ throw new Error("delaysFromTimestamps: timestamps must not be empty");
69
+ }
70
+ if (!Number.isFinite(totalDurationMs) || totalDurationMs < 0) {
71
+ throw new Error(`delaysFromTimestamps: totalDurationMs must be a non-negative finite number, got ${totalDurationMs}`);
72
+ }
73
+ for (let i = 0; i < tMs.length; i++) {
74
+ const t = tMs[i];
75
+ if (!Number.isFinite(t) || t < 0) {
76
+ throw new Error(`delaysFromTimestamps: timestamp ${i} is not a non-negative finite number`);
77
+ }
78
+ if (i > 0 && t < tMs[i - 1]) {
79
+ throw new Error(`delaysFromTimestamps: timestamps must be non-decreasing (index ${i}: ${tMs[i - 1]} -> ${t})`);
80
+ }
81
+ }
82
+ const last = tMs[tMs.length - 1];
83
+ if (totalDurationMs + EPS < last) {
84
+ throw new Error(`delaysFromTimestamps: totalDurationMs (${totalDurationMs}) is before the last timestamp (${last})`);
85
+ }
86
+ const n = tMs.length;
87
+ const delays = new Array(n);
88
+ for (let i = 0; i < n; i++) {
89
+ const end = i === n - 1 ? totalDurationMs : tMs[i + 1];
90
+ delays[i] = end - tMs[i];
91
+ }
92
+ // Fold the lead-in so the delays cover [0, totalDurationMs].
93
+ delays[0] = delays[0] + tMs[0];
94
+ let total = 0;
95
+ let headroom = 0;
96
+ for (let i = 0; i < n; i++) {
97
+ if (delays[i] < MIN_DELAY_MS)
98
+ delays[i] = MIN_DELAY_MS;
99
+ total += delays[i];
100
+ headroom += delays[i] - MIN_DELAY_MS;
101
+ }
102
+ const maxDelayMs = options.maxDelayMs ?? MAX_DELAY_MS;
103
+ if (maxDelayMs < MIN_DELAY_MS) {
104
+ throw new Error(`delaysFromTimestamps: maxDelayMs (${maxDelayMs}) is below MIN_DELAY_MS (${MIN_DELAY_MS})`);
105
+ }
106
+ let surplus = total - totalDurationMs;
107
+ if (surplus <= EPS || headroom <= 0)
108
+ return applyCeiling(delays, maxDelayMs);
109
+ // Proportional reclaim: cannot push any frame below the floor because each
110
+ // frame gives up at most its own share of the available headroom.
111
+ const take = Math.min(surplus, headroom);
112
+ for (let i = 0; i < n; i++) {
113
+ const room = delays[i] - MIN_DELAY_MS;
114
+ if (room <= 0)
115
+ continue;
116
+ delays[i] = delays[i] - (room / headroom) * take;
117
+ if (delays[i] < MIN_DELAY_MS)
118
+ delays[i] = MIN_DELAY_MS;
119
+ }
120
+ // Second pass absorbs float drift from the proportional split.
121
+ let achieved = 0;
122
+ for (let i = 0; i < n; i++)
123
+ achieved += delays[i];
124
+ surplus = achieved - totalDurationMs;
125
+ for (let i = 0; i < n && surplus > EPS; i++) {
126
+ const room = delays[i] - MIN_DELAY_MS;
127
+ if (room <= 0)
128
+ continue;
129
+ const shave = Math.min(room, surplus);
130
+ delays[i] = delays[i] - shave;
131
+ surplus -= shave;
132
+ }
133
+ return applyCeiling(delays, maxDelayMs);
134
+ }
135
+ /** Cap in place. Applied last so nothing can push a delay back over the limit. */
136
+ function applyCeiling(delays, maxDelayMs) {
137
+ for (let i = 0; i < delays.length; i++) {
138
+ if (delays[i] > maxDelayMs)
139
+ delays[i] = maxDelayMs;
140
+ }
141
+ return delays;
142
+ }
143
+ /**
144
+ * Pair items (frame paths, buffers) with legal delays, repeating any item held
145
+ * longer than the ceiling instead of truncating its delay.
146
+ *
147
+ * `delaysFromTimestamps` must cap a long hold, which shortens playback. This
148
+ * keeps real time: a frame held for 166_768ms becomes three copies of that same
149
+ * frame at ~55_589ms each. The result pairs 1:1 with its items, which is what
150
+ * the encoders require (`delays.length === frames.length`), and the delays sum
151
+ * to the same total as the uncapped timeline.
152
+ *
153
+ * Splitting evenly rather than emitting `[65535, 65535, 35698]` keeps the
154
+ * repeats visually indistinguishable in a scrubber and avoids a stray short
155
+ * frame at the end of a hold.
156
+ *
157
+ * @param items One entry per frame, in capture order.
158
+ * @throws if `items` and `tMs` differ in length, or on any input
159
+ * `delaysFromTimestamps` rejects.
160
+ */
161
+ export function expandForPlayback(items, tMs, totalDurationMs, options = {}) {
162
+ if (items.length !== tMs.length) {
163
+ throw new Error(`expandForPlayback: ${items.length} items but ${tMs.length} timestamps - they must pair 1:1`);
164
+ }
165
+ const maxDelayMs = options.maxDelayMs ?? MAX_DELAY_MS;
166
+ // Uncapped, so the true duration of a long hold survives to be split.
167
+ const trueDelays = delaysFromTimestamps(tMs, totalDurationMs, {
168
+ maxDelayMs: Number.POSITIVE_INFINITY,
169
+ });
170
+ const outItems = [];
171
+ const outDelays = [];
172
+ for (let i = 0; i < items.length; i++) {
173
+ const delay = trueDelays[i];
174
+ if (delay <= maxDelayMs) {
175
+ outItems.push(items[i]);
176
+ outDelays.push(delay);
177
+ continue;
178
+ }
179
+ const repeats = Math.ceil(delay / maxDelayMs);
180
+ const each = delay / repeats;
181
+ for (let r = 0; r < repeats; r++) {
182
+ outItems.push(items[i]);
183
+ outDelays.push(each);
184
+ }
185
+ }
186
+ return { items: outItems, delays: outDelays };
187
+ }
188
+ /**
189
+ * Intervals where capture stalled: a delta larger than
190
+ * `gapFactor * (1000 / targetFps)`.
191
+ *
192
+ * @throws if `targetFps` or `gapFactor` are not positive.
193
+ */
194
+ export function findFrameGaps(tMs, targetFps, gapFactor = 3) {
195
+ if (!Number.isFinite(targetFps) || targetFps <= 0) {
196
+ throw new Error(`findFrameGaps: targetFps must be positive, got ${targetFps}`);
197
+ }
198
+ if (!Number.isFinite(gapFactor) || gapFactor <= 0) {
199
+ throw new Error(`findFrameGaps: gapFactor must be positive, got ${gapFactor}`);
200
+ }
201
+ const threshold = gapFactor * (1000 / targetFps);
202
+ const gaps = [];
203
+ for (let i = 0; i + 1 < tMs.length; i++) {
204
+ const delta = tMs[i + 1] - tMs[i];
205
+ if (delta > threshold)
206
+ gaps.push({ after_index: i, gap_ms: delta });
207
+ }
208
+ return gaps;
209
+ }
210
+ /**
211
+ * Clamp a capture rect into the viewport.
212
+ *
213
+ * Origin is clamped to the viewport first, then the extent is trimmed to what
214
+ * remains. `clamped` is true whenever any component changed, which the manifest
215
+ * surfaces as `region_clamped`.
216
+ *
217
+ * @throws if the input rect or the viewport has zero/negative area, or if the
218
+ * rect lies entirely outside the viewport (nothing left to capture).
219
+ */
220
+ export function clampRect(rect, viewport) {
221
+ if (!Number.isFinite(viewport.width) || !Number.isFinite(viewport.height)) {
222
+ throw new Error("clampRect: viewport dimensions must be finite");
223
+ }
224
+ if (viewport.width <= 0 || viewport.height <= 0) {
225
+ throw new Error(`clampRect: viewport must have positive area, got ${viewport.width}x${viewport.height}`);
226
+ }
227
+ if (!Number.isFinite(rect.x) ||
228
+ !Number.isFinite(rect.y) ||
229
+ !Number.isFinite(rect.width) ||
230
+ !Number.isFinite(rect.height)) {
231
+ throw new Error("clampRect: rect components must be finite");
232
+ }
233
+ if (rect.width <= 0 || rect.height <= 0) {
234
+ throw new Error(`clampRect: rect must have positive area, got ${rect.width}x${rect.height}`);
235
+ }
236
+ const x = Math.min(Math.max(rect.x, 0), viewport.width);
237
+ const y = Math.min(Math.max(rect.y, 0), viewport.height);
238
+ // Trim the far edge back inside the viewport, then re-measure from the
239
+ // clamped origin so the width can never go negative.
240
+ const right = Math.min(rect.x + rect.width, viewport.width);
241
+ const bottom = Math.min(rect.y + rect.height, viewport.height);
242
+ const width = Math.max(right - x, 0);
243
+ const height = Math.max(bottom - y, 0);
244
+ if (width <= 0 || height <= 0) {
245
+ throw new Error(`clampRect: rect ${rect.width}x${rect.height} at (${rect.x}, ${rect.y}) lies outside the ` +
246
+ `${viewport.width}x${viewport.height} viewport`);
247
+ }
248
+ const clamped = x !== rect.x || y !== rect.y || width !== rect.width || height !== rect.height;
249
+ return { rect: { x, y, width, height }, clamped };
250
+ }
251
+ //# sourceMappingURL=timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/recording/timing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,316 @@
1
+ /**
2
+ * CBrowser - Cognitive Browser Automation
3
+ * Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
4
+ * Learn more at https://cbrowser.ai - MIT License
5
+ */
6
+ /**
7
+ * Recording manifest contract.
8
+ *
9
+ * The manifest is the single artifact that describes a recording run: how it
10
+ * was captured, what was captured, and every frame that made it into the
11
+ * output. Encoders, the CLI, and any downstream consumer read this file rather
12
+ * than re-deriving state from the frame directory.
13
+ *
14
+ * Schemas are zod v4; every exported TS type is inferred from its schema so the
15
+ * runtime contract and the compile-time contract cannot drift.
16
+ */
17
+ import { z } from "zod";
18
+ /**
19
+ * Manifest format version emitted by this build.
20
+ *
21
+ * v2 (2026-07-19) added the two-tier change signal: `key_frames`,
22
+ * `change_points_saturated` and `ssim_thresholds`, all OPTIONAL in the schema
23
+ * (expand-contract, so older files keep parsing) but always emitted, so
24
+ * `version >= 2` reliably signals their presence.
25
+ *
26
+ * v3 (2026-07-19) changed the DETECTOR behind those fields from global SSIM to
27
+ * {@link SSIM_THRESHOLDS} window-min ({@link changeScore}), and re-tuned the
28
+ * thresholds to the new scale (0.999/0.9 -> 0.95/0.6). The field shapes are
29
+ * unchanged, but a v3 `change_points` is not numerically comparable to a v2
30
+ * one — the version is the signal for that, since the values in
31
+ * `ssim_thresholds` alone cannot convey that the underlying metric changed.
32
+ * Readers wanting a uniform view across versions should use {@link changeSignal}.
33
+ */
34
+ export declare const MANIFEST_VERSION = 3;
35
+ /**
36
+ * Change-detection thresholds for the two-tier signal.
37
+ *
38
+ * These are measured against the {@link changeScore} scale (minimum SSIM over an
39
+ * 8x8 window grid on a 128px signature), NOT global SSIM. The scale is entirely
40
+ * different from a whole-frame metric and the two are not interchangeable — a
41
+ * localized text change reads ~0.87 under changeScore versus ~0.9998 under
42
+ * global SSIM. The producer MUST feed these thresholds changeScore values;
43
+ * `ssim_thresholds` records them in every manifest so a reader never guesses.
44
+ *
45
+ * `CHANGE` (0.95) — "did anything change". Below the practical noise floor
46
+ * (an unchanging page scores exactly 1.0; window-min does not fire on codec
47
+ * noise) and above a 12px counter tick (~0.87), which is the localized change
48
+ * global SSIM could not see at any resolution.
49
+ *
50
+ * `KEY` (0.6) — "did something notable happen". The sparse tier, and the same
51
+ * signal that sets each frame's `anomaly` flag. It sits in the wide gap between
52
+ * a minor tick (~0.87, excluded) and a real interaction like a form fill
53
+ * (~0.21, included), so background chrome does not flood it. Because KEY <
54
+ * CHANGE, `key_frames` is necessarily a subset of `change_points`;
55
+ * {@link changeSignalIssues} checks a manifest honours it.
56
+ *
57
+ * Derived from measurement on five fixtures (static / ticking counter / two
58
+ * form fills / banner toggle / full-viewport animation), 2026-07-19. The
59
+ * counter fixture is why the two tiers matter: change_points fires on every
60
+ * tick and saturates, while key_frames stays empty — so key_frames is the
61
+ * usable signal precisely when change_points is not.
62
+ */
63
+ export declare const SSIM_THRESHOLDS: {
64
+ readonly change: 0.95;
65
+ readonly key: 0.6;
66
+ };
67
+ /**
68
+ * Fraction of frames above which `change_points` is considered saturated —
69
+ * carrying no discriminating information for that capture.
70
+ */
71
+ export declare const CHANGE_SATURATION_RATIO = 0.7;
72
+ export declare const EngineSchema: z.ZodEnum<{
73
+ chromium: "chromium";
74
+ firefox: "firefox";
75
+ webkit: "webkit";
76
+ }>;
77
+ export declare const CaptureMethodSchema: z.ZodEnum<{
78
+ "cdp-screencast": "cdp-screencast";
79
+ "screenshot-loop": "screenshot-loop";
80
+ }>;
81
+ export declare const ViewportSchema: z.ZodObject<{
82
+ width: z.ZodNumber;
83
+ height: z.ZodNumber;
84
+ deviceScaleFactor: z.ZodNumber;
85
+ }, z.core.$strip>;
86
+ export declare const RectSchema: z.ZodObject<{
87
+ x: z.ZodNumber;
88
+ y: z.ZodNumber;
89
+ width: z.ZodNumber;
90
+ height: z.ZodNumber;
91
+ }, z.core.$strip>;
92
+ export declare const DimsSchema: z.ZodObject<{
93
+ width: z.ZodNumber;
94
+ height: z.ZodNumber;
95
+ }, z.core.$strip>;
96
+ /**
97
+ * What the recorder was pointed at. Discriminated on `kind` so encoders can
98
+ * switch exhaustively without guessing from the presence of optional fields.
99
+ */
100
+ export declare const TargetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
101
+ kind: z.ZodLiteral<"viewport">;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ kind: z.ZodLiteral<"region">;
104
+ rect: z.ZodObject<{
105
+ x: z.ZodNumber;
106
+ y: z.ZodNumber;
107
+ width: z.ZodNumber;
108
+ height: z.ZodNumber;
109
+ }, z.core.$strip>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ kind: z.ZodLiteral<"element">;
112
+ selector: z.ZodString;
113
+ padding: z.ZodNumber;
114
+ }, z.core.$strip>], "kind">;
115
+ /**
116
+ * Console and network records are kept deliberately permissive: the recorder
117
+ * owns their exact shape and may attach engine-specific extras. Only the fields
118
+ * the encoders and reports actually read are required; unknown keys survive
119
+ * parsing untouched.
120
+ */
121
+ export declare const ConsoleEntrySchema: z.ZodObject<{
122
+ t_ms: z.ZodNumber;
123
+ type: z.ZodString;
124
+ text: z.ZodString;
125
+ }, z.core.$loose>;
126
+ export declare const NetworkEntrySchema: z.ZodObject<{
127
+ t_ms: z.ZodNumber;
128
+ url: z.ZodString;
129
+ method: z.ZodOptional<z.ZodString>;
130
+ status: z.ZodOptional<z.ZodNumber>;
131
+ failed: z.ZodOptional<z.ZodBoolean>;
132
+ }, z.core.$loose>;
133
+ export declare const FrameSchema: z.ZodObject<{
134
+ index: z.ZodNumber;
135
+ t_ms: z.ZodNumber;
136
+ path: z.ZodString;
137
+ crop: z.ZodObject<{
138
+ x: z.ZodNumber;
139
+ y: z.ZodNumber;
140
+ width: z.ZodNumber;
141
+ height: z.ZodNumber;
142
+ }, z.core.$strip>;
143
+ ssim_prev: z.ZodNullable<z.ZodNumber>;
144
+ anomaly: z.ZodBoolean;
145
+ tracking: z.ZodNullable<z.ZodEnum<{
146
+ ok: "ok";
147
+ stale: "stale";
148
+ }>>;
149
+ console: z.ZodArray<z.ZodObject<{
150
+ t_ms: z.ZodNumber;
151
+ type: z.ZodString;
152
+ text: z.ZodString;
153
+ }, z.core.$loose>>;
154
+ network: z.ZodArray<z.ZodObject<{
155
+ t_ms: z.ZodNumber;
156
+ url: z.ZodString;
157
+ method: z.ZodOptional<z.ZodString>;
158
+ status: z.ZodOptional<z.ZodNumber>;
159
+ failed: z.ZodOptional<z.ZodBoolean>;
160
+ }, z.core.$loose>>;
161
+ }, z.core.$strip>;
162
+ export declare const FrameGapSchema: z.ZodObject<{
163
+ after_index: z.ZodNumber;
164
+ gap_ms: z.ZodNumber;
165
+ }, z.core.$strip>;
166
+ export declare const RecordingManifestSchema: z.ZodObject<{
167
+ version: z.ZodNumber;
168
+ slug: z.ZodString;
169
+ engine: z.ZodEnum<{
170
+ chromium: "chromium";
171
+ firefox: "firefox";
172
+ webkit: "webkit";
173
+ }>;
174
+ capture_method: z.ZodEnum<{
175
+ "cdp-screencast": "cdp-screencast";
176
+ "screenshot-loop": "screenshot-loop";
177
+ }>;
178
+ target_fps: z.ZodNumber;
179
+ actual_fps: z.ZodNumber;
180
+ viewport: z.ZodObject<{
181
+ width: z.ZodNumber;
182
+ height: z.ZodNumber;
183
+ deviceScaleFactor: z.ZodNumber;
184
+ }, z.core.$strip>;
185
+ target: z.ZodDiscriminatedUnion<[z.ZodObject<{
186
+ kind: z.ZodLiteral<"viewport">;
187
+ }, z.core.$strip>, z.ZodObject<{
188
+ kind: z.ZodLiteral<"region">;
189
+ rect: z.ZodObject<{
190
+ x: z.ZodNumber;
191
+ y: z.ZodNumber;
192
+ width: z.ZodNumber;
193
+ height: z.ZodNumber;
194
+ }, z.core.$strip>;
195
+ }, z.core.$strip>, z.ZodObject<{
196
+ kind: z.ZodLiteral<"element">;
197
+ selector: z.ZodString;
198
+ padding: z.ZodNumber;
199
+ }, z.core.$strip>], "kind">;
200
+ start_trigger: z.ZodNullable<z.ZodString>;
201
+ stop_trigger: z.ZodNullable<z.ZodString>;
202
+ trigger_timeout: z.ZodBoolean;
203
+ started_at: z.ZodString;
204
+ duration_ms: z.ZodNumber;
205
+ output_dims: z.ZodObject<{
206
+ width: z.ZodNumber;
207
+ height: z.ZodNumber;
208
+ }, z.core.$strip>;
209
+ frames: z.ZodArray<z.ZodObject<{
210
+ index: z.ZodNumber;
211
+ t_ms: z.ZodNumber;
212
+ path: z.ZodString;
213
+ crop: z.ZodObject<{
214
+ x: z.ZodNumber;
215
+ y: z.ZodNumber;
216
+ width: z.ZodNumber;
217
+ height: z.ZodNumber;
218
+ }, z.core.$strip>;
219
+ ssim_prev: z.ZodNullable<z.ZodNumber>;
220
+ anomaly: z.ZodBoolean;
221
+ tracking: z.ZodNullable<z.ZodEnum<{
222
+ ok: "ok";
223
+ stale: "stale";
224
+ }>>;
225
+ console: z.ZodArray<z.ZodObject<{
226
+ t_ms: z.ZodNumber;
227
+ type: z.ZodString;
228
+ text: z.ZodString;
229
+ }, z.core.$loose>>;
230
+ network: z.ZodArray<z.ZodObject<{
231
+ t_ms: z.ZodNumber;
232
+ url: z.ZodString;
233
+ method: z.ZodOptional<z.ZodString>;
234
+ status: z.ZodOptional<z.ZodNumber>;
235
+ failed: z.ZodOptional<z.ZodBoolean>;
236
+ }, z.core.$loose>>;
237
+ }, z.core.$strip>>;
238
+ change_points: z.ZodArray<z.ZodNumber>;
239
+ key_frames: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
240
+ change_points_saturated: z.ZodOptional<z.ZodBoolean>;
241
+ ssim_thresholds: z.ZodOptional<z.ZodObject<{
242
+ change: z.ZodNumber;
243
+ key: z.ZodNumber;
244
+ }, z.core.$strip>>;
245
+ method: z.ZodOptional<z.ZodString>;
246
+ frame_gaps: z.ZodArray<z.ZodObject<{
247
+ after_index: z.ZodNumber;
248
+ gap_ms: z.ZodNumber;
249
+ }, z.core.$strip>>;
250
+ region_clamped: z.ZodOptional<z.ZodBoolean>;
251
+ artifacts: z.ZodRecord<z.ZodString, z.ZodString>;
252
+ }, z.core.$strip>;
253
+ export type Engine = z.infer<typeof EngineSchema>;
254
+ export type CaptureMethod = z.infer<typeof CaptureMethodSchema>;
255
+ export type Viewport = z.infer<typeof ViewportSchema>;
256
+ export type Rect = z.infer<typeof RectSchema>;
257
+ export type Dims = z.infer<typeof DimsSchema>;
258
+ export type RecordingTarget = z.infer<typeof TargetSchema>;
259
+ export type ConsoleEntry = z.infer<typeof ConsoleEntrySchema>;
260
+ export type NetworkEntry = z.infer<typeof NetworkEntrySchema>;
261
+ export type Frame = z.infer<typeof FrameSchema>;
262
+ export type FrameGap = z.infer<typeof FrameGapSchema>;
263
+ export type RecordingManifest = z.infer<typeof RecordingManifestSchema>;
264
+ /**
265
+ * Parse and validate a manifest.
266
+ *
267
+ * Accepts an already-parsed object or a raw JSON string. Throws an `Error`
268
+ * whose message lists every failing path, so a malformed manifest reports all
269
+ * of its problems at once instead of one per run.
270
+ */
271
+ /**
272
+ * Uniform view of the change signal across manifest versions.
273
+ *
274
+ * A pre-v2 manifest has only `change_points`, computed with a whole-frame
275
+ * (global) SSIM detector — a DIFFERENT metric from the current window-min
276
+ * {@link SSIM_THRESHOLDS} scale, so the two are not numerically comparable. For
277
+ * such a file this returns the single list it has as both tiers rather than
278
+ * inventing a key/change split it never carried, and marks `legacy: true` so a
279
+ * reader knows the tiers are not really separated and the thresholds are not on
280
+ * the current scale. `saturated` is false because the old global detector could
281
+ * not saturate the way window-min can.
282
+ *
283
+ * This is NOT the exact identity an earlier version of this comment claimed:
284
+ * once the detector changed from global SSIM to window-min, an old file's
285
+ * change points stopped mapping onto today's `key_frames`. Reporting the one
286
+ * list for both tiers is the honest fallback, not a translation.
287
+ *
288
+ * Use this in readers instead of touching the optional fields directly, so a
289
+ * pre-v2 file does not read as "no key frames" when its change_points are the
290
+ * only signal it has.
291
+ */
292
+ export declare function changeSignal(manifest: RecordingManifest): {
293
+ changePoints: number[];
294
+ keyFrames: number[];
295
+ saturated: boolean;
296
+ thresholds: {
297
+ change: number;
298
+ key: number;
299
+ };
300
+ legacy: boolean;
301
+ manifestVersion: number;
302
+ };
303
+ /**
304
+ * Consistency problems in a manifest's change signal, as human-readable
305
+ * strings. Empty means consistent.
306
+ *
307
+ * These are invariants the two-tier design guarantees by construction, so a
308
+ * violation means the producer is miscomputing rather than that the file is
309
+ * merely old. Deliberately NOT enforced inside {@link parseManifest}: readers
310
+ * such as `capture status` open whatever manifest is most recent, and failing
311
+ * the read outright would turn a reporting bug into an unreadable capture. Call
312
+ * this where you want the check — tests, or a diagnostic path.
313
+ */
314
+ export declare function changeSignalIssues(manifest: RecordingManifest): string[];
315
+ export declare function parseManifest(input: unknown): RecordingManifest;
316
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/recording/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,eAAe;;;CAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAM3C,eAAO,MAAM,YAAY;;;;EAA4C,CAAC;AAEtE,eAAO,MAAM,mBAAmB;;;EAAgD,CAAC;AAEjF,eAAO,MAAM,cAAc;;;;iBAIzB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;iBAKrB,CAAC;AAEH,eAAO,MAAM,UAAU;;;iBAGrB,CAAC;AAMH;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;2BAQvB,CAAC;AAMH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;iBAMrB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;iBAQrB,CAAC;AAMX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBtB,CAAC;AAEH,eAAO,MAAM,cAAc;;;iBAIzB,CAAC;AAMH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgElC,CAAC;AAMH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAChD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAMxE;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG;IACzD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB,CAaA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,EAAE,CA2CxE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAsB/D"}