recordable 0.1.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/dist/actions.d.ts +140 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +184 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/audio/track.d.ts +45 -0
  8. package/dist/audio/track.d.ts.map +1 -0
  9. package/dist/audio/track.js +61 -0
  10. package/dist/audio/track.js.map +1 -0
  11. package/dist/browser/cursor.d.ts +33 -0
  12. package/dist/browser/cursor.d.ts.map +1 -0
  13. package/dist/browser/cursor.js +118 -0
  14. package/dist/browser/cursor.js.map +1 -0
  15. package/dist/browser/dom.d.ts +31 -0
  16. package/dist/browser/dom.d.ts.map +1 -0
  17. package/dist/browser/dom.js +134 -0
  18. package/dist/browser/dom.js.map +1 -0
  19. package/dist/browser/play-button.d.ts +11 -0
  20. package/dist/browser/play-button.d.ts.map +1 -0
  21. package/dist/browser/play-button.js +87 -0
  22. package/dist/browser/play-button.js.map +1 -0
  23. package/dist/browser/runtime.d.ts +66 -0
  24. package/dist/browser/runtime.d.ts.map +1 -0
  25. package/dist/browser/runtime.js +271 -0
  26. package/dist/browser/runtime.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +131 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/compose/mix.d.ts +13 -0
  32. package/dist/compose/mix.d.ts.map +1 -0
  33. package/dist/compose/mix.js +50 -0
  34. package/dist/compose/mix.js.map +1 -0
  35. package/dist/compose/recordable.d.ts +149 -0
  36. package/dist/compose/recordable.d.ts.map +1 -0
  37. package/dist/compose/recordable.js +337 -0
  38. package/dist/compose/recordable.js.map +1 -0
  39. package/dist/compose/session.d.ts +38 -0
  40. package/dist/compose/session.d.ts.map +1 -0
  41. package/dist/compose/session.js +122 -0
  42. package/dist/compose/session.js.map +1 -0
  43. package/dist/config.d.ts +93 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +64 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/errors.d.ts +13 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +21 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/ffmpeg.d.ts +8 -0
  52. package/dist/ffmpeg.d.ts.map +1 -0
  53. package/dist/ffmpeg.js +55 -0
  54. package/dist/ffmpeg.js.map +1 -0
  55. package/dist/formats/json.d.ts +12 -0
  56. package/dist/formats/json.d.ts.map +1 -0
  57. package/dist/formats/json.js +20 -0
  58. package/dist/formats/json.js.map +1 -0
  59. package/dist/formats/markdown/method.d.ts +25 -0
  60. package/dist/formats/markdown/method.d.ts.map +1 -0
  61. package/dist/formats/markdown/method.js +48 -0
  62. package/dist/formats/markdown/method.js.map +1 -0
  63. package/dist/formats/markdown/parse.d.ts +44 -0
  64. package/dist/formats/markdown/parse.d.ts.map +1 -0
  65. package/dist/formats/markdown/parse.js +143 -0
  66. package/dist/formats/markdown/parse.js.map +1 -0
  67. package/dist/fs.d.ts +9 -0
  68. package/dist/fs.d.ts.map +1 -0
  69. package/dist/fs.js +30 -0
  70. package/dist/fs.js.map +1 -0
  71. package/dist/index.d.ts +10 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +6 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/logger.d.ts +21 -0
  76. package/dist/logger.d.ts.map +1 -0
  77. package/dist/logger.js +45 -0
  78. package/dist/logger.js.map +1 -0
  79. package/dist/schema.d.ts +5 -0
  80. package/dist/schema.d.ts.map +1 -0
  81. package/dist/schema.js +100 -0
  82. package/dist/schema.js.map +1 -0
  83. package/dist/script.d.ts +21 -0
  84. package/dist/script.d.ts.map +1 -0
  85. package/dist/script.js +26 -0
  86. package/dist/script.js.map +1 -0
  87. package/dist/targets.d.ts +6 -0
  88. package/dist/targets.d.ts.map +1 -0
  89. package/dist/targets.js +13 -0
  90. package/dist/targets.js.map +1 -0
  91. package/dist/timing.d.ts +41 -0
  92. package/dist/timing.d.ts.map +1 -0
  93. package/dist/timing.js +149 -0
  94. package/dist/timing.js.map +1 -0
  95. package/dist/utils.d.ts +3 -0
  96. package/dist/utils.d.ts.map +1 -0
  97. package/dist/utils.js +8 -0
  98. package/dist/utils.js.map +1 -0
  99. package/dist/validate.d.ts +8 -0
  100. package/dist/validate.d.ts.map +1 -0
  101. package/dist/validate.js +54 -0
  102. package/dist/validate.js.map +1 -0
  103. package/dist/video/recorder.d.ts +57 -0
  104. package/dist/video/recorder.d.ts.map +1 -0
  105. package/dist/video/recorder.js +238 -0
  106. package/dist/video/recorder.js.map +1 -0
  107. package/dist/video/stitch.d.ts +15 -0
  108. package/dist/video/stitch.d.ts.map +1 -0
  109. package/dist/video/stitch.js +111 -0
  110. package/dist/video/stitch.js.map +1 -0
  111. package/dist/voiceover/alignment.d.ts +14 -0
  112. package/dist/voiceover/alignment.d.ts.map +1 -0
  113. package/dist/voiceover/alignment.js +13 -0
  114. package/dist/voiceover/alignment.js.map +1 -0
  115. package/dist/voiceover/cache.d.ts +22 -0
  116. package/dist/voiceover/cache.d.ts.map +1 -0
  117. package/dist/voiceover/cache.js +55 -0
  118. package/dist/voiceover/cache.js.map +1 -0
  119. package/dist/voiceover/compile.d.ts +35 -0
  120. package/dist/voiceover/compile.d.ts.map +1 -0
  121. package/dist/voiceover/compile.js +194 -0
  122. package/dist/voiceover/compile.js.map +1 -0
  123. package/dist/voiceover/elevenlabs.d.ts +16 -0
  124. package/dist/voiceover/elevenlabs.d.ts.map +1 -0
  125. package/dist/voiceover/elevenlabs.js +66 -0
  126. package/dist/voiceover/elevenlabs.js.map +1 -0
  127. package/dist/voiceover/index.d.ts +7 -0
  128. package/dist/voiceover/index.d.ts.map +1 -0
  129. package/dist/voiceover/index.js +8 -0
  130. package/dist/voiceover/index.js.map +1 -0
  131. package/dist/voiceover/mock.d.ts +15 -0
  132. package/dist/voiceover/mock.d.ts.map +1 -0
  133. package/dist/voiceover/mock.js +41 -0
  134. package/dist/voiceover/mock.js.map +1 -0
  135. package/dist/voiceover/types.d.ts +31 -0
  136. package/dist/voiceover/types.d.ts.map +1 -0
  137. package/dist/voiceover/types.js +10 -0
  138. package/dist/voiceover/types.js.map +1 -0
  139. package/package.json +86 -0
  140. package/recordable.schema.json +738 -0
@@ -0,0 +1,45 @@
1
+ /** One clip placed on the timeline (recorded-time milliseconds). */
2
+ export interface AudioClip {
3
+ path: string;
4
+ startMs: number;
5
+ durationMs: number;
6
+ volume?: number;
7
+ }
8
+ /** An ordered set of audio clips, each pinned to a caller-supplied recorded-time
9
+ * position. Mixed onto the finished video at finalise by `mix.ts`. */
10
+ export declare class AudioTrack {
11
+ private readonly clips;
12
+ /** Number of clips on the track. */
13
+ get length(): number;
14
+ /** The clips in document order (read-only view for the mixer). */
15
+ list(): readonly AudioClip[];
16
+ /**
17
+ * Add a clip at `startMs` (recorded time). Probes its duration so the caller
18
+ * can block for it when `audio({ wait: true })`. Throws if the file is missing.
19
+ */
20
+ add(path: string, startMs: number, options?: {
21
+ volume?: number;
22
+ }): Promise<{
23
+ startMs: number;
24
+ durationMs: number;
25
+ }>;
26
+ }
27
+ /**
28
+ * Build the `filter_complex` chain that delays each clip to its `startMs`,
29
+ * applies volume, and mixes them. Input index `i+1` (input 0 is the video).
30
+ * Returns the filter parts and the label to `-map` as the output audio.
31
+ */
32
+ export declare function audioFilterGraph(clips: readonly AudioClip[]): {
33
+ filters: string[];
34
+ mapLabel: string;
35
+ };
36
+ /**
37
+ * Clips that run past the video end (beyond `tolMs` slack). Per the timing
38
+ * contract the video defines the length, so an overrun is the author's cue to
39
+ * add a trailing `wait()`; the mixer warns and lets `-t` truncate.
40
+ */
41
+ export declare function audioOverruns(clips: readonly AudioClip[], videoMs: number, tolMs?: number): {
42
+ path: string;
43
+ overMs: number;
44
+ }[];
45
+ //# sourceMappingURL=track.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/audio/track.ts"],"names":[],"mappings":"AAYA,oEAAoE;AACpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;uEACuE;AACvE,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IAEzC,oCAAoC;IACpC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,kEAAkE;IAClE,IAAI,IAAI,SAAS,SAAS,EAAE;IAI5B;;;OAGG;IACG,GAAG,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAChC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAOpD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAeA;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,OAAO,EAAE,MAAM,EACf,KAAK,SAAK,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAOpC"}
@@ -0,0 +1,61 @@
1
+ import { existsSync } from "node:fs";
2
+ import { getDuration } from "../ffmpeg.js";
3
+ import { RecordableError } from "../errors.js";
4
+ /** An ordered set of audio clips, each pinned to a caller-supplied recorded-time
5
+ * position. Mixed onto the finished video at finalise by `mix.ts`. */
6
+ export class AudioTrack {
7
+ clips = [];
8
+ /** Number of clips on the track. */
9
+ get length() {
10
+ return this.clips.length;
11
+ }
12
+ /** The clips in document order (read-only view for the mixer). */
13
+ list() {
14
+ return this.clips;
15
+ }
16
+ /**
17
+ * Add a clip at `startMs` (recorded time). Probes its duration so the caller
18
+ * can block for it when `audio({ wait: true })`. Throws if the file is missing.
19
+ */
20
+ async add(path, startMs, options = {}) {
21
+ if (!existsSync(path))
22
+ throw new RecordableError("FILE_NOT_FOUND", `audio: file not found: ${path}`);
23
+ const durationMs = (await getDuration(path)) * 1000;
24
+ this.clips.push({ path, startMs, durationMs, volume: options.volume });
25
+ return { startMs, durationMs };
26
+ }
27
+ }
28
+ /**
29
+ * Build the `filter_complex` chain that delays each clip to its `startMs`,
30
+ * applies volume, and mixes them. Input index `i+1` (input 0 is the video).
31
+ * Returns the filter parts and the label to `-map` as the output audio.
32
+ */
33
+ export function audioFilterGraph(clips) {
34
+ const labels = [];
35
+ const filters = clips.map((c, i) => {
36
+ const chain = [`adelay=${Math.round(c.startMs)}:all=1`];
37
+ if (c.volume != null && c.volume !== 1)
38
+ chain.push(`volume=${c.volume}`);
39
+ labels.push(`[a${i}]`);
40
+ return `[${i + 1}:a]${chain.join(",")}[a${i}]`;
41
+ });
42
+ if (clips.length === 1)
43
+ return { filters, mapLabel: "a0" };
44
+ filters.push(`${labels.join("")}amix=inputs=${clips.length}:normalize=0[aout]`);
45
+ return { filters, mapLabel: "aout" };
46
+ }
47
+ /**
48
+ * Clips that run past the video end (beyond `tolMs` slack). Per the timing
49
+ * contract the video defines the length, so an overrun is the author's cue to
50
+ * add a trailing `wait()`; the mixer warns and lets `-t` truncate.
51
+ */
52
+ export function audioOverruns(clips, videoMs, tolMs = 50) {
53
+ const over = [];
54
+ for (const c of clips) {
55
+ const overMs = Math.round(c.startMs + c.durationMs - videoMs);
56
+ if (overMs > tolMs)
57
+ over.push({ path: c.path, overMs });
58
+ }
59
+ return over;
60
+ }
61
+ //# sourceMappingURL=track.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/audio/track.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAkB/C;uEACuE;AACvE,MAAM,OAAO,UAAU;IACJ,KAAK,GAAgB,EAAE,CAAC;IAEzC,oCAAoC;IACpC,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,kEAAkE;IAClE,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CACP,IAAY,EACZ,OAAe,EACf,UAA+B,EAAE;QAEjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACnB,MAAM,IAAI,eAAe,CAAC,gBAAgB,EAAE,0BAA0B,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QACpD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IACjC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA2B;IAI1D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE3D,OAAO,CAAC,IAAI,CACV,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,KAAK,CAAC,MAAM,oBAAoB,CAClE,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA2B,EAC3B,OAAe,EACf,KAAK,GAAG,EAAE;IAEV,MAAM,IAAI,GAAuC,EAAE,CAAC;IACpD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC;QAC9D,IAAI,MAAM,GAAG,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type Page } from "puppeteer";
2
+ /** The page's current zoom transform, needed to position the overlay correctly. */
3
+ export interface ZoomState {
4
+ tx: number;
5
+ ty: number;
6
+ s: number;
7
+ }
8
+ /**
9
+ * An animated cursor overlay drawn into the page. Tracks its own position so
10
+ * each move can ease from where it last was, and dips on click for a tactile
11
+ * press effect. Also drives the real Puppeteer mouse so hover/click still fire.
12
+ */
13
+ export declare class Cursor {
14
+ private pos;
15
+ /**
16
+ * Draw the cursor SVG and hide the native pointer. Idempotent per document.
17
+ * Renders the overlay at the last-known position (carried across navigations)
18
+ * and syncs the real mouse there so hover state stays consistent.
19
+ */
20
+ inject(page: Page, zoom?: ZoomState): Promise<void>;
21
+ /** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
22
+ moveTo(page: Page, toX: number, toY: number, zoom: ZoomState): Promise<void>;
23
+ /**
24
+ * Convert viewport coords → document coords for the overlay's transform.
25
+ * When documentElement has a CSS transform (zoom), position:fixed children
26
+ * are positioned relative to that ancestor (not the viewport) and scroll with
27
+ * the page, so we add scroll and apply the inverse zoom transform.
28
+ */
29
+ private _toDocCoords;
30
+ /** Briefly scale the cursor down to signal a press. */
31
+ clickEffect(page: Page): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,qBAAa,MAAM;IAIjB,OAAO,CAAC,GAAG,CAAkB;IAE7B;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAE,SAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsDjF,0EAA0E;IACpE,MAAM,CACV,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,IAAI,CAAC;IAiChB;;;;;OAKG;YACW,YAAY;IAa1B,uDAAuD;IACjD,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAa7C"}
@@ -0,0 +1,118 @@
1
+ import { sleep } from "../utils.js";
2
+ import { cursorMoveMs, PRESS_DOWN_MS, PRESS_SETTLE_MS } from "../timing.js";
3
+ const CURSOR_ID = "__recordable_cursor__";
4
+ /**
5
+ * An animated cursor overlay drawn into the page. Tracks its own position so
6
+ * each move can ease from where it last was, and dips on click for a tactile
7
+ * press effect. Also drives the real Puppeteer mouse so hover/click still fire.
8
+ */
9
+ export class Cursor {
10
+ // Persisted across injects so the overlay survives navigation: a fresh page
11
+ // re-injects, and we want the cursor to reappear where it last was (like a
12
+ // real pointer) rather than snapping to the top-left corner.
13
+ pos = { x: 0, y: 0 };
14
+ /**
15
+ * Draw the cursor SVG and hide the native pointer. Idempotent per document.
16
+ * Renders the overlay at the last-known position (carried across navigations)
17
+ * and syncs the real mouse there so hover state stays consistent.
18
+ */
19
+ async inject(page, zoom = { tx: 0, ty: 0, s: 1 }) {
20
+ // Bail early when there's nothing to do or we can't yet: already present, an
21
+ // iframe, or the new document's <body> hasn't parsed. A too-early call (e.g.
22
+ // from a navigation event) is a no-op; the next moveTo re-injects when ready.
23
+ const skip = await page.evaluate((id) => window !== window.parent ||
24
+ !document.body ||
25
+ !!document.getElementById(id), CURSOR_ID);
26
+ if (skip)
27
+ return;
28
+ const { cx, cy } = await this._toDocCoords(page, this.pos.x, this.pos.y, zoom);
29
+ await page.evaluate(({ id, cx, cy }) => {
30
+ const style = document.createElement("style");
31
+ style.textContent = `
32
+ * { cursor: none !important; }
33
+ #${id} {
34
+ position: fixed;
35
+ top: 0; left: 0;
36
+ margin: -2px 0 0 -4px;
37
+ z-index: 2147483647;
38
+ pointer-events: none;
39
+ will-change: transform;
40
+ transition: transform 0.15s;
41
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4));
42
+ }
43
+ #${id}.pressing {
44
+ transform: var(--recordable-pos) scale(0.88) !important;
45
+ transition: transform 0.08s !important;
46
+ }
47
+ `;
48
+ document.head.appendChild(style);
49
+ const cursor = document.createElement("div");
50
+ cursor.id = id;
51
+ // Place at the carried-over position with no transition, so it appears
52
+ // there immediately instead of animating in from the corner.
53
+ cursor.style.transition = "none";
54
+ cursor.style.setProperty("--recordable-pos", `translate(${cx}px, ${cy}px)`);
55
+ cursor.style.transform = `translate(${cx}px, ${cy}px)`;
56
+ cursor.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
57
+ <path d="M4 2 L4 19 L8.5 14.5 L12 22 L14 21 L10.5 13.5 L17 13.5 Z"
58
+ fill="white" stroke="#1e1b4b" stroke-width="1.2" stroke-linejoin="round"/>
59
+ </svg>`;
60
+ document.body.appendChild(cursor);
61
+ }, { id: CURSOR_ID, cx, cy });
62
+ await page.mouse.move(this.pos.x, this.pos.y);
63
+ }
64
+ /** Ease the overlay (and the real mouse) to viewport coords `toX,toY`. */
65
+ async moveTo(page, toX, toY, zoom) {
66
+ // Self-heal: a navigation (incl. click-triggered ones with no following
67
+ // visit/waitFor) wipes the overlay. Re-inject at the carried position before
68
+ // animating, so the move is always visible — never an instant, cursor-less jump.
69
+ await this.inject(page, zoom);
70
+ const dx = toX - this.pos.x;
71
+ const dy = toY - this.pos.y;
72
+ const dist = Math.sqrt(dx * dx + dy * dy);
73
+ const dur = cursorMoveMs(dist);
74
+ const { cx, cy } = await this._toDocCoords(page, toX, toY, zoom);
75
+ await page.evaluate(({ id, cx, cy, dur }) => new Promise((resolve) => {
76
+ const cursor = document.getElementById(id);
77
+ if (!cursor) {
78
+ resolve();
79
+ return;
80
+ }
81
+ cursor.style.transition = `transform ${dur}ms cubic-bezier(0.4,0,0.2,1)`;
82
+ cursor.style.setProperty("--recordable-pos", `translate(${cx}px, ${cy}px)`);
83
+ cursor.style.transform = `translate(${cx}px, ${cy}px)`;
84
+ setTimeout(resolve, dur);
85
+ }), { id: CURSOR_ID, cx, cy, dur });
86
+ await page.mouse.move(toX, toY);
87
+ this.pos = { x: toX, y: toY };
88
+ }
89
+ /**
90
+ * Convert viewport coords → document coords for the overlay's transform.
91
+ * When documentElement has a CSS transform (zoom), position:fixed children
92
+ * are positioned relative to that ancestor (not the viewport) and scroll with
93
+ * the page, so we add scroll and apply the inverse zoom transform.
94
+ */
95
+ async _toDocCoords(page, x, y, { tx, ty, s }) {
96
+ const hasTransform = s !== 1 || tx !== 0 || ty !== 0;
97
+ const scroll = hasTransform
98
+ ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }))
99
+ : { x: 0, y: 0 };
100
+ return { cx: (x + scroll.x - tx) / s, cy: (y + scroll.y - ty) / s };
101
+ }
102
+ /** Briefly scale the cursor down to signal a press. */
103
+ async clickEffect(page) {
104
+ await page.evaluate((id) => {
105
+ const cursor = document.getElementById(id);
106
+ if (!cursor)
107
+ return;
108
+ cursor.classList.add("pressing");
109
+ void cursor.offsetWidth;
110
+ }, CURSOR_ID);
111
+ await sleep(PRESS_DOWN_MS);
112
+ await page.evaluate((id) => {
113
+ document.getElementById(id)?.classList.remove("pressing");
114
+ }, CURSOR_ID);
115
+ await sleep(PRESS_SETTLE_MS);
116
+ }
117
+ }
118
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/browser/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type Page } from "puppeteer";
2
+ /** Coordinates in viewport pixels. */
3
+ export interface Point {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ /** Resolve a target and wait for the element to exist in the DOM, then return its handle. */
8
+ export declare function getHandle(page: Page, target: string): Promise<import("puppeteer").ElementHandle<Element>>;
9
+ /** Centre coords of a target element, jittered up to 20% of each dimension. */
10
+ export declare function getElementCenter(page: Page, target: string): Promise<Point>;
11
+ /**
12
+ * Resolve an origin string to viewport pixel coordinates.
13
+ * Accepts CSS position keywords/percentages or an element selector.
14
+ */
15
+ export declare function originToCoords(page: Page, origin: string): Promise<Point>;
16
+ /** Animate the page's scroll position to `targetY` over `duration` ms with an ease curve. */
17
+ export declare function smoothScroll(page: Page, targetY: number, duration: number): Promise<void>;
18
+ /**
19
+ * Smooth-scroll to an element or position:
20
+ * - `"top"` / `"bottom"` → page extremes
21
+ * - number → absolute Y pixel position
22
+ * - CSS selector or `text:` prefix → element centred in the viewport
23
+ */
24
+ export declare function smoothScrollToTarget(page: Page, target: string | number, duration: number): Promise<void>;
25
+ /**
26
+ * Scroll `target` into view if it lies outside the visible viewport (keeping
27
+ * `margin` px clear on each side). No-op when the element is already fully
28
+ * visible. `speed` (px/s) sets the scroll duration.
29
+ */
30
+ export declare function scrollIntoView(page: Page, target: string, margin: number, speed: number): Promise<void>;
31
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtC,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,6FAA6F;AAC7F,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,uDAMzD;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,CAShB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,CA4BhB;AAED,6FAA6F;AAC7F,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,134 @@
1
+ import { isPositionValue, resolveTarget } from "../targets.js";
2
+ /** Resolve a target and wait for the element to exist in the DOM, then return its handle. */
3
+ export async function getHandle(page, target) {
4
+ try {
5
+ return await page.locator(resolveTarget(target)).waitHandle();
6
+ }
7
+ catch {
8
+ throw new Error(`Could not find target: "${target}"`);
9
+ }
10
+ }
11
+ /** Centre coords of a target element, jittered up to 20% of each dimension. */
12
+ export async function getElementCenter(page, target) {
13
+ const handle = await getHandle(page, target);
14
+ const box = await handle.boundingBox();
15
+ if (!box)
16
+ throw new Error(`No bounding box for "${target}"`);
17
+ const offset = (range) => (Math.random() - 0.5) * range * 0.4;
18
+ return {
19
+ x: box.x + box.width / 2 + offset(box.width),
20
+ y: box.y + box.height / 2 + offset(box.height),
21
+ };
22
+ }
23
+ /**
24
+ * Resolve an origin string to viewport pixel coordinates.
25
+ * Accepts CSS position keywords/percentages or an element selector.
26
+ */
27
+ export async function originToCoords(page, origin) {
28
+ if (!isPositionValue(origin))
29
+ return getElementCenter(page, origin);
30
+ return page.evaluate((origin) => {
31
+ const vw = window.innerWidth;
32
+ const vh = window.innerHeight;
33
+ const tokens = origin.trim().toLowerCase().split(/\s+/);
34
+ const kw = {
35
+ left: 0,
36
+ top: 0,
37
+ center: 50,
38
+ right: 100,
39
+ bottom: 100,
40
+ };
41
+ if (tokens.length === 1) {
42
+ const [t] = tokens;
43
+ const p = t in kw ? kw[t] : parseFloat(t);
44
+ if (t === "top" || t === "bottom")
45
+ return { x: vw / 2, y: (vh * p) / 100 };
46
+ if (t === "left" || t === "right")
47
+ return { x: (vw * p) / 100, y: vh / 2 };
48
+ return { x: (vw * p) / 100, y: (vh * p) / 100 };
49
+ }
50
+ const yAxis = ["top", "bottom"];
51
+ const [a, b] = yAxis.includes(tokens[0]) ? [tokens[1], tokens[0]] : tokens;
52
+ const px = a in kw ? kw[a] : parseFloat(a);
53
+ const py = b in kw ? kw[b] : parseFloat(b);
54
+ return { x: (vw * px) / 100, y: (vh * py) / 100 };
55
+ }, origin);
56
+ }
57
+ /** Animate the page's scroll position to `targetY` over `duration` ms with an ease curve. */
58
+ export async function smoothScroll(page, targetY, duration) {
59
+ await page.evaluate(({ targetY, duration }) => {
60
+ return new Promise((resolve) => {
61
+ const startY = window.scrollY;
62
+ const dist = targetY - startY;
63
+ const frames = Math.ceil(duration / 16);
64
+ let i = 0;
65
+ const id = setInterval(() => {
66
+ i++;
67
+ const p = Math.min(i / frames, 1);
68
+ const e = p < 0.5 ? 4 * p * p * p : (p - 1) * (2 * p - 2) * (2 * p - 2) + 1;
69
+ window.scrollTo(0, startY + dist * e);
70
+ if (p >= 1) {
71
+ clearInterval(id);
72
+ resolve();
73
+ }
74
+ }, 16);
75
+ });
76
+ }, { targetY, duration });
77
+ }
78
+ /**
79
+ * Smooth-scroll to an element or position:
80
+ * - `"top"` / `"bottom"` → page extremes
81
+ * - number → absolute Y pixel position
82
+ * - CSS selector or `text:` prefix → element centred in the viewport
83
+ */
84
+ export async function smoothScrollToTarget(page, target, duration) {
85
+ if (typeof target === "number")
86
+ return smoothScroll(page, target, duration);
87
+ if (target === "top")
88
+ return smoothScroll(page, 0, duration);
89
+ if (target === "bottom") {
90
+ const bottom = await page.evaluate(() => document.body.scrollHeight);
91
+ return smoothScroll(page, bottom, duration);
92
+ }
93
+ const handle = await getHandle(page, target);
94
+ const y = await page.evaluate((el, vh) => {
95
+ const rect = el.getBoundingClientRect();
96
+ return window.scrollY + rect.top + rect.height / 2 - vh / 2;
97
+ }, handle, page.viewport()?.height ?? 900);
98
+ return smoothScroll(page, y, duration);
99
+ }
100
+ /**
101
+ * Scroll `target` into view if it lies outside the visible viewport (keeping
102
+ * `margin` px clear on each side). No-op when the element is already fully
103
+ * visible. `speed` (px/s) sets the scroll duration.
104
+ */
105
+ export async function scrollIntoView(page, target, margin, speed) {
106
+ const handle = await getHandle(page, target);
107
+ const scrollY = await page.evaluate((el, margin) => {
108
+ const rect = el.getBoundingClientRect();
109
+ const vh = window.innerHeight;
110
+ const comfort = margin * 2;
111
+ if (rect.top >= comfort && rect.bottom <= vh - comfort)
112
+ return null;
113
+ // Tall element: top-align with margin
114
+ if (rect.height > vh - margin * 2)
115
+ return window.scrollY + rect.top - margin;
116
+ // Element extends below the bottom comfort zone: scroll just enough to
117
+ // show it fully, rather than trying to centre it (which often overshoots
118
+ // the page's max scroll and leaves the element at the viewport edge).
119
+ if (rect.bottom > vh - margin)
120
+ return window.scrollY + rect.bottom - (vh - margin);
121
+ // Element extends above the top comfort zone: scroll to show top
122
+ if (rect.top < margin)
123
+ return window.scrollY + rect.top - margin;
124
+ // In view but within the comfort band: centre it
125
+ return window.scrollY + rect.top + rect.height / 2 - vh / 2;
126
+ }, handle, margin);
127
+ if (scrollY === null)
128
+ return;
129
+ const currentY = await page.evaluate(() => window.scrollY);
130
+ const dist = Math.abs(scrollY - currentY);
131
+ const duration = Math.max(200, (dist / speed) * 1000);
132
+ await smoothScroll(page, scrollY, duration);
133
+ }
134
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/browser/dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare const PLAY_BUTTON_ID = "__recordable_play__";
2
+ export declare const PLAY_BINDING = "__recordablePlay__";
3
+ /**
4
+ * The button injection as a *string*, not the `injectPlayButton` function object.
5
+ * esbuild/tsx (via `keepNames`) rewrites the inner arrows to `__name(() => …)`;
6
+ * Puppeteer injects a function via `.toString()`, so that `__name` call would leak
7
+ * into the page and throw (it's undefined there). Wrapping in a closure that
8
+ * defines an `__name` shim makes the snippet bundler-proof.
9
+ */
10
+ export declare function playButtonScript(message: string, id: string, binding: string): string;
11
+ //# sourceMappingURL=play-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"play-button.d.ts","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,wBAAwB,CAAC;AACpD,eAAO,MAAM,YAAY,uBAAuB,CAAC;AA0EjD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,GACd,MAAM,CAGR"}
@@ -0,0 +1,87 @@
1
+ export const PLAY_BUTTON_ID = "__recordable_play__";
2
+ export const PLAY_BINDING = "__recordablePlay__";
3
+ /**
4
+ * Injected into the page to render the in-page ▶ Play button. Runs in the
5
+ * browser context, so it must be fully self-contained (no outer references).
6
+ */
7
+ function injectPlayButton(message, id, binding) {
8
+ // Skip iframes — only the top document gets the button.
9
+ if (window !== window.parent)
10
+ return;
11
+ const build = () => {
12
+ if (document.getElementById(id))
13
+ return;
14
+ const wrap = document.createElement("div");
15
+ wrap.id = id;
16
+ wrap.style.cssText = [
17
+ "position:fixed",
18
+ "left:50%",
19
+ // Top-anchored: a headful window is shorter than the emulated viewport, so
20
+ // a bottom pill renders below the fold, out of the user's reach.
21
+ "top:24px",
22
+ "transform:translateX(-50%)",
23
+ "z-index:2147483647",
24
+ "pointer-events:auto",
25
+ "display:flex",
26
+ "align-items:center",
27
+ "gap:12px",
28
+ "padding:10px 18px 10px 12px",
29
+ "background:rgba(20,18,40,0.92)",
30
+ "color:#fff",
31
+ "border-radius:999px",
32
+ "box-shadow:0 8px 28px rgba(0,0,0,0.4)",
33
+ "font-family:system-ui,-apple-system,Segoe UI,sans-serif",
34
+ ].join(";");
35
+ const btn = document.createElement("button");
36
+ btn.style.cssText = [
37
+ "all:unset",
38
+ "cursor:pointer",
39
+ "display:inline-flex",
40
+ "align-items:center",
41
+ "justify-content:center",
42
+ "width:34px",
43
+ "height:34px",
44
+ "border-radius:50%",
45
+ "background:#5b54e8",
46
+ "color:#fff",
47
+ "font-size:13px",
48
+ ].join(";");
49
+ btn.textContent = "▶";
50
+ const label = document.createElement("span");
51
+ label.textContent = message;
52
+ label.style.cssText =
53
+ "font-size:14px;line-height:1;white-space:nowrap;cursor:default";
54
+ wrap.appendChild(btn);
55
+ wrap.appendChild(label);
56
+ let fired = false;
57
+ const fire = () => {
58
+ if (fired)
59
+ return;
60
+ fired = true;
61
+ wrap.remove();
62
+ const fn = window[binding];
63
+ if (typeof fn === "function")
64
+ fn();
65
+ };
66
+ // Click only — no Enter/Space binding, so the live page keeps its own
67
+ // keyboard (e.g. Enter to submit a form) during the manual step.
68
+ btn.addEventListener("click", fire);
69
+ document.body.appendChild(wrap);
70
+ };
71
+ if (document.body)
72
+ build();
73
+ else
74
+ document.addEventListener("DOMContentLoaded", build);
75
+ }
76
+ /**
77
+ * The button injection as a *string*, not the `injectPlayButton` function object.
78
+ * esbuild/tsx (via `keepNames`) rewrites the inner arrows to `__name(() => …)`;
79
+ * Puppeteer injects a function via `.toString()`, so that `__name` call would leak
80
+ * into the page and throw (it's undefined there). Wrapping in a closure that
81
+ * defines an `__name` shim makes the snippet bundler-proof.
82
+ */
83
+ export function playButtonScript(message, id, binding) {
84
+ const args = [message, id, binding].map((a) => JSON.stringify(a)).join(",");
85
+ return `(() => { const __name = (f) => f; (${injectPlayButton.toString()})(${args}); })()`;
86
+ }
87
+ //# sourceMappingURL=play-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"play-button.js","sourceRoot":"","sources":["../../src/browser/play-button.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AACpD,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAEjD;;;GAGG;AACH,SAAS,gBAAgB,CACvB,OAAe,EACf,EAAU,EACV,OAAe;IAEf,wDAAwD;IACxD,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO;IACrC,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAAE,OAAO;QACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG;YACnB,gBAAgB;YAChB,UAAU;YACV,2EAA2E;YAC3E,iEAAiE;YACjE,UAAU;YACV,4BAA4B;YAC5B,oBAAoB;YACpB,qBAAqB;YACrB,cAAc;YACd,oBAAoB;YACpB,UAAU;YACV,6BAA6B;YAC7B,gCAAgC;YAChC,YAAY;YACZ,qBAAqB;YACrB,uCAAuC;YACvC,yDAAyD;SAC1D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG;YAClB,WAAW;YACX,gBAAgB;YAChB,qBAAqB;YACrB,oBAAoB;YACpB,wBAAwB;YACxB,YAAY;YACZ,aAAa;YACb,mBAAmB;YACnB,oBAAoB;YACpB,YAAY;YACZ,gBAAgB;SACjB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC;QACtB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC7C,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;QAC5B,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,gEAAgE,CAAC;QACnE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,IAAI,KAAK;gBAAE,OAAO;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,EAAE,GAAI,MAAgD,CAAC,OAAO,CAAC,CAAC;YACtE,IAAI,OAAO,EAAE,KAAK,UAAU;gBAAE,EAAE,EAAE,CAAC;QACrC,CAAC,CAAC;QACF,sEAAsE;QACtE,iEAAiE;QACjE,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,IAAI;QAAE,KAAK,EAAE,CAAC;;QACtB,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,EAAU,EACV,OAAe;IAEf,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,OAAO,sCAAsC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,IAAI,SAAS,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type Page, type GoToOptions } from "puppeteer";
2
+ import type { ResolvedConfig, WaitForOptions } from "../config.js";
3
+ import { type Logger } from "../logger.js";
4
+ import { type ZoomState } from "./cursor.js";
5
+ export declare class Runtime {
6
+ private readonly getCfg;
7
+ private readonly log;
8
+ private readonly cursor;
9
+ private zoom;
10
+ private playResolver;
11
+ private playBindingExposed;
12
+ constructor(getCfg: () => ResolvedConfig, log: Logger);
13
+ /** The current zoom transform — the session re-reads it when re-injecting the
14
+ * cursor overlay across navigations. */
15
+ get zoomState(): ZoomState;
16
+ /** Drop any zoom transform (a fresh document carries none). */
17
+ resetZoomState(): void;
18
+ /** (Re-)inject the cursor overlay at the carried position, if enabled. */
19
+ injectCursor(page: Page): Promise<void>;
20
+ visit(page: Page, url: string, options?: GoToOptions): Promise<void>;
21
+ waitFor(page: Page, target: string, options?: WaitForOptions): Promise<void>;
22
+ click(page: Page, target: string): Promise<void>;
23
+ hover(page: Page, target: string): Promise<void>;
24
+ type(page: Page, target: string, text: string, options?: {
25
+ duration?: number;
26
+ }): Promise<void>;
27
+ clear(page: Page, target: string): Promise<void>;
28
+ select(page: Page, target: string, value: string): Promise<void>;
29
+ key(page: Page, key: string): Promise<void>;
30
+ mouse(page: Page, target: string | {
31
+ x: number;
32
+ y: number;
33
+ }): Promise<void>;
34
+ scroll(page: Page, target: string | number, options?: {
35
+ duration?: number;
36
+ }): Promise<void>;
37
+ zoomTo(page: Page, level: number, options?: {
38
+ origin?: string;
39
+ duration?: number;
40
+ }): Promise<void>;
41
+ resetZoom(page: Page, options?: {
42
+ duration?: number;
43
+ }): Promise<void>;
44
+ /** Click a target's centre. Uses page.mouse.click() not ElementHandle.click()
45
+ * to avoid Puppeteer's built-in scrollIntoView overriding our centred scroll. */
46
+ private _click;
47
+ /**
48
+ * Click at viewport coords and, if the click triggers a navigation, wait for the
49
+ * new page to settle before returning — so a navigating `click()` behaves like a
50
+ * `visit()` and the next action lands on the loaded page rather than racing it.
51
+ * In-page clicks pay only a short probe.
52
+ */
53
+ private _clickPoint;
54
+ /** Move to viewport coords, animating the overlay when the cursor is enabled. */
55
+ private _moveTo;
56
+ /** Scroll a target into view using the configured margin/speed. */
57
+ private _scrollIntoView;
58
+ /**
59
+ * Block until the user resumes — by clicking the in-page ▶ Play button, or by
60
+ * pressing Enter in the *terminal*. The in-page button is click-only so the live
61
+ * page keeps its own keyboard (Enter to submit a form, etc.); the terminal
62
+ * fallback means a resume is still reachable if the button fails to render.
63
+ */
64
+ waitForPlay(page: Page, message: string): Promise<void>;
65
+ }
66
+ //# sourceMappingURL=runtime.d.ts.map