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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cam Parry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # recordable
2
+
3
+ Programmatic, repeatable browser screen recording. Describe a session as a fluent
4
+ chain of actions — `visit`, `click`, `type`, `zoom`, `scroll` — and `recordable`
5
+ drives a real [Puppeteer](https://pptr.dev/) browser and captures a clean MP4,
6
+ complete with an animated cursor, smooth zooming/scrolling, and human-like typing.
7
+
8
+ Because the recording is _code_, it's deterministic and re-runnable: regenerate the
9
+ exact same capture whenever the UI changes — for product demos, onboarding clips,
10
+ documentation GIFs, release notes, or visual regression footage.
11
+
12
+ ```ts
13
+ import { Recordable } from "recordable";
14
+
15
+ await new Recordable({ typingSpeed: 120 })
16
+ .pause() // skip the initial page load
17
+ .visit("https://example.com")
18
+ .resume()
19
+ .zoom(1.5, { origin: "#newsletter" })
20
+ .type("#email", "hello@example.com")
21
+ .click("text:Sign up")
22
+ .scroll("bottom")
23
+ .resetZoom()
24
+ .wait(1500)
25
+ .run(); // finalises automatically — no start()/stop()
26
+ ```
27
+
28
+ Recording is **on by default** and finalises when `.run()` ends — there's no
29
+ `start()`/`stop()`. Use `pause()` / `resume()` to carve out anything you don't
30
+ want on camera; every captured segment is stitched into one seamless MP4.
31
+
32
+ ## Features
33
+
34
+ - **Fluent, queued API** — chain actions; nothing runs until `.run()`.
35
+ - **Deterministic & repeatable** — the recording is code, so it reproduces exactly.
36
+ - **Animated cursor overlay** with realistic movement and click feedback.
37
+ - **Smooth zoom & scroll** that animate origin and scale together.
38
+ - **Human-like typing** with jitter and natural pauses.
39
+ - **Element targeting** by CSS selector or visible text (`text:` prefix).
40
+ - **Off-camera segments** — `pause()`/`resume()` skip setup, navigations, or whole
41
+ screens; segments are auto-stitched into one seamless video.
42
+ - **Manual steps / logins** — `resumeOnInput()` waits for an in-page ▶ Play button
43
+ (see below), so you can sign in by hand before recording.
44
+ - **Auto-scroll** to bring elements into view before interacting.
45
+ - **Declarative JSON scripts + CLI** — author a recording as JSON (with a published
46
+ schema for editor autocomplete) and run it with `npx recordable demo.json`, no
47
+ install or TypeScript required.
48
+
49
+ ## Install
50
+
51
+ ```sh
52
+ npm install recordable
53
+ ```
54
+
55
+ Frames are captured via the Chrome DevTools Protocol and encoded with **FFmpeg** —
56
+ there's no external screen-recorder dependency. The ffmpeg binary ships via
57
+ [`@ffmpeg-installer/ffmpeg`](https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg),
58
+ so there's nothing else to install (a system `ffmpeg` on your `PATH` is used as a
59
+ fallback).
60
+
61
+ ## Declarative scripts (JSON)
62
+
63
+ You don't have to write TypeScript. A recording can be a plain **JSON** file — an
64
+ array of `{ action, ... }` actions that map 1:1 onto the chainable API, optionally
65
+ wrapped with a `config`:
66
+
67
+ ```json
68
+ {
69
+ "$schema": "https://raw.githubusercontent.com/paragramagency/recordable/main/recordable.schema.json",
70
+ "config": { "typingSpeed": 14 },
71
+ "actions": [
72
+ { "action": "pause" },
73
+ { "action": "visit", "url": "https://example.com" },
74
+ { "action": "resume" },
75
+ { "action": "zoom", "level": 1.5, "origin": "#newsletter" },
76
+ { "action": "type", "target": "#email", "text": "hello@example.com" },
77
+ { "action": "click", "target": "text:Sign up" },
78
+ { "action": "waitFor", "target": "text:Thanks", "state": "visible" },
79
+ { "action": "resetZoom" }
80
+ ]
81
+ }
82
+ ```
83
+
84
+ Each step's keys are the named arguments of the matching method — `type(target, text)`
85
+ → `{ "action": "type", "target": …, "text": … }`; `waitFor`'s `state`/`timeout` are
86
+ top-level keys.
87
+
88
+ **Editor support.** Add the `"$schema"` line above (a URL, or a relative path to a
89
+ local copy) and your editor gives you autocomplete, required-key checking, and
90
+ typo catching for every action — no TypeScript needed. The schema is published as
91
+ `recordable.schema.json`.
92
+
93
+ Run a script from code with `runScript` (or `fromJSON` to build without running):
94
+
95
+ ```ts
96
+ import { runScript } from "recordable";
97
+ import demo from "./demo.json" with { type: "json" };
98
+
99
+ await runScript(demo);
100
+ ```
101
+
102
+ ### CLI
103
+
104
+ Or run a JSON file directly — **no install required** via `npx`:
105
+
106
+ ```sh
107
+ npx recordable demo.json
108
+ ```
109
+
110
+ ```
111
+ recordable <script.json> [options]
112
+
113
+ --check Validate the script and exit (no browser, no recording)
114
+ --headless Run without a visible browser window
115
+ --silent Suppress recorder console output
116
+ --out-dir <dir> Output directory (overrides the script's config)
117
+ --name <name> Output filename (without extension)
118
+ --no-timestamp Don't prepend an ISO timestamp to the filename
119
+ -h, --help Show this help
120
+ ```
121
+
122
+ Relative `visit` URLs (e.g. `"./index.html"`) and a relative `outputDir` resolve
123
+ against the script file, so a script, its mockups, and its output stay together
124
+ regardless of where you run it from. `--out-dir` overrides the output location
125
+ (taken relative to the current directory). `--check` validates a script in CI or
126
+ while authoring without launching a browser.
127
+
128
+ ## Off-camera work & seamless segments
129
+
130
+ `pause()` stops the camera but the chain keeps running, so anything up to the
131
+ next `resume()` happens off-camera — page loads, data setup, even navigating to a
132
+ different screen. Each recorded stretch is a segment, and they're concatenated
133
+ (losslessly, by stream-copy where possible) into a single MP4 on `.run()`:
134
+
135
+ ```ts
136
+ await new Recordable()
137
+ .visit("/dashboard")
138
+ .click("text:Reports") // recorded
139
+ .pause()
140
+ .visit("/admin") // off-camera: jump to another screen, reset state…
141
+ .click("text:Seed demo data")
142
+ .resume()
143
+ .click("text:Run report") // recorded again — stitched seamlessly to the above
144
+ .run();
145
+ ```
146
+
147
+ ## Recording behind a login (manual steps)
148
+
149
+ Run **headful** (`headless: false`) so the Chrome window is interactive. Keep the
150
+ camera off while you sign in by hand, then `resumeOnInput()` waits for you to
151
+ click an **in-page ▶ Play button** (or press Enter) before recording resumes:
152
+
153
+ ```ts
154
+ await new Recordable({ headless: false })
155
+ .pause() // camera off — the login isn't recorded
156
+ .visit("https://app.example.com/login")
157
+ .resumeOnInput("Log in, then click ▶ Play to start recording")
158
+ .visit("https://app.example.com/dashboard")
159
+ .click("text:New project")
160
+ .run();
161
+ ```
162
+
163
+ - **`resumeOnInput(message?)`** injects a ▶ Play button into the page itself and
164
+ blocks until you click it (Enter in the terminal also works). The button is
165
+ re-injected across navigations, so it survives login redirects.
166
+ - Prefer an automatic trigger? Use **`waitFor("#dashboard")`** after `resume()` to
167
+ carry on once a post-login element appears — no clicking required.
168
+
169
+ Because the manual step sits inside a `pause()`, the sign-in never appears in the
170
+ video.
171
+
172
+ ## Intros, outros & mid-rolls
173
+
174
+ `insert(path)` splices an external video clip into the timeline at that point —
175
+ its position decides the role: first call is an intro, last is an outro, anything
176
+ in between is a mid-roll. The clip is normalized to the recording's resolution,
177
+ fps, and codec so the join is seamless.
178
+
179
+ ```ts
180
+ new Recordable()
181
+ .insert("intro.mp4", { fadeIn: 500, fadeOut: 600 }) // plays first
182
+ .visit("https://example.com")
183
+ .click("text:Get started")
184
+ .insert("feature-promo.mp4", { fadeIn: 600, fadeOut: 600 }) // mid-roll
185
+ .scroll("bottom")
186
+ .insert("outro.mp4", { fadeOut: 500 }) // plays last
187
+ .run();
188
+ ```
189
+
190
+ No `pause()`/`resume()` needed — `insert` seals the current segment and recording
191
+ resumes into a fresh one on the next action automatically. (Audio on the clip is
192
+ currently dropped; voiceover support is on the roadmap.)
193
+
194
+ **Cross-fades.** Pass `fadeIn` / `fadeOut` (ms) to dissolve rather than hard-cut.
195
+ A fade blends the clip with the **neighbouring recorded footage** (a true
196
+ cross-dissolve), or fades from/to **black** at the timeline ends where there's no
197
+ neighbour — so an intro's `fadeIn` fades up from black and dissolves into the page
198
+ on `fadeOut`, while an outro's `fadeOut` dissolves down to black. Omit them for a
199
+ hard cut. A cross-fade of _d_ ms overlaps the two pieces by _d_, shortening the
200
+ timeline by that much at each faded boundary.
201
+
202
+ ## API
203
+
204
+ Create an instance with optional [config](#configuration), chain actions, then
205
+ `await .run()`.
206
+
207
+ ### Recording
208
+
209
+ Recording is on by default and finalises automatically on `.run()`. These control
210
+ what lands on camera:
211
+
212
+ | Method | Description |
213
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
214
+ | `pause()` | Stop capturing; the chain keeps running off-camera. |
215
+ | `resume()` | Resume capturing in a fresh segment, immediately. |
216
+ | `resumeOnInput(message?)` | Resume only after the user clicks the in-page ▶ Play button (or presses Enter). |
217
+ | `insert(path, opts?)` | Splice an external clip (intro / outro / mid-roll) into the timeline; `opts.fadeIn`/`fadeOut` (ms) cross-fade it. |
218
+
219
+ ### Navigation & waiting
220
+
221
+ | Method | Description |
222
+ | ------------------------ | --------------------------------------------------------------- |
223
+ | `visit(url, options?)` | Navigate and wait for the page to settle. |
224
+ | `waitFor(target, opts?)` | Wait for an element to become `visible` / `hidden` / `present`. |
225
+ | `wait(ms)` | Pause the sequence for `ms` milliseconds. |
226
+
227
+ ### Interactions
228
+
229
+ | Method | Description |
230
+ | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
231
+ | `click(target)` | Click an element. |
232
+ | `hover(target)` | Move onto an element to reveal `:hover` state (no click). |
233
+ | `type(target, text, { duration? })` | Type into a field with human-like timing; `duration` (ms) spreads keystrokes evenly with no jitter. |
234
+ | `clear(target)` | Select-all + delete the contents of a field. |
235
+ | `select(target, value)` | Choose an option in a native `<select>` (the OS-drawn option list isn't captured — see note below). |
236
+ | `key(key)` | Press a key, e.g. `"Escape"`, `"Enter"`, `"Tab"`. |
237
+ | `mouse(target \| {x, y})` | Move the cursor to an element or coordinates. |
238
+
239
+ > The browser draws an open `<select>`'s option list with the OS, outside the page,
240
+ > so the screencast can't capture it — `select()` shows the cursor and the value
241
+ > changing, but not the dropdown. For an on-camera dropdown, build a custom one from
242
+ > `click()`s.
243
+
244
+ ### Camera
245
+
246
+ | Method | Description |
247
+ | ------------------------------------- | ----------------------------------------------------------------- |
248
+ | `scroll(target, { duration? })` | Smooth-scroll to `"top"`/`"bottom"`, a selector, or a Y position. |
249
+ | `zoom(level, { origin?, duration? })` | Smoothly scale from an origin (keyword, `%`, or selector). |
250
+ | `resetZoom({ duration? })` | Smoothly return to 1×. |
251
+ | `setConfig(config)` | Merge config mid-sequence (takes effect at that point). |
252
+
253
+ ### Targeting
254
+
255
+ Anywhere a `target` is accepted you can pass:
256
+
257
+ - a **CSS selector** — `"#id"`, `".card"`, `'[name="email"]'`
258
+ - a **`text:` prefix** — `"text:Sign up"` matches by visible text
259
+
260
+ ## Configuration
261
+
262
+ All options are optional; defaults shown.
263
+
264
+ ```ts
265
+ new Recordable({
266
+ viewport: { width: 1920, height: 1080 },
267
+ fps: 30,
268
+ outputDir: "./output",
269
+ outputName: "recordable",
270
+ outputTimestamp: true, // prepend an ISO timestamp to the filename
271
+ headless: false,
272
+ typingSpeed: 7, // characters per second
273
+ videoCrf: 18, // lower = better quality, larger file
274
+ videoCodec: "libx264",
275
+ videoPreset: "ultrafast",
276
+ zoomDuration: 600, // ms
277
+ actionDelay: 300, // ms inserted between every action
278
+ silent: false,
279
+ autoScroll: true, // scroll elements into view before interacting
280
+ scrollMargin: 120, // px kept around an element when auto-scrolling
281
+ scrollSpeed: 1500, // px/s
282
+ scrollDuration: 1200, // ms for the scroll action's transition
283
+ cursor: true, // show the animated cursor overlay
284
+ visitTimeout: 30_000, // ms for navigation / waitFor
285
+ });
286
+ ```
287
+
288
+ ## Development
289
+
290
+ ```sh
291
+ npm install
292
+ npm run build # type-check + emit dist/ with .d.ts
293
+ npm run gen:schema # regenerate recordable.schema.json from the action manifest
294
+ npx tsx my-script.ts # run a recording script directly
295
+ node dist/cli.js demo.json # run a JSON script through the CLI locally
296
+ ```
297
+
298
+ The JSON action set and its schema are both generated from one manifest in
299
+ `src/actions.ts`; run `npm run gen:schema` after changing it.
300
+
301
+ ## License
302
+
303
+ MIT © Cam Parry
@@ -0,0 +1,140 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Per-action argument schema (the keyed args, excluding the `action`
4
+ * discriminator). strictObject so an unknown key (a typo) fails validation.
5
+ */
6
+ declare const ACTIONS: {
7
+ pause: z.ZodObject<{}, z.core.$strict>;
8
+ resume: z.ZodObject<{}, z.core.$strict>;
9
+ resumeOnInput: z.ZodObject<{
10
+ message: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strict>;
12
+ insert: z.ZodObject<{
13
+ path: z.ZodString;
14
+ fadeIn: z.ZodOptional<z.ZodNumber>;
15
+ fadeOut: z.ZodOptional<z.ZodNumber>;
16
+ }, z.core.$strict>;
17
+ audio: z.ZodObject<{
18
+ path: z.ZodString;
19
+ wait: z.ZodOptional<z.ZodBoolean>;
20
+ volume: z.ZodOptional<z.ZodNumber>;
21
+ }, z.core.$strict>;
22
+ setConfig: z.ZodObject<{
23
+ config: z.ZodObject<{
24
+ viewport: z.ZodDefault<z.ZodObject<{
25
+ width: z.ZodNumber;
26
+ height: z.ZodNumber;
27
+ }, z.core.$strict>>;
28
+ fps: z.ZodDefault<z.ZodNumber>;
29
+ outputDir: z.ZodDefault<z.ZodString>;
30
+ outputName: z.ZodDefault<z.ZodString>;
31
+ outputTimestamp: z.ZodDefault<z.ZodBoolean>;
32
+ assetsDir: z.ZodDefault<z.ZodString>;
33
+ headless: z.ZodDefault<z.ZodBoolean>;
34
+ launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
35
+ typingSpeed: z.ZodDefault<z.ZodNumber>;
36
+ videoCrf: z.ZodDefault<z.ZodNumber>;
37
+ videoCodec: z.ZodDefault<z.ZodString>;
38
+ videoPreset: z.ZodDefault<z.ZodString>;
39
+ zoomDuration: z.ZodDefault<z.ZodNumber>;
40
+ actionDelay: z.ZodDefault<z.ZodNumber>;
41
+ silent: z.ZodDefault<z.ZodBoolean>;
42
+ autoScroll: z.ZodDefault<z.ZodBoolean>;
43
+ scrollMargin: z.ZodDefault<z.ZodNumber>;
44
+ scrollSpeed: z.ZodDefault<z.ZodNumber>;
45
+ scrollDuration: z.ZodDefault<z.ZodNumber>;
46
+ cursor: z.ZodDefault<z.ZodBoolean>;
47
+ visitTimeout: z.ZodDefault<z.ZodNumber>;
48
+ baseDir: z.ZodDefault<z.ZodString>;
49
+ }, z.core.$strict>;
50
+ }, z.core.$strict>;
51
+ visit: z.ZodObject<{
52
+ url: z.ZodString;
53
+ waitUntil: z.ZodOptional<z.ZodString>;
54
+ timeout: z.ZodOptional<z.ZodNumber>;
55
+ referer: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strict>;
57
+ waitFor: z.ZodObject<{
58
+ target: z.ZodString;
59
+ state: z.ZodOptional<z.ZodEnum<{
60
+ visible: "visible";
61
+ hidden: "hidden";
62
+ present: "present";
63
+ }>>;
64
+ timeout: z.ZodOptional<z.ZodNumber>;
65
+ }, z.core.$strict>;
66
+ click: z.ZodObject<{
67
+ target: z.ZodString;
68
+ }, z.core.$strict>;
69
+ hover: z.ZodObject<{
70
+ target: z.ZodString;
71
+ }, z.core.$strict>;
72
+ type: z.ZodObject<{
73
+ target: z.ZodString;
74
+ text: z.ZodString;
75
+ duration: z.ZodOptional<z.ZodNumber>;
76
+ }, z.core.$strict>;
77
+ clear: z.ZodObject<{
78
+ target: z.ZodString;
79
+ }, z.core.$strict>;
80
+ select: z.ZodObject<{
81
+ target: z.ZodString;
82
+ value: z.ZodString;
83
+ }, z.core.$strict>;
84
+ key: z.ZodObject<{
85
+ key: z.ZodString;
86
+ }, z.core.$strict>;
87
+ mouse: z.ZodObject<{
88
+ target: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
89
+ x: z.ZodNumber;
90
+ y: z.ZodNumber;
91
+ }, z.core.$strict>]>;
92
+ }, z.core.$strict>;
93
+ scroll: z.ZodObject<{
94
+ target: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
95
+ duration: z.ZodOptional<z.ZodNumber>;
96
+ }, z.core.$strict>;
97
+ zoom: z.ZodObject<{
98
+ level: z.ZodNumber;
99
+ origin: z.ZodOptional<z.ZodString>;
100
+ duration: z.ZodOptional<z.ZodNumber>;
101
+ }, z.core.$strict>;
102
+ resetZoom: z.ZodObject<{
103
+ duration: z.ZodOptional<z.ZodNumber>;
104
+ }, z.core.$strict>;
105
+ wait: z.ZodObject<{
106
+ ms: z.ZodNumber;
107
+ }, z.core.$strict>;
108
+ };
109
+ /** A single action: the action name plus its flat named arguments. */
110
+ export type Action = {
111
+ action: string;
112
+ [key: string]: unknown;
113
+ };
114
+ /**
115
+ * Validate one keyed action against the manifest: the action must exist and its
116
+ * argument *values* (and key names) must match the action's schema — so a wrong
117
+ * type (`{ action: "zoom", level: "big" }`) or a typo'd key fails here. Shared by
118
+ * the JSON loader and the Markdown mapper.
119
+ */
120
+ export declare function validateAction(step: Action): void;
121
+ /**
122
+ * Turn one flat action into the positional argument list for its method.
123
+ *
124
+ * Optional positionals that are absent become `undefined` — JavaScript default
125
+ * parameters then apply, so a present later arg never lands in the wrong slot.
126
+ * Bag keys collapse into a single trailing options object.
127
+ */
128
+ declare function buildArgs(step: Action, name: string): unknown[];
129
+ /**
130
+ * Map a positional method call — `{ name, args }` as produced by the Markdown
131
+ * parser — onto a flat keyed {@link Action}, the same IR the JSON layer uses.
132
+ * Positional args are named by manifest order; a trailing options object is
133
+ * flattened to top-level keys. The result is validated, so value/key typos throw.
134
+ */
135
+ export declare function callToAction(name: string, args: readonly unknown[]): Action;
136
+ /** The action manifest, exported so schema/docs tooling can read it. */
137
+ export { ACTIONS };
138
+ /** Exported for unit tests: map a keyed action to its positional method args. */
139
+ export { buildArgs };
140
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAezB;;;GAGG;AACH,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyD0B,CAAC;AAWxC,sEAAsE;AACtE,MAAM,MAAM,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAuChE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBjD;AAED;;;;;;GAMG;AACH,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAcxD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CA2C3E;AAED,wEAAwE;AACxE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,iFAAiF;AACjF,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,184 @@
1
+ import * as z from "zod";
2
+ import { RecordableError } from "./errors.js";
3
+ import { ConfigSchema } from "./config.js";
4
+ // ─── Action model ────────────────────────────────────────────────────────────
5
+ //
6
+ // A single action is a flat `{ action, ...args }` object that maps ~1:1 onto the
7
+ // chainable API. The ACTIONS manifest below is the single source of truth: one
8
+ // Zod schema per action drives value-level validation, the published JSON Schema
9
+ // (see schema.ts), and the Markdown marker mapping. The document that strings
10
+ // actions together — the `Script` type and its helpers — lives in script.ts.
11
+ const STATE = z.enum(["visible", "hidden", "present"]);
12
+ const XY = z.strictObject({ x: z.number(), y: z.number() });
13
+ /**
14
+ * Per-action argument schema (the keyed args, excluding the `action`
15
+ * discriminator). strictObject so an unknown key (a typo) fails validation.
16
+ */
17
+ const ACTIONS = {
18
+ // Recording control
19
+ pause: z.strictObject({}),
20
+ resume: z.strictObject({}),
21
+ resumeOnInput: z.strictObject({ message: z.string().optional() }),
22
+ insert: z.strictObject({
23
+ path: z.string(),
24
+ fadeIn: z.number().optional(),
25
+ fadeOut: z.number().optional(),
26
+ }),
27
+ audio: z.strictObject({
28
+ path: z.string(),
29
+ wait: z.boolean().optional(),
30
+ volume: z.number().optional(),
31
+ }),
32
+ setConfig: z.strictObject({ config: ConfigSchema }),
33
+ // Navigation
34
+ visit: z.strictObject({
35
+ url: z.string(),
36
+ waitUntil: z.string().optional(),
37
+ timeout: z.number().optional(),
38
+ referer: z.string().optional(),
39
+ }),
40
+ waitFor: z.strictObject({
41
+ target: z.string(),
42
+ state: STATE.optional(),
43
+ timeout: z.number().optional(),
44
+ }),
45
+ // Interactions
46
+ click: z.strictObject({ target: z.string() }),
47
+ hover: z.strictObject({ target: z.string() }),
48
+ type: z.strictObject({
49
+ target: z.string(),
50
+ text: z.string(),
51
+ duration: z.number().optional(),
52
+ }),
53
+ clear: z.strictObject({ target: z.string() }),
54
+ select: z.strictObject({ target: z.string(), value: z.string() }),
55
+ key: z.strictObject({ key: z.string() }),
56
+ mouse: z.strictObject({ target: z.union([z.string(), XY]) }),
57
+ // Scrolling / zoom
58
+ scroll: z.strictObject({
59
+ target: z.union([z.string(), z.number()]),
60
+ duration: z.number().optional(),
61
+ }),
62
+ zoom: z.strictObject({
63
+ level: z.number(),
64
+ origin: z.string().optional(),
65
+ duration: z.number().optional(),
66
+ }),
67
+ resetZoom: z.strictObject({ duration: z.number().optional() }),
68
+ // Timing
69
+ wait: z.strictObject({ ms: z.number() }),
70
+ };
71
+ /**
72
+ * Keys that are optional yet passed *positionally* in Markdown method calls
73
+ * (rather than gathered into the trailing options bag) — the only per-action
74
+ * fact not derivable from the schema.
75
+ */
76
+ const POSITIONAL_OPTIONAL = {
77
+ resumeOnInput: ["message"],
78
+ };
79
+ // ─── Manifest derivation ─────────────────────────────────────────────────────
80
+ //
81
+ // Positional/bag layout is derived from each action's Zod `.shape`: keys in
82
+ // declaration order, with optionality read off `ZodOptional`.
83
+ const shapeOf = (name) => ACTIONS[name].shape;
84
+ /** All argument keys, in declaration order. */
85
+ const keysInOrder = (name) => Object.keys(shapeOf(name));
86
+ /** Whether `key` is optional for `name` (a `ZodOptional` in the shape). */
87
+ const isOptional = (name, key) => shapeOf(name)[key] instanceof z.ZodOptional;
88
+ /** Keys passed positionally: the required ones plus any flagged positional-optional. */
89
+ const positionalKeys = (name) => keysInOrder(name).filter((k) => !isOptional(name, k) || POSITIONAL_OPTIONAL[name]?.includes(k));
90
+ /** Keys gathered into the trailing options bag: optional and not positional. */
91
+ const bagKeys = (name) => keysInOrder(name).filter((k) => isOptional(name, k) && !POSITIONAL_OPTIONAL[name]?.includes(k));
92
+ /** One readable line per issue: `<path>: <message>`. */
93
+ function formatIssues(error) {
94
+ return error.issues
95
+ .map((issue) => {
96
+ const path = issue.path.join(".");
97
+ return path ? `${path}: ${issue.message}` : issue.message;
98
+ })
99
+ .join("; ");
100
+ }
101
+ /**
102
+ * Validate one keyed action against the manifest: the action must exist and its
103
+ * argument *values* (and key names) must match the action's schema — so a wrong
104
+ * type (`{ action: "zoom", level: "big" }`) or a typo'd key fails here. Shared by
105
+ * the JSON loader and the Markdown mapper.
106
+ */
107
+ export function validateAction(step) {
108
+ const schema = ACTIONS[step.action];
109
+ if (!schema) {
110
+ throw new RecordableError("CONFIG_INVALID", `Unknown action "${step.action}" — valid actions: ${Object.keys(ACTIONS).join(", ")}`);
111
+ }
112
+ const { action: _action, ...rest } = step;
113
+ const result = schema.safeParse(rest);
114
+ if (!result.success) {
115
+ throw new RecordableError("CONFIG_INVALID", `Action "${step.action}": ${formatIssues(result.error)}`);
116
+ }
117
+ }
118
+ /**
119
+ * Turn one flat action into the positional argument list for its method.
120
+ *
121
+ * Optional positionals that are absent become `undefined` — JavaScript default
122
+ * parameters then apply, so a present later arg never lands in the wrong slot.
123
+ * Bag keys collapse into a single trailing options object.
124
+ */
125
+ function buildArgs(step, name) {
126
+ const args = [];
127
+ for (const key of positionalKeys(name))
128
+ args.push(step[key]);
129
+ const bag = bagKeys(name);
130
+ if (bag.length) {
131
+ const opts = {};
132
+ for (const key of bag)
133
+ if (key in step)
134
+ opts[key] = step[key];
135
+ args.push(Object.keys(opts).length ? opts : undefined);
136
+ }
137
+ // Trim trailing undefineds so the method's own defaults apply cleanly.
138
+ while (args.length && args[args.length - 1] === undefined)
139
+ args.pop();
140
+ return args;
141
+ }
142
+ /**
143
+ * Map a positional method call — `{ name, args }` as produced by the Markdown
144
+ * parser — onto a flat keyed {@link Action}, the same IR the JSON layer uses.
145
+ * Positional args are named by manifest order; a trailing options object is
146
+ * flattened to top-level keys. The result is validated, so value/key typos throw.
147
+ */
148
+ export function callToAction(name, args) {
149
+ const schema = ACTIONS[name];
150
+ if (!schema) {
151
+ throw new Error(`Unknown action "${name}" — valid actions: ${Object.keys(ACTIONS).join(", ")}`);
152
+ }
153
+ const step = { action: name };
154
+ let i = 0;
155
+ for (const key of positionalKeys(name)) {
156
+ if (i < args.length)
157
+ step[key] = args[i++];
158
+ else if (!isOptional(name, key))
159
+ throw new Error(`Action "${name}" is missing required "${key}"`);
160
+ }
161
+ const bag = bagKeys(name);
162
+ if (i < args.length && bag.length) {
163
+ const obj = args[i++];
164
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
165
+ throw new Error(`Action "${name}": expected a trailing options object, got ${JSON.stringify(obj)}`);
166
+ }
167
+ for (const [k, v] of Object.entries(obj)) {
168
+ if (!bag.includes(k)) {
169
+ throw new Error(`Action "${name}": unknown key "${k}" — valid keys: ${bag.join(", ")}`);
170
+ }
171
+ step[k] = v;
172
+ }
173
+ }
174
+ if (i < args.length) {
175
+ throw new Error(`Action "${name}": too many arguments (expected at most ${i}, got ${args.length})`);
176
+ }
177
+ validateAction(step);
178
+ return step;
179
+ }
180
+ /** The action manifest, exported so schema/docs tooling can read it. */
181
+ export { ACTIONS };
182
+ /** Exported for unit tests: map a keyed action to its positional method args. */
183
+ export { buildArgs };
184
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}