chrome-devtools-axi 0.1.1 → 0.1.3
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.
- package/README.md +103 -33
- package/dist/bin/chrome-devtools-axi.js +0 -0
- package/dist/src/cli.d.ts +58 -0
- package/dist/src/cli.js +992 -41
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +6 -1
- package/dist/src/client.js +27 -6
- package/dist/src/client.js.map +1 -1
- package/dist/src/hooks.d.ts +44 -0
- package/dist/src/hooks.js +88 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/snapshot.d.ts +6 -0
- package/dist/src/snapshot.js +9 -0
- package/dist/src/snapshot.js.map +1 -1
- package/package.json +2 -2
package/dist/src/cli.js
CHANGED
|
@@ -1,12 +1,548 @@
|
|
|
1
1
|
import { encode } from "@toon-format/toon";
|
|
2
|
-
import { CdpError, callTool, ensureBridge, stopBridge } from "./client.js";
|
|
3
|
-
import {
|
|
2
|
+
import { CdpError, callTool, ensureBridge, getSessionStatus, stopBridge } from "./client.js";
|
|
3
|
+
import { installHooks } from "./hooks.js";
|
|
4
|
+
import { countRefs, extractTitle, truncateSnapshot } from "./snapshot.js";
|
|
4
5
|
import { getSuggestions } from "./suggestions.js";
|
|
5
6
|
const HELP = `usage: chrome-devtools-axi <command> [args]
|
|
6
|
-
commands[
|
|
7
|
-
open <url>, snapshot, click @<uid>, fill @<uid> <text>,
|
|
8
|
-
press <key>, scroll <dir>, back, wait <ms|text>, eval <js>,
|
|
7
|
+
commands[33]:
|
|
8
|
+
open <url>, snapshot, screenshot <path>, click @<uid>, fill @<uid> <text>,
|
|
9
|
+
type <text>, press <key>, scroll <dir>, back, wait <ms|text>, eval <js>,
|
|
10
|
+
hover @<uid>, drag @<from> @<to>, fillform @<uid>=<val>..., dialog <action>,
|
|
11
|
+
upload @<uid> <path>, pages, newpage <url>, selectpage <id>, closepage <id>,
|
|
12
|
+
resize <w> <h>, emulate, console, console-get <id>, network,
|
|
13
|
+
network-get [id], lighthouse, perf-start, perf-stop,
|
|
14
|
+
perf-insight <set> <name>, heap <path>, start, stop
|
|
9
15
|
`;
|
|
16
|
+
const COMMAND_HELP = {
|
|
17
|
+
open: `usage: chrome-devtools-axi open <url> [--full]
|
|
18
|
+
Navigate to a URL and capture an accessibility snapshot.
|
|
19
|
+
|
|
20
|
+
args:
|
|
21
|
+
<url> URL to navigate to (required)
|
|
22
|
+
|
|
23
|
+
flags:
|
|
24
|
+
--full Show complete snapshot without truncation
|
|
25
|
+
|
|
26
|
+
examples:
|
|
27
|
+
chrome-devtools-axi open https://example.com
|
|
28
|
+
chrome-devtools-axi open https://example.com --full`,
|
|
29
|
+
screenshot: `usage: chrome-devtools-axi screenshot <path> [--uid @<uid>] [--full-page] [--format png|jpeg|webp]
|
|
30
|
+
Save a screenshot to a file.
|
|
31
|
+
|
|
32
|
+
args:
|
|
33
|
+
<path> File path to save the screenshot (required)
|
|
34
|
+
|
|
35
|
+
flags:
|
|
36
|
+
--uid @<uid> Capture a specific element instead of the full viewport
|
|
37
|
+
--full-page Capture the entire scrollable page
|
|
38
|
+
--format <fmt> Image format: png (default), jpeg, or webp
|
|
39
|
+
|
|
40
|
+
examples:
|
|
41
|
+
chrome-devtools-axi screenshot ./page.png
|
|
42
|
+
chrome-devtools-axi screenshot ./element.png --uid @3
|
|
43
|
+
chrome-devtools-axi screenshot ./full.png --full-page --format jpeg`,
|
|
44
|
+
snapshot: `usage: chrome-devtools-axi snapshot [--full]
|
|
45
|
+
Capture the current page accessibility snapshot.
|
|
46
|
+
|
|
47
|
+
flags:
|
|
48
|
+
--full Show complete snapshot without truncation
|
|
49
|
+
|
|
50
|
+
examples:
|
|
51
|
+
chrome-devtools-axi snapshot
|
|
52
|
+
chrome-devtools-axi snapshot --full`,
|
|
53
|
+
click: `usage: chrome-devtools-axi click @<uid> [--full]
|
|
54
|
+
Click an interactive element by its ref from the snapshot.
|
|
55
|
+
|
|
56
|
+
args:
|
|
57
|
+
@<uid> Element ref from snapshot (required)
|
|
58
|
+
|
|
59
|
+
flags:
|
|
60
|
+
--full Show complete snapshot without truncation
|
|
61
|
+
|
|
62
|
+
examples:
|
|
63
|
+
chrome-devtools-axi click @1
|
|
64
|
+
chrome-devtools-axi click @12 --full`,
|
|
65
|
+
fill: `usage: chrome-devtools-axi fill @<uid> <text> [--full]
|
|
66
|
+
Fill a form field with text.
|
|
67
|
+
|
|
68
|
+
args:
|
|
69
|
+
@<uid> Element ref from snapshot (required)
|
|
70
|
+
<text> Text to fill (required)
|
|
71
|
+
|
|
72
|
+
flags:
|
|
73
|
+
--full Show complete snapshot without truncation
|
|
74
|
+
|
|
75
|
+
examples:
|
|
76
|
+
chrome-devtools-axi fill @3 "hello world"
|
|
77
|
+
chrome-devtools-axi fill @3 "search query" --full`,
|
|
78
|
+
type: `usage: chrome-devtools-axi type <text> [--full]
|
|
79
|
+
Type text at the currently focused element.
|
|
80
|
+
|
|
81
|
+
args:
|
|
82
|
+
<text> Text to type (required)
|
|
83
|
+
|
|
84
|
+
flags:
|
|
85
|
+
--full Show complete snapshot without truncation
|
|
86
|
+
|
|
87
|
+
examples:
|
|
88
|
+
chrome-devtools-axi type "hello"
|
|
89
|
+
chrome-devtools-axi type "search query" --full`,
|
|
90
|
+
press: `usage: chrome-devtools-axi press <key> [--full]
|
|
91
|
+
Press a keyboard key.
|
|
92
|
+
|
|
93
|
+
args:
|
|
94
|
+
<key> Key name, e.g. Enter, Tab, Escape, ArrowDown (required)
|
|
95
|
+
|
|
96
|
+
flags:
|
|
97
|
+
--full Show complete snapshot without truncation
|
|
98
|
+
|
|
99
|
+
examples:
|
|
100
|
+
chrome-devtools-axi press Enter
|
|
101
|
+
chrome-devtools-axi press Tab --full`,
|
|
102
|
+
scroll: `usage: chrome-devtools-axi scroll <direction> [--full]
|
|
103
|
+
Scroll the page in a direction.
|
|
104
|
+
|
|
105
|
+
args:
|
|
106
|
+
<direction> up, down, top, or bottom (default: down)
|
|
107
|
+
|
|
108
|
+
flags:
|
|
109
|
+
--full Show complete snapshot without truncation
|
|
110
|
+
|
|
111
|
+
examples:
|
|
112
|
+
chrome-devtools-axi scroll down
|
|
113
|
+
chrome-devtools-axi scroll top --full`,
|
|
114
|
+
back: `usage: chrome-devtools-axi back [--full]
|
|
115
|
+
Navigate back in browser history.
|
|
116
|
+
|
|
117
|
+
flags:
|
|
118
|
+
--full Show complete snapshot without truncation
|
|
119
|
+
|
|
120
|
+
examples:
|
|
121
|
+
chrome-devtools-axi back
|
|
122
|
+
chrome-devtools-axi back --full`,
|
|
123
|
+
wait: `usage: chrome-devtools-axi wait <ms|text>
|
|
124
|
+
Wait for a duration or for text to appear on the page.
|
|
125
|
+
|
|
126
|
+
args:
|
|
127
|
+
<ms> Milliseconds to wait (numeric)
|
|
128
|
+
<text> Text to wait for (string)
|
|
129
|
+
|
|
130
|
+
examples:
|
|
131
|
+
chrome-devtools-axi wait 2000
|
|
132
|
+
chrome-devtools-axi wait "Submit"`,
|
|
133
|
+
eval: `usage: chrome-devtools-axi eval <js>
|
|
134
|
+
Evaluate JavaScript in the page context.
|
|
135
|
+
|
|
136
|
+
args:
|
|
137
|
+
<js> JavaScript expression (required)
|
|
138
|
+
|
|
139
|
+
examples:
|
|
140
|
+
chrome-devtools-axi eval "document.title"
|
|
141
|
+
chrome-devtools-axi eval "document.querySelectorAll('a').length"`,
|
|
142
|
+
start: `usage: chrome-devtools-axi start
|
|
143
|
+
Start the bridge server (launches headless Chrome).
|
|
144
|
+
|
|
145
|
+
examples:
|
|
146
|
+
chrome-devtools-axi start`,
|
|
147
|
+
stop: `usage: chrome-devtools-axi stop
|
|
148
|
+
Stop the bridge server and close the browser.
|
|
149
|
+
|
|
150
|
+
examples:
|
|
151
|
+
chrome-devtools-axi stop`,
|
|
152
|
+
// Page management
|
|
153
|
+
pages: `usage: chrome-devtools-axi pages
|
|
154
|
+
List all open pages/tabs in the browser.
|
|
155
|
+
|
|
156
|
+
examples:
|
|
157
|
+
chrome-devtools-axi pages`,
|
|
158
|
+
newpage: `usage: chrome-devtools-axi newpage <url> [--background] [--full]
|
|
159
|
+
Open a new tab and navigate to a URL.
|
|
160
|
+
|
|
161
|
+
args:
|
|
162
|
+
<url> URL to open (required)
|
|
163
|
+
|
|
164
|
+
flags:
|
|
165
|
+
--background Open in background without bringing to front
|
|
166
|
+
--full Show complete snapshot without truncation
|
|
167
|
+
|
|
168
|
+
examples:
|
|
169
|
+
chrome-devtools-axi newpage https://example.com
|
|
170
|
+
chrome-devtools-axi newpage https://example.com --background`,
|
|
171
|
+
selectpage: `usage: chrome-devtools-axi selectpage <id> [--full]
|
|
172
|
+
Switch to a tab by page ID.
|
|
173
|
+
|
|
174
|
+
args:
|
|
175
|
+
<id> Page ID from the pages command (required)
|
|
176
|
+
|
|
177
|
+
flags:
|
|
178
|
+
--full Show complete snapshot without truncation
|
|
179
|
+
|
|
180
|
+
examples:
|
|
181
|
+
chrome-devtools-axi selectpage 1`,
|
|
182
|
+
closepage: `usage: chrome-devtools-axi closepage <id>
|
|
183
|
+
Close a tab by page ID. The last open page cannot be closed.
|
|
184
|
+
|
|
185
|
+
args:
|
|
186
|
+
<id> Page ID from the pages command (required)
|
|
187
|
+
|
|
188
|
+
examples:
|
|
189
|
+
chrome-devtools-axi closepage 2`,
|
|
190
|
+
resize: `usage: chrome-devtools-axi resize <width> <height>
|
|
191
|
+
Resize the browser viewport.
|
|
192
|
+
|
|
193
|
+
args:
|
|
194
|
+
<width> Width in pixels (required)
|
|
195
|
+
<height> Height in pixels (required)
|
|
196
|
+
|
|
197
|
+
examples:
|
|
198
|
+
chrome-devtools-axi resize 1280 720
|
|
199
|
+
chrome-devtools-axi resize 390 844`,
|
|
200
|
+
// Interaction
|
|
201
|
+
hover: `usage: chrome-devtools-axi hover @<uid> [--full]
|
|
202
|
+
Hover over an element to trigger hover states.
|
|
203
|
+
|
|
204
|
+
args:
|
|
205
|
+
@<uid> Element ref from snapshot (required)
|
|
206
|
+
|
|
207
|
+
flags:
|
|
208
|
+
--full Show complete snapshot without truncation
|
|
209
|
+
|
|
210
|
+
examples:
|
|
211
|
+
chrome-devtools-axi hover @5`,
|
|
212
|
+
drag: `usage: chrome-devtools-axi drag @<from> @<to> [--full]
|
|
213
|
+
Drag an element onto another element.
|
|
214
|
+
|
|
215
|
+
args:
|
|
216
|
+
@<from> Element to drag (required)
|
|
217
|
+
@<to> Element to drop onto (required)
|
|
218
|
+
|
|
219
|
+
flags:
|
|
220
|
+
--full Show complete snapshot without truncation
|
|
221
|
+
|
|
222
|
+
examples:
|
|
223
|
+
chrome-devtools-axi drag @3 @7`,
|
|
224
|
+
fillform: `usage: chrome-devtools-axi fillform @<uid>=<value>... [--full]
|
|
225
|
+
Fill multiple form fields at once.
|
|
226
|
+
|
|
227
|
+
args:
|
|
228
|
+
@<uid>=<value> One or more field entries (required)
|
|
229
|
+
|
|
230
|
+
flags:
|
|
231
|
+
--full Show complete snapshot without truncation
|
|
232
|
+
|
|
233
|
+
examples:
|
|
234
|
+
chrome-devtools-axi fillform @1="hello" @2="world"
|
|
235
|
+
chrome-devtools-axi fillform @3="user@email.com" @4="password123"`,
|
|
236
|
+
dialog: `usage: chrome-devtools-axi dialog <accept|dismiss> [text]
|
|
237
|
+
Handle a browser dialog (alert, confirm, prompt).
|
|
238
|
+
|
|
239
|
+
args:
|
|
240
|
+
<action> accept or dismiss (required)
|
|
241
|
+
[text] Optional text to enter into a prompt dialog
|
|
242
|
+
|
|
243
|
+
examples:
|
|
244
|
+
chrome-devtools-axi dialog accept
|
|
245
|
+
chrome-devtools-axi dialog dismiss
|
|
246
|
+
chrome-devtools-axi dialog accept "confirmed"`,
|
|
247
|
+
upload: `usage: chrome-devtools-axi upload @<uid> <path> [--full]
|
|
248
|
+
Upload a file through a file input element.
|
|
249
|
+
|
|
250
|
+
args:
|
|
251
|
+
@<uid> File input element ref from snapshot (required)
|
|
252
|
+
<path> Local file path to upload (required)
|
|
253
|
+
|
|
254
|
+
flags:
|
|
255
|
+
--full Show complete snapshot without truncation
|
|
256
|
+
|
|
257
|
+
examples:
|
|
258
|
+
chrome-devtools-axi upload @5 ./photo.jpg`,
|
|
259
|
+
// Emulation
|
|
260
|
+
emulate: `usage: chrome-devtools-axi emulate [flags]
|
|
261
|
+
Emulate device features on the selected page.
|
|
262
|
+
|
|
263
|
+
flags:
|
|
264
|
+
--viewport <spec> Viewport like "390x844x3,mobile,touch"
|
|
265
|
+
--color-scheme <value> dark | light | auto
|
|
266
|
+
--network <condition> Offline | Slow 3G | Fast 3G | Slow 4G | Fast 4G
|
|
267
|
+
--cpu <rate> CPU throttling rate 1-20
|
|
268
|
+
--geolocation <lat>x<lon> Geolocation like "37.7749x-122.4194"
|
|
269
|
+
--user-agent <string> Custom user agent string
|
|
270
|
+
|
|
271
|
+
examples:
|
|
272
|
+
chrome-devtools-axi emulate --viewport "390x844x3,mobile" --color-scheme dark
|
|
273
|
+
chrome-devtools-axi emulate --network "Slow 3G" --cpu 4`,
|
|
274
|
+
// DevTools debugging
|
|
275
|
+
console: `usage: chrome-devtools-axi console [--type <type>] [--limit <n>] [--page <n>]
|
|
276
|
+
List console messages for the current page.
|
|
277
|
+
|
|
278
|
+
flags:
|
|
279
|
+
--type <type> Filter by message type (error, warn, log, etc.)
|
|
280
|
+
--limit <n> Maximum messages to return
|
|
281
|
+
--page <n> Page number (0-based)
|
|
282
|
+
|
|
283
|
+
examples:
|
|
284
|
+
chrome-devtools-axi console
|
|
285
|
+
chrome-devtools-axi console --type error --limit 50`,
|
|
286
|
+
"console-get": `usage: chrome-devtools-axi console-get <id>
|
|
287
|
+
Get a specific console message by ID.
|
|
288
|
+
|
|
289
|
+
args:
|
|
290
|
+
<id> Message ID from the console command (required)
|
|
291
|
+
|
|
292
|
+
examples:
|
|
293
|
+
chrome-devtools-axi console-get 3`,
|
|
294
|
+
network: `usage: chrome-devtools-axi network [--type <type>] [--limit <n>] [--page <n>]
|
|
295
|
+
List network requests for the current page.
|
|
296
|
+
|
|
297
|
+
flags:
|
|
298
|
+
--type <type> Filter by resource type (fetch, xhr, document, etc.)
|
|
299
|
+
--limit <n> Maximum requests to return
|
|
300
|
+
--page <n> Page number (0-based)
|
|
301
|
+
|
|
302
|
+
examples:
|
|
303
|
+
chrome-devtools-axi network
|
|
304
|
+
chrome-devtools-axi network --type fetch --limit 50`,
|
|
305
|
+
"network-get": `usage: chrome-devtools-axi network-get [id] [--response-file <path>] [--request-file <path>]
|
|
306
|
+
Get a specific network request. If id is omitted, gets the selected request.
|
|
307
|
+
|
|
308
|
+
args:
|
|
309
|
+
[id] Request ID from the network command (optional)
|
|
310
|
+
|
|
311
|
+
flags:
|
|
312
|
+
--response-file <path> Save response body to file
|
|
313
|
+
--request-file <path> Save request body to file
|
|
314
|
+
|
|
315
|
+
examples:
|
|
316
|
+
chrome-devtools-axi network-get 42
|
|
317
|
+
chrome-devtools-axi network-get 42 --response-file ./response.json`,
|
|
318
|
+
// Performance
|
|
319
|
+
lighthouse: `usage: chrome-devtools-axi lighthouse [--device <device>] [--mode <mode>] [--output-dir <path>]
|
|
320
|
+
Run a Lighthouse audit for accessibility, SEO, and best practices.
|
|
321
|
+
|
|
322
|
+
flags:
|
|
323
|
+
--device <device> desktop (default) or mobile
|
|
324
|
+
--mode <mode> navigation (default) or snapshot
|
|
325
|
+
--output-dir <path> Directory for reports
|
|
326
|
+
|
|
327
|
+
examples:
|
|
328
|
+
chrome-devtools-axi lighthouse
|
|
329
|
+
chrome-devtools-axi lighthouse --device mobile --output-dir ./reports`,
|
|
330
|
+
"perf-start": `usage: chrome-devtools-axi perf-start [--no-reload] [--no-auto-stop] [--file <path>]
|
|
331
|
+
Start a performance trace recording.
|
|
332
|
+
|
|
333
|
+
flags:
|
|
334
|
+
--no-reload Don't reload the page when starting
|
|
335
|
+
--no-auto-stop Don't automatically stop the trace
|
|
336
|
+
--file <path> Save raw trace data to file
|
|
337
|
+
|
|
338
|
+
examples:
|
|
339
|
+
chrome-devtools-axi perf-start
|
|
340
|
+
chrome-devtools-axi perf-start --no-reload --file trace.json.gz`,
|
|
341
|
+
"perf-stop": `usage: chrome-devtools-axi perf-stop [--file <path>]
|
|
342
|
+
Stop the active performance trace recording.
|
|
343
|
+
|
|
344
|
+
flags:
|
|
345
|
+
--file <path> Save raw trace data to file
|
|
346
|
+
|
|
347
|
+
examples:
|
|
348
|
+
chrome-devtools-axi perf-stop
|
|
349
|
+
chrome-devtools-axi perf-stop --file trace.json.gz`,
|
|
350
|
+
"perf-insight": `usage: chrome-devtools-axi perf-insight <set-id> <insight-name>
|
|
351
|
+
Analyze a specific performance insight from a trace.
|
|
352
|
+
|
|
353
|
+
args:
|
|
354
|
+
<set-id> Insight set ID from trace results (required)
|
|
355
|
+
<insight-name> Insight name, e.g. "DocumentLatency" (required)
|
|
356
|
+
|
|
357
|
+
examples:
|
|
358
|
+
chrome-devtools-axi perf-insight set1 DocumentLatency
|
|
359
|
+
chrome-devtools-axi perf-insight set1 LCPBreakdown`,
|
|
360
|
+
heap: `usage: chrome-devtools-axi heap <path>
|
|
361
|
+
Capture a heap snapshot for memory leak debugging.
|
|
362
|
+
|
|
363
|
+
args:
|
|
364
|
+
<path> File path to save the .heapsnapshot file (required)
|
|
365
|
+
|
|
366
|
+
examples:
|
|
367
|
+
chrome-devtools-axi heap ./snapshot.heapsnapshot`,
|
|
368
|
+
};
|
|
369
|
+
export function getCommandHelp(command) {
|
|
370
|
+
return COMMAND_HELP[command] ?? null;
|
|
371
|
+
}
|
|
372
|
+
export function parseScreenshotArgs(args) {
|
|
373
|
+
let filePath = null;
|
|
374
|
+
let uid;
|
|
375
|
+
let fullPage = false;
|
|
376
|
+
let format;
|
|
377
|
+
for (let i = 0; i < args.length; i++) {
|
|
378
|
+
const a = args[i];
|
|
379
|
+
if (a === "--uid" && i + 1 < args.length) {
|
|
380
|
+
const raw = args[++i];
|
|
381
|
+
uid = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
382
|
+
}
|
|
383
|
+
else if (a === "--full-page") {
|
|
384
|
+
fullPage = true;
|
|
385
|
+
}
|
|
386
|
+
else if (a === "--format" && i + 1 < args.length) {
|
|
387
|
+
format = args[++i];
|
|
388
|
+
}
|
|
389
|
+
else if (!a.startsWith("--")) {
|
|
390
|
+
filePath = a;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return { filePath, uid, fullPage, format };
|
|
394
|
+
}
|
|
395
|
+
export function formatScreenshotOutput(filePath) {
|
|
396
|
+
return encode({ screenshot: filePath });
|
|
397
|
+
}
|
|
398
|
+
/** Parse MCP list_pages markdown into structured data. */
|
|
399
|
+
export function parsePagesList(text) {
|
|
400
|
+
const pages = [];
|
|
401
|
+
for (const line of text.split("\n")) {
|
|
402
|
+
const m = line.match(/^(\d+):\s+(\S+)(\s+\[selected\])?/);
|
|
403
|
+
if (m) {
|
|
404
|
+
pages.push({ id: parseInt(m[1], 10), url: m[2], selected: !!m[3] });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return pages;
|
|
408
|
+
}
|
|
409
|
+
/** Format raw MCP text result as AXI output: labeled block + truncation + suggestions. */
|
|
410
|
+
export function formatMcpResult(label, text, suggestions) {
|
|
411
|
+
const blocks = [];
|
|
412
|
+
const tr = truncateSnapshot(text, false, 2000);
|
|
413
|
+
blocks.push(`${label}:\n${tr.text.trimEnd()}`);
|
|
414
|
+
if (tr.truncated) {
|
|
415
|
+
blocks[0] += `\n ... (truncated, ${tr.totalLength} chars total)`;
|
|
416
|
+
}
|
|
417
|
+
if (suggestions.length > 0) {
|
|
418
|
+
blocks.push(renderHelp(suggestions));
|
|
419
|
+
}
|
|
420
|
+
return renderOutput(blocks);
|
|
421
|
+
}
|
|
422
|
+
export function parseFillFormArgs(args) {
|
|
423
|
+
const entries = [];
|
|
424
|
+
for (const arg of args) {
|
|
425
|
+
if (arg === "--full")
|
|
426
|
+
continue;
|
|
427
|
+
const match = arg.match(/^@([^=]+)=(.+)$/);
|
|
428
|
+
if (!match)
|
|
429
|
+
continue;
|
|
430
|
+
const uid = match[1];
|
|
431
|
+
let value = match[2];
|
|
432
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
433
|
+
value = value.slice(1, -1);
|
|
434
|
+
}
|
|
435
|
+
entries.push({ uid, value });
|
|
436
|
+
}
|
|
437
|
+
return { entries };
|
|
438
|
+
}
|
|
439
|
+
export function parseEmulateArgs(args) {
|
|
440
|
+
const result = {};
|
|
441
|
+
let i = 0;
|
|
442
|
+
while (i < args.length) {
|
|
443
|
+
switch (args[i]) {
|
|
444
|
+
case "--viewport":
|
|
445
|
+
result.viewport = args[++i];
|
|
446
|
+
break;
|
|
447
|
+
case "--color-scheme":
|
|
448
|
+
result.colorScheme = args[++i];
|
|
449
|
+
break;
|
|
450
|
+
case "--network":
|
|
451
|
+
result.networkConditions = args[++i];
|
|
452
|
+
break;
|
|
453
|
+
case "--cpu":
|
|
454
|
+
result.cpuThrottlingRate = parseInt(args[++i], 10);
|
|
455
|
+
break;
|
|
456
|
+
case "--geolocation":
|
|
457
|
+
result.geolocation = args[++i];
|
|
458
|
+
break;
|
|
459
|
+
case "--user-agent":
|
|
460
|
+
result.userAgent = args[++i];
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
i++;
|
|
464
|
+
}
|
|
465
|
+
return JSON.parse(JSON.stringify(result));
|
|
466
|
+
}
|
|
467
|
+
export function parseConsoleArgs(args) {
|
|
468
|
+
const result = {};
|
|
469
|
+
for (let i = 0; i < args.length; i++) {
|
|
470
|
+
if (args[i] === "--type" && i + 1 < args.length) {
|
|
471
|
+
result.types = [args[++i]];
|
|
472
|
+
}
|
|
473
|
+
else if (args[i] === "--limit" && i + 1 < args.length) {
|
|
474
|
+
result.pageSize = parseInt(args[++i], 10);
|
|
475
|
+
}
|
|
476
|
+
else if (args[i] === "--page" && i + 1 < args.length) {
|
|
477
|
+
result.pageIdx = parseInt(args[++i], 10);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
export function parseNetworkArgs(args) {
|
|
483
|
+
const result = {};
|
|
484
|
+
for (let i = 0; i < args.length; i++) {
|
|
485
|
+
if (args[i] === "--type" && i + 1 < args.length) {
|
|
486
|
+
result.resourceTypes = [args[++i]];
|
|
487
|
+
}
|
|
488
|
+
else if (args[i] === "--limit" && i + 1 < args.length) {
|
|
489
|
+
result.pageSize = parseInt(args[++i], 10);
|
|
490
|
+
}
|
|
491
|
+
else if (args[i] === "--page" && i + 1 < args.length) {
|
|
492
|
+
result.pageIdx = parseInt(args[++i], 10);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
export function parseNetworkGetArgs(args) {
|
|
498
|
+
const result = {};
|
|
499
|
+
for (let i = 0; i < args.length; i++) {
|
|
500
|
+
if (args[i] === "--response-file" && i + 1 < args.length) {
|
|
501
|
+
result.responseFilePath = args[++i];
|
|
502
|
+
}
|
|
503
|
+
else if (args[i] === "--request-file" && i + 1 < args.length) {
|
|
504
|
+
result.requestFilePath = args[++i];
|
|
505
|
+
}
|
|
506
|
+
else if (!args[i].startsWith("--")) {
|
|
507
|
+
result.reqid = parseInt(args[i], 10);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
export function parseLighthouseArgs(args) {
|
|
513
|
+
const result = {};
|
|
514
|
+
for (let i = 0; i < args.length; i++) {
|
|
515
|
+
switch (args[i]) {
|
|
516
|
+
case "--device":
|
|
517
|
+
result.device = args[++i];
|
|
518
|
+
break;
|
|
519
|
+
case "--mode":
|
|
520
|
+
result.mode = args[++i];
|
|
521
|
+
break;
|
|
522
|
+
case "--output-dir":
|
|
523
|
+
result.outputDirPath = args[++i];
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return result;
|
|
528
|
+
}
|
|
529
|
+
export function parsePerfStartArgs(args) {
|
|
530
|
+
const result = {};
|
|
531
|
+
for (let i = 0; i < args.length; i++) {
|
|
532
|
+
switch (args[i]) {
|
|
533
|
+
case "--no-reload":
|
|
534
|
+
result.reload = false;
|
|
535
|
+
break;
|
|
536
|
+
case "--no-auto-stop":
|
|
537
|
+
result.autoStop = false;
|
|
538
|
+
break;
|
|
539
|
+
case "--file":
|
|
540
|
+
result.filePath = args[++i];
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return result;
|
|
545
|
+
}
|
|
10
546
|
function renderHelp(lines) {
|
|
11
547
|
if (lines.length === 0)
|
|
12
548
|
return "";
|
|
@@ -40,7 +576,7 @@ function parseSnapshotFromResponse(response) {
|
|
|
40
576
|
return nextHeading === -1 ? trimmed.trimEnd() : trimmed.slice(0, nextHeading).trimEnd();
|
|
41
577
|
}
|
|
42
578
|
/** Format page metadata (TOON) + raw snapshot + suggestions. */
|
|
43
|
-
function formatPageOutput(snapshot, command, url) {
|
|
579
|
+
function formatPageOutput(snapshot, command, url, full = false) {
|
|
44
580
|
const title = extractTitle(snapshot);
|
|
45
581
|
const refs = countRefs(snapshot);
|
|
46
582
|
const blocks = [];
|
|
@@ -53,17 +589,31 @@ function formatPageOutput(snapshot, command, url) {
|
|
|
53
589
|
page.refs = refs;
|
|
54
590
|
blocks.push(encode({ page }));
|
|
55
591
|
// Raw snapshot (not TOON-encoded — already token-efficient tree format)
|
|
56
|
-
|
|
592
|
+
const tr = truncateSnapshot(snapshot, full);
|
|
593
|
+
let snapshotBlock = `snapshot:\n${tr.text.trimEnd()}`;
|
|
594
|
+
if (tr.truncated) {
|
|
595
|
+
snapshotBlock += `\n ... (truncated, ${tr.totalLength} chars total)`;
|
|
596
|
+
}
|
|
597
|
+
blocks.push(snapshotBlock);
|
|
57
598
|
// Contextual suggestions
|
|
58
599
|
const suggestions = getSuggestions({ command, url, snapshot });
|
|
600
|
+
if (tr.truncated) {
|
|
601
|
+
suggestions.push(`Run \`chrome-devtools-axi ${command}${url ? " " + url : ""} --full\` to see complete snapshot`);
|
|
602
|
+
}
|
|
59
603
|
if (suggestions.length > 0) {
|
|
60
604
|
blocks.push(renderHelp(suggestions));
|
|
61
605
|
}
|
|
62
606
|
return renderOutput(blocks);
|
|
63
607
|
}
|
|
64
|
-
/** Strip the
|
|
608
|
+
/** Strip everything before the actual accessibility tree (MCP may prepend status lines and headers). */
|
|
65
609
|
function stripSnapshotHeader(text) {
|
|
66
|
-
|
|
610
|
+
// Find the first line that looks like a tree node (uid= or RootWebArea)
|
|
611
|
+
const lines = text.split("\n");
|
|
612
|
+
const treeStart = lines.findIndex((l) => /\bRootWebArea\b|\buid=/.test(l));
|
|
613
|
+
if (treeStart > 0)
|
|
614
|
+
return lines.slice(treeStart).join("\n");
|
|
615
|
+
// Fallback: strip known headers
|
|
616
|
+
return text.replace(/^[\s\S]*?##\s+Latest page snapshot\s*\n/, "");
|
|
67
617
|
}
|
|
68
618
|
/** Strip leading @ from uid ref. */
|
|
69
619
|
function parseUid(arg) {
|
|
@@ -87,7 +637,7 @@ const SCROLL_FUNCTIONS = {
|
|
|
87
637
|
top: "window.scrollTo(0, 0)",
|
|
88
638
|
bottom: "window.scrollTo(0, document.body.scrollHeight)",
|
|
89
639
|
};
|
|
90
|
-
async function handleOpen(args) {
|
|
640
|
+
async function handleOpen(args, full) {
|
|
91
641
|
const url = args[0];
|
|
92
642
|
if (!url) {
|
|
93
643
|
throw new CdpError("Missing URL", "VALIDATION_ERROR", [
|
|
@@ -96,13 +646,30 @@ async function handleOpen(args) {
|
|
|
96
646
|
}
|
|
97
647
|
await callTool("navigate_page", { type: "url", url });
|
|
98
648
|
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
99
|
-
return formatPageOutput(snapshot, "open", url);
|
|
649
|
+
return formatPageOutput(snapshot, "open", url, full);
|
|
100
650
|
}
|
|
101
|
-
async function handleSnapshot() {
|
|
651
|
+
async function handleSnapshot(full) {
|
|
102
652
|
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
103
|
-
return formatPageOutput(snapshot, "snapshot");
|
|
653
|
+
return formatPageOutput(snapshot, "snapshot", undefined, full);
|
|
104
654
|
}
|
|
105
|
-
async function
|
|
655
|
+
async function handleScreenshot(args) {
|
|
656
|
+
const parsed = parseScreenshotArgs(args);
|
|
657
|
+
if (!parsed.filePath) {
|
|
658
|
+
throw new CdpError("Missing file path", "VALIDATION_ERROR", [
|
|
659
|
+
"Run `chrome-devtools-axi screenshot ./page.png` to save a screenshot",
|
|
660
|
+
]);
|
|
661
|
+
}
|
|
662
|
+
const toolArgs = { filePath: parsed.filePath };
|
|
663
|
+
if (parsed.uid)
|
|
664
|
+
toolArgs.uid = parsed.uid;
|
|
665
|
+
if (parsed.fullPage)
|
|
666
|
+
toolArgs.fullPage = true;
|
|
667
|
+
if (parsed.format)
|
|
668
|
+
toolArgs.format = parsed.format;
|
|
669
|
+
await callTool("take_screenshot", toolArgs);
|
|
670
|
+
return formatScreenshotOutput(parsed.filePath);
|
|
671
|
+
}
|
|
672
|
+
async function handleClick(args, full) {
|
|
106
673
|
const uid = args[0];
|
|
107
674
|
if (!uid) {
|
|
108
675
|
throw new CdpError("Missing element ref", "VALIDATION_ERROR", [
|
|
@@ -110,9 +677,9 @@ async function handleClick(args) {
|
|
|
110
677
|
]);
|
|
111
678
|
}
|
|
112
679
|
const snapshot = await callWithSnapshot("click", { uid: parseUid(uid) });
|
|
113
|
-
return formatPageOutput(snapshot, "click");
|
|
680
|
+
return formatPageOutput(snapshot, "click", undefined, full);
|
|
114
681
|
}
|
|
115
|
-
async function handleFill(args) {
|
|
682
|
+
async function handleFill(args, full) {
|
|
116
683
|
const uid = args[0];
|
|
117
684
|
const value = args.slice(1).join(" ");
|
|
118
685
|
if (!uid) {
|
|
@@ -126,9 +693,9 @@ async function handleFill(args) {
|
|
|
126
693
|
]);
|
|
127
694
|
}
|
|
128
695
|
const snapshot = await callWithSnapshot("fill", { uid: parseUid(uid), value });
|
|
129
|
-
return formatPageOutput(snapshot, "fill");
|
|
696
|
+
return formatPageOutput(snapshot, "fill", undefined, full);
|
|
130
697
|
}
|
|
131
|
-
async function handlePress(args) {
|
|
698
|
+
async function handlePress(args, full) {
|
|
132
699
|
const key = args[0];
|
|
133
700
|
if (!key) {
|
|
134
701
|
throw new CdpError("Missing key name", "VALIDATION_ERROR", [
|
|
@@ -136,9 +703,9 @@ async function handlePress(args) {
|
|
|
136
703
|
]);
|
|
137
704
|
}
|
|
138
705
|
const snapshot = await callWithSnapshot("press_key", { key });
|
|
139
|
-
return formatPageOutput(snapshot, "press");
|
|
706
|
+
return formatPageOutput(snapshot, "press", undefined, full);
|
|
140
707
|
}
|
|
141
|
-
async function handleType(args) {
|
|
708
|
+
async function handleType(args, full) {
|
|
142
709
|
const text = args.join(" ");
|
|
143
710
|
if (!text) {
|
|
144
711
|
throw new CdpError("Missing text", "VALIDATION_ERROR", [
|
|
@@ -147,9 +714,9 @@ async function handleType(args) {
|
|
|
147
714
|
}
|
|
148
715
|
await callTool("type_text", { text });
|
|
149
716
|
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
150
|
-
return formatPageOutput(snapshot, "type");
|
|
717
|
+
return formatPageOutput(snapshot, "type", undefined, full);
|
|
151
718
|
}
|
|
152
|
-
async function handleScroll(args) {
|
|
719
|
+
async function handleScroll(args, full) {
|
|
153
720
|
const dir = (args[0] ?? "down").toLowerCase();
|
|
154
721
|
const fn = SCROLL_FUNCTIONS[dir];
|
|
155
722
|
if (!fn) {
|
|
@@ -159,12 +726,12 @@ async function handleScroll(args) {
|
|
|
159
726
|
}
|
|
160
727
|
await callTool("evaluate_script", { function: fn });
|
|
161
728
|
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
162
|
-
return formatPageOutput(snapshot, "scroll");
|
|
729
|
+
return formatPageOutput(snapshot, "scroll", undefined, full);
|
|
163
730
|
}
|
|
164
|
-
async function handleBack() {
|
|
731
|
+
async function handleBack(full) {
|
|
165
732
|
await callTool("navigate_page", { type: "back" });
|
|
166
733
|
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
167
|
-
return formatPageOutput(snapshot, "back");
|
|
734
|
+
return formatPageOutput(snapshot, "back", undefined, full);
|
|
168
735
|
}
|
|
169
736
|
async function handleWait(args) {
|
|
170
737
|
const target = args[0];
|
|
@@ -190,6 +757,22 @@ async function handleWait(args) {
|
|
|
190
757
|
blocks.push(renderHelp(suggestions));
|
|
191
758
|
return renderOutput(blocks);
|
|
192
759
|
}
|
|
760
|
+
/** Wrap JS input in an arrow function for MCP evaluate_script. */
|
|
761
|
+
function wrapJsExpression(js) {
|
|
762
|
+
return `() => (${js.trim()})`;
|
|
763
|
+
}
|
|
764
|
+
/** Extract the actual value from MCP evaluate_script response. */
|
|
765
|
+
function parseEvalResult(output) {
|
|
766
|
+
// MCP wraps results in: "Script ran on page and returned:\n```json\n<value>\n```"
|
|
767
|
+
const jsonBlock = output.match(/```json\n([\s\S]*?)\n```/);
|
|
768
|
+
if (jsonBlock)
|
|
769
|
+
return jsonBlock[1].trim();
|
|
770
|
+
// Fallback: strip the preamble if present
|
|
771
|
+
const preamble = "Script ran on page and returned:";
|
|
772
|
+
if (output.includes(preamble))
|
|
773
|
+
return output.slice(output.indexOf(preamble) + preamble.length).trim();
|
|
774
|
+
return output.trim();
|
|
775
|
+
}
|
|
193
776
|
async function handleEval(args) {
|
|
194
777
|
const js = args.join(" ");
|
|
195
778
|
if (!js) {
|
|
@@ -197,9 +780,9 @@ async function handleEval(args) {
|
|
|
197
780
|
'Run `chrome-devtools-axi eval "document.title"` to evaluate JavaScript',
|
|
198
781
|
]);
|
|
199
782
|
}
|
|
200
|
-
const output = await callTool("evaluate_script", { function: js });
|
|
783
|
+
const output = await callTool("evaluate_script", { function: wrapJsExpression(js) });
|
|
201
784
|
const blocks = [];
|
|
202
|
-
blocks.push(encode({ result: output
|
|
785
|
+
blocks.push(encode({ result: parseEvalResult(output) }));
|
|
203
786
|
const suggestions = getSuggestions({ command: "eval" });
|
|
204
787
|
if (suggestions.length > 0)
|
|
205
788
|
blocks.push(renderHelp(suggestions));
|
|
@@ -209,44 +792,352 @@ async function handleStart() {
|
|
|
209
792
|
const port = await ensureBridge();
|
|
210
793
|
return encode({ status: "ready", port });
|
|
211
794
|
}
|
|
795
|
+
export function formatStopOutput(wasStopped) {
|
|
796
|
+
return encode({ status: wasStopped ? "stopped" : "stopped (no-op)" });
|
|
797
|
+
}
|
|
212
798
|
async function handleStop() {
|
|
213
|
-
await stopBridge();
|
|
214
|
-
return
|
|
799
|
+
const wasStopped = await stopBridge();
|
|
800
|
+
return formatStopOutput(wasStopped);
|
|
801
|
+
}
|
|
802
|
+
// --- Page management handlers ---
|
|
803
|
+
async function handlePages() {
|
|
804
|
+
const result = await callTool("list_pages");
|
|
805
|
+
const pages = parsePagesList(result);
|
|
806
|
+
if (pages.length === 0) {
|
|
807
|
+
return "pages: 0 pages open";
|
|
808
|
+
}
|
|
809
|
+
const blocks = [];
|
|
810
|
+
const header = `pages[${pages.length}]{id,url,selected}:`;
|
|
811
|
+
const rows = pages.map((p) => ` ${p.id},${p.url},${p.selected}`);
|
|
812
|
+
blocks.push(`${header}\n${rows.join("\n")}`);
|
|
813
|
+
blocks.push(renderHelp([
|
|
814
|
+
"Run `chrome-devtools-axi selectpage <id>` to switch tabs",
|
|
815
|
+
"Run `chrome-devtools-axi newpage <url>` to open a new tab",
|
|
816
|
+
]));
|
|
817
|
+
return renderOutput(blocks);
|
|
818
|
+
}
|
|
819
|
+
async function handleNewPage(args, full) {
|
|
820
|
+
const url = args.filter((a) => !a.startsWith("--"))[0];
|
|
821
|
+
if (!url) {
|
|
822
|
+
throw new CdpError("Missing URL", "VALIDATION_ERROR", [
|
|
823
|
+
"Run `chrome-devtools-axi newpage https://example.com` to open a new tab",
|
|
824
|
+
]);
|
|
825
|
+
}
|
|
826
|
+
const background = args.includes("--background");
|
|
827
|
+
const toolArgs = { url };
|
|
828
|
+
if (background)
|
|
829
|
+
toolArgs.background = true;
|
|
830
|
+
await callTool("new_page", toolArgs);
|
|
831
|
+
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
832
|
+
return formatPageOutput(snapshot, "newpage", url, full);
|
|
833
|
+
}
|
|
834
|
+
async function handleSelectPage(args, full) {
|
|
835
|
+
const id = args[0];
|
|
836
|
+
if (!id) {
|
|
837
|
+
throw new CdpError("Missing page ID", "VALIDATION_ERROR", [
|
|
838
|
+
"Run `chrome-devtools-axi selectpage <id>` — get ID from `pages` command",
|
|
839
|
+
]);
|
|
840
|
+
}
|
|
841
|
+
const pageId = parseInt(id, 10);
|
|
842
|
+
if (isNaN(pageId)) {
|
|
843
|
+
throw new CdpError(`Invalid page ID: ${id}`, "VALIDATION_ERROR", [
|
|
844
|
+
"Run `chrome-devtools-axi pages` to list available page IDs",
|
|
845
|
+
]);
|
|
846
|
+
}
|
|
847
|
+
await callTool("select_page", { pageId });
|
|
848
|
+
const snapshot = stripSnapshotHeader(await callTool("take_snapshot"));
|
|
849
|
+
return formatPageOutput(snapshot, "selectpage", undefined, full);
|
|
850
|
+
}
|
|
851
|
+
async function handleClosePage(args) {
|
|
852
|
+
const id = args[0];
|
|
853
|
+
if (!id) {
|
|
854
|
+
throw new CdpError("Missing page ID", "VALIDATION_ERROR", [
|
|
855
|
+
"Run `chrome-devtools-axi closepage <id>` — get ID from `pages` command",
|
|
856
|
+
]);
|
|
857
|
+
}
|
|
858
|
+
const pageId = parseInt(id, 10);
|
|
859
|
+
if (isNaN(pageId)) {
|
|
860
|
+
throw new CdpError(`Invalid page ID: ${id}`, "VALIDATION_ERROR", [
|
|
861
|
+
"Run `chrome-devtools-axi pages` to list available page IDs",
|
|
862
|
+
]);
|
|
863
|
+
}
|
|
864
|
+
// Check page count before closing — last page can't be closed
|
|
865
|
+
const beforeResult = await callTool("list_pages");
|
|
866
|
+
const pagesBefore = parsePagesList(beforeResult);
|
|
867
|
+
if (pagesBefore.length <= 1) {
|
|
868
|
+
const blocks = [encode({ status: "cannot close the last open page (no-op)" })];
|
|
869
|
+
blocks.push(renderHelp([
|
|
870
|
+
"Run `chrome-devtools-axi newpage <url>` to open another tab first",
|
|
871
|
+
"Run `chrome-devtools-axi stop` to shut down the browser entirely",
|
|
872
|
+
]));
|
|
873
|
+
return renderOutput(blocks);
|
|
874
|
+
}
|
|
875
|
+
await callTool("close_page", { pageId });
|
|
876
|
+
return encode({ status: "closed", pageId });
|
|
877
|
+
}
|
|
878
|
+
async function handleResize(args) {
|
|
879
|
+
const [widthStr, heightStr] = args;
|
|
880
|
+
if (!widthStr || !heightStr) {
|
|
881
|
+
throw new CdpError("Missing width and/or height", "VALIDATION_ERROR", [
|
|
882
|
+
"Run `chrome-devtools-axi resize 1280 720` to resize the viewport",
|
|
883
|
+
]);
|
|
884
|
+
}
|
|
885
|
+
const width = parseInt(widthStr, 10);
|
|
886
|
+
const height = parseInt(heightStr, 10);
|
|
887
|
+
if (isNaN(width) || isNaN(height)) {
|
|
888
|
+
throw new CdpError("Width and height must be numbers", "VALIDATION_ERROR", [
|
|
889
|
+
"Run `chrome-devtools-axi resize 1280 720` to resize the viewport",
|
|
890
|
+
]);
|
|
891
|
+
}
|
|
892
|
+
await callTool("resize_page", { width, height });
|
|
893
|
+
return encode({ resized: { width, height } });
|
|
894
|
+
}
|
|
895
|
+
// --- Interaction handlers ---
|
|
896
|
+
async function handleHover(args, full) {
|
|
897
|
+
const uid = args[0];
|
|
898
|
+
if (!uid) {
|
|
899
|
+
throw new CdpError("Missing element ref", "VALIDATION_ERROR", [
|
|
900
|
+
"Run `chrome-devtools-axi hover @<uid>` — get uid from snapshot",
|
|
901
|
+
]);
|
|
902
|
+
}
|
|
903
|
+
const snapshot = await callWithSnapshot("hover", { uid: parseUid(uid) });
|
|
904
|
+
return formatPageOutput(snapshot, "hover", undefined, full);
|
|
905
|
+
}
|
|
906
|
+
async function handleDrag(args, full) {
|
|
907
|
+
const from = args[0];
|
|
908
|
+
const to = args[1];
|
|
909
|
+
if (!from || !to) {
|
|
910
|
+
throw new CdpError("Missing element refs", "VALIDATION_ERROR", [
|
|
911
|
+
"Run `chrome-devtools-axi drag @<from> @<to>` — get uids from snapshot",
|
|
912
|
+
]);
|
|
913
|
+
}
|
|
914
|
+
const snapshot = await callWithSnapshot("drag", { from_uid: parseUid(from), to_uid: parseUid(to) });
|
|
915
|
+
return formatPageOutput(snapshot, "drag", undefined, full);
|
|
916
|
+
}
|
|
917
|
+
async function handleFillForm(args, full) {
|
|
918
|
+
const { entries } = parseFillFormArgs(args);
|
|
919
|
+
if (entries.length === 0) {
|
|
920
|
+
throw new CdpError("No valid field entries", "VALIDATION_ERROR", [
|
|
921
|
+
'Run `chrome-devtools-axi fillform @1="hello" @2="world"` to fill multiple fields',
|
|
922
|
+
]);
|
|
923
|
+
}
|
|
924
|
+
const snapshot = await callWithSnapshot("fill_form", { elements: entries });
|
|
925
|
+
return formatPageOutput(snapshot, "fillform", undefined, full);
|
|
926
|
+
}
|
|
927
|
+
async function handleDialog(args) {
|
|
928
|
+
const action = args[0];
|
|
929
|
+
if (!action || (action !== "accept" && action !== "dismiss")) {
|
|
930
|
+
throw new CdpError("Missing or invalid action", "VALIDATION_ERROR", [
|
|
931
|
+
"Run `chrome-devtools-axi dialog accept` or `chrome-devtools-axi dialog dismiss`",
|
|
932
|
+
]);
|
|
933
|
+
}
|
|
934
|
+
const params = { action };
|
|
935
|
+
const promptText = args.slice(1).join(" ");
|
|
936
|
+
if (promptText)
|
|
937
|
+
params.promptText = promptText;
|
|
938
|
+
await callTool("handle_dialog", params);
|
|
939
|
+
return encode({ dialog: action });
|
|
940
|
+
}
|
|
941
|
+
async function handleUpload(args, full) {
|
|
942
|
+
const uid = args[0];
|
|
943
|
+
const filePath = args[1];
|
|
944
|
+
if (!uid) {
|
|
945
|
+
throw new CdpError("Missing element ref", "VALIDATION_ERROR", [
|
|
946
|
+
"Run `chrome-devtools-axi upload @<uid> <path>` — get uid from snapshot",
|
|
947
|
+
]);
|
|
948
|
+
}
|
|
949
|
+
if (!filePath) {
|
|
950
|
+
throw new CdpError("Missing file path", "VALIDATION_ERROR", [
|
|
951
|
+
"Run `chrome-devtools-axi upload @<uid> /path/to/file` to upload a file",
|
|
952
|
+
]);
|
|
953
|
+
}
|
|
954
|
+
const snapshot = await callWithSnapshot("upload_file", { uid: parseUid(uid), filePath });
|
|
955
|
+
return formatPageOutput(snapshot, "upload", undefined, full);
|
|
956
|
+
}
|
|
957
|
+
// --- Emulation handler ---
|
|
958
|
+
async function handleEmulate(args) {
|
|
959
|
+
const parsed = parseEmulateArgs(args);
|
|
960
|
+
const mcpArgs = {};
|
|
961
|
+
if (parsed.viewport !== undefined)
|
|
962
|
+
mcpArgs.viewport = parsed.viewport;
|
|
963
|
+
if (parsed.colorScheme !== undefined)
|
|
964
|
+
mcpArgs.colorScheme = parsed.colorScheme;
|
|
965
|
+
if (parsed.networkConditions !== undefined)
|
|
966
|
+
mcpArgs.networkConditions = parsed.networkConditions;
|
|
967
|
+
if (parsed.cpuThrottlingRate !== undefined)
|
|
968
|
+
mcpArgs.cpuThrottlingRate = parsed.cpuThrottlingRate;
|
|
969
|
+
if (parsed.geolocation !== undefined)
|
|
970
|
+
mcpArgs.geolocation = parsed.geolocation;
|
|
971
|
+
if (parsed.userAgent !== undefined)
|
|
972
|
+
mcpArgs.userAgent = parsed.userAgent;
|
|
973
|
+
await callTool("emulate", mcpArgs);
|
|
974
|
+
return encode({ emulated: parsed });
|
|
975
|
+
}
|
|
976
|
+
// --- DevTools debugging handlers ---
|
|
977
|
+
async function handleConsole(args) {
|
|
978
|
+
const parsed = parseConsoleArgs(args);
|
|
979
|
+
const mcpArgs = {};
|
|
980
|
+
if (parsed.types)
|
|
981
|
+
mcpArgs.types = parsed.types;
|
|
982
|
+
if (parsed.pageSize !== undefined)
|
|
983
|
+
mcpArgs.pageSize = parsed.pageSize;
|
|
984
|
+
if (parsed.pageIdx !== undefined)
|
|
985
|
+
mcpArgs.pageIdx = parsed.pageIdx;
|
|
986
|
+
const result = await callTool("list_console_messages", mcpArgs);
|
|
987
|
+
return formatMcpResult("console", result, [
|
|
988
|
+
"Run `chrome-devtools-axi console-get <id>` to see a specific message",
|
|
989
|
+
"Run `chrome-devtools-axi console --type error` to filter by type",
|
|
990
|
+
]);
|
|
991
|
+
}
|
|
992
|
+
async function handleConsoleGet(args) {
|
|
993
|
+
const id = args[0];
|
|
994
|
+
if (!id) {
|
|
995
|
+
throw new CdpError("Missing console message id", "VALIDATION_ERROR", [
|
|
996
|
+
"Run `chrome-devtools-axi console-get <id>` — get id from `chrome-devtools-axi console`",
|
|
997
|
+
]);
|
|
998
|
+
}
|
|
999
|
+
const result = await callTool("get_console_message", { msgid: parseInt(id, 10) });
|
|
1000
|
+
return formatMcpResult("message", result, []);
|
|
1001
|
+
}
|
|
1002
|
+
async function handleNetwork(args) {
|
|
1003
|
+
const parsed = parseNetworkArgs(args);
|
|
1004
|
+
const mcpArgs = {};
|
|
1005
|
+
if (parsed.resourceTypes)
|
|
1006
|
+
mcpArgs.resourceTypes = parsed.resourceTypes;
|
|
1007
|
+
if (parsed.pageSize !== undefined)
|
|
1008
|
+
mcpArgs.pageSize = parsed.pageSize;
|
|
1009
|
+
if (parsed.pageIdx !== undefined)
|
|
1010
|
+
mcpArgs.pageIdx = parsed.pageIdx;
|
|
1011
|
+
const result = await callTool("list_network_requests", mcpArgs);
|
|
1012
|
+
return formatMcpResult("network", result, [
|
|
1013
|
+
"Run `chrome-devtools-axi network-get <id>` to see request details",
|
|
1014
|
+
"Run `chrome-devtools-axi network --type fetch` to filter by type",
|
|
1015
|
+
]);
|
|
1016
|
+
}
|
|
1017
|
+
async function handleNetworkGet(args) {
|
|
1018
|
+
const parsed = parseNetworkGetArgs(args);
|
|
1019
|
+
const mcpArgs = {};
|
|
1020
|
+
if (parsed.reqid !== undefined)
|
|
1021
|
+
mcpArgs.reqid = parsed.reqid;
|
|
1022
|
+
if (parsed.responseFilePath)
|
|
1023
|
+
mcpArgs.responseFilePath = parsed.responseFilePath;
|
|
1024
|
+
if (parsed.requestFilePath)
|
|
1025
|
+
mcpArgs.requestFilePath = parsed.requestFilePath;
|
|
1026
|
+
const result = await callTool("get_network_request", mcpArgs);
|
|
1027
|
+
return formatMcpResult("request", result, []);
|
|
1028
|
+
}
|
|
1029
|
+
// --- Performance handlers ---
|
|
1030
|
+
async function handleLighthouse(args) {
|
|
1031
|
+
const opts = parseLighthouseArgs(args);
|
|
1032
|
+
const result = await callTool("lighthouse_audit", opts);
|
|
1033
|
+
return formatMcpResult("lighthouse", result, []);
|
|
1034
|
+
}
|
|
1035
|
+
async function handlePerfStart(args) {
|
|
1036
|
+
const opts = parsePerfStartArgs(args);
|
|
1037
|
+
const toolArgs = {};
|
|
1038
|
+
if (opts.reload !== undefined)
|
|
1039
|
+
toolArgs.reload = opts.reload;
|
|
1040
|
+
if (opts.autoStop !== undefined)
|
|
1041
|
+
toolArgs.autoStop = opts.autoStop;
|
|
1042
|
+
if (opts.filePath !== undefined)
|
|
1043
|
+
toolArgs.filePath = opts.filePath;
|
|
1044
|
+
await callTool("performance_start_trace", toolArgs);
|
|
1045
|
+
return encode({ trace: "started", ...opts });
|
|
1046
|
+
}
|
|
1047
|
+
async function handlePerfStop(args) {
|
|
1048
|
+
const toolArgs = {};
|
|
1049
|
+
for (let i = 0; i < args.length; i++) {
|
|
1050
|
+
if (args[i] === "--file")
|
|
1051
|
+
toolArgs.filePath = args[++i];
|
|
1052
|
+
}
|
|
1053
|
+
const result = await callTool("performance_stop_trace", toolArgs);
|
|
1054
|
+
return formatMcpResult("trace", result, [
|
|
1055
|
+
"Run `chrome-devtools-axi perf-insight <set-id> <insight-name>` to analyze insights",
|
|
1056
|
+
]);
|
|
1057
|
+
}
|
|
1058
|
+
async function handlePerfInsight(args) {
|
|
1059
|
+
const [setId, insightName] = args;
|
|
1060
|
+
if (!setId || !insightName) {
|
|
1061
|
+
throw new CdpError("Missing required arguments", "VALIDATION_ERROR", [
|
|
1062
|
+
"Run `chrome-devtools-axi perf-insight <set-id> <insight-name>` to analyze an insight",
|
|
1063
|
+
]);
|
|
1064
|
+
}
|
|
1065
|
+
const result = await callTool("performance_analyze_insight", { insightSetId: setId, insightName });
|
|
1066
|
+
return formatMcpResult("insight", result, []);
|
|
1067
|
+
}
|
|
1068
|
+
async function handleHeap(args) {
|
|
1069
|
+
const filePath = args[0];
|
|
1070
|
+
if (!filePath) {
|
|
1071
|
+
throw new CdpError("Missing file path", "VALIDATION_ERROR", [
|
|
1072
|
+
"Run `chrome-devtools-axi heap ./snapshot.heapsnapshot` to take a heap snapshot",
|
|
1073
|
+
]);
|
|
1074
|
+
}
|
|
1075
|
+
await callTool("take_memory_snapshot", { filePath });
|
|
1076
|
+
return encode({ heap: filePath });
|
|
1077
|
+
}
|
|
1078
|
+
async function handleHome(full) {
|
|
1079
|
+
const result = await getSessionStatus();
|
|
1080
|
+
if (!result) {
|
|
1081
|
+
const blocks = [encode({ browser: "no active session" })];
|
|
1082
|
+
blocks.push(renderHelp(["Run `chrome-devtools-axi open <url>` to start browsing"]));
|
|
1083
|
+
return renderOutput(blocks);
|
|
1084
|
+
}
|
|
1085
|
+
const snapshot = stripSnapshotHeader(result);
|
|
1086
|
+
return formatPageOutput(snapshot, "snapshot", undefined, full);
|
|
215
1087
|
}
|
|
216
1088
|
export async function main(argv) {
|
|
1089
|
+
// Best-effort hook installation on every invocation
|
|
1090
|
+
try {
|
|
1091
|
+
installHooks();
|
|
1092
|
+
}
|
|
1093
|
+
catch { /* silent */ }
|
|
217
1094
|
const args = [...argv];
|
|
1095
|
+
const full = args.includes("--full");
|
|
1096
|
+
const filteredArgs = args.filter((a) => a !== "--full");
|
|
1097
|
+
const command = filteredArgs[0] ?? "";
|
|
1098
|
+
const commandArgs = filteredArgs.slice(1);
|
|
1099
|
+
// Per-subcommand help: `chrome-devtools-axi open --help`
|
|
1100
|
+
if (command && (commandArgs.includes("--help") || commandArgs.includes("-h"))) {
|
|
1101
|
+
const help = getCommandHelp(command);
|
|
1102
|
+
if (help) {
|
|
1103
|
+
process.stdout.write(help + "\n");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
// Global help: `chrome-devtools-axi --help`
|
|
218
1108
|
if (args.includes("--help") || args.includes("-h")) {
|
|
219
1109
|
process.stdout.write(HELP);
|
|
220
1110
|
return;
|
|
221
1111
|
}
|
|
222
|
-
const command = args[0] ?? "";
|
|
223
|
-
const commandArgs = args.slice(1);
|
|
224
1112
|
try {
|
|
225
1113
|
let output;
|
|
226
1114
|
switch (command) {
|
|
227
1115
|
case "open":
|
|
228
|
-
output = await handleOpen(commandArgs);
|
|
1116
|
+
output = await handleOpen(commandArgs, full);
|
|
229
1117
|
break;
|
|
230
1118
|
case "snapshot":
|
|
231
|
-
output = await handleSnapshot();
|
|
1119
|
+
output = await handleSnapshot(full);
|
|
1120
|
+
break;
|
|
1121
|
+
case "screenshot":
|
|
1122
|
+
output = await handleScreenshot(commandArgs);
|
|
232
1123
|
break;
|
|
233
1124
|
case "click":
|
|
234
|
-
output = await handleClick(commandArgs);
|
|
1125
|
+
output = await handleClick(commandArgs, full);
|
|
235
1126
|
break;
|
|
236
1127
|
case "fill":
|
|
237
|
-
output = await handleFill(commandArgs);
|
|
1128
|
+
output = await handleFill(commandArgs, full);
|
|
238
1129
|
break;
|
|
239
1130
|
case "type":
|
|
240
|
-
output = await handleType(commandArgs);
|
|
1131
|
+
output = await handleType(commandArgs, full);
|
|
241
1132
|
break;
|
|
242
1133
|
case "press":
|
|
243
|
-
output = await handlePress(commandArgs);
|
|
1134
|
+
output = await handlePress(commandArgs, full);
|
|
244
1135
|
break;
|
|
245
1136
|
case "scroll":
|
|
246
|
-
output = await handleScroll(commandArgs);
|
|
1137
|
+
output = await handleScroll(commandArgs, full);
|
|
247
1138
|
break;
|
|
248
1139
|
case "back":
|
|
249
|
-
output = await handleBack();
|
|
1140
|
+
output = await handleBack(full);
|
|
250
1141
|
break;
|
|
251
1142
|
case "wait":
|
|
252
1143
|
output = await handleWait(commandArgs);
|
|
@@ -254,6 +1145,66 @@ export async function main(argv) {
|
|
|
254
1145
|
case "eval":
|
|
255
1146
|
output = await handleEval(commandArgs);
|
|
256
1147
|
break;
|
|
1148
|
+
case "hover":
|
|
1149
|
+
output = await handleHover(commandArgs, full);
|
|
1150
|
+
break;
|
|
1151
|
+
case "drag":
|
|
1152
|
+
output = await handleDrag(commandArgs, full);
|
|
1153
|
+
break;
|
|
1154
|
+
case "fillform":
|
|
1155
|
+
output = await handleFillForm(commandArgs, full);
|
|
1156
|
+
break;
|
|
1157
|
+
case "dialog":
|
|
1158
|
+
output = await handleDialog(commandArgs);
|
|
1159
|
+
break;
|
|
1160
|
+
case "upload":
|
|
1161
|
+
output = await handleUpload(commandArgs, full);
|
|
1162
|
+
break;
|
|
1163
|
+
case "pages":
|
|
1164
|
+
output = await handlePages();
|
|
1165
|
+
break;
|
|
1166
|
+
case "newpage":
|
|
1167
|
+
output = await handleNewPage(commandArgs, full);
|
|
1168
|
+
break;
|
|
1169
|
+
case "selectpage":
|
|
1170
|
+
output = await handleSelectPage(commandArgs, full);
|
|
1171
|
+
break;
|
|
1172
|
+
case "closepage":
|
|
1173
|
+
output = await handleClosePage(commandArgs);
|
|
1174
|
+
break;
|
|
1175
|
+
case "resize":
|
|
1176
|
+
output = await handleResize(commandArgs);
|
|
1177
|
+
break;
|
|
1178
|
+
case "emulate":
|
|
1179
|
+
output = await handleEmulate(commandArgs);
|
|
1180
|
+
break;
|
|
1181
|
+
case "console":
|
|
1182
|
+
output = await handleConsole(commandArgs);
|
|
1183
|
+
break;
|
|
1184
|
+
case "console-get":
|
|
1185
|
+
output = await handleConsoleGet(commandArgs);
|
|
1186
|
+
break;
|
|
1187
|
+
case "network":
|
|
1188
|
+
output = await handleNetwork(commandArgs);
|
|
1189
|
+
break;
|
|
1190
|
+
case "network-get":
|
|
1191
|
+
output = await handleNetworkGet(commandArgs);
|
|
1192
|
+
break;
|
|
1193
|
+
case "lighthouse":
|
|
1194
|
+
output = await handleLighthouse(commandArgs);
|
|
1195
|
+
break;
|
|
1196
|
+
case "perf-start":
|
|
1197
|
+
output = await handlePerfStart(commandArgs);
|
|
1198
|
+
break;
|
|
1199
|
+
case "perf-stop":
|
|
1200
|
+
output = await handlePerfStop(commandArgs);
|
|
1201
|
+
break;
|
|
1202
|
+
case "perf-insight":
|
|
1203
|
+
output = await handlePerfInsight(commandArgs);
|
|
1204
|
+
break;
|
|
1205
|
+
case "heap":
|
|
1206
|
+
output = await handleHeap(commandArgs);
|
|
1207
|
+
break;
|
|
257
1208
|
case "start":
|
|
258
1209
|
output = await handleStart();
|
|
259
1210
|
break;
|
|
@@ -261,8 +1212,8 @@ export async function main(argv) {
|
|
|
261
1212
|
output = await handleStop();
|
|
262
1213
|
break;
|
|
263
1214
|
case "":
|
|
264
|
-
// No command =
|
|
265
|
-
output = await
|
|
1215
|
+
// No command = home view (status if running, hint if not)
|
|
1216
|
+
output = await handleHome(full);
|
|
266
1217
|
break;
|
|
267
1218
|
default:
|
|
268
1219
|
process.stdout.write(renderError(`Unknown command: ${command}`, "UNKNOWN", [
|