chrome-relay 0.2.5 → 0.3.1
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/dist/cli.js +172 -12
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -154,7 +154,7 @@ async function callTool(name, args) {
|
|
|
154
154
|
// src/program.ts
|
|
155
155
|
function buildProgram() {
|
|
156
156
|
const program = new Command();
|
|
157
|
-
program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through a local bridge.").version(CHROME_RELAY_VERSION).showHelpAfterError().addHelpText(
|
|
157
|
+
program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through a local bridge.").version(CHROME_RELAY_VERSION).showHelpAfterError().option("--group <name>", "target the active tab of a named group window (works at top level too)").enablePositionalOptions().addHelpText(
|
|
158
158
|
"after",
|
|
159
159
|
`
|
|
160
160
|
|
|
@@ -197,7 +197,14 @@ Notes:
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
function tabOpt(cmd) {
|
|
200
|
-
return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v));
|
|
200
|
+
return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v)).option("--group <name>", "target the active tab of a named group window (see `chrome-relay group`)");
|
|
201
|
+
}
|
|
202
|
+
function baseArgs(opts) {
|
|
203
|
+
const args = {};
|
|
204
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
205
|
+
const effectiveGroup = opts.group ?? program.opts().group;
|
|
206
|
+
if (effectiveGroup) args.groupName = effectiveGroup;
|
|
207
|
+
return args;
|
|
201
208
|
}
|
|
202
209
|
program.command("tabs").description("List open Chrome windows and tabs.").action(async () => {
|
|
203
210
|
await run("get_windows_and_tabs", {});
|
|
@@ -223,7 +230,7 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
|
|
|
223
230
|
process.exit(1);
|
|
224
231
|
}
|
|
225
232
|
const args = { url };
|
|
226
|
-
|
|
233
|
+
Object.assign(args, baseArgs(opts));
|
|
227
234
|
if (opts.new) args.newTab = true;
|
|
228
235
|
if (opts.inactive) args.active = false;
|
|
229
236
|
await run("chrome_navigate", args);
|
|
@@ -247,7 +254,7 @@ full-tab screenshot when an agent only needs to see one component.
|
|
|
247
254
|
)
|
|
248
255
|
).action(async (opts) => {
|
|
249
256
|
const args = {};
|
|
250
|
-
|
|
257
|
+
Object.assign(args, baseArgs(opts));
|
|
251
258
|
if (opts.full) args.fullPage = true;
|
|
252
259
|
if (opts.bbox) args.bbox = opts.bbox;
|
|
253
260
|
if (opts.selector) args.selector = opts.selector;
|
|
@@ -276,7 +283,7 @@ full-tab screenshot when an agent only needs to see one component.
|
|
|
276
283
|
program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
|
|
277
284
|
).action(async (opts) => {
|
|
278
285
|
const args = {};
|
|
279
|
-
|
|
286
|
+
Object.assign(args, baseArgs(opts));
|
|
280
287
|
if (opts.interactive) args.interactiveOnly = true;
|
|
281
288
|
await run("chrome_read_page", args);
|
|
282
289
|
});
|
|
@@ -284,14 +291,14 @@ full-tab screenshot when an agent only needs to see one component.
|
|
|
284
291
|
program.command("click <selector>").description("Click an element by CSS selector.")
|
|
285
292
|
).action(async (selector, opts) => {
|
|
286
293
|
const args = { selector };
|
|
287
|
-
|
|
294
|
+
Object.assign(args, baseArgs(opts));
|
|
288
295
|
await run("chrome_click_element", args);
|
|
289
296
|
});
|
|
290
297
|
tabOpt(
|
|
291
298
|
program.command("fill <selector> <value>").description("Fill an input or textarea.")
|
|
292
299
|
).action(async (selector, value, opts) => {
|
|
293
300
|
const args = { selector, value };
|
|
294
|
-
|
|
301
|
+
Object.assign(args, baseArgs(opts));
|
|
295
302
|
await run("chrome_fill_or_select", args);
|
|
296
303
|
});
|
|
297
304
|
tabOpt(
|
|
@@ -310,7 +317,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
|
|
|
310
317
|
)
|
|
311
318
|
).action(async (keys, opts) => {
|
|
312
319
|
const args = { keys };
|
|
313
|
-
|
|
320
|
+
Object.assign(args, baseArgs(opts));
|
|
314
321
|
await run("chrome_keyboard", args);
|
|
315
322
|
});
|
|
316
323
|
tabOpt(
|
|
@@ -331,7 +338,7 @@ When to pick which:
|
|
|
331
338
|
)
|
|
332
339
|
).action(async (text, opts) => {
|
|
333
340
|
const args = { text };
|
|
334
|
-
|
|
341
|
+
Object.assign(args, baseArgs(opts));
|
|
335
342
|
if (opts.selector) args.selector = opts.selector;
|
|
336
343
|
await run("chrome_type", args);
|
|
337
344
|
});
|
|
@@ -353,7 +360,7 @@ Notes:
|
|
|
353
360
|
)
|
|
354
361
|
).action(async (code, opts) => {
|
|
355
362
|
const args = { code };
|
|
356
|
-
|
|
363
|
+
Object.assign(args, baseArgs(opts));
|
|
357
364
|
if (typeof opts.timeoutMs === "number") args.timeoutMs = opts.timeoutMs;
|
|
358
365
|
await run("chrome_evaluate", args);
|
|
359
366
|
});
|
|
@@ -399,19 +406,172 @@ Notes:
|
|
|
399
406
|
viewport.command("preset <name>").description("Apply a named device preset (iphone-14, pixel-7, desktop-1440, etc).")
|
|
400
407
|
).action(async (name, opts) => {
|
|
401
408
|
const args = { action: "preset", name };
|
|
402
|
-
|
|
409
|
+
Object.assign(args, baseArgs(opts));
|
|
403
410
|
await run("chrome_viewport", args);
|
|
404
411
|
});
|
|
405
412
|
tabOpt(
|
|
406
413
|
viewport.command("clear").description("Drop the viewport override and return the tab to its native size.")
|
|
407
414
|
).action(async (opts) => {
|
|
408
415
|
const args = { action: "clear" };
|
|
409
|
-
|
|
416
|
+
Object.assign(args, baseArgs(opts));
|
|
410
417
|
await run("chrome_viewport", args);
|
|
411
418
|
});
|
|
412
419
|
viewport.command("list").description("List available presets.").action(async () => {
|
|
413
420
|
await run("chrome_viewport", { action: "list" });
|
|
414
421
|
});
|
|
422
|
+
program.command("self-reload").description("Restart the chrome-relay extension's service worker (picks up newly built code).").action(async () => {
|
|
423
|
+
await run("chrome_self_reload", {});
|
|
424
|
+
});
|
|
425
|
+
tabOpt(
|
|
426
|
+
program.command("ax").description("Extract the accessibility tree \u2014 ~30\xD7 smaller than `read` and more semantic.").option("-i, --interactive-only", "filter to actionable roles (button, link, textbox, ...)").option("--root <role>", "start from the first node matching this role (e.g. 'main')").option("--include-subframes", "walk subframes too (default: top frame only)").addHelpText(
|
|
427
|
+
"after",
|
|
428
|
+
`
|
|
429
|
+
|
|
430
|
+
Examples:
|
|
431
|
+
chrome-relay ax --tab 123
|
|
432
|
+
chrome-relay ax --tab 123 --interactive-only
|
|
433
|
+
chrome-relay ax --tab 123 --root main --interactive-only
|
|
434
|
+
|
|
435
|
+
Notes:
|
|
436
|
+
Each node carries an "id" \u2014 that's the backendDOMNodeId. Pass it to
|
|
437
|
+
\`chrome-relay click-ax --node <id>\` to click without a CSS selector.
|
|
438
|
+
`
|
|
439
|
+
)
|
|
440
|
+
).action(async (opts) => {
|
|
441
|
+
const args = baseArgs(opts);
|
|
442
|
+
if (opts.interactiveOnly) args.interactiveOnly = true;
|
|
443
|
+
if (opts.root) args.rootRole = opts.root;
|
|
444
|
+
if (opts.includeSubframes) args.includeSubframes = true;
|
|
445
|
+
await run("chrome_ax", args);
|
|
446
|
+
});
|
|
447
|
+
tabOpt(
|
|
448
|
+
program.command("click-ax").description("Click an element by its backendDOMNodeId from a previous `ax` call.").requiredOption("--node <id>", "backendDOMNodeId from `chrome-relay ax`", (v) => Number(v)).addHelpText(
|
|
449
|
+
"after",
|
|
450
|
+
`
|
|
451
|
+
|
|
452
|
+
Examples:
|
|
453
|
+
chrome-relay click-ax --tab 123 --node 456
|
|
454
|
+
|
|
455
|
+
Notes:
|
|
456
|
+
Throws explicitly if the node id is stale (page mutated since you called
|
|
457
|
+
\`ax\`). Re-run \`ax\` and pass the fresh id.
|
|
458
|
+
`
|
|
459
|
+
)
|
|
460
|
+
).action(async (opts) => {
|
|
461
|
+
const args = baseArgs(opts);
|
|
462
|
+
args.node = opts.node;
|
|
463
|
+
await run("chrome_click_ax", args);
|
|
464
|
+
});
|
|
465
|
+
const group = program.command("group").description("Manage named Chrome windows so multiple agents can drive separate windows.").addHelpText(
|
|
466
|
+
"after",
|
|
467
|
+
`
|
|
468
|
+
|
|
469
|
+
Examples:
|
|
470
|
+
chrome-relay group create bidsmith-h01 --url https://reddit.com
|
|
471
|
+
chrome-relay group list
|
|
472
|
+
chrome-relay --group bidsmith-h01 navigate https://news.ycombinator.com
|
|
473
|
+
chrome-relay --group bidsmith-h01 screenshot -o evidence.png
|
|
474
|
+
chrome-relay group close bidsmith-h01
|
|
475
|
+
|
|
476
|
+
Notes:
|
|
477
|
+
Hard lifecycle: if you manually close the group's window, the next
|
|
478
|
+
--group operation fails loudly until you run \`group close\` + \`group create\` again.
|
|
479
|
+
If you pass both --tab and --group on the same command, --tab wins.
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
group.command("create <name>").description("Open a new Chrome window and bind it to <name>.").option("--url <url>", "initial URL (default about:blank)").option("--label <label>", "human-readable description shown in popup/list").action(async (name, opts) => {
|
|
483
|
+
const args = { action: "create", name };
|
|
484
|
+
if (opts.url) args.url = opts.url;
|
|
485
|
+
if (opts.label) args.label = opts.label;
|
|
486
|
+
await run("chrome_group", args);
|
|
487
|
+
});
|
|
488
|
+
group.command("list").description("List all known groups + whether their window is still alive.").action(async () => {
|
|
489
|
+
await run("chrome_group", { action: "list" });
|
|
490
|
+
});
|
|
491
|
+
group.command("close <name>").description("Close the group's window (if alive) and remove the binding.").action(async (name) => {
|
|
492
|
+
await run("chrome_group", { action: "close", name });
|
|
493
|
+
});
|
|
494
|
+
const network = program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.").addHelpText(
|
|
495
|
+
"after",
|
|
496
|
+
`
|
|
497
|
+
|
|
498
|
+
Examples:
|
|
499
|
+
chrome-relay network --tab 123 # last 200 requests
|
|
500
|
+
chrome-relay network --tab 123 --filter api.example.com # url substring
|
|
501
|
+
chrome-relay network --tab 123 --status failed # only failures
|
|
502
|
+
chrome-relay network --tab 123 --method POST
|
|
503
|
+
chrome-relay network --tab 123 --body <requestId> # lazy body fetch
|
|
504
|
+
chrome-relay network --tab 123 har > capture.har # HAR export
|
|
505
|
+
chrome-relay network --tab 123 --clear
|
|
506
|
+
|
|
507
|
+
Privacy:
|
|
508
|
+
Capturing network traffic includes Authorization headers, cookies, and
|
|
509
|
+
request/response bodies. The capture stays in the extension's memory and
|
|
510
|
+
is wiped on tab close. Don't run this on a tab whose state you wouldn't
|
|
511
|
+
share with the agent invoking chrome-relay.
|
|
512
|
+
|
|
513
|
+
Notes:
|
|
514
|
+
Bodies are NOT eagerly buffered \u2014 Chrome GCs response bodies ~30s after
|
|
515
|
+
the request finishes. Use \`--body <id>\` promptly. WebSocket frames and
|
|
516
|
+
SSE streams are out of scope.
|
|
517
|
+
`
|
|
518
|
+
);
|
|
519
|
+
tabOpt(
|
|
520
|
+
network.command("read", { isDefault: true }).description("List captured network entries.").option("--filter <substr>", "url substring filter").option("--status <bucket>", "ok | redirect | client_error | server_error | failed").option("--method <verb>", "exact method, e.g. POST").option("--limit <n>", "cap response length", (v) => Number(v))
|
|
521
|
+
).action(async (opts) => {
|
|
522
|
+
const args = baseArgs(opts);
|
|
523
|
+
if (opts.filter) args.filter = opts.filter;
|
|
524
|
+
if (opts.status) args.status = opts.status;
|
|
525
|
+
if (opts.method) args.method = opts.method;
|
|
526
|
+
if (typeof opts.limit === "number") args.limit = opts.limit;
|
|
527
|
+
await run("chrome_network", args);
|
|
528
|
+
});
|
|
529
|
+
tabOpt(
|
|
530
|
+
network.command("body <requestId>").description("Fetch the response body for one request (lazy; may fail if GC'd).")
|
|
531
|
+
).action(async (requestId, opts) => {
|
|
532
|
+
const args = { ...baseArgs(opts), action: "body", requestId };
|
|
533
|
+
await run("chrome_network", args);
|
|
534
|
+
});
|
|
535
|
+
tabOpt(
|
|
536
|
+
network.command("har").description("Emit HAR-compatible JSON for the captured entries.").option("--filter <substr>", "url substring filter").option("--status <bucket>", "status bucket filter")
|
|
537
|
+
).action(async (opts) => {
|
|
538
|
+
const args = { ...baseArgs(opts), action: "har" };
|
|
539
|
+
if (opts.filter) args.filter = opts.filter;
|
|
540
|
+
if (opts.status) args.status = opts.status;
|
|
541
|
+
await run("chrome_network", args);
|
|
542
|
+
});
|
|
543
|
+
tabOpt(
|
|
544
|
+
network.command("clear").description("Wipe the network buffer for this tab.")
|
|
545
|
+
).action(async (opts) => {
|
|
546
|
+
const args = { ...baseArgs(opts), action: "clear" };
|
|
547
|
+
await run("chrome_network", args);
|
|
548
|
+
});
|
|
549
|
+
tabOpt(
|
|
550
|
+
program.command("console").description("Read console.log/warn/error + page exceptions (ring buffer, last 200).").option("--level <levels>", "comma-separated: log,info,warn,error,debug,exception").option("--since <id>", "only return entries with id > since (live-tail-ish)", (v) => Number(v)).option("--limit <n>", "cap response length", (v) => Number(v)).option("--clear", "wipe the buffer (no read)").addHelpText(
|
|
551
|
+
"after",
|
|
552
|
+
`
|
|
553
|
+
|
|
554
|
+
Examples:
|
|
555
|
+
chrome-relay console --tab 123
|
|
556
|
+
chrome-relay console --tab 123 --level error,exception
|
|
557
|
+
chrome-relay console --tab 123 --since 50 # entries newer than id 50 (tail-style polling)
|
|
558
|
+
chrome-relay console --tab 123 --clear
|
|
559
|
+
|
|
560
|
+
Notes:
|
|
561
|
+
Ring buffer holds the last 200 entries per tab (or 256 KB, whichever first).
|
|
562
|
+
Wipes on tab close. First call on a tab subscribes; subsequent calls are
|
|
563
|
+
instant in-memory reads.
|
|
564
|
+
`
|
|
565
|
+
)
|
|
566
|
+
).action(async (opts) => {
|
|
567
|
+
const args = {};
|
|
568
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
569
|
+
if (opts.clear) args.action = "clear";
|
|
570
|
+
if (opts.level) args.levels = opts.level;
|
|
571
|
+
if (typeof opts.since === "number") args.since = opts.since;
|
|
572
|
+
if (typeof opts.limit === "number") args.limit = opts.limit;
|
|
573
|
+
await run("chrome_console", args);
|
|
574
|
+
});
|
|
415
575
|
return program;
|
|
416
576
|
}
|
|
417
577
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-relay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"tsup": "^8.4.0",
|
|
29
29
|
"vitest": "^3.0.0",
|
|
30
|
-
"@chrome-relay/protocol": "0.
|
|
30
|
+
"@chrome-relay/protocol": "0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsup",
|