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 +65 -49
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
509
|
-
chrome-relay network --tab 123 --filter api.example.com
|
|
510
|
-
chrome-relay network --tab 123 --status failed
|
|
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
|
|
513
|
-
chrome-relay network --tab 123
|
|
514
|
-
chrome-relay network --tab 123 --
|
|
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>\`
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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("--
|
|
546
|
-
).action(async (opts) => {
|
|
547
|
-
const args = { ...baseArgs(opts), action: "har" };
|
|
548
|
-
if (opts.
|
|
549
|
-
|
|
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
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-relay",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
30
|
+
"@chrome-relay/protocol": "0.3.2"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsup",
|