screencast-axi 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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +555 -0
  3. package/dist/bin/screencast-axi.d.ts +2 -0
  4. package/dist/bin/screencast-axi.js +16 -0
  5. package/dist/bin/screencast-axi.js.map +1 -0
  6. package/dist/src/auth/strategies.d.ts +54 -0
  7. package/dist/src/auth/strategies.js +137 -0
  8. package/dist/src/auth/strategies.js.map +1 -0
  9. package/dist/src/auth/types.d.ts +65 -0
  10. package/dist/src/auth/types.js +2 -0
  11. package/dist/src/auth/types.js.map +1 -0
  12. package/dist/src/browser.d.ts +100 -0
  13. package/dist/src/browser.js +168 -0
  14. package/dist/src/browser.js.map +1 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.js +160 -0
  17. package/dist/src/cli.js.map +1 -0
  18. package/dist/src/commands/auth.d.ts +4 -0
  19. package/dist/src/commands/auth.js +207 -0
  20. package/dist/src/commands/auth.js.map +1 -0
  21. package/dist/src/commands/check.d.ts +12 -0
  22. package/dist/src/commands/check.js +103 -0
  23. package/dist/src/commands/check.js.map +1 -0
  24. package/dist/src/commands/doctor.d.ts +15 -0
  25. package/dist/src/commands/doctor.js +131 -0
  26. package/dist/src/commands/doctor.js.map +1 -0
  27. package/dist/src/commands/guide.d.ts +6 -0
  28. package/dist/src/commands/guide.js +257 -0
  29. package/dist/src/commands/guide.js.map +1 -0
  30. package/dist/src/commands/home.d.ts +12 -0
  31. package/dist/src/commands/home.js +78 -0
  32. package/dist/src/commands/home.js.map +1 -0
  33. package/dist/src/commands/list.d.ts +6 -0
  34. package/dist/src/commands/list.js +128 -0
  35. package/dist/src/commands/list.js.map +1 -0
  36. package/dist/src/commands/record.d.ts +7 -0
  37. package/dist/src/commands/record.js +266 -0
  38. package/dist/src/commands/record.js.map +1 -0
  39. package/dist/src/commands/scaffold.d.ts +4 -0
  40. package/dist/src/commands/scaffold.js +122 -0
  41. package/dist/src/commands/scaffold.js.map +1 -0
  42. package/dist/src/commands/setup.d.ts +14 -0
  43. package/dist/src/commands/setup.js +137 -0
  44. package/dist/src/commands/setup.js.map +1 -0
  45. package/dist/src/config.d.ts +126 -0
  46. package/dist/src/config.js +275 -0
  47. package/dist/src/config.js.map +1 -0
  48. package/dist/src/director.d.ts +217 -0
  49. package/dist/src/director.js +415 -0
  50. package/dist/src/director.js.map +1 -0
  51. package/dist/src/duration.d.ts +33 -0
  52. package/dist/src/duration.js +68 -0
  53. package/dist/src/duration.js.map +1 -0
  54. package/dist/src/encode.d.ts +67 -0
  55. package/dist/src/encode.js +225 -0
  56. package/dist/src/encode.js.map +1 -0
  57. package/dist/src/errors.d.ts +19 -0
  58. package/dist/src/errors.js +27 -0
  59. package/dist/src/errors.js.map +1 -0
  60. package/dist/src/flags.d.ts +39 -0
  61. package/dist/src/flags.js +130 -0
  62. package/dist/src/flags.js.map +1 -0
  63. package/dist/src/forensics.d.ts +51 -0
  64. package/dist/src/forensics.js +107 -0
  65. package/dist/src/forensics.js.map +1 -0
  66. package/dist/src/hooks.d.ts +9 -0
  67. package/dist/src/hooks.js +20 -0
  68. package/dist/src/hooks.js.map +1 -0
  69. package/dist/src/index.d.ts +18 -0
  70. package/dist/src/index.js +18 -0
  71. package/dist/src/index.js.map +1 -0
  72. package/dist/src/inventory.d.ts +45 -0
  73. package/dist/src/inventory.js +89 -0
  74. package/dist/src/inventory.js.map +1 -0
  75. package/dist/src/manifest.d.ts +89 -0
  76. package/dist/src/manifest.js +137 -0
  77. package/dist/src/manifest.js.map +1 -0
  78. package/dist/src/output.d.ts +8 -0
  79. package/dist/src/output.js +2 -0
  80. package/dist/src/output.js.map +1 -0
  81. package/dist/src/overlay.d.ts +105 -0
  82. package/dist/src/overlay.js +302 -0
  83. package/dist/src/overlay.js.map +1 -0
  84. package/dist/src/reference.d.ts +1 -0
  85. package/dist/src/reference.js +108 -0
  86. package/dist/src/reference.js.map +1 -0
  87. package/dist/src/run.d.ts +82 -0
  88. package/dist/src/run.js +321 -0
  89. package/dist/src/run.js.map +1 -0
  90. package/dist/src/skill.d.ts +12 -0
  91. package/dist/src/skill.js +69 -0
  92. package/dist/src/skill.js.map +1 -0
  93. package/dist/src/toolchain.d.ts +62 -0
  94. package/dist/src/toolchain.js +127 -0
  95. package/dist/src/toolchain.js.map +1 -0
  96. package/dist/src/types.d.ts +88 -0
  97. package/dist/src/types.js +18 -0
  98. package/dist/src/types.js.map +1 -0
  99. package/dist/src/version.d.ts +10 -0
  100. package/dist/src/version.js +11 -0
  101. package/dist/src/version.js.map +1 -0
  102. package/package.json +93 -0
  103. package/skills/screencast-axi/SKILL.md +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Valentyn Morenko
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,555 @@
1
+ # screencast-axi
2
+
3
+ Record a scripted browser workflow as a watchable clip: a drawn cursor, burnt-in captions, and
4
+ mp4 + webm + a poster out the other end. Built to [AXI](https://github.com/kunchenguid/axi)
5
+ conventions, so an agent can drive it as comfortably as a person can.
6
+
7
+ <picture>
8
+ <source srcset="docs/demo.anim.webp" type="image/webp">
9
+ <img src="docs/demo.gif" alt="Three acts: a prompt asking for a clip, the scenario that gets written, and the recording it produces in a real app." width="640">
10
+ </picture>
11
+
12
+ <sup>The ask, the scenario that came back, and the take it produced - itself recorded by this tool,
13
+ from [`demo/scenarios/demo.ts`](demo/scenarios/demo.ts). Re-record it with `pnpm demo`.</sup>
14
+
15
+ > **Status: 0.x and not yet published.** The recorder works end to end; the command surface is
16
+ > still growing. See [Roadmap](#roadmap).
17
+
18
+ ## What the tool does
19
+
20
+ The clips below were produced by this repo - the scenarios are in
21
+ [`demo/scenarios/`](demo/scenarios/), and `pnpm demo:usecases` re-records them.
22
+
23
+ ### Point it at any site and describe the workflow
24
+
25
+ There is no integration to write: no plugin for the site, no fixtures, no test IDs added to the
26
+ page. A scenario names what to do, and the recorder does it.
27
+
28
+ > _"Record a clip of searching Wikipedia for Ada Lovelace, opening the article, and jumping to the
29
+ > Work section. Around ten seconds."_
30
+
31
+ <picture>
32
+ <source srcset="docs/usecase-anysite.anim.webp" type="image/webp">
33
+ <img src="docs/usecase-anysite.gif" alt="Searching Wikipedia, watching live suggestions arrive, opening the article and jumping to a section." width="480">
34
+ </picture>
35
+
36
+ <sup>The whole scenario is [50 lines](demo/scenarios/anysite.ts), most of it narration and
37
+ comments.</sup>
38
+
39
+ ### Records a sign-in, or skips past one
40
+
41
+ > _"Sign in to our staging dashboard and record a 15 second clip of creating an invoice. I will
42
+ > do the login myself."_
43
+
44
+ A login is an ordinary workflow as far as the recorder is concerned: a field, a field, a button.
45
+ It is worth filming, because it is the part of a product most demos skip.
46
+
47
+ <picture>
48
+ <source srcset="docs/usecase-login.anim.webp" type="image/webp">
49
+ <img src="docs/usecase-login.gif" alt="A real login form being filled in and submitted, landing on the page behind it." width="460">
50
+ </picture>
51
+
52
+ For a real app you usually want the opposite - the clip should open already through the door
53
+ rather than spend its first seconds on a form. So signing in becomes a one-time human step, and
54
+ this is what that looks like from your side:
55
+
56
+ > **A real Chrome window opens on your screen, at the site you are recording.**
57
+ > Sign in there however that site wants: password, SSO, a magic link, two-factor, a passkey.
58
+ > Then **close the window** - closing it is the signal that you are done.
59
+ > Nothing is ever typed into the terminal, and no credential passes through this package. The
60
+ > session is saved into a Chrome profile on your machine, and every later take reuses it.
61
+
62
+ An agent can open that window for you rather than making you retype a command, but it cannot sign
63
+ in for you and will not try: without `--interactive` it stops and says a person is needed, and it
64
+ refuses outright where no window could appear, such as a headless CI box. The wait is bounded, so
65
+ it can never hang.
66
+
67
+ For a login you can script, an `AuthStrategy` object in the config does the same job in typed code
68
+ ([worked example](demo/auth/form-login.ts)). Either way it runs before recording starts, so none
69
+ of it lands in the clip.
70
+
71
+ ### Renders at phone and tablet viewports
72
+
73
+ > _"Record our onboarding on an iPhone, portrait, about fifteen seconds - I want it for the app
74
+ > store listing."_
75
+
76
+ A device preset does more than set a width. Its `isMobile` and `hasTouch` flags decide whether the
77
+ site's own `@media (hover: none)` and touch rules apply at all, and it carries the right user
78
+ agent - so this is the mobile layout the site actually serves, not a desktop squeezed narrow.
79
+ Playwright ships 140+ presets; `--viewport 390x844` covers the rest.
80
+
81
+ <picture>
82
+ <source srcset="docs/usecase-mobile.anim.webp" type="image/webp">
83
+ <img src="docs/usecase-mobile.gif" alt="The same workflow recorded in a portrait phone viewport." width="240">
84
+ </picture>
85
+
86
+ <sup>The video is captured at the CSS viewport - 390x664 here - because Playwright composites the
87
+ page into the video canvas without scaling up. `deviceScaleFactor` still changes how the page
88
+ renders and which images it picks, but not the resolution of the recording.</sup>
89
+
90
+ ### Shows you what it is doing
91
+
92
+ A scenario is arbitrary code driving a real browser, often one signed into your own account, so
93
+ "is this safe to run" deserves a better answer than "read the TypeScript".
94
+
95
+ ```sh
96
+ screencast-axi rehearse <id> --headed
97
+ ```
98
+
99
+ `--headed` runs it in a real window instead of hidden, so you can watch the whole thing. It does
100
+ not change the output - the clip is identical either way - so it costs nothing but the window.
101
+
102
+ Watching answers the question once, though, and only while you sit there. So a rehearsal also
103
+ prints what it did, in a form you can read before running, diff after an edit, and keep:
104
+
105
+ ```
106
+ $ screencast-axi rehearse usecase-login
107
+
108
+ rehearsed: usecase-login
109
+ duration_s: 10.8
110
+ pace: 1
111
+ viewport: 900x540
112
+ steps[4]: A real login form,"Username, then password",Submitted for real,And the page behind it
113
+ hosts[1]: the-internet.herokuapp.com
114
+ performed[10]:
115
+ - at_s: 0.2
116
+ did: goto
117
+ target: "https://the-internet.herokuapp.com/login"
118
+ - at_s: 1.8
119
+ did: waitFor
120
+ target: #username
121
+ - at_s: 1.8
122
+ did: step
123
+ detail: A real login form
124
+ - at_s: 2.9
125
+ did: step
126
+ detail: "Username, then password"
127
+ - at_s: 2.9
128
+ did: type
129
+ target: #username
130
+ detail: tomsmith
131
+ - at_s: 4.3
132
+ did: type
133
+ target: #password
134
+ detail: •••••••• (20 chars)
135
+ - at_s: 7.1
136
+ did: step
137
+ detail: Submitted for real
138
+ - at_s: 7.1
139
+ did: click
140
+ target: "button[type=submit]"
141
+ - at_s: 8.2
142
+ did: waitFor
143
+ target: h2
144
+ - at_s: 8.8
145
+ did: step
146
+ detail: And the page behind it
147
+ ```
148
+
149
+ Note the password: a field the log would otherwise leak records its shape and nothing else.
150
+
151
+ `hosts` is the short answer to where it went, taken from the pages the browser actually reached -
152
+ so a redirect or a navigation buried in `setup()` shows up too. A list of one host reads very
153
+ differently from a list of nine.
154
+
155
+ ### Aims a clip at a length
156
+
157
+ ```sh
158
+ screencast-axi record tour --duration 30s
159
+ ```
160
+
161
+ One measuring pass, then it solves for the pace that lands near the target. A take is
162
+ `fixed + pace x scalable` - the site's own waits do not get slower because the recorder does - so
163
+ the solve uses the measured split rather than assuming everything scales. Pace is clamped to a
164
+ watchable range, and a target outside it is reported rather than obeyed.
165
+
166
+ ### Emits what the destination needs
167
+
168
+ mp4 and webm every time, plus a poster frame. `--gif` and `--webp` add looping images for the
169
+ places a `<video>` does not render - a README, an npm page, an email. Every image on this page is
170
+ one of them. See [Output formats](#output-formats) for what each costs.
171
+
172
+ Drag-and-drop is handled too, including the HTML5 protocol that Chromium will not synthesise from
173
+ mouse events - the interaction most likely to look like a teleport if a recorder cuts corners.
174
+
175
+ ## Why a script, not a screen recorder
176
+
177
+ A screencast is a performance with a script, not a recording of work being done. What makes one
178
+ watchable is a set of constants - a settle before each click, an eased pointer glide, a consistent
179
+ hold on each caption - and constants only help if you can run the same thing again and get the
180
+ same thing back. A scenario file survives a product change, gets reviewed in a pull request, and
181
+ can be re-cut at a different length without re-deciding anything.
182
+
183
+ ## Quick start
184
+
185
+ No config needed. Point it at any site:
186
+
187
+ ```sh
188
+ npx -y screencast-axi scaffold product-tour --url https://example.com
189
+ # fill in the run() body
190
+ npx -y screencast-axi rehearse ./scenarios/product-tour.ts
191
+ npx -y screencast-axi record ./scenarios/product-tour.ts
192
+ ```
193
+
194
+ `rehearse` runs the scenario without encoding, so a stale selector surfaces in seconds rather than
195
+ a minute - and the failure comes back with the URL it reached, a screenshot, and what each part of
196
+ the selector actually matched.
197
+
198
+ A scenario is TypeScript:
199
+
200
+ ```ts
201
+ import { defineScenario } from "screencast-axi";
202
+
203
+ export default defineScenario({
204
+ id: "product-tour",
205
+ title: "A three-stop tour",
206
+ description: "The pages that matter, in order.",
207
+ steps: ["Where the work lives", "How it gets organised", "And what comes out"],
208
+
209
+ async run(d) {
210
+ await d.tour([
211
+ { path: "/", step: 0, scroll: 0.6 },
212
+ { path: "/features", step: 1, scroll: 0.5 },
213
+ { path: "/pricing", step: 2 },
214
+ ]);
215
+ },
216
+ });
217
+ ```
218
+
219
+ Each line in `steps` goes on screen once, in order. The same array becomes the burnt-in caption
220
+ and the manifest's step list, so the written workflow cannot drift from the recorded one - a take
221
+ that skips a line fails.
222
+
223
+ ## Install
224
+
225
+ The skill is installed from GitHub; the CLI is pulled on demand, so there is nothing global:
226
+
227
+ ```sh
228
+ npx skills add Valzon/screencast-axi --skill screencast-axi -g
229
+ ```
230
+
231
+ ## Prerequisites
232
+
233
+ | Piece | Required | How it is found |
234
+ | ----------- | -------- | -------------------------------------------------------------------- |
235
+ | Node | >= 20 | |
236
+ | Chromium | yes | Playwright downloads it |
237
+ | `ffmpeg` | yes | `$SCREENCAST_FFMPEG`, then an installed `ffmpeg-static`, then `PATH` |
238
+ | WebP poster | optional | ffmpeg's `libwebp`, else `cwebp`, else a PNG poster |
239
+
240
+ ffmpeg is not bundled: it is 80MB+ per platform, and _which_ build you have matters - many builds
241
+ (Homebrew's among them) ship without `libwebp`, which is why the poster has a fallback chain
242
+ rather than one hard requirement. If you would rather not install it system-wide,
243
+ `pnpm add -D ffmpeg-static` and the cascade finds it.
244
+
245
+ **Platforms:** macOS and Linux are tested, including in CI. Windows is intended to work but is
246
+ **unverified** - the known risks are argument quoting in spawned ffmpeg filter strings, path
247
+ separators inside those arguments, and Chrome's lock on a persistent profile directory.
248
+
249
+ ## Recording a page behind a login
250
+
251
+ The strategy that needs no code for the site you are recording is `profileAuth()`. A person runs
252
+ this once:
253
+
254
+ ```sh
255
+ npx -y screencast-axi auth login --interactive
256
+ ```
257
+
258
+ A browser opens, they sign in however that site wants - OAuth, SSO, a magic link, two-factor - and
259
+ close the window. The session lives in a persistent Chrome profile and every take reuses it. No
260
+ credential is handled by this package.
261
+
262
+ `auth login` never reads stdin, so an agent can open the window on the user's screen rather than
263
+ making them retype a command. It cannot hang: the wait is bounded, and it refuses up front where
264
+ no window could appear.
265
+
266
+ Also shipped: `storageStateAuth({ path })` for a portable session file, and
267
+ `basicAuth({ username, password })` for staging environments. Anything else is an `AuthStrategy`
268
+ object written in the config - typed and debuggable rather than a shelled-out script.
269
+
270
+ > A scenario that creates or drags something **writes to whatever it is pointed at**. Prefer
271
+ > read-only scenarios against production, or point at staging.
272
+
273
+ ## Seeing what a scenario does
274
+
275
+ A scenario is arbitrary code driving a real browser, often one signed into your own account, so
276
+ "what will this actually do" deserves a better answer than "read the TypeScript". Two:
277
+
278
+ ```sh
279
+ screencast-axi rehearse <id> # prints every action it took
280
+ screencast-axi rehearse <id> --headed # and shows you it happening
281
+ ```
282
+
283
+ A rehearsal prints `performed` - every goto, click, typed value, drag and scroll, in order, with
284
+ timings - and `hosts`, the short answer to where the script went. `--headed` opens a real window
285
+ and does not change the output; the clip is identical either way.
286
+
287
+ ## Output formats
288
+
289
+ Every take produces an mp4, a webm and a poster. Looping images are opt-in with `--gif` and
290
+ `--webp`, for the places a `<video>` does not render - a README, an npm page, an email.
291
+
292
+ Measured on a real 16.7s app screencast, all at 800px and 15fps:
293
+
294
+ | Format | Size | vs mp4 |
295
+ | ------------- | -------- | ------ |
296
+ | mp4 (h264) | 182 KB | 1x |
297
+ | animated WebP | 944 KB | 5.2x |
298
+ | GIF | 2,034 KB | 11.2x |
299
+
300
+ Prefer WebP where it renders: same content, roughly half the bytes, full colour rather than a
301
+ 256-entry palette. Quality is not the deciding factor for flat app UI - 192 colours plus dithering
302
+ keeps small text legible - but a GIF has no controls, no seeking, no poster frame, cannot be
303
+ paused, and ignores `prefers-reduced-motion`. It is an export, not a storage format.
304
+
305
+ ## Configuration
306
+
307
+ Optional. `screencast.config.ts` at the repo root, found by walking up from the working directory:
308
+
309
+ ```ts
310
+ import { defineConfig, profileAuth } from "screencast-axi";
311
+
312
+ export default defineConfig({
313
+ baseUrl: "http://localhost:3000",
314
+ scenarios: ["scenarios/*.ts"],
315
+ outDir: "public/demos",
316
+ viewport: { width: 1440, height: 900 },
317
+
318
+ browser: { profileDir: ".screencast/profile" },
319
+ auth: profileAuth({ signedInSelector: "[data-testid=user-menu]" }),
320
+
321
+ overlay: {
322
+ accent: "#4f46e5",
323
+ // Page chrome that should not end up in the footage.
324
+ hideSelectors: ["#cookie-banner", "nextjs-portal"],
325
+ },
326
+ });
327
+ ```
328
+
329
+ Relative paths resolve against the config file, never the shell's working directory, so a command
330
+ means the same thing from anywhere in a repo.
331
+
332
+ ## Mobile, and aiming at a length
333
+
334
+ ```sh
335
+ screencast-axi record tour --device "iPhone 13" --orientation portrait --duration 20s
336
+ ```
337
+
338
+ Device presets come from Playwright's registry, so a phone clip is captured at its real device
339
+ pixels rather than its CSS viewport - the difference between readable UI text and a smear.
340
+
341
+ `--duration` runs one measuring pass, then solves for the pace that lands near the target. A take
342
+ is `fixed + pace x scalable`: the site's own waits do not get slower because the recorder does, so
343
+ the solve uses the measured split rather than assuming everything scales.
344
+
345
+ ## Reading the manifest
346
+
347
+ Every take writes `manifest.json` beside the clips. A site can read it at build time without
348
+ pulling in Playwright or a browser:
349
+
350
+ ```ts
351
+ import { readManifest, clipFilesFor } from "screencast-axi/manifest";
352
+ ```
353
+
354
+ That entry point has no runtime dependencies at all.
355
+
356
+ ## Command reference
357
+
358
+ <!-- reference:start -->
359
+
360
+ Every command takes `--help`. Flags always come after the command:
361
+ `screencast-axi <command> [args] [flags]`. An unknown flag is a usage error rather than
362
+ something quietly ignored.
363
+
364
+ #### `init`
365
+
366
+ Write a config, a scenarios directory and a gitignore entry.
367
+
368
+ ```sh
369
+ screencast-axi init
370
+ ```
371
+
372
+ | Flag | What it does |
373
+ | ------------- | --------------------------------- |
374
+ | `--out <dir>` | Where clips should be written. |
375
+ | `--url <url>` | Base URL for the site you record. |
376
+ | `--force` | Overwrite an existing config. |
377
+
378
+ #### `scaffold`
379
+
380
+ Write a scenario skeleton so the boilerplate is never what goes wrong.
381
+
382
+ ```sh
383
+ screencast-axi scaffold <id>
384
+ ```
385
+
386
+ | Flag | What it does |
387
+ | ----------------- | ----------------------------------------- |
388
+ | `--url <url>` | Base URL the scenario opens. |
389
+ | `--title <text>` | Human title for the clip. |
390
+ | `--dir <path>` | Where to write it. |
391
+ | `--tour <value>` | Write an n-stop page walkthrough instead. |
392
+ | `--device <name>` | Playwright device preset. |
393
+
394
+ #### `rehearse`
395
+
396
+ Run a scenario without recording, and print every action it took.
397
+
398
+ ```sh
399
+ screencast-axi rehearse <id|path...>
400
+ ```
401
+
402
+ | Flag | What it does |
403
+ | ------------------- | ----------------------------------------- |
404
+ | `--config <path>` | Path to a config file. |
405
+ | `--base-url <url>` | Override the scenario's base URL. |
406
+ | `--pace <value>` | Speed multiplier; lower is faster. |
407
+ | `--device <name>` | Playwright device preset. |
408
+ | `--viewport <WxH>` | Explicit size, e.g. 390x844. |
409
+ | `--orientation <o>` | portrait or landscape. |
410
+ | `--headed` | Watch it happen in a real browser window. |
411
+ | `--auth <name>` | Named auth strategy from the config. |
412
+ | `--no-auth` | Record signed out. |
413
+
414
+ #### `record`
415
+
416
+ Run a scenario for real and encode the clip.
417
+
418
+ ```sh
419
+ screencast-axi record <id|path...>
420
+ ```
421
+
422
+ | Flag | What it does |
423
+ | ---------------------- | --------------------------------------------------------------------- |
424
+ | `--config <path>` | Path to a config file. |
425
+ | `--base-url <url>` | Override the scenario's base URL. |
426
+ | `--pace <value>` | Speed multiplier; lower is faster. |
427
+ | `--device <name>` | Playwright device preset. |
428
+ | `--viewport <WxH>` | Explicit size, e.g. 390x844. |
429
+ | `--orientation <o>` | portrait or landscape. |
430
+ | `--headed` | Watch it happen in a real browser window. |
431
+ | `--auth <name>` | Named auth strategy from the config. |
432
+ | `--no-auth` | Record signed out. |
433
+ | `--duration <30s>` | Aim for this length, e.g. 30s (measures first, then solves for pace). |
434
+ | `--out <dir>` | Output directory. |
435
+ | `--all` | Record every scenario the config lists. |
436
+ | `--full` | Include the full action log. |
437
+ | `--gif` | Also emit a looping GIF. |
438
+ | `--webp` | Also emit a looping WebP (half a GIF's size). |
439
+ | `--loop-width <value>` | Width of the looping formats. |
440
+ | `--loop-fps <value>` | Frame rate of the looping formats. |
441
+ | `--keep-raw` | Keep the raw capture for inspection. |
442
+
443
+ #### `list`
444
+
445
+ Every scenario, with what needs re-shooting.
446
+
447
+ ```sh
448
+ screencast-axi list
449
+ ```
450
+
451
+ | Flag | What it does |
452
+ | ----------------- | ----------------------------------------- |
453
+ | `--config <path>` | Path to a config file. |
454
+ | `--full` | Every field, not the four-column summary. |
455
+ | `--stale` | Only what needs re-shooting. |
456
+ | `--tag <name>` | Filter by scenario tag. Repeatable. |
457
+
458
+ #### `show`
459
+
460
+ One clip in full: files, sizes, narration, when it was shot.
461
+
462
+ ```sh
463
+ screencast-axi show <id>
464
+ ```
465
+
466
+ | Flag | What it does |
467
+ | ----------------- | ------------------------------ |
468
+ | `--config <path>` | Path to a config file. |
469
+ | `--full` | Include untruncated step text. |
470
+
471
+ #### `check`
472
+
473
+ Cross-reference the manifest, the scenarios and the files on disk.
474
+
475
+ ```sh
476
+ screencast-axi check
477
+ ```
478
+
479
+ | Flag | What it does |
480
+ | ----------------- | --------------------------------------------------------------- |
481
+ | `--config <path>` | Path to a config file. |
482
+ | `--fix-orphans` | Delete manifest entries and media with no scenario behind them. |
483
+
484
+ #### `doctor`
485
+
486
+ Check everything a recording needs, in one pass.
487
+
488
+ ```sh
489
+ screencast-axi doctor
490
+ ```
491
+
492
+ | Flag | What it does |
493
+ | ----------------- | ---------------------- |
494
+ | `--config <path>` | Path to a config file. |
495
+
496
+ #### `setup`
497
+
498
+ Install the browser; name anything you must install yourself.
499
+
500
+ ```sh
501
+ screencast-axi setup
502
+ ```
503
+
504
+ | Flag | What it does |
505
+ | ----------------- | ----------------------------------- |
506
+ | `--browsers-only` | Install Chromium and stop. |
507
+ | `--scope <s>` | For `setup hooks`: user or project. |
508
+
509
+ #### `auth`
510
+
511
+ Sign in by hand once, or check the saved session still works.
512
+
513
+ ```sh
514
+ screencast-axi auth login|check [name]
515
+ ```
516
+
517
+ | Flag | What it does |
518
+ | --------------------- | ------------------------------------------------------------ |
519
+ | `--config <path>` | Path to a config file. |
520
+ | `--base-url <url>` | Site to sign in to. |
521
+ | `--interactive` | Required for `login`: opens a real browser. |
522
+ | `--save-state <path>` | Write a Playwright storage state here as well. |
523
+ | `--headed` | Show the browser for `check`. |
524
+ | `--wait <5m>` | How long `login` waits for the window to close (default 5m). |
525
+
526
+ #### `guide`
527
+
528
+ Topic-sized guidance, pulled one topic at a time.
529
+
530
+ ```sh
531
+ screencast-axi guide [topic]
532
+ ```
533
+
534
+ _No flags._
535
+ <!-- reference:end -->
536
+
537
+ ## Roadmap
538
+
539
+ Publishing to npm. The command surface is complete; `setup hooks` is declared but not implemented
540
+ and says so rather than pretending.
541
+
542
+ ## Contributing
543
+
544
+ ```sh
545
+ pnpm install
546
+ pnpm build && pnpm test && pnpm typecheck && pnpm format
547
+ ```
548
+
549
+ `skills/screencast-axi/SKILL.md` is generated from `src/skill.ts` - edit the generator, run
550
+ `pnpm build:skill`, and commit the result. CI fails if the two drift, and the generator throws if
551
+ the stub grows past its character cap.
552
+
553
+ ## License
554
+
555
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { tryFastPath } from "axi-sdk-js/fast-path";
3
+ import { VERSION } from "../src/version.js";
4
+ // `screencast-axi --help | head` closes the pipe early, and an unhandled EPIPE
5
+ // turns that into a stack trace. Piping output somewhere that stops reading is
6
+ // ordinary shell use, not an error worth reporting.
7
+ process.stdout.on("error", (error) => {
8
+ if (error.code === "EPIPE")
9
+ process.exit(0);
10
+ throw error;
11
+ });
12
+ if (!tryFastPath(process.argv.slice(2), { version: VERSION })) {
13
+ const { main } = await import("../src/cli.js");
14
+ await main();
15
+ }
16
+ //# sourceMappingURL=screencast-axi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screencast-axi.js","sourceRoot":"","sources":["../../bin/screencast-axi.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C,+EAA+E;AAC/E,+EAA+E;AAC/E,oDAAoD;AACpD,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAA4B,EAAE,EAAE;IAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,KAAK,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC9D,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IAC/C,MAAM,IAAI,EAAE,CAAC;AACf,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { AuthStrategy } from "./types.js";
2
+ /** Signed-out. The default, and correct for most public surfaces. */
3
+ export declare function noAuth(): AuthStrategy;
4
+ export interface ProfileAuthOptions {
5
+ /** Where to land after signing in, so `auth check` can prove it worked. */
6
+ readonly landingPath?: string;
7
+ /** Something only a signed-in page renders. The strongest check available. */
8
+ readonly signedInSelector?: string;
9
+ /**
10
+ * Cookie the app sets once its own session is live.
11
+ *
12
+ * `name=value` checks the value too. Presence alone is a weak test: plenty
13
+ * of sites set the same cookie to a signed-out value (GitHub sends
14
+ * `logged_in=no`), so a name-only check quietly passes when it should not.
15
+ */
16
+ readonly signedInCookie?: string;
17
+ readonly label?: string;
18
+ }
19
+ /**
20
+ * Sign in once by hand; every take afterwards reuses the session.
21
+ *
22
+ * This is the strategy that makes the tool work on a site nobody wrote a
23
+ * strategy for. The browser opens, a person signs in however that site wants -
24
+ * OAuth, SSO, a magic link, two-factor - and closes the window. The session
25
+ * lives in the persistent Chrome profile, so no code here has to know anything
26
+ * about how that site authenticates, and no credential is ever handled by this
27
+ * package.
28
+ *
29
+ * It needs `browser.profileDir` set, since a persistent profile is the thing
30
+ * doing the remembering.
31
+ */
32
+ export declare function profileAuth(options?: ProfileAuthOptions): AuthStrategy;
33
+ export interface StorageStateOptions {
34
+ /** Playwright storage-state JSON. Relative paths anchor to the config. */
35
+ readonly path: string;
36
+ readonly landingPath?: string;
37
+ readonly signedInSelector?: string;
38
+ }
39
+ /**
40
+ * A saved Playwright session file.
41
+ *
42
+ * Portable and CI-friendly in a way a Chrome profile is not: it is one JSON
43
+ * file that can be produced once and committed to a secret store. Needs an
44
+ * isolated context, so it cannot be combined with `browser.profileDir`.
45
+ */
46
+ export declare function storageStateAuth(options: StorageStateOptions): AuthStrategy;
47
+ export interface BasicAuthOptions {
48
+ readonly username: string;
49
+ readonly password: string;
50
+ }
51
+ /** HTTP basic auth, which is how most staging environments are gated. */
52
+ export declare function basicAuth(options: BasicAuthOptions): AuthStrategy;
53
+ /** Splits `name=value` into its parts; a bare name means "any value". */
54
+ export declare function splitCookie(spec: string): [name: string, value: string | undefined];