@crouton-kit/capture 1.3.8 → 1.3.10

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 (136) hide show
  1. package/bin/capture +29841 -8539
  2. package/package.json +6 -3
  3. package/src/capture.ts +86 -173
  4. package/src/cdp/a11y.ts +113 -14
  5. package/src/cdp/args.ts +300 -205
  6. package/src/cdp/bridge/protocol.ts +93 -6
  7. package/src/cdp/bridge/server.ts +118 -53
  8. package/src/cdp/bridge/spawn.ts +53 -6
  9. package/src/cdp/client.ts +13 -3
  10. package/src/cdp/commands/bridge-serve.ts +20 -14
  11. package/src/cdp/commands/cdp.ts +259 -90
  12. package/src/cdp/commands/gate-guard.ts +43 -0
  13. package/src/cdp/commands/lib.ts +339 -213
  14. package/src/cdp/commands/measure/census.ts +76 -20
  15. package/src/cdp/commands/measure/check.ts +100 -26
  16. package/src/cdp/commands/measure/diff.ts +109 -21
  17. package/src/cdp/commands/measure/explain.ts +121 -19
  18. package/src/cdp/commands/measure/index.ts +31 -39
  19. package/src/cdp/commands/measure/map-ax.ts +52 -0
  20. package/src/cdp/commands/measure/map-focus.ts +41 -13
  21. package/src/cdp/commands/measure/map-layers.ts +49 -15
  22. package/src/cdp/commands/measure/map-scroll.ts +85 -15
  23. package/src/cdp/commands/measure/snap.ts +405 -25
  24. package/src/cdp/commands/measure/sweep.ts +217 -20
  25. package/src/cdp/commands/motion/index.ts +18 -17
  26. package/src/cdp/commands/motion/jank.ts +119 -9
  27. package/src/cdp/commands/motion/mask.ts +81 -12
  28. package/src/cdp/commands/motion/rec.ts +520 -19
  29. package/src/cdp/commands/motion/response.ts +65 -15
  30. package/src/cdp/commands/motion/timeline.ts +139 -14
  31. package/src/cdp/commands/page/click.ts +194 -0
  32. package/src/cdp/commands/page/elements.ts +154 -0
  33. package/src/cdp/commands/page/exec.ts +317 -0
  34. package/src/cdp/commands/page/index.ts +72 -0
  35. package/src/cdp/commands/page/navigate.ts +330 -0
  36. package/src/cdp/commands/page/scroll.ts +107 -0
  37. package/src/cdp/commands/page/shot.ts +208 -0
  38. package/src/cdp/commands/page/type.ts +104 -0
  39. package/src/cdp/commands/tab/index.ts +59 -0
  40. package/src/cdp/commands/tab/list.ts +167 -0
  41. package/src/cdp/commands/tab/network.ts +162 -0
  42. package/src/cdp/commands/tab/open.ts +90 -0
  43. package/src/cdp/commands/tab/reset.ts +160 -0
  44. package/src/cdp/connection.ts +368 -37
  45. package/src/cdp/detect.ts +42 -13
  46. package/src/cdp/dispatch.ts +74 -28
  47. package/src/cdp/har-recorder.ts +648 -115
  48. package/src/cdp/leaf-grammar.ts +99 -0
  49. package/src/cdp/measure/census.ts +205 -0
  50. package/src/cdp/measure/check.ts +318 -0
  51. package/src/cdp/measure/collectors/animation.ts +385 -0
  52. package/src/cdp/measure/collectors/ax.ts +278 -0
  53. package/src/cdp/measure/collectors/focus.ts +1041 -0
  54. package/src/cdp/measure/collectors/forms.ts +905 -0
  55. package/src/cdp/measure/collectors/geometry.ts +1038 -0
  56. package/src/cdp/measure/collectors/hittest.ts +835 -0
  57. package/src/cdp/measure/collectors/layers.ts +697 -0
  58. package/src/cdp/measure/collectors/media.ts +522 -0
  59. package/src/cdp/measure/collectors/pixels.ts +1584 -0
  60. package/src/cdp/measure/collectors/queries.ts +464 -0
  61. package/src/cdp/measure/collectors/scroll.ts +599 -0
  62. package/src/cdp/measure/collectors/states.ts +992 -0
  63. package/src/cdp/measure/collectors/style-provenance.ts +643 -0
  64. package/src/cdp/measure/collectors/styles.ts +355 -0
  65. package/src/cdp/measure/collectors/text.ts +713 -0
  66. package/src/cdp/measure/diff.ts +470 -0
  67. package/src/cdp/measure/explain.ts +498 -0
  68. package/src/cdp/measure/map-ax.ts +633 -0
  69. package/src/cdp/measure/map-focus.ts +188 -0
  70. package/src/cdp/measure/map-layers.ts +177 -0
  71. package/src/cdp/measure/map-scroll.ts +317 -0
  72. package/src/cdp/measure/redaction.ts +51 -0
  73. package/src/cdp/measure/settle.ts +1051 -0
  74. package/src/cdp/measure/snapshot.ts +390 -0
  75. package/src/cdp/measure/sweep.ts +330 -0
  76. package/src/cdp/measure/types.ts +220 -0
  77. package/src/cdp/measure/v2/collectors.ts +19 -0
  78. package/src/cdp/measure/v2/orchestrator.ts +46 -0
  79. package/src/cdp/measure/v2/snap-selection.ts +33 -0
  80. package/src/cdp/measure/v2/target-lock.ts +15 -0
  81. package/src/cdp/measure/v2/target.ts +16 -0
  82. package/src/cdp/motion/jank.ts +433 -0
  83. package/src/cdp/motion/mask.ts +421 -0
  84. package/src/cdp/motion/recorder.ts +764 -0
  85. package/src/cdp/motion/response.ts +356 -0
  86. package/src/cdp/motion/timeline.ts +215 -0
  87. package/src/cdp/record.ts +5 -212
  88. package/src/cdp/recorder-bridge.ts +1478 -105
  89. package/src/cdp/recorder-client.ts +326 -0
  90. package/src/cdp/scope-lock.ts +73 -0
  91. package/src/cdp/screenshot.ts +199 -66
  92. package/src/cdp/targets.ts +21 -1
  93. package/src/cdp/timing.ts +15 -10
  94. package/src/cdp/types.ts +22 -7
  95. package/src/cdp/viewport.ts +21 -0
  96. package/src/cdp.ts +12 -58
  97. package/src/command-help.ts +29 -0
  98. package/src/command-resolution.ts +22 -0
  99. package/src/command-walker.ts +35 -0
  100. package/src/contracts/a11y.ts +33 -0
  101. package/src/contracts/cursor.ts +35 -0
  102. package/src/contracts/index.ts +15 -0
  103. package/src/contracts/motion.ts +19 -0
  104. package/src/contracts/neutral.ts +56 -0
  105. package/src/contracts/primitives.ts +205 -0
  106. package/src/contracts/release.ts +17 -0
  107. package/src/contracts/results.ts +208 -0
  108. package/src/contracts/routes.ts +140 -0
  109. package/src/contracts/snapshot.ts +92 -0
  110. package/src/cursor/index.ts +13 -0
  111. package/src/cursor/service.ts +310 -0
  112. package/src/errors.ts +71 -0
  113. package/src/har-manager.ts +992 -41
  114. package/src/interact.ts +410 -98
  115. package/src/output/artifact-lifecycle.ts +371 -0
  116. package/src/output/artifact.ts +6 -0
  117. package/src/output/exact-raw.ts +114 -0
  118. package/src/output/render.ts +396 -2
  119. package/src/output/structured.ts +211 -0
  120. package/src/registry.ts +208 -0
  121. package/src/session/artifacts.ts +452 -200
  122. package/src/session/commands.ts +1162 -397
  123. package/src/session/coordinator.ts +251 -0
  124. package/src/session/har-filter.ts +57 -0
  125. package/src/session/log-tailer.ts +653 -0
  126. package/src/session-context.ts +279 -51
  127. package/src/vault/bundle.ts +32 -17
  128. package/src/cdp/commands/exec.ts +0 -105
  129. package/src/cdp/commands/har.ts +0 -128
  130. package/src/cdp/commands/tabs.ts +0 -183
  131. package/src/cdp/commands/traffic.ts +0 -121
  132. package/src/cdp/commands/ui.ts +0 -172
  133. package/src/cdp/execute.ts +0 -113
  134. package/src/cdp/har-output.ts +0 -47
  135. package/src/cdp/usage.ts +0 -79
  136. package/src/tab-session.ts +0 -91
@@ -0,0 +1,317 @@
1
+ /**
2
+ * `page exec <code> | --file <path>` — run arbitrary JavaScript in the tab.
3
+ *
4
+ * A bare expression evaluates directly; a statement body may use top-level
5
+ * `return`/`await` (wrapped in an async IIFE by `buildExecExpression`);
6
+ * leading static imports bundle the forked vault libs before any CDP
7
+ * connection is opened (fail fast). The JSON-serialized return value is
8
+ * rendered inline in the `<exec-result>` block under a generous cap; a
9
+ * result exceeding the cap is additionally written WHOLE to a private
10
+ * artifact file — the active session's `page/` dir when a session exists,
11
+ * else a one-shot artifact dir (`createOneshotSession('page')`), never a
12
+ * loose `/tmp` file — and the block carries that file's absolute path.
13
+ * `--json` mirrors the block with the value at full fidelity. During a live
14
+ * composed recording the CDP routes through the recorder socket unmarked
15
+ * (`Runtime.evaluate` is never a landmark).
16
+ */
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import { type ParsedArgs } from '../../types.js';
20
+ import { withPageAction, type SettleFacts } from '../../connection.js';
21
+ import { CaptureError, captureError } from '../../../errors.js';
22
+ import { buildExecExpression } from '../../exec-expression.js';
23
+ import { getActiveSession } from '../../../session-context.js';
24
+ import { createOneshotSession } from '../../../session/commands.js';
25
+ import { writePrivateFile } from '../../../session/artifacts.js';
26
+ import { withScopeSerialization } from '../../scope-lock.js';
27
+ import { isRecorderHeldClient } from '../../recorder-client.js';
28
+ import { hasImports, bundleExec } from '../../../vault/bundle.js';
29
+ import {
30
+ capped,
31
+ data,
32
+ emitResult,
33
+ fact,
34
+ line,
35
+ text,
36
+ type FactLine,
37
+ } from '../../../output/render.js';
38
+
39
+ /** Generous inline cap for the serialized result value: big enough for a
40
+ * typical scrape/query result, still bounded in the prose block. `--json`
41
+ * carries the value uncapped (full fidelity); a result larger than this is
42
+ * also spilled whole to an artifact file. */
43
+ const GENEROUS_RESULT_CAP = 4000;
44
+
45
+ const DEFAULT_SETTLE_MS = 3000;
46
+
47
+ const USAGE = `capture page exec <code> | --file <path> — run arbitrary JavaScript in the tab: a bare expression evaluates directly; a statement body may use top-level return/await (wrapped in an async IIFE); leading static imports bundle the forked vault libs (dev checkout only)
48
+
49
+ input:
50
+ <code> JS source as one argument (exactly one of <code> / --file)
51
+ --file <path> read the JS source from a file instead of inline
52
+ --settle <ms> network-settle window after execution so an active session's HAR captures the requests the code triggers (default: ${DEFAULT_SETTLE_MS}; 0 disables); the block reports the measured requested/waited settle
53
+ --target <id> target a tab explicitly (default: the active session tab; with no active session, --target or --url is required)
54
+ --url <pattern> target the first tab whose URL matches <pattern>
55
+ output:
56
+ <exec-result result-chars=…> — the JSON-serialized return value inline, escaped and capped at ${GENEROUS_RESULT_CAP} chars; a larger result is also written whole to a private artifact file (the active session's page/ dir, else a one-shot artifact dir) whose absolute path appears in the block. --json mirrors the same block with the value at full fidelity.
57
+ effects:
58
+ runs the code in the page with full DOM/JS access — whatever the code mutates, it mutates; emulates focus on the tab for the duration of the call (disabled again before the command returns); writes the oversize-result artifact file when the inline cap is exceeded`;
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Test-injectable dependency seam (repo CDP-stub pattern — see
62
+ // `test/page-exec.test.ts`).
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export interface PageExecDeps {
66
+ withPageAction: typeof withPageAction;
67
+ getActiveSession: typeof getActiveSession;
68
+ createOneshotSession: typeof createOneshotSession;
69
+ isRecorderHeldClient: typeof isRecorderHeldClient;
70
+ }
71
+
72
+ let deps: PageExecDeps = { withPageAction, getActiveSession, createOneshotSession, isRecorderHeldClient };
73
+
74
+ /** Swap the connection/session seams for the CDP-stub tests. */
75
+ export function __setPageExecDepsForTest(overrides: Partial<PageExecDeps>): () => void {
76
+ const previous = deps;
77
+ deps = { ...deps, ...overrides };
78
+ return () => { deps = previous; };
79
+ }
80
+
81
+ function emitExecError(parsed: ParsedArgs, code: string, summary: FactLine, followUp?: FactLine): void {
82
+ emitResult(
83
+ { tag: 'error', attrs: { command: 'page exec', code }, summary, followUp },
84
+ { json: parsed.json },
85
+ );
86
+ process.exitCode = 1;
87
+ }
88
+
89
+ /**
90
+ * Writes an over-cap result whole to a private (0600) artifact file: into
91
+ * the active session's `page/` dir when a session exists, else into a fresh
92
+ * one-shot artifact dir. Returns the absolute file path.
93
+ */
94
+ function spillWholeResult(payload: string): string {
95
+ const name = `exec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`;
96
+ const session = deps.getActiveSession();
97
+ const filePath = session
98
+ ? path.join(session.dir, 'page', name)
99
+ : path.join(deps.createOneshotSession('page').artifactsDir, name);
100
+ writePrivateFile(filePath, payload);
101
+ return filePath;
102
+ }
103
+
104
+ interface EvalResult {
105
+ result?: { value?: unknown };
106
+ exceptionDetails?: { exception?: { description?: string } };
107
+ }
108
+
109
+ /** The structural client surface the focus/evaluation scope drives — the
110
+ * subset of `CDPClient` both a direct connection and the recorder-held
111
+ * adapter satisfy. */
112
+ interface ExecClient {
113
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
114
+ }
115
+
116
+ /**
117
+ * The one focus-emulation state transaction: focus true → evaluate → focus
118
+ * false. A lost enable response does not prove Chrome rejected the override,
119
+ * so restoration ownership is claimed BEFORE the enable is awaited and the
120
+ * matching disable is always sent exactly once in `finally`. A disable
121
+ * failure prevents success: alone it throws a typed cleanup failure; paired
122
+ * with a primary failure it throws an `AggregateError` preserving both.
123
+ */
124
+ async function focusScopedEvaluate(client: ExecClient, expression: string): Promise<EvalResult> {
125
+ let ownsFocusOverride = false;
126
+ let primaryFailed = false;
127
+ let primaryError: unknown;
128
+ try {
129
+ // Emulate focus so network requests the code triggers aren't deferred
130
+ // by the browser, without foregrounding the tab.
131
+ ownsFocusOverride = true;
132
+ await client.send('Emulation.setFocusEmulationEnabled', { enabled: true });
133
+ return (await client.send('Runtime.evaluate', {
134
+ expression,
135
+ awaitPromise: true,
136
+ returnByValue: true,
137
+ })) as EvalResult;
138
+ } catch (error) {
139
+ primaryFailed = true;
140
+ primaryError = error;
141
+ throw error;
142
+ } finally {
143
+ if (ownsFocusOverride) {
144
+ try {
145
+ await client.send('Emulation.setFocusEmulationEnabled', { enabled: false });
146
+ } catch (error) {
147
+ const cleanupError = captureError(
148
+ 'cleanup',
149
+ 'focus_cleanup_failed',
150
+ 'Emulation.setFocusEmulationEnabled(false) failed after page exec — the tab may retain a focus-emulation override.',
151
+ error,
152
+ );
153
+ if (primaryFailed) {
154
+ throw new AggregateError(
155
+ [primaryError, cleanupError],
156
+ 'page exec failed and focus-emulation cleanup also failed.',
157
+ { cause: primaryError },
158
+ );
159
+ }
160
+ throw cleanupError;
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Runs the focus/evaluation scope, serialized when the connection is the
168
+ * recorder-held one. The recorder holds ONE persistent tab connection whose
169
+ * focus-emulation state outlives any single command, so two concurrent
170
+ * routed execs could otherwise clear one another's live override; the
171
+ * entire three-request scope therefore runs under the owning session's
172
+ * `.focus-scope.lock` via the shared scope-serialization authority
173
+ * (`../../scope-lock.js`), passing this module's injectable seams so the
174
+ * test-controlled deps govern the held/session branch. A direct connection
175
+ * needs no lock — its emulation state is scoped to its own websocket
176
+ * session, which closes with the command.
177
+ */
178
+ async function evaluateWithFocusEmulation(client: ExecClient, expression: string): Promise<EvalResult> {
179
+ return withScopeSerialization(
180
+ client,
181
+ 'focus',
182
+ 'page exec',
183
+ () => focusScopedEvaluate(client, expression),
184
+ { isRecorderHeldClient: deps.isRecorderHeldClient, getActiveSession: deps.getActiveSession },
185
+ );
186
+ }
187
+
188
+ export async function cmdPageExec(parsed: ParsedArgs, _args: string[]): Promise<void> {
189
+ if (parsed.help) {
190
+ console.log(USAGE);
191
+ return;
192
+ }
193
+
194
+ let code: string;
195
+ if (parsed.file !== undefined) {
196
+ if (parsed.positional.length > 0) {
197
+ return emitExecError(
198
+ parsed,
199
+ 'invalid_input',
200
+ text`received: both inline <code> and --file; expected exactly one source of JS to run.`,
201
+ );
202
+ }
203
+ try {
204
+ code = fs.readFileSync(parsed.file, 'utf-8');
205
+ } catch (err) {
206
+ return emitExecError(
207
+ parsed,
208
+ 'file_unreadable',
209
+ fact`received: --file ${parsed.file} which could not be read (${err instanceof Error ? err.message : String(err)}); expected: a readable JS source file.`,
210
+ );
211
+ }
212
+ } else {
213
+ if (parsed.positional.length !== 1 || !parsed.positional[0]) {
214
+ return emitExecError(
215
+ parsed,
216
+ 'invalid_input',
217
+ fact`received: ${parsed.positional.length} positional arguments; expected exactly one <code> argument (or --file <path>).`,
218
+ );
219
+ }
220
+ code = parsed.positional[0];
221
+ }
222
+
223
+ // Import-driven exec: leading static imports bundle the forked vault libs
224
+ // on the fly (esbuild) BEFORE opening a tab — fail fast, no wasted CDP
225
+ // connection. Plain exec (no imports) skips this entirely.
226
+ let prebuilt: string | undefined;
227
+ if (hasImports(code)) {
228
+ try {
229
+ prebuilt = await bundleExec(code);
230
+ } catch (err) {
231
+ return emitExecError(
232
+ parsed,
233
+ 'bundle_failed',
234
+ fact`vault import bundling failed: ${err instanceof Error ? err.message : String(err)}`,
235
+ );
236
+ }
237
+ }
238
+
239
+ const settle = parsed.settle ?? DEFAULT_SETTLE_MS;
240
+
241
+ // A prebuilt bundle is already a complete IIFE returning the user's
242
+ // promise; otherwise buildExecExpression wraps statement bodies. Pure
243
+ // string work — built before any connection is opened.
244
+ const expression = buildExecExpression(code, prebuilt);
245
+
246
+ let evalResult: EvalResult;
247
+ let settleFacts: SettleFacts;
248
+ try {
249
+ // connection.ts derives its per-invocation label from parsed.command,
250
+ // which the router leaves as the branch token 'page' — restore the verb
251
+ // so a routed exec is labeled `exec:…`. Runtime.evaluate is never a
252
+ // markable method, so the routed call stays unmarked in events.jsonl.
253
+ const outcome = await deps.withPageAction(
254
+ { ...parsed, command: 'exec' },
255
+ { settleMs: settle },
256
+ (client) => evaluateWithFocusEmulation(client, expression),
257
+ );
258
+ evalResult = outcome.result;
259
+ settleFacts = outcome.settle;
260
+ } catch (err) {
261
+ // A typed failure (e.g. `recorder_unavailable` from strict A2 routing,
262
+ // `har_missing`, or a lone `focus_cleanup_failed`) crosses the boundary
263
+ // unrelabeled — only a genuinely untyped error is the exec-specific
264
+ // `exec_failed`.
265
+ if (err instanceof CaptureError) throw err;
266
+ if (err instanceof AggregateError && err.errors.length === 2) {
267
+ // The primary+disable dual failure — preserve both facts distinctly.
268
+ const message = (value: unknown) => (value instanceof Error ? value.message : String(value));
269
+ return emitExecError(
270
+ parsed,
271
+ 'exec_failed',
272
+ fact`execution failed (${message(err.errors[0])}) and focus-emulation cleanup also failed (${message(err.errors[1])})`,
273
+ );
274
+ }
275
+ return emitExecError(
276
+ parsed,
277
+ 'exec_failed',
278
+ fact`execution failed: ${err instanceof Error ? err.message : String(err)}`,
279
+ text`Check a CDP-enabled browser is running and a tab is targetable (probe: capture tab list).`,
280
+ );
281
+ }
282
+
283
+ if (evalResult.exceptionDetails) {
284
+ const description = evalResult.exceptionDetails.exception?.description ?? 'Unknown error';
285
+ return emitExecError(
286
+ parsed,
287
+ 'exec_exception',
288
+ line(text`the page threw during execution: `, data(capped(description, GENEROUS_RESULT_CAP))),
289
+ );
290
+ }
291
+
292
+ const payload = JSON.stringify(evalResult.result?.value) ?? 'undefined';
293
+
294
+ let spillPath: string | undefined;
295
+ if (payload.length > GENEROUS_RESULT_CAP) {
296
+ spillPath = spillWholeResult(payload);
297
+ }
298
+
299
+ const inlineCap = parsed.json ? payload.length : GENEROUS_RESULT_CAP;
300
+ const sections: FactLine[] = [line(text`result: `, data(capped(payload, inlineCap)))];
301
+ if (spillPath) {
302
+ sections.push(fact`full result (${payload.length} chars) written whole to ${spillPath}`);
303
+ }
304
+ sections.push(fact`settle: requested ${settleFacts.requestedMs}ms, waited ${settleFacts.waitedMs}ms`);
305
+
306
+ emitResult(
307
+ {
308
+ tag: 'exec-result',
309
+ attrs: {
310
+ 'result-chars': payload.length,
311
+ 'result-file': spillPath,
312
+ },
313
+ sections,
314
+ },
315
+ { json: parsed.json },
316
+ );
317
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `capture page` branch router — every verb against the live session tab:
3
+ * driving (click, type, scroll, navigate, exec) and looking (shot,
4
+ * elements).
5
+ *
6
+ * Shifts the consumed leaf token out of `parsed.positional` before handing
7
+ * off, so every leaf command still finds its own primary target (a target
8
+ * selector, a URL, code, ...) at `positional[0]` exactly as every other
9
+ * capture command does — leaf commands never need to know they're nested
10
+ * under a branch.
11
+ */
12
+ import { invalidInput } from '../../../errors.js';
13
+ import { type ParsedArgs } from '../../types.js';
14
+ import { cmdPageClick } from './click.js';
15
+ import { cmdPageType } from './type.js';
16
+ import { cmdPageScroll } from './scroll.js';
17
+ import { cmdPageNavigate } from './navigate.js';
18
+ import { cmdPageExec } from './exec.js';
19
+ import { cmdPageShot } from './shot.js';
20
+ import { cmdPageElements } from './elements.js';
21
+
22
+ /** Root-help representation of this branch, assembled by `src/capture.ts`. */
23
+ export const COMMAND_BLOCK = `<command name="page">
24
+ verbs against the live session tab — act (click, type, scroll, navigate, exec) and look (shot, elements)
25
+ use when driving or inspecting the page a session opened; all verbs auto-target the active session tab, --target/--url override
26
+ click · type · scroll · navigate · exec · shot · elements — \`capture page -h\`
27
+ </command>`;
28
+
29
+ export const PAGE_USAGE = `capture page — verbs against the live session tab: driving, looking, targeting.
30
+
31
+ All verbs auto-target the active session tab; --target/--url override. The
32
+ driving verbs (click, type, scroll) resolve exactly one element via the
33
+ unified target grammar — bare CSS selector, ax:<name>, axid:<id>,
34
+ backend:<id> — and reject an ambiguous target with the candidate list.
35
+ During a live composed recording every verb routes through the recorder.
36
+
37
+ <subcommand name="click" args="<target> [--no-screenshot] [--settle <ms>]" whenToUse="dispatch a real click on one resolved element"/>
38
+ <subcommand name="type" args="<text> [--into <target>] [--no-screenshot] [--settle <ms>]" whenToUse="type text into the focused element or one resolved field"/>
39
+ <subcommand name="scroll" args="<target> --to <top|bottom|px> [--no-screenshot]" whenToUse="scroll one resolved container to a position"/>
40
+ <subcommand name="navigate" args="<url> [--settle <ms>]" whenToUse="navigate the tab and wait for load + settle"/>
41
+ <subcommand name="exec" args="<code> | --file <path>" whenToUse="run arbitrary JS in the tab (expressions, return, await)"/>
42
+ <subcommand name="shot" args="[--viewport <WxH>] [--full-page] [--out <path>]" whenToUse="look at the page right now without acting"/>
43
+ <subcommand name="elements" args="[--all] [--limit <n>]" whenToUse="list what can be acted on — role, name, backend:<id> per element"/>
44
+
45
+ capture page <leaf> -h Per-leaf usage`;
46
+
47
+ export async function pageMain(parsed: ParsedArgs, args: string[]): Promise<void> {
48
+ const leaf = parsed.positional[0];
49
+ const rest: ParsedArgs = { ...parsed, positional: parsed.positional.slice(1) };
50
+
51
+ switch (leaf) {
52
+ case 'click':
53
+ return cmdPageClick(rest, args);
54
+ case 'type':
55
+ return cmdPageType(rest, args);
56
+ case 'scroll':
57
+ return cmdPageScroll(rest, args);
58
+ case 'navigate':
59
+ return cmdPageNavigate(rest, args);
60
+ case 'exec':
61
+ return cmdPageExec(rest, args);
62
+ case 'shot':
63
+ return cmdPageShot(rest, args);
64
+ case 'elements':
65
+ return cmdPageElements(rest, args);
66
+ case undefined:
67
+ console.log(PAGE_USAGE);
68
+ return;
69
+ default:
70
+ throw invalidInput(`Unknown page leaf: ${leaf}.`, 'unknown_command');
71
+ }
72
+ }