chrome-relay 0.5.13 → 0.5.14

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
@@ -37,26 +37,60 @@ function requireString(obj, key, tool) {
37
37
  }
38
38
  return v;
39
39
  }
40
- function optString(obj, key) {
40
+ function rejectWrongType(obj, key, expected, tool) {
41
+ throw new RelayError({
42
+ code: "invalid_arguments",
43
+ message: `${tool ?? "<unknown tool>"}: \`${key}\` must be ${expected} (got ${typeof obj[key]}).`,
44
+ tool,
45
+ phase: "parse_arguments",
46
+ details: { field: key, expected, received: obj[key] },
47
+ retryable: false
48
+ });
49
+ }
50
+ function optString(obj, key, tool) {
41
51
  const v = obj[key];
42
- return typeof v === "string" && v ? v : void 0;
52
+ if (v === void 0 || v === null)
53
+ return void 0;
54
+ if (typeof v !== "string")
55
+ rejectWrongType(obj, key, "a string", tool);
56
+ return v || void 0;
43
57
  }
44
- function optNumber(obj, key) {
58
+ function optNumber(obj, key, tool) {
45
59
  const v = obj[key];
46
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
60
+ if (v === void 0 || v === null)
61
+ return void 0;
62
+ if (typeof v !== "number" || !Number.isFinite(v))
63
+ rejectWrongType(obj, key, "a finite number", tool);
64
+ return v;
47
65
  }
48
- function optBool(obj, key) {
66
+ function optBool(obj, key, tool) {
49
67
  const v = obj[key];
50
- return typeof v === "boolean" ? v : void 0;
68
+ if (v === void 0 || v === null)
69
+ return void 0;
70
+ if (typeof v !== "boolean")
71
+ rejectWrongType(obj, key, "a boolean", tool);
72
+ return v;
51
73
  }
52
- function parseTargetArgs(obj) {
74
+ function parseTargetArgs(obj, tool) {
53
75
  const out = {};
54
- if (typeof obj.tabId === "number")
76
+ if (obj.tabId !== void 0 && obj.tabId !== null) {
77
+ if (typeof obj.tabId !== "number" || !Number.isFinite(obj.tabId)) {
78
+ rejectWrongType(obj, "tabId", "a finite number", tool);
79
+ }
55
80
  out.tabId = obj.tabId;
56
- if (typeof obj.workspaceName === "string" && obj.workspaceName)
57
- out.workspaceName = obj.workspaceName;
58
- if (typeof obj.groupName === "string" && obj.groupName)
59
- out.groupName = obj.groupName;
81
+ }
82
+ if (obj.workspaceName !== void 0 && obj.workspaceName !== null) {
83
+ if (typeof obj.workspaceName !== "string")
84
+ rejectWrongType(obj, "workspaceName", "a string", tool);
85
+ if (obj.workspaceName)
86
+ out.workspaceName = obj.workspaceName;
87
+ }
88
+ if (obj.groupName !== void 0 && obj.groupName !== null) {
89
+ if (typeof obj.groupName !== "string")
90
+ rejectWrongType(obj, "groupName", "a string", tool);
91
+ if (obj.groupName)
92
+ out.groupName = obj.groupName;
93
+ }
60
94
  return out;
61
95
  }
62
96
  var init_shared = __esm({
@@ -69,10 +103,7 @@ var init_shared = __esm({
69
103
  // ../protocol/dist/args/navigate.js
70
104
  function parseChromeNavigateArgs(input) {
71
105
  const obj = asObject(input, TOOL_NAMES.NAVIGATE);
72
- const out = {
73
- url: requireString(obj, "url", TOOL_NAMES.NAVIGATE),
74
- ...parseTargetArgs(obj)
75
- };
106
+ const out = { url: requireString(obj, "url", TOOL_NAMES.NAVIGATE) };
76
107
  if (typeof obj.tabId === "string" && obj.tabId) {
77
108
  const n = Number(obj.tabId);
78
109
  if (!Number.isFinite(n)) {
@@ -87,17 +118,23 @@ function parseChromeNavigateArgs(input) {
87
118
  }
88
119
  out.tabId = n;
89
120
  } else {
90
- const n = optNumber(obj, "tabId");
121
+ const n = optNumber(obj, "tabId", TOOL_NAMES.NAVIGATE);
91
122
  if (n !== void 0)
92
123
  out.tabId = n;
93
124
  }
94
- const newTab = optBool(obj, "newTab");
125
+ const { tabId: _, ...rest } = obj;
126
+ const target = parseTargetArgs(rest, TOOL_NAMES.NAVIGATE);
127
+ if (target.workspaceName)
128
+ out.workspaceName = target.workspaceName;
129
+ if (target.groupName)
130
+ out.groupName = target.groupName;
131
+ const newTab = optBool(obj, "newTab", TOOL_NAMES.NAVIGATE);
95
132
  if (newTab !== void 0)
96
133
  out.newTab = newTab;
97
- const active = optBool(obj, "active");
134
+ const active = optBool(obj, "active", TOOL_NAMES.NAVIGATE);
98
135
  if (active !== void 0)
99
136
  out.active = active;
100
- const allowPartial = optBool(obj, "allowPartial");
137
+ const allowPartial = optBool(obj, "allowPartial", TOOL_NAMES.NAVIGATE);
101
138
  if (allowPartial !== void 0)
102
139
  out.allowPartial = allowPartial;
103
140
  void optString;
@@ -114,13 +151,13 @@ var init_navigate = __esm({
114
151
  // ../protocol/dist/args/hover.js
115
152
  function parseChromeHoverArgs(input) {
116
153
  const obj = asObject(input, TOOL_NAMES.HOVER);
117
- const target = parseTargetArgs(obj);
118
- const x = optNumber(obj, "x");
119
- const y = optNumber(obj, "y");
154
+ const target = parseTargetArgs(obj, TOOL_NAMES.HOVER);
155
+ const x = optNumber(obj, "x", TOOL_NAMES.HOVER);
156
+ const y = optNumber(obj, "y", TOOL_NAMES.HOVER);
120
157
  if (x !== void 0 && y !== void 0) {
121
158
  return { ...target, kind: "coords", x, y };
122
159
  }
123
- const selector = optString(obj, "selector");
160
+ const selector = optString(obj, "selector", TOOL_NAMES.HOVER);
124
161
  if (selector) {
125
162
  return { ...target, kind: "selector", selector };
126
163
  }
@@ -866,7 +903,7 @@ var init_dist = __esm({
866
903
  import { Command } from "commander";
867
904
 
868
905
  // src/index.ts
869
- var CHROME_RELAY_VERSION = true ? "0.5.13" : "0.0.0-dev";
906
+ var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
870
907
 
871
908
  // src/commands/shared.ts
872
909
  init_dist();
@@ -914,6 +951,11 @@ async function callTool(name, args) {
914
951
  }
915
952
 
916
953
  // src/commands/shared.ts
954
+ function makeWithBase(baseArgs) {
955
+ return function withBase(opts, extras) {
956
+ return { ...baseArgs(opts), ...extras ?? {} };
957
+ };
958
+ }
917
959
  function tabOpt(cmd) {
918
960
  return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v)).option("--workspace <name>", "target the active tab in a named workspace window (see `chrome-relay workspace`)").option("--group <name>", "target the active tab in a named tab-group (see `chrome-relay group`)");
919
961
  }
@@ -1100,6 +1142,15 @@ async function runDoctor() {
1100
1142
 
1101
1143
  // src/release-notes.ts
1102
1144
  var RELEASE_NOTES = {
1145
+ "0.5.14": [
1146
+ "Optional parser fields are now strict \u2014 passing a present-but-wrong-type value (e.g. `{newTab: 'yes'}` instead of `true`) throws RelayError(invalid_arguments) instead of silently being dropped. undefined/null still means 'caller omitted the field' and the handler uses its default.",
1147
+ "parseTargetArgs is strict too: `{tabId: '5'}` rejects (numeric tabId required). Navigate is the one exception \u2014 it coerces string tabId for back-compat since it's used as a 'reference window' rather than a strict target.",
1148
+ "CLI tab-group --tabs arg now forwarded as the raw comma-separated string. The protocol parser (parseChromeGroupArgs) does the strict per-element parsing so `--tabs 1,foo,3` errors instead of silently becoming [1,3].",
1149
+ "Deleted the dead duplicate parser module at apps/extension/src/browser/parsers.ts and its test file. The protocol parsers replace them; behavior identical.",
1150
+ "HAR creator.version now reads from chrome.runtime.getManifest() instead of the hardcoded stale '0.2.x'. Drift-proof.",
1151
+ "New CLI helper `withBase(opts, extras)` collapses the `const args = {}; Object.assign(args, baseArgs(opts)); args.foo = bar` pattern into one expression. All four command modules migrated \u2014 `Object.assign(args, baseArgs(opts))` is gone from the codebase.",
1152
+ "Tests: +2 strict-optional cases. Total 407 (was 433 \u2014 28 dropped were duplicates of protocol parsers)."
1153
+ ],
1103
1154
  "0.5.13": [
1104
1155
  "Protocol arg-parser coverage complete (code-quality-hardening Risk 1 \u2014 finished). Every one of the 22 tools now has an executable parser in @chrome-relay/protocol that returns a typed args object with `code:'invalid_arguments'` errors on malformed input.",
1105
1156
  "New parsers in this release: parseChrome{ReadPage,Click,Fill,Keyboard,Type,Evaluate,SwitchTab,CloseTabs,Ax,ClickAx,Screenshot,Viewport,Console,Workspace,Group,Screencast}Args, plus parseGetWindowsAndTabsArgs and parseChromeSelfReloadArgs.",
@@ -1299,7 +1350,7 @@ function registerInstallUpdate(program) {
1299
1350
 
1300
1351
  // src/commands/navigation.ts
1301
1352
  function registerNavigation(ctx) {
1302
- const { program, baseArgs, run } = ctx;
1353
+ const { program, withBase, run } = ctx;
1303
1354
  program.command("tabs [verb]").description("List open Chrome windows and tabs. (verb 'list' is accepted as alias)").action(async (verb) => {
1304
1355
  if (verb && verb !== "list") {
1305
1356
  process.stderr.write(`unknown tabs verb: ${verb}. Use 'tabs' or 'tabs list'.
@@ -1328,11 +1379,10 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1328
1379
  );
1329
1380
  process.exit(1);
1330
1381
  }
1331
- const args = { url };
1332
- Object.assign(args, baseArgs(opts));
1333
- if (opts.new) args.newTab = true;
1334
- if (opts.inactive) args.active = false;
1335
- await run("chrome_navigate", args);
1382
+ const extras = { url };
1383
+ if (opts.new) extras.newTab = true;
1384
+ if (opts.inactive) extras.active = false;
1385
+ await run("chrome_navigate", withBase(opts, extras));
1336
1386
  });
1337
1387
  program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
1338
1388
  await run("chrome_switch_tab", { tabId: Number(tabId) });
@@ -1348,20 +1398,16 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1348
1398
 
1349
1399
  // src/commands/input.ts
1350
1400
  function registerInput(ctx) {
1351
- const { program, baseArgs, run } = ctx;
1401
+ const { program, withBase, run } = ctx;
1352
1402
  tabOpt(
1353
1403
  program.command("click <selector>").description("Click an element by CSS selector.")
1354
1404
  ).action(async (selector, opts) => {
1355
- const args = { selector };
1356
- Object.assign(args, baseArgs(opts));
1357
- await run("chrome_click_element", args);
1405
+ await run("chrome_click_element", withBase(opts, { selector }));
1358
1406
  });
1359
1407
  tabOpt(
1360
1408
  program.command("fill <selector> <value>").description("Fill an input or textarea.")
1361
1409
  ).action(async (selector, value, opts) => {
1362
- const args = { selector, value };
1363
- Object.assign(args, baseArgs(opts));
1364
- await run("chrome_fill_or_select", args);
1410
+ await run("chrome_fill_or_select", withBase(opts, { selector, value }));
1365
1411
  });
1366
1412
  tabOpt(
1367
1413
  program.command("keys <keys>").description("Press a single key or chord via trusted CDP input (e.g. Enter, Cmd+K).").addHelpText(
@@ -1378,9 +1424,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
1378
1424
  `
1379
1425
  )
1380
1426
  ).action(async (keys, opts) => {
1381
- const args = { keys };
1382
- Object.assign(args, baseArgs(opts));
1383
- await run("chrome_keyboard", args);
1427
+ await run("chrome_keyboard", withBase(opts, { keys }));
1384
1428
  });
1385
1429
  tabOpt(
1386
1430
  program.command("type <text>").description("Insert text via trusted CDP input. Works in contenteditable / Draft.js / Lexical.").option("-s, --selector <selector>", "focus this element first").addHelpText(
@@ -1399,10 +1443,9 @@ When to pick which:
1399
1443
  `
1400
1444
  )
1401
1445
  ).action(async (text, opts) => {
1402
- const args = { text };
1403
- Object.assign(args, baseArgs(opts));
1404
- if (opts.selector) args.selector = opts.selector;
1405
- await run("chrome_type", args);
1446
+ const extras = { text };
1447
+ if (opts.selector) extras.selector = opts.selector;
1448
+ await run("chrome_type", withBase(opts, extras));
1406
1449
  });
1407
1450
  tabOpt(
1408
1451
  program.command("js <code>").description("Evaluate JavaScript in the page MAIN world. Use `return` for the value.").option("--timeout-ms <ms>", "execution timeout in milliseconds (default 15000)", (v) => Number(v)).addHelpText(
@@ -1421,10 +1464,9 @@ Notes:
1421
1464
  `
1422
1465
  )
1423
1466
  ).action(async (code, opts) => {
1424
- const args = { code };
1425
- Object.assign(args, baseArgs(opts));
1426
- if (typeof opts.timeoutMs === "number") args.timeoutMs = opts.timeoutMs;
1427
- await run("chrome_evaluate", args);
1467
+ const extras = { code };
1468
+ if (typeof opts.timeoutMs === "number") extras.timeoutMs = opts.timeoutMs;
1469
+ await run("chrome_evaluate", withBase(opts, extras));
1428
1470
  });
1429
1471
  tabOpt(
1430
1472
  program.command("hover [selector]").description("Move the pointer over an element or coordinates. Fires :hover styles.").option("--x <px>", "explicit x coordinate (CSS pixels)", (v) => Number(v)).option("--y <px>", "explicit y coordinate (CSS pixels)", (v) => Number(v)).addHelpText(
@@ -1440,21 +1482,20 @@ tooltip appearance, etc.) that a bare click would skip past too quickly.
1440
1482
  `
1441
1483
  )
1442
1484
  ).action(async (selector, opts) => {
1443
- const args = {};
1444
- Object.assign(args, baseArgs(opts));
1445
- if (selector) args.selector = selector;
1485
+ const extras = {};
1486
+ if (selector) extras.selector = selector;
1446
1487
  if (typeof opts.x === "number" && typeof opts.y === "number") {
1447
- args.x = opts.x;
1448
- args.y = opts.y;
1488
+ extras.x = opts.x;
1489
+ extras.y = opts.y;
1449
1490
  }
1450
- await run("chrome_hover", args);
1491
+ await run("chrome_hover", withBase(opts, extras));
1451
1492
  });
1452
1493
  }
1453
1494
 
1454
1495
  // src/commands/capture.ts
1455
1496
  import { writeFileSync } from "fs";
1456
1497
  function registerCapture(ctx) {
1457
- const { program, baseArgs, run } = ctx;
1498
+ const { program, withBase, run } = ctx;
1458
1499
  tabOpt(
1459
1500
  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(
1460
1501
  "after",
@@ -1473,13 +1514,13 @@ full-tab screenshot when an agent only needs to see one component.
1473
1514
  `
1474
1515
  )
1475
1516
  ).action(async (opts) => {
1476
- const args = {};
1477
- Object.assign(args, baseArgs(opts));
1478
- if (opts.full) args.fullPage = true;
1479
- if (opts.bbox) args.bbox = opts.bbox;
1480
- if (opts.selector) args.selector = opts.selector;
1481
- if (typeof opts.padding === "number") args.padding = opts.padding;
1482
- if (typeof opts.maxEdge === "number") args.maxEdge = opts.maxEdge;
1517
+ const extras = {};
1518
+ if (opts.full) extras.fullPage = true;
1519
+ if (opts.bbox) extras.bbox = opts.bbox;
1520
+ if (opts.selector) extras.selector = opts.selector;
1521
+ if (typeof opts.padding === "number") extras.padding = opts.padding;
1522
+ if (typeof opts.maxEdge === "number") extras.maxEdge = opts.maxEdge;
1523
+ const args = withBase(opts, extras);
1483
1524
  try {
1484
1525
  const result = await callTool("chrome_screenshot", args);
1485
1526
  if (opts.out && result && typeof result === "object") {
@@ -1503,10 +1544,9 @@ full-tab screenshot when an agent only needs to see one component.
1503
1544
  tabOpt(
1504
1545
  program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
1505
1546
  ).action(async (opts) => {
1506
- const args = {};
1507
- Object.assign(args, baseArgs(opts));
1508
- if (opts.interactive) args.interactiveOnly = true;
1509
- await run("chrome_read_page", args);
1547
+ const extras = {};
1548
+ if (opts.interactive) extras.interactiveOnly = true;
1549
+ await run("chrome_read_page", withBase(opts, extras));
1510
1550
  });
1511
1551
  tabOpt(
1512
1552
  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(
@@ -1524,11 +1564,11 @@ Notes:
1524
1564
  `
1525
1565
  )
1526
1566
  ).action(async (opts) => {
1527
- const args = baseArgs(opts);
1528
- if (opts.interactiveOnly) args.interactiveOnly = true;
1529
- if (opts.root) args.rootRole = opts.root;
1530
- if (opts.includeSubframes) args.includeSubframes = true;
1531
- await run("chrome_ax", args);
1567
+ const extras = {};
1568
+ if (opts.interactiveOnly) extras.interactiveOnly = true;
1569
+ if (opts.root) extras.rootRole = opts.root;
1570
+ if (opts.includeSubframes) extras.includeSubframes = true;
1571
+ await run("chrome_ax", withBase(opts, extras));
1532
1572
  });
1533
1573
  tabOpt(
1534
1574
  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(
@@ -1544,9 +1584,7 @@ Notes:
1544
1584
  `
1545
1585
  )
1546
1586
  ).action(async (opts) => {
1547
- const args = baseArgs(opts);
1548
- args.node = opts.node;
1549
- await run("chrome_click_ax", args);
1587
+ await run("chrome_click_ax", withBase(opts, { node: opts.node }));
1550
1588
  });
1551
1589
  const screencast = program.command("screencast").description("Record a tab via CDP (paint-driven). Requires an active tab.").addHelpText(
1552
1590
  "after",
@@ -1571,20 +1609,18 @@ Notes:
1571
1609
  tabOpt(
1572
1610
  screencast.command("start").description("Begin screencast capture on a tab.").option("--format <fmt>", "jpeg | png (default jpeg)").option("--quality <n>", "jpeg quality 0-100 (default 80)", (v) => Number(v)).option("--max-width <px>", "downscale; aspect preserved", (v) => Number(v)).option("--max-height <px>", "downscale; aspect preserved", (v) => Number(v)).option("--every-nth <n>", "throttle: keep 1 in N frames (default 1)", (v) => Number(v))
1573
1611
  ).action(async (opts) => {
1574
- const args = { action: "start" };
1575
- Object.assign(args, baseArgs(opts));
1576
- if (opts.format) args.format = opts.format;
1577
- if (typeof opts.quality === "number") args.quality = opts.quality;
1578
- if (typeof opts.maxWidth === "number") args.maxWidth = opts.maxWidth;
1579
- if (typeof opts.maxHeight === "number") args.maxHeight = opts.maxHeight;
1580
- if (typeof opts.everyNth === "number") args.everyNthFrame = opts.everyNth;
1581
- await run("chrome_screencast", args);
1612
+ const extras = { action: "start" };
1613
+ if (opts.format) extras.format = opts.format;
1614
+ if (typeof opts.quality === "number") extras.quality = opts.quality;
1615
+ if (typeof opts.maxWidth === "number") extras.maxWidth = opts.maxWidth;
1616
+ if (typeof opts.maxHeight === "number") extras.maxHeight = opts.maxHeight;
1617
+ if (typeof opts.everyNth === "number") extras.everyNthFrame = opts.everyNth;
1618
+ await run("chrome_screencast", withBase(opts, extras));
1582
1619
  });
1583
1620
  tabOpt(
1584
1621
  screencast.command("stop").description("Stop the screencast and emit frames (or write to disk).").option("-o, --out <dir>", "write frames as JPEGs into this directory (created if missing)").option("--gif", "after writing frames, ffmpeg them into <dir>.gif (requires ffmpeg on PATH)").option("--mp4", "after writing frames, ffmpeg them into <dir>.mp4 (requires ffmpeg on PATH)").option("--fps <n>", "assumed framerate when invoking ffmpeg (default 15)", (v) => Number(v)).option("--no-dedupe", "keep raw frames; default collapses consecutive identical frames via SHA-256").option("--allow-missing-ffmpeg", "with --gif/--mp4: skip ffmpeg step (and emit a warning) if ffmpeg isn't on PATH, instead of failing with external_dependency_missing")
1585
1622
  ).action(async (opts) => {
1586
- const args = { action: "stop" };
1587
- Object.assign(args, baseArgs(opts));
1623
+ const args = withBase(opts, { action: "stop" });
1588
1624
  try {
1589
1625
  const result = await callTool("chrome_screencast", args);
1590
1626
  if (!opts.out) {
@@ -1719,7 +1755,7 @@ function netFilterArgs(opts) {
1719
1755
  return a;
1720
1756
  }
1721
1757
  function registerSessions(ctx) {
1722
- const { program, baseArgs, run } = ctx;
1758
+ const { program, withBase, run } = ctx;
1723
1759
  const viewport = program.command("viewport").description("Emulate device viewport, DPR, mobile flag, touch, and user agent.").addHelpText(
1724
1760
  "after",
1725
1761
  `
@@ -1740,27 +1776,22 @@ Notes:
1740
1776
  tabOpt(
1741
1777
  viewport.command("set").description("Apply explicit viewport dimensions.").requiredOption("--width <px>", "viewport width in CSS pixels", (v) => Number(v)).requiredOption("--height <px>", "viewport height in CSS pixels", (v) => Number(v)).option("--dpr <ratio>", "device pixel ratio (1, 2, 3...)", (v) => Number(v)).option("--mobile", "set the mobile flag (affects meta viewport interpretation)").option("--touch", "enable touch event emulation").option("--user-agent <ua>", "override the User-Agent header")
1742
1778
  ).action(async (opts) => {
1743
- const args = { action: "set", width: opts.width, height: opts.height };
1744
- Object.assign(args, baseArgs(opts));
1745
- if (opts.dpr !== void 0) args.dpr = opts.dpr;
1746
- if (opts.mobile) args.mobile = true;
1747
- if (opts.touch) args.hasTouch = true;
1748
- if (opts.userAgent) args.userAgent = opts.userAgent;
1749
- await run("chrome_viewport", args);
1779
+ const extras = { action: "set", width: opts.width, height: opts.height };
1780
+ if (opts.dpr !== void 0) extras.dpr = opts.dpr;
1781
+ if (opts.mobile) extras.mobile = true;
1782
+ if (opts.touch) extras.hasTouch = true;
1783
+ if (opts.userAgent) extras.userAgent = opts.userAgent;
1784
+ await run("chrome_viewport", withBase(opts, extras));
1750
1785
  });
1751
1786
  tabOpt(
1752
1787
  viewport.command("preset <name>").description("Apply a named device preset (iphone-14, pixel-7, desktop-1440, etc).")
1753
1788
  ).action(async (name, opts) => {
1754
- const args = { action: "preset", name };
1755
- Object.assign(args, baseArgs(opts));
1756
- await run("chrome_viewport", args);
1789
+ await run("chrome_viewport", withBase(opts, { action: "preset", name }));
1757
1790
  });
1758
1791
  tabOpt(
1759
1792
  viewport.command("clear").description("Drop the viewport override and return the tab to its native size.")
1760
1793
  ).action(async (opts) => {
1761
- const args = { action: "clear" };
1762
- Object.assign(args, baseArgs(opts));
1763
- await run("chrome_viewport", args);
1794
+ await run("chrome_viewport", withBase(opts, { action: "clear" }));
1764
1795
  });
1765
1796
  viewport.command("list").description("List available presets.").action(async () => {
1766
1797
  await run("chrome_viewport", { action: "list" });
@@ -1821,8 +1852,7 @@ Notes:
1821
1852
  `
1822
1853
  );
1823
1854
  group.command("create <name>").description("Group existing tabs into a new tab-group bound to <name>.").requiredOption("--tabs <ids>", "comma-separated tab IDs to group, e.g. 123,456,789").option("--color <color>", "grey | blue | red | yellow | green | pink | purple | cyan | orange").option("--collapsed", "create the group in its collapsed state").action(async (name, opts) => {
1824
- const args = { action: "create", name };
1825
- args.tabIds = String(opts.tabs).split(",").map((s) => Number(s.trim())).filter(Number.isFinite);
1855
+ const args = { action: "create", name, tabIds: String(opts.tabs) };
1826
1856
  if (opts.color) args.color = opts.color;
1827
1857
  if (opts.collapsed) args.collapsed = true;
1828
1858
  await run("chrome_group", args);
@@ -1834,12 +1864,10 @@ Notes:
1834
1864
  await run("chrome_group", { action: "close", name });
1835
1865
  });
1836
1866
  group.command("add <name>").description("Add existing tabs to an existing tab-group.").requiredOption("--tabs <ids>", "comma-separated tab IDs to add").action(async (name, opts) => {
1837
- const tabIds = String(opts.tabs).split(",").map((s) => Number(s.trim())).filter(Number.isFinite);
1838
- await run("chrome_group", { action: "add", name, tabIds });
1867
+ await run("chrome_group", { action: "add", name, tabIds: String(opts.tabs) });
1839
1868
  });
1840
1869
  group.command("remove").description("Ungroup specific tabs (they remain open, just outside any tab-group).").requiredOption("--tabs <ids>", "comma-separated tab IDs to ungroup").action(async (opts) => {
1841
- const tabIds = String(opts.tabs).split(",").map((s) => Number(s.trim())).filter(Number.isFinite);
1842
- await run("chrome_group", { action: "remove", tabIds });
1870
+ await run("chrome_group", { action: "remove", tabIds: String(opts.tabs) });
1843
1871
  });
1844
1872
  const network = tabOpt(netFilterOpts(
1845
1873
  program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.")
@@ -1869,41 +1897,38 @@ Notes:
1869
1897
  WebSocket frames and SSE streams are out of scope.
1870
1898
  `
1871
1899
  ).action(async (opts) => {
1872
- const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
1873
- await run("chrome_network", args);
1900
+ await run("chrome_network", withBase(opts, netFilterArgs(opts)));
1874
1901
  });
1875
1902
  tabOpt(netFilterOpts(
1876
1903
  network.command("read").description("(alias) list captured network entries.")
1877
1904
  )).action(async (opts) => {
1878
- const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
1879
- await run("chrome_network", args);
1905
+ await run("chrome_network", withBase(opts, netFilterArgs(opts)));
1880
1906
  });
1881
1907
  tabOpt(
1882
1908
  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")
1883
1909
  ).action(async (requestId, opts) => {
1884
- const args = { ...baseArgs(opts), action: "body", requestId };
1885
- if (opts.full) args.full = true;
1886
- if (typeof opts.head === "number") args.head = opts.head;
1887
- await run("chrome_network", args);
1910
+ const extras = { action: "body", requestId };
1911
+ if (opts.full) extras.full = true;
1912
+ if (typeof opts.head === "number") extras.head = opts.head;
1913
+ await run("chrome_network", withBase(opts, extras));
1888
1914
  });
1889
1915
  tabOpt(netFilterOpts(
1890
1916
  network.command("har").description("Emit HAR-compatible JSON for the captured entries.").option("--with-bodies", "fetch response bodies before emitting; strict by default \u2014 fails if any body cannot be fetched").option("--best-effort-bodies", "with --with-bodies: keep the HAR even when some bodies are missing/errored (legacy behavior); per-entry _chrome_relay.bodyState/bodyError records what failed")
1891
1917
  )).action(async (opts) => {
1892
- const args = { ...baseArgs(opts), ...netFilterArgs(opts), action: "har" };
1893
- if (opts.withBodies) args.withBodies = true;
1894
- if (opts.bestEffortBodies) args.bestEffortBodies = true;
1918
+ const extras = { ...netFilterArgs(opts), action: "har" };
1919
+ if (opts.withBodies) extras.withBodies = true;
1920
+ if (opts.bestEffortBodies) extras.bestEffortBodies = true;
1895
1921
  if (!opts.withBodies) {
1896
1922
  process.stderr.write(
1897
1923
  "[chrome-relay] HAR exported WITHOUT response bodies. Pass --with-bodies to include them (strict by default; add --best-effort-bodies to allow per-entry misses).\n"
1898
1924
  );
1899
1925
  }
1900
- await run("chrome_network", args);
1926
+ await run("chrome_network", withBase(opts, extras));
1901
1927
  });
1902
1928
  tabOpt(
1903
1929
  network.command("clear").description("Wipe the network buffer for this tab.")
1904
1930
  ).action(async (opts) => {
1905
- const args = { ...baseArgs(opts), action: "clear" };
1906
- await run("chrome_network", args);
1931
+ await run("chrome_network", withBase(opts, { action: "clear" }));
1907
1932
  });
1908
1933
  tabOpt(
1909
1934
  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(
@@ -1923,12 +1948,12 @@ Notes:
1923
1948
  `
1924
1949
  )
1925
1950
  ).action(async (opts) => {
1926
- const args = baseArgs(opts);
1927
- if (opts.clear) args.action = "clear";
1928
- if (opts.level) args.levels = opts.level;
1929
- if (typeof opts.since === "number") args.since = opts.since;
1930
- if (typeof opts.limit === "number") args.limit = opts.limit;
1931
- await run("chrome_console", args);
1951
+ const extras = {};
1952
+ if (opts.clear) extras.action = "clear";
1953
+ if (opts.level) extras.levels = opts.level;
1954
+ if (typeof opts.since === "number") extras.since = opts.since;
1955
+ if (typeof opts.limit === "number") extras.limit = opts.limit;
1956
+ await run("chrome_console", withBase(opts, extras));
1932
1957
  });
1933
1958
  }
1934
1959
 
@@ -1955,9 +1980,11 @@ Notes:
1955
1980
  Tools attach via CDP and run on backgrounded tabs without stealing focus.
1956
1981
  `
1957
1982
  );
1983
+ const baseArgs = makeBaseArgs(program);
1958
1984
  const ctx = {
1959
1985
  program,
1960
- baseArgs: makeBaseArgs(program),
1986
+ baseArgs,
1987
+ withBase: makeWithBase(baseArgs),
1961
1988
  run: runTool
1962
1989
  };
1963
1990
  registerInstallUpdate(program);
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.13" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -48,7 +48,7 @@ function toBridgeError(unknownErr, fallbackTool) {
48
48
  }
49
49
 
50
50
  // src/index.ts
51
- var CHROME_RELAY_VERSION = true ? "0.5.13" : "0.0.0-dev";
51
+ var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
52
52
 
53
53
  // src/release-notes.ts
54
54
  function compareSemver(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",