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
@@ -0,0 +1,225 @@
1
+ import { mkdir, rm, stat } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ import { detectToolchain, installHint, missingFfmpegError, runOrThrow, } from "./toolchain.js";
4
+ export const DEFAULT_ENCODE_SETTINGS = {
5
+ width: 1280,
6
+ fps: 30,
7
+ mp4: { crf: 23, preset: "slow", profile: "high" },
8
+ webm: { crf: 34 },
9
+ poster: { quality: 82 },
10
+ gif: false,
11
+ animatedWebp: false,
12
+ gifWidth: 800,
13
+ gifFps: 15,
14
+ animatedWebpQuality: 55,
15
+ };
16
+ const QUIET = ["-y", "-hide_banner", "-loglevel", "error"];
17
+ /**
18
+ * Turns the raw Playwright capture into web deliverables.
19
+ *
20
+ * - mp4 (h264, yuv420p, faststart) - the one a page plays.
21
+ * - webm (vp9) - smaller, served first via `<source>`.
22
+ * - poster - the frame shown before the video decodes, so nothing pops in.
23
+ * - gif - optional, opt-in.
24
+ */
25
+ export async function encode(opts) {
26
+ const toolchain = opts.toolchain ?? (await detectToolchain());
27
+ if (!toolchain.ffmpeg)
28
+ throw missingFfmpegError();
29
+ const ffmpeg = toolchain.ffmpeg.path;
30
+ await mkdir(opts.outDir, { recursive: true });
31
+ // Input seek: `-ss` before `-i` is the fast, frame-accurate-enough form for
32
+ // trimming dead air off the head.
33
+ const trim = opts.trimStart > 0.05 ? ["-ss", opts.trimStart.toFixed(2)] : [];
34
+ // `-2` keeps the height even, which h264's yuv420p requires.
35
+ const scale = `scale=${opts.width}:-2:flags=lanczos`;
36
+ const mp4 = join(opts.outDir, `${opts.id}.mp4`);
37
+ const webm = join(opts.outDir, `${opts.id}.webm`);
38
+ await runOrThrow(ffmpeg, [
39
+ ...QUIET,
40
+ ...trim,
41
+ "-i",
42
+ opts.input,
43
+ "-vf",
44
+ `fps=${opts.fps},${scale}`,
45
+ "-an",
46
+ "-c:v",
47
+ "libx264",
48
+ "-profile:v",
49
+ opts.mp4.profile,
50
+ "-preset",
51
+ opts.mp4.preset,
52
+ "-crf",
53
+ String(opts.mp4.crf),
54
+ "-pix_fmt",
55
+ "yuv420p",
56
+ "-movflags",
57
+ "+faststart",
58
+ mp4,
59
+ ]);
60
+ await runOrThrow(ffmpeg, [
61
+ ...QUIET,
62
+ ...trim,
63
+ "-i",
64
+ opts.input,
65
+ "-vf",
66
+ `fps=${opts.fps},${scale}`,
67
+ "-an",
68
+ "-c:v",
69
+ "libvpx-vp9",
70
+ "-crf",
71
+ String(opts.webm.crf),
72
+ "-b:v",
73
+ "0",
74
+ "-row-mt",
75
+ "1",
76
+ "-deadline",
77
+ "good",
78
+ webm,
79
+ ]);
80
+ const poster = await encodePoster(opts, toolchain, ffmpeg, trim, scale);
81
+ // Both looping formats come from the same palette pass, so asking for the
82
+ // pair costs one extra conversion rather than a second encode.
83
+ let gif;
84
+ let animatedWebp;
85
+ if (opts.gif || opts.animatedWebp) {
86
+ const wantsGif = opts.gif;
87
+ gif = join(opts.outDir, `${opts.id}.gif`);
88
+ const palette = join(opts.outDir, `.${opts.id}.palette.png`);
89
+ const gifScale = `fps=${opts.gifFps},scale=${opts.gifWidth}:-1:flags=lanczos`;
90
+ await runOrThrow(ffmpeg, [
91
+ ...QUIET,
92
+ ...trim,
93
+ "-i",
94
+ opts.input,
95
+ "-vf",
96
+ `${gifScale},palettegen=max_colors=192:stats_mode=diff`,
97
+ palette,
98
+ ]);
99
+ await runOrThrow(ffmpeg, [
100
+ ...QUIET,
101
+ ...trim,
102
+ "-i",
103
+ opts.input,
104
+ "-i",
105
+ palette,
106
+ "-lavfi",
107
+ `${gifScale}[x];[x][1:v]paletteuse=dither=sierra2_4a:diff_mode=rectangle`,
108
+ "-loop",
109
+ "0",
110
+ gif,
111
+ ]);
112
+ await rm(palette, { force: true });
113
+ if (opts.animatedWebp) {
114
+ // gif2webp rather than ffmpeg: the same builds that lack libwebp for the
115
+ // poster lack it here too, and gif2webp ships in the same package as the
116
+ // cwebp the poster already falls back to.
117
+ const converter = toolchain.gif2webp;
118
+ if (!converter) {
119
+ throw new Error("gif2webp was not found, so an animated WebP cannot be produced. " +
120
+ `Install it with \`${installHint("cwebp")}\` (same package as cwebp), ` +
121
+ "or drop `animatedWebp` and use the GIF.");
122
+ }
123
+ animatedWebp = join(opts.outDir, `${opts.id}.anim.webp`);
124
+ await runOrThrow(converter.path, [
125
+ "-quiet",
126
+ "-lossy",
127
+ "-q",
128
+ String(opts.animatedWebpQuality),
129
+ "-m",
130
+ "6",
131
+ gif,
132
+ "-o",
133
+ animatedWebp,
134
+ ]);
135
+ }
136
+ if (!wantsGif) {
137
+ await rm(gif, { force: true });
138
+ gif = undefined;
139
+ }
140
+ }
141
+ const sizes = {};
142
+ for (const file of [mp4, webm, poster, gif, animatedWebp].filter((f) => Boolean(f))) {
143
+ sizes[basename(file)] = (await stat(file)).size;
144
+ }
145
+ return {
146
+ mp4,
147
+ webm,
148
+ poster,
149
+ ...(gif ? { gif } : {}),
150
+ ...(animatedWebp ? { animatedWebp } : {}),
151
+ sizes,
152
+ posterEncoder: toolchain.posterEncoder,
153
+ };
154
+ }
155
+ /**
156
+ * The poster, by whichever route this machine can take.
157
+ *
158
+ * WebP matters here: a 1280x800 screenshot of an app is roughly 300 KB as PNG
159
+ * and 40 KB as WebP, and a page loads every poster up front because it is the
160
+ * frame a not-yet-playing clip shows. But WebP is an optimisation, not a
161
+ * requirement - falling back to PNG keeps a recording working on a machine
162
+ * with neither encoder.
163
+ */
164
+ async function encodePoster(opts, toolchain, ffmpeg, trim, scale) {
165
+ if (toolchain.posterEncoder === "png") {
166
+ const png = join(opts.outDir, `${opts.id}.png`);
167
+ await runOrThrow(ffmpeg, [
168
+ ...QUIET,
169
+ ...trim,
170
+ "-i",
171
+ opts.input,
172
+ "-vf",
173
+ scale,
174
+ "-frames:v",
175
+ "1",
176
+ png,
177
+ ]);
178
+ return png;
179
+ }
180
+ const webp = join(opts.outDir, `${opts.id}.webp`);
181
+ if (toolchain.posterEncoder === "ffmpeg") {
182
+ await runOrThrow(ffmpeg, [
183
+ ...QUIET,
184
+ ...trim,
185
+ "-i",
186
+ opts.input,
187
+ "-vf",
188
+ scale,
189
+ "-frames:v",
190
+ "1",
191
+ "-c:v",
192
+ "libwebp",
193
+ "-quality",
194
+ String(opts.poster.quality),
195
+ webp,
196
+ ]);
197
+ return webp;
198
+ }
199
+ // cwebp: extract a frame, convert, drop the intermediate.
200
+ const frame = join(opts.outDir, `.${opts.id}.poster.png`);
201
+ await runOrThrow(ffmpeg, [
202
+ ...QUIET,
203
+ ...trim,
204
+ "-i",
205
+ opts.input,
206
+ "-vf",
207
+ scale,
208
+ "-frames:v",
209
+ "1",
210
+ frame,
211
+ ]);
212
+ await runOrThrow(toolchain.cwebp?.path ?? "cwebp", [
213
+ "-quiet",
214
+ "-q",
215
+ String(opts.poster.quality),
216
+ "-m",
217
+ "6",
218
+ frame,
219
+ "-o",
220
+ webp,
221
+ ]);
222
+ await rm(frame, { force: true });
223
+ return webp;
224
+ }
225
+ //# sourceMappingURL=encode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../src/encode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The library's own error type. Zero dependencies, on purpose.
3
+ *
4
+ * `axi-sdk-js` is ESM-only with an `import`-only export map, so anything that
5
+ * imports it cannot be resolved through a CJS path - which is exactly how a
6
+ * TypeScript config file gets loaded, and how a bundler may resolve a website's
7
+ * build-time import of `screencast-axi/manifest`.
8
+ *
9
+ * So the SDK stays behind the CLI boundary: everything else throws this, and
10
+ * `cli.ts` converts it to an `AxiError` on the way out. The structure matches
11
+ * what the SDK renders - a message, a machine-readable code and actionable
12
+ * suggestions - so nothing is lost in the conversion.
13
+ */
14
+ export declare class ScreencastError extends Error {
15
+ readonly code: string;
16
+ readonly suggestions: string[];
17
+ constructor(message: string, code: string, suggestions?: string[]);
18
+ }
19
+ export declare function isScreencastError(value: unknown): value is ScreencastError;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The library's own error type. Zero dependencies, on purpose.
3
+ *
4
+ * `axi-sdk-js` is ESM-only with an `import`-only export map, so anything that
5
+ * imports it cannot be resolved through a CJS path - which is exactly how a
6
+ * TypeScript config file gets loaded, and how a bundler may resolve a website's
7
+ * build-time import of `screencast-axi/manifest`.
8
+ *
9
+ * So the SDK stays behind the CLI boundary: everything else throws this, and
10
+ * `cli.ts` converts it to an `AxiError` on the way out. The structure matches
11
+ * what the SDK renders - a message, a machine-readable code and actionable
12
+ * suggestions - so nothing is lost in the conversion.
13
+ */
14
+ export class ScreencastError extends Error {
15
+ code;
16
+ suggestions;
17
+ constructor(message, code, suggestions = []) {
18
+ super(message);
19
+ this.name = "ScreencastError";
20
+ this.code = code;
21
+ this.suggestions = suggestions;
22
+ }
23
+ }
24
+ export function isScreencastError(value) {
25
+ return value instanceof ScreencastError;
26
+ }
27
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAS;IACb,WAAW,CAAW;IAE/B,YAAY,OAAe,EAAE,IAAY,EAAE,WAAW,GAAa,EAAE;QACnE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,KAAK,YAAY,eAAe,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Spec-driven flag parsing.
3
+ *
4
+ * The SDK owns the shape of the invocation (`<bin> <command> ...`) but not the
5
+ * flags inside a command, so this fills that gap with one rule: an unknown
6
+ * flag is a usage error, never a silently dropped argument. A dropped flag is
7
+ * worse than an error - the command appears to succeed while doing something
8
+ * the caller did not ask for - and an agent that cannot see the mistake will
9
+ * repeat it.
10
+ *
11
+ * Errors are self-correcting in one turn: they name the offending flag, the
12
+ * nearest real one when there is a plausible match, and the full list of what
13
+ * this command accepts, so nothing has to go and read `--help` to recover.
14
+ */
15
+ export type FlagValue = string | number | boolean | string[];
16
+ export interface FlagSpec {
17
+ readonly kind: "boolean" | "string" | "number";
18
+ readonly description: string;
19
+ /** Placeholder shown in help, e.g. `dir` renders as `--out <dir>`. */
20
+ readonly placeholder?: string;
21
+ /** Collect every occurrence instead of keeping the last. String flags only. */
22
+ readonly repeat?: boolean;
23
+ }
24
+ export type FlagSpecs = Record<string, FlagSpec>;
25
+ export interface ParsedArgs {
26
+ readonly positionals: readonly string[];
27
+ readonly flags: Readonly<Record<string, FlagValue>>;
28
+ }
29
+ /** The closest known flag to `input`, when one is close enough to mean it. */
30
+ export declare function nearestFlag(input: string, known: readonly string[]): string | null;
31
+ /**
32
+ * Parses `args` against `specs`.
33
+ *
34
+ * Supports `--flag value`, `--flag=value`, `--no-flag` for booleans, and `--`
35
+ * to stop flag parsing. Everything else is a positional.
36
+ */
37
+ export declare function parseFlags(args: readonly string[], specs: FlagSpecs): ParsedArgs;
38
+ /** Renders a command's flags for its `--help` block. */
39
+ export declare function renderFlagHelp(specs: FlagSpecs): string[];
@@ -0,0 +1,130 @@
1
+ import { ScreencastError } from "./errors.js";
2
+ /**
3
+ * Levenshtein distance, bounded by the shorter string. Used only to decide
4
+ * whether a typo is close enough to a real flag to be worth suggesting.
5
+ */
6
+ function distance(a, b) {
7
+ const rows = a.length + 1;
8
+ const cols = b.length + 1;
9
+ let prev = Array.from({ length: cols }, (_, i) => i);
10
+ for (let i = 1; i < rows; i++) {
11
+ const curr = [i, ...Array(cols - 1).fill(0)];
12
+ for (let j = 1; j < cols; j++) {
13
+ const substitution = (prev[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1);
14
+ const deletion = (prev[j] ?? 0) + 1;
15
+ const insertion = (curr[j - 1] ?? 0) + 1;
16
+ curr[j] = Math.min(substitution, deletion, insertion);
17
+ }
18
+ prev = curr;
19
+ }
20
+ return prev[cols - 1] ?? Math.max(a.length, b.length);
21
+ }
22
+ /** The closest known flag to `input`, when one is close enough to mean it. */
23
+ export function nearestFlag(input, known) {
24
+ let best = null;
25
+ let bestDistance = Infinity;
26
+ for (const candidate of known) {
27
+ const d = distance(input, candidate);
28
+ if (d < bestDistance) {
29
+ bestDistance = d;
30
+ best = candidate;
31
+ }
32
+ }
33
+ // Two edits on a short flag is already a different flag, not a typo.
34
+ return best !== null && bestDistance <= Math.max(2, Math.floor(input.length / 3)) ? best : null;
35
+ }
36
+ function usage(specs) {
37
+ const names = Object.keys(specs);
38
+ return names.length > 0 ? `Accepts: ${names.map((n) => `--${n}`).join(", ")}` : "";
39
+ }
40
+ function unknownFlag(name, specs) {
41
+ const known = Object.keys(specs);
42
+ const near = nearestFlag(name, known);
43
+ const help = [
44
+ ...(near ? [`Did you mean \`--${near}\`?`] : []),
45
+ ...(known.length > 0 ? [usage(specs)] : ["This command accepts no flags"]),
46
+ ];
47
+ throw new ScreencastError(`Unknown flag: --${name}`, "VALIDATION_ERROR", help);
48
+ }
49
+ function numeric(name, raw) {
50
+ const value = Number(raw);
51
+ if (!Number.isFinite(value)) {
52
+ throw new ScreencastError(`--${name} expects a number, got \`${raw}\``, "VALIDATION_ERROR", [
53
+ `Example: --${name} 30`,
54
+ ]);
55
+ }
56
+ return value;
57
+ }
58
+ /**
59
+ * Parses `args` against `specs`.
60
+ *
61
+ * Supports `--flag value`, `--flag=value`, `--no-flag` for booleans, and `--`
62
+ * to stop flag parsing. Everything else is a positional.
63
+ */
64
+ export function parseFlags(args, specs) {
65
+ const positionals = [];
66
+ const flags = {};
67
+ let literal = false;
68
+ for (let i = 0; i < args.length; i++) {
69
+ const arg = args[i];
70
+ if (literal || !arg.startsWith("--")) {
71
+ positionals.push(arg);
72
+ continue;
73
+ }
74
+ if (arg === "--") {
75
+ literal = true;
76
+ continue;
77
+ }
78
+ const eq = arg.indexOf("=");
79
+ const name = (eq === -1 ? arg.slice(2) : arg.slice(2, eq)).trim();
80
+ const inlineValue = eq === -1 ? null : arg.slice(eq + 1);
81
+ // `--no-x` turns off a boolean `x`. Only meaningful for booleans, so an
82
+ // unknown `--no-y` still reports the flag the caller actually typed.
83
+ if (!specs[name] && name.startsWith("no-") && specs[name.slice(3)]?.kind === "boolean") {
84
+ flags[name.slice(3)] = false;
85
+ continue;
86
+ }
87
+ const spec = specs[name];
88
+ if (!spec)
89
+ unknownFlag(name, specs);
90
+ if (spec.kind === "boolean") {
91
+ if (inlineValue !== null && inlineValue !== "true" && inlineValue !== "false") {
92
+ throw new ScreencastError(`--${name} is a switch and takes no value`, "VALIDATION_ERROR", [
93
+ `Pass \`--${name}\` on its own, or \`--no-${name}\` to turn it off`,
94
+ ]);
95
+ }
96
+ flags[name] = inlineValue !== "false";
97
+ continue;
98
+ }
99
+ const raw = inlineValue ?? args[++i];
100
+ if (raw === undefined) {
101
+ throw new ScreencastError(`--${name} expects a value`, "VALIDATION_ERROR", [
102
+ `Pass it as \`--${name} <${spec.placeholder ?? "value"}>\``,
103
+ ]);
104
+ }
105
+ if (spec.kind === "number") {
106
+ flags[name] = numeric(name, raw);
107
+ }
108
+ else if (spec.repeat) {
109
+ const existing = flags[name];
110
+ flags[name] = Array.isArray(existing) ? [...existing, raw] : [raw];
111
+ }
112
+ else {
113
+ flags[name] = raw;
114
+ }
115
+ }
116
+ return { positionals, flags };
117
+ }
118
+ /** Renders a command's flags for its `--help` block. */
119
+ export function renderFlagHelp(specs) {
120
+ const entries = Object.entries(specs);
121
+ if (entries.length === 0)
122
+ return [];
123
+ const rendered = entries.map(([name, spec]) => ({
124
+ left: spec.kind === "boolean" ? `--${name}` : `--${name} <${spec.placeholder ?? "value"}>`,
125
+ description: spec.description,
126
+ }));
127
+ const width = Math.max(...rendered.map((r) => r.left.length));
128
+ return rendered.map((r) => ` ${r.left.padEnd(width)} ${r.description}`);
129
+ }
130
+ //# sourceMappingURL=flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/flags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * What a failed take leaves behind.
4
+ *
5
+ * Writing a scenario blind is the real cost of scripting rather than driving a
6
+ * browser live: a stale selector surfaces as a bare `TimeoutError`, with no
7
+ * page, no URL and nothing to look at, and the next attempt is another minute
8
+ * of guessing. Capturing the page's state before the context closes turns that
9
+ * into one sighted iteration, which is most of what a live session would have
10
+ * bought.
11
+ *
12
+ * All of it is written to disk and reported as absolute paths - never inlined
13
+ * into the output, where a screenshot would be megabytes of base64 nobody
14
+ * asked for.
15
+ */
16
+ export interface NearMatch {
17
+ readonly selector: string;
18
+ readonly count: number;
19
+ readonly visible: boolean;
20
+ }
21
+ export interface Forensics {
22
+ readonly url?: string;
23
+ readonly title?: string;
24
+ readonly screenshot?: string;
25
+ readonly html?: string;
26
+ readonly nearMatches?: readonly NearMatch[];
27
+ /** Anything that went wrong while collecting the above. */
28
+ readonly notes?: readonly string[];
29
+ }
30
+ /**
31
+ * Selector fragments worth reporting counts for.
32
+ *
33
+ * A descendant selector that matches nothing tells you nothing about *which*
34
+ * part is wrong. Its pieces do: if `.grid` matches once and `.grid .row`
35
+ * matches zero times, the container rendered and the rows did not, which is a
36
+ * data problem rather than a selector typo.
37
+ */
38
+ export declare function selectorCandidates(selector: string): string[];
39
+ /** Pulls a CSS selector out of a Playwright timeout message, if there is one. */
40
+ export declare function selectorFromError(message: string): string | null;
41
+ export interface CaptureOptions {
42
+ readonly page: Page;
43
+ readonly rawDir: string;
44
+ readonly id: string;
45
+ readonly error: unknown;
46
+ }
47
+ /**
48
+ * Collects everything useful about a failure. Never throws: a problem here
49
+ * must not replace the scenario's own error with a worse one.
50
+ */
51
+ export declare function captureFailure(options: CaptureOptions): Promise<Forensics>;
@@ -0,0 +1,107 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Selector fragments worth reporting counts for.
5
+ *
6
+ * A descendant selector that matches nothing tells you nothing about *which*
7
+ * part is wrong. Its pieces do: if `.grid` matches once and `.grid .row`
8
+ * matches zero times, the container rendered and the rows did not, which is a
9
+ * data problem rather than a selector typo.
10
+ */
11
+ export function selectorCandidates(selector) {
12
+ const trimmed = selector.trim();
13
+ if (!trimmed || /[>~+]/.test(trimmed))
14
+ return [trimmed].filter(Boolean);
15
+ const parts = trimmed.split(/\s+/).filter(Boolean);
16
+ if (parts.length < 2)
17
+ return [trimmed];
18
+ const candidates = [];
19
+ // Progressively shorter prefixes, then the trailing piece on its own.
20
+ for (let i = parts.length - 1; i >= 1; i--) {
21
+ candidates.push(parts.slice(0, i).join(" "));
22
+ }
23
+ const last = parts[parts.length - 1];
24
+ if (last && !candidates.includes(last))
25
+ candidates.push(last);
26
+ return candidates;
27
+ }
28
+ /** Pulls a CSS selector out of a Playwright timeout message, if there is one. */
29
+ export function selectorFromError(message) {
30
+ const patterns = [
31
+ /waiting for locator\('([^']+)'\)/,
32
+ /locator\('([^']+)'\)/,
33
+ /selector "([^"]+)"/,
34
+ /no element matched "([^"]+)"/,
35
+ ];
36
+ for (const pattern of patterns) {
37
+ const match = pattern.exec(message);
38
+ if (match?.[1])
39
+ return match[1];
40
+ }
41
+ return null;
42
+ }
43
+ async function probe(page, selector) {
44
+ try {
45
+ const locator = page.locator(selector);
46
+ const count = await locator.count();
47
+ const visible = count > 0
48
+ ? await locator
49
+ .first()
50
+ .isVisible()
51
+ .catch(() => false)
52
+ : false;
53
+ return { selector, count, visible };
54
+ }
55
+ catch {
56
+ return { selector, count: 0, visible: false };
57
+ }
58
+ }
59
+ /**
60
+ * Collects everything useful about a failure. Never throws: a problem here
61
+ * must not replace the scenario's own error with a worse one.
62
+ */
63
+ export async function captureFailure(options) {
64
+ const { page, rawDir, id, error } = options;
65
+ const notes = [];
66
+ const result = {};
67
+ try {
68
+ await mkdir(rawDir, { recursive: true });
69
+ }
70
+ catch {
71
+ return { notes: ["could not create the scratch directory"] };
72
+ }
73
+ try {
74
+ result.url = page.url();
75
+ }
76
+ catch {
77
+ notes.push("page was already closed");
78
+ return { ...result, notes };
79
+ }
80
+ try {
81
+ result.title = await page.title();
82
+ }
83
+ catch {
84
+ notes.push("could not read the page title");
85
+ }
86
+ try {
87
+ const screenshot = join(rawDir, `${id}.failure.png`);
88
+ await page.screenshot({ path: screenshot, fullPage: false });
89
+ result.screenshot = screenshot;
90
+ }
91
+ catch {
92
+ notes.push("could not take a screenshot");
93
+ }
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ const selector = selectorFromError(message);
96
+ if (selector) {
97
+ const candidates = selectorCandidates(selector);
98
+ const matches = [];
99
+ for (const candidate of candidates.slice(0, 4)) {
100
+ matches.push(await probe(page, candidate));
101
+ }
102
+ if (matches.length > 0)
103
+ result.nearMatches = matches;
104
+ }
105
+ return { ...result, ...(notes.length > 0 ? { notes } : {}) };
106
+ }
107
+ //# sourceMappingURL=forensics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forensics.js","sourceRoot":"","sources":["../../src/forensics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { AxiStructuredOutput } from "./output.js";
2
+ /**
3
+ * Session integration.
4
+ *
5
+ * The AXI convention is a setup command that registers hooks with the agent
6
+ * harness, so the tool is ambient rather than something to remember. That is
7
+ * not built yet, and saying so beats registering something that does nothing.
8
+ */
9
+ export declare function installHooks(scope: string | undefined): AxiStructuredOutput;
@@ -0,0 +1,20 @@
1
+ import { ScreencastError } from "./errors.js";
2
+ /**
3
+ * Session integration.
4
+ *
5
+ * The AXI convention is a setup command that registers hooks with the agent
6
+ * harness, so the tool is ambient rather than something to remember. That is
7
+ * not built yet, and saying so beats registering something that does nothing.
8
+ */
9
+ export function installHooks(scope) {
10
+ if (scope && scope !== "user" && scope !== "project") {
11
+ throw new ScreencastError("--scope must be user or project", "VALIDATION_ERROR", [
12
+ "Example: --scope project",
13
+ ]);
14
+ }
15
+ throw new ScreencastError("Session hooks are not implemented yet", "NOT_IMPLEMENTED", [
16
+ "Install the skill instead: `npx skills add Valzon/screencast-axi --skill screencast-axi -g`",
17
+ "The CLI works without either - `npx -y screencast-axi --help`",
18
+ ]);
19
+ }
20
+ //# sourceMappingURL=hooks.js.map