chrome-relay 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +170 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -197,7 +197,13 @@ 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
+ if (opts.group) args.groupName = opts.group;
206
+ return args;
201
207
  }
202
208
  program.command("tabs").description("List open Chrome windows and tabs.").action(async () => {
203
209
  await run("get_windows_and_tabs", {});
@@ -224,6 +230,7 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
224
230
  }
225
231
  const args = { url };
226
232
  if (opts.tab !== void 0) args.tabId = opts.tab;
233
+ if (opts.group) args.groupName = opts.group;
227
234
  if (opts.new) args.newTab = true;
228
235
  if (opts.inactive) args.active = false;
229
236
  await run("chrome_navigate", args);
@@ -248,6 +255,7 @@ full-tab screenshot when an agent only needs to see one component.
248
255
  ).action(async (opts) => {
249
256
  const args = {};
250
257
  if (opts.tab !== void 0) args.tabId = opts.tab;
258
+ if (opts.group) args.groupName = opts.group;
251
259
  if (opts.full) args.fullPage = true;
252
260
  if (opts.bbox) args.bbox = opts.bbox;
253
261
  if (opts.selector) args.selector = opts.selector;
@@ -277,6 +285,7 @@ full-tab screenshot when an agent only needs to see one component.
277
285
  ).action(async (opts) => {
278
286
  const args = {};
279
287
  if (opts.tab !== void 0) args.tabId = opts.tab;
288
+ if (opts.group) args.groupName = opts.group;
280
289
  if (opts.interactive) args.interactiveOnly = true;
281
290
  await run("chrome_read_page", args);
282
291
  });
@@ -285,6 +294,7 @@ full-tab screenshot when an agent only needs to see one component.
285
294
  ).action(async (selector, opts) => {
286
295
  const args = { selector };
287
296
  if (opts.tab !== void 0) args.tabId = opts.tab;
297
+ if (opts.group) args.groupName = opts.group;
288
298
  await run("chrome_click_element", args);
289
299
  });
290
300
  tabOpt(
@@ -292,6 +302,7 @@ full-tab screenshot when an agent only needs to see one component.
292
302
  ).action(async (selector, value, opts) => {
293
303
  const args = { selector, value };
294
304
  if (opts.tab !== void 0) args.tabId = opts.tab;
305
+ if (opts.group) args.groupName = opts.group;
295
306
  await run("chrome_fill_or_select", args);
296
307
  });
297
308
  tabOpt(
@@ -311,6 +322,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
311
322
  ).action(async (keys, opts) => {
312
323
  const args = { keys };
313
324
  if (opts.tab !== void 0) args.tabId = opts.tab;
325
+ if (opts.group) args.groupName = opts.group;
314
326
  await run("chrome_keyboard", args);
315
327
  });
316
328
  tabOpt(
@@ -332,6 +344,7 @@ When to pick which:
332
344
  ).action(async (text, opts) => {
333
345
  const args = { text };
334
346
  if (opts.tab !== void 0) args.tabId = opts.tab;
347
+ if (opts.group) args.groupName = opts.group;
335
348
  if (opts.selector) args.selector = opts.selector;
336
349
  await run("chrome_type", args);
337
350
  });
@@ -354,6 +367,7 @@ Notes:
354
367
  ).action(async (code, opts) => {
355
368
  const args = { code };
356
369
  if (opts.tab !== void 0) args.tabId = opts.tab;
370
+ if (opts.group) args.groupName = opts.group;
357
371
  if (typeof opts.timeoutMs === "number") args.timeoutMs = opts.timeoutMs;
358
372
  await run("chrome_evaluate", args);
359
373
  });
@@ -400,6 +414,7 @@ Notes:
400
414
  ).action(async (name, opts) => {
401
415
  const args = { action: "preset", name };
402
416
  if (opts.tab !== void 0) args.tabId = opts.tab;
417
+ if (opts.group) args.groupName = opts.group;
403
418
  await run("chrome_viewport", args);
404
419
  });
405
420
  tabOpt(
@@ -407,11 +422,165 @@ Notes:
407
422
  ).action(async (opts) => {
408
423
  const args = { action: "clear" };
409
424
  if (opts.tab !== void 0) args.tabId = opts.tab;
425
+ if (opts.group) args.groupName = opts.group;
410
426
  await run("chrome_viewport", args);
411
427
  });
412
428
  viewport.command("list").description("List available presets.").action(async () => {
413
429
  await run("chrome_viewport", { action: "list" });
414
430
  });
431
+ program.command("self-reload").description("Restart the chrome-relay extension's service worker (picks up newly built code).").action(async () => {
432
+ await run("chrome_self_reload", {});
433
+ });
434
+ tabOpt(
435
+ 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(
436
+ "after",
437
+ `
438
+
439
+ Examples:
440
+ chrome-relay ax --tab 123
441
+ chrome-relay ax --tab 123 --interactive-only
442
+ chrome-relay ax --tab 123 --root main --interactive-only
443
+
444
+ Notes:
445
+ Each node carries an "id" \u2014 that's the backendDOMNodeId. Pass it to
446
+ \`chrome-relay click-ax --node <id>\` to click without a CSS selector.
447
+ `
448
+ )
449
+ ).action(async (opts) => {
450
+ const args = baseArgs(opts);
451
+ if (opts.interactiveOnly) args.interactiveOnly = true;
452
+ if (opts.root) args.rootRole = opts.root;
453
+ if (opts.includeSubframes) args.includeSubframes = true;
454
+ await run("chrome_ax", args);
455
+ });
456
+ tabOpt(
457
+ 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(
458
+ "after",
459
+ `
460
+
461
+ Examples:
462
+ chrome-relay click-ax --tab 123 --node 456
463
+
464
+ Notes:
465
+ Throws explicitly if the node id is stale (page mutated since you called
466
+ \`ax\`). Re-run \`ax\` and pass the fresh id.
467
+ `
468
+ )
469
+ ).action(async (opts) => {
470
+ const args = baseArgs(opts);
471
+ args.node = opts.node;
472
+ await run("chrome_click_ax", args);
473
+ });
474
+ const group = program.command("group").description("Manage named Chrome windows so multiple agents can drive separate windows.").addHelpText(
475
+ "after",
476
+ `
477
+
478
+ Examples:
479
+ chrome-relay group create bidsmith-h01 --url https://reddit.com
480
+ chrome-relay group list
481
+ chrome-relay --group bidsmith-h01 navigate https://news.ycombinator.com
482
+ chrome-relay --group bidsmith-h01 screenshot -o evidence.png
483
+ chrome-relay group close bidsmith-h01
484
+
485
+ Notes:
486
+ Hard lifecycle: if you manually close the group's window, the next
487
+ --group operation fails loudly until you run \`group close\` + \`group create\` again.
488
+ If you pass both --tab and --group on the same command, --tab wins.
489
+ `
490
+ );
491
+ 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) => {
492
+ const args = { action: "create", name };
493
+ if (opts.url) args.url = opts.url;
494
+ if (opts.label) args.label = opts.label;
495
+ await run("chrome_group", args);
496
+ });
497
+ group.command("list").description("List all known groups + whether their window is still alive.").action(async () => {
498
+ await run("chrome_group", { action: "list" });
499
+ });
500
+ group.command("close <name>").description("Close the group's window (if alive) and remove the binding.").action(async (name) => {
501
+ await run("chrome_group", { action: "close", name });
502
+ });
503
+ const network = program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.").addHelpText(
504
+ "after",
505
+ `
506
+
507
+ Examples:
508
+ chrome-relay network --tab 123 # last 200 requests
509
+ chrome-relay network --tab 123 --filter api.example.com # url substring
510
+ chrome-relay network --tab 123 --status failed # only failures
511
+ chrome-relay network --tab 123 --method POST
512
+ chrome-relay network --tab 123 --body <requestId> # lazy body fetch
513
+ chrome-relay network --tab 123 har > capture.har # HAR export
514
+ chrome-relay network --tab 123 --clear
515
+
516
+ Privacy:
517
+ Capturing network traffic includes Authorization headers, cookies, and
518
+ request/response bodies. The capture stays in the extension's memory and
519
+ is wiped on tab close. Don't run this on a tab whose state you wouldn't
520
+ share with the agent invoking chrome-relay.
521
+
522
+ Notes:
523
+ Bodies are NOT eagerly buffered \u2014 Chrome GCs response bodies ~30s after
524
+ the request finishes. Use \`--body <id>\` promptly. WebSocket frames and
525
+ SSE streams are out of scope.
526
+ `
527
+ );
528
+ tabOpt(
529
+ 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))
530
+ ).action(async (opts) => {
531
+ const args = baseArgs(opts);
532
+ if (opts.filter) args.filter = opts.filter;
533
+ if (opts.status) args.status = opts.status;
534
+ if (opts.method) args.method = opts.method;
535
+ if (typeof opts.limit === "number") args.limit = opts.limit;
536
+ await run("chrome_network", args);
537
+ });
538
+ tabOpt(
539
+ network.command("body <requestId>").description("Fetch the response body for one request (lazy; may fail if GC'd).")
540
+ ).action(async (requestId, opts) => {
541
+ const args = { ...baseArgs(opts), action: "body", requestId };
542
+ await run("chrome_network", args);
543
+ });
544
+ tabOpt(
545
+ network.command("har").description("Emit HAR-compatible JSON for the captured entries.").option("--filter <substr>", "url substring filter").option("--status <bucket>", "status bucket filter")
546
+ ).action(async (opts) => {
547
+ const args = { ...baseArgs(opts), action: "har" };
548
+ if (opts.filter) args.filter = opts.filter;
549
+ if (opts.status) args.status = opts.status;
550
+ await run("chrome_network", args);
551
+ });
552
+ tabOpt(
553
+ network.command("clear").description("Wipe the network buffer for this tab.")
554
+ ).action(async (opts) => {
555
+ const args = { ...baseArgs(opts), action: "clear" };
556
+ await run("chrome_network", args);
557
+ });
558
+ tabOpt(
559
+ 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(
560
+ "after",
561
+ `
562
+
563
+ Examples:
564
+ chrome-relay console --tab 123
565
+ chrome-relay console --tab 123 --level error,exception
566
+ chrome-relay console --tab 123 --since 50 # entries newer than id 50 (tail-style polling)
567
+ chrome-relay console --tab 123 --clear
568
+
569
+ Notes:
570
+ Ring buffer holds the last 200 entries per tab (or 256 KB, whichever first).
571
+ Wipes on tab close. First call on a tab subscribes; subsequent calls are
572
+ instant in-memory reads.
573
+ `
574
+ )
575
+ ).action(async (opts) => {
576
+ const args = {};
577
+ if (opts.tab !== void 0) args.tabId = opts.tab;
578
+ if (opts.clear) args.action = "clear";
579
+ if (opts.level) args.levels = opts.level;
580
+ if (typeof opts.since === "number") args.since = opts.since;
581
+ if (typeof opts.limit === "number") args.limit = opts.limit;
582
+ await run("chrome_console", args);
583
+ });
415
584
  return program;
416
585
  }
417
586
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
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.2.4"
30
+ "@chrome-relay/protocol": "0.3.0"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsup",