chrome-relay 0.3.0 → 0.3.2

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 CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import { writeFileSync } from "fs";
6
6
 
7
7
  // src/index.ts
8
- var CHROME_RELAY_VERSION = "0.2.3";
8
+ var CHROME_RELAY_VERSION = true ? "0.3.2" : "0.0.0-dev";
9
9
 
10
10
  // src/install/install.ts
11
11
  import os from "os";
@@ -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
 
@@ -202,10 +202,16 @@ Notes:
202
202
  function baseArgs(opts) {
203
203
  const args = {};
204
204
  if (opts.tab !== void 0) args.tabId = opts.tab;
205
- if (opts.group) args.groupName = opts.group;
205
+ const effectiveGroup = opts.group ?? program.opts().group;
206
+ if (effectiveGroup) args.groupName = effectiveGroup;
206
207
  return args;
207
208
  }
208
- program.command("tabs").description("List open Chrome windows and tabs.").action(async () => {
209
+ program.command("tabs [verb]").description("List open Chrome windows and tabs. (verb 'list' is accepted as alias)").action(async (verb) => {
210
+ if (verb && verb !== "list") {
211
+ process.stderr.write(`unknown tabs verb: ${verb}. Use 'tabs' or 'tabs list'.
212
+ `);
213
+ process.exit(1);
214
+ }
209
215
  await run("get_windows_and_tabs", {});
210
216
  });
211
217
  tabOpt(
@@ -229,14 +235,13 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
229
235
  process.exit(1);
230
236
  }
231
237
  const args = { url };
232
- if (opts.tab !== void 0) args.tabId = opts.tab;
233
- if (opts.group) args.groupName = opts.group;
238
+ Object.assign(args, baseArgs(opts));
234
239
  if (opts.new) args.newTab = true;
235
240
  if (opts.inactive) args.active = false;
236
241
  await run("chrome_navigate", args);
237
242
  });
238
243
  tabOpt(
239
- program.command("screenshot").description("Capture a screenshot of any tab without activating it.").option("--full", "capture beyond the viewport (full page)").option("--bbox <rect>", "capture a region: 'x,y,width,height' (pixels)").option("--selector <css>", "capture the bounding box of a CSS selector").option("--padding <px>", "pixels of padding around --selector region", (v) => Number(v)).option("-o, --out <path>", "save image to path (base64 PNG decoded)").addHelpText(
244
+ program.command("screenshot").description("Capture a screenshot of any tab without activating it.").option("--full", "capture beyond the viewport (full page)").option("--bbox <rect>", "capture a region: 'x,y,width,height' (pixels)").option("--selector <css>", "capture the bounding box of a CSS selector").option("--padding <px>", "pixels of padding around --selector region", (v) => Number(v)).option("--max-edge <px>", "downscale so longer edge \u2264 this many pixels (no default; opt-in)", (v) => Number(v)).option("-o, --out <path>", "save image to path (base64 PNG decoded)").addHelpText(
240
245
  "after",
241
246
  `
242
247
 
@@ -254,12 +259,12 @@ full-tab screenshot when an agent only needs to see one component.
254
259
  )
255
260
  ).action(async (opts) => {
256
261
  const args = {};
257
- if (opts.tab !== void 0) args.tabId = opts.tab;
258
- if (opts.group) args.groupName = opts.group;
262
+ Object.assign(args, baseArgs(opts));
259
263
  if (opts.full) args.fullPage = true;
260
264
  if (opts.bbox) args.bbox = opts.bbox;
261
265
  if (opts.selector) args.selector = opts.selector;
262
266
  if (typeof opts.padding === "number") args.padding = opts.padding;
267
+ if (typeof opts.maxEdge === "number") args.maxEdge = opts.maxEdge;
263
268
  try {
264
269
  const result = await callTool("chrome_screenshot", args);
265
270
  if (opts.out && result && typeof result === "object") {
@@ -284,8 +289,7 @@ full-tab screenshot when an agent only needs to see one component.
284
289
  program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
285
290
  ).action(async (opts) => {
286
291
  const args = {};
287
- if (opts.tab !== void 0) args.tabId = opts.tab;
288
- if (opts.group) args.groupName = opts.group;
292
+ Object.assign(args, baseArgs(opts));
289
293
  if (opts.interactive) args.interactiveOnly = true;
290
294
  await run("chrome_read_page", args);
291
295
  });
@@ -293,16 +297,14 @@ full-tab screenshot when an agent only needs to see one component.
293
297
  program.command("click <selector>").description("Click an element by CSS selector.")
294
298
  ).action(async (selector, opts) => {
295
299
  const args = { selector };
296
- if (opts.tab !== void 0) args.tabId = opts.tab;
297
- if (opts.group) args.groupName = opts.group;
300
+ Object.assign(args, baseArgs(opts));
298
301
  await run("chrome_click_element", args);
299
302
  });
300
303
  tabOpt(
301
304
  program.command("fill <selector> <value>").description("Fill an input or textarea.")
302
305
  ).action(async (selector, value, opts) => {
303
306
  const args = { selector, value };
304
- if (opts.tab !== void 0) args.tabId = opts.tab;
305
- if (opts.group) args.groupName = opts.group;
307
+ Object.assign(args, baseArgs(opts));
306
308
  await run("chrome_fill_or_select", args);
307
309
  });
308
310
  tabOpt(
@@ -321,8 +323,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
321
323
  )
322
324
  ).action(async (keys, opts) => {
323
325
  const args = { keys };
324
- if (opts.tab !== void 0) args.tabId = opts.tab;
325
- if (opts.group) args.groupName = opts.group;
326
+ Object.assign(args, baseArgs(opts));
326
327
  await run("chrome_keyboard", args);
327
328
  });
328
329
  tabOpt(
@@ -343,8 +344,7 @@ When to pick which:
343
344
  )
344
345
  ).action(async (text, opts) => {
345
346
  const args = { text };
346
- if (opts.tab !== void 0) args.tabId = opts.tab;
347
- if (opts.group) args.groupName = opts.group;
347
+ Object.assign(args, baseArgs(opts));
348
348
  if (opts.selector) args.selector = opts.selector;
349
349
  await run("chrome_type", args);
350
350
  });
@@ -366,8 +366,7 @@ Notes:
366
366
  )
367
367
  ).action(async (code, opts) => {
368
368
  const args = { code };
369
- if (opts.tab !== void 0) args.tabId = opts.tab;
370
- if (opts.group) args.groupName = opts.group;
369
+ Object.assign(args, baseArgs(opts));
371
370
  if (typeof opts.timeoutMs === "number") args.timeoutMs = opts.timeoutMs;
372
371
  await run("chrome_evaluate", args);
373
372
  });
@@ -413,16 +412,14 @@ Notes:
413
412
  viewport.command("preset <name>").description("Apply a named device preset (iphone-14, pixel-7, desktop-1440, etc).")
414
413
  ).action(async (name, opts) => {
415
414
  const args = { action: "preset", name };
416
- if (opts.tab !== void 0) args.tabId = opts.tab;
417
- if (opts.group) args.groupName = opts.group;
415
+ Object.assign(args, baseArgs(opts));
418
416
  await run("chrome_viewport", args);
419
417
  });
420
418
  tabOpt(
421
419
  viewport.command("clear").description("Drop the viewport override and return the tab to its native size.")
422
420
  ).action(async (opts) => {
423
421
  const args = { action: "clear" };
424
- if (opts.tab !== void 0) args.tabId = opts.tab;
425
- if (opts.group) args.groupName = opts.group;
422
+ Object.assign(args, baseArgs(opts));
426
423
  await run("chrome_viewport", args);
427
424
  });
428
425
  viewport.command("list").description("List available presets.").action(async () => {
@@ -500,18 +497,32 @@ Notes:
500
497
  group.command("close <name>").description("Close the group's window (if alive) and remove the binding.").action(async (name) => {
501
498
  await run("chrome_group", { action: "close", name });
502
499
  });
503
- const network = program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.").addHelpText(
500
+ function netFilterOpts(cmd) {
501
+ return cmd.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));
502
+ }
503
+ function netFilterArgs(opts) {
504
+ const a = {};
505
+ if (opts.filter) a.filter = opts.filter;
506
+ if (opts.status) a.status = opts.status;
507
+ if (opts.method) a.method = opts.method;
508
+ if (typeof opts.limit === "number") a.limit = opts.limit;
509
+ return a;
510
+ }
511
+ const network = tabOpt(netFilterOpts(
512
+ program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.")
513
+ )).addHelpText(
504
514
  "after",
505
515
  `
506
516
 
507
517
  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
518
+ chrome-relay network --tab 123 # last 200 requests
519
+ chrome-relay network --tab 123 --filter api.example.com # url substring
520
+ chrome-relay network --tab 123 --status failed
511
521
  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
522
+ chrome-relay network body <requestId> --tab 123 # lazy body fetch
523
+ chrome-relay network har --tab 123 > capture.har # HAR (metadata only)
524
+ chrome-relay network har --tab 123 --with-bodies > full.har # HAR with bodies
525
+ chrome-relay network clear --tab 123
515
526
 
516
527
  Privacy:
517
528
  Capturing network traffic includes Authorization headers, cookies, and
@@ -521,32 +532,37 @@ Privacy:
521
532
 
522
533
  Notes:
523
534
  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.
535
+ the request finishes. Use \`--body <id>\` or \`har --with-bodies\` promptly.
536
+ WebSocket frames and SSE streams are out of scope.
526
537
  `
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
538
  ).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;
539
+ const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
540
+ await run("chrome_network", args);
541
+ });
542
+ tabOpt(netFilterOpts(
543
+ network.command("read").description("(alias) list captured network entries.")
544
+ )).action(async (opts) => {
545
+ const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
536
546
  await run("chrome_network", args);
537
547
  });
538
548
  tabOpt(
539
- network.command("body <requestId>").description("Fetch the response body for one request (lazy; may fail if GC'd).")
549
+ network.command("body <requestId>").description("Fetch the response body for one request (lazy; may fail if GC'd).").option("--head <bytes>", "truncate to first N bytes", (v) => Number(v)).option("--full", "return the full body \u2014 default truncates to 8 KB")
540
550
  ).action(async (requestId, opts) => {
541
551
  const args = { ...baseArgs(opts), action: "body", requestId };
552
+ if (opts.full) args.full = true;
553
+ if (typeof opts.head === "number") args.head = opts.head;
542
554
  await run("chrome_network", args);
543
555
  });
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;
556
+ tabOpt(netFilterOpts(
557
+ network.command("har").description("Emit HAR-compatible JSON for the captured entries.").option("--with-bodies", "fetch response bodies before emitting (best-effort; bodies GC'd by Chrome become null)")
558
+ )).action(async (opts) => {
559
+ const args = { ...baseArgs(opts), ...netFilterArgs(opts), action: "har" };
560
+ if (opts.withBodies) args.withBodies = true;
561
+ else {
562
+ process.stderr.write(
563
+ "[chrome-relay] HAR exported WITHOUT response bodies. Pass --with-bodies to include them (best-effort; bodies older than ~30s may be unavailable).\n"
564
+ );
565
+ }
550
566
  await run("chrome_network", args);
551
567
  });
552
568
  tabOpt(
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare const CHROME_RELAY_VERSION = "0.2.3";
1
+ declare const CHROME_RELAY_VERSION: string;
2
2
 
3
3
  export { CHROME_RELAY_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = "0.2.3";
2
+ var CHROME_RELAY_VERSION = true ? "0.3.2" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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.3.0"
30
+ "@chrome-relay/protocol": "0.3.2"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsup",