chrome-relay 0.5.13 → 0.5.15

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,140 @@ 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;
65
+ }
66
+ function optPositiveNumber(obj, key, tool) {
67
+ const v = optNumber(obj, key, tool);
68
+ if (v === void 0)
69
+ return void 0;
70
+ if (v <= 0) {
71
+ throw new RelayError({
72
+ code: "invalid_arguments",
73
+ message: `${tool ?? "<unknown tool>"}: \`${key}\` must be > 0 (got ${v}).`,
74
+ tool,
75
+ phase: "parse_arguments",
76
+ details: { field: key, expected: "> 0", received: v },
77
+ retryable: false
78
+ });
79
+ }
80
+ return v;
81
+ }
82
+ function optNonNegativeNumber(obj, key, tool) {
83
+ const v = optNumber(obj, key, tool);
84
+ if (v === void 0)
85
+ return void 0;
86
+ if (v < 0) {
87
+ throw new RelayError({
88
+ code: "invalid_arguments",
89
+ message: `${tool ?? "<unknown tool>"}: \`${key}\` must be >= 0 (got ${v}).`,
90
+ tool,
91
+ phase: "parse_arguments",
92
+ details: { field: key, expected: ">= 0", received: v },
93
+ retryable: false
94
+ });
95
+ }
96
+ return v;
97
+ }
98
+ function coerceTabId(raw, tool) {
99
+ if (typeof raw === "number") {
100
+ if (!Number.isFinite(raw)) {
101
+ throw new RelayError({
102
+ code: "invalid_arguments",
103
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a finite number.`,
104
+ tool,
105
+ phase: "parse_arguments",
106
+ details: { received: raw },
107
+ retryable: false
108
+ });
109
+ }
110
+ return raw;
111
+ }
112
+ if (typeof raw === "string") {
113
+ const trimmed = raw.trim();
114
+ if (!trimmed) {
115
+ throw new RelayError({
116
+ code: "invalid_arguments",
117
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a number; got a blank string.`,
118
+ tool,
119
+ phase: "parse_arguments",
120
+ details: { received: raw },
121
+ retryable: false
122
+ });
123
+ }
124
+ const n = Number(trimmed);
125
+ if (!Number.isFinite(n)) {
126
+ throw new RelayError({
127
+ code: "invalid_arguments",
128
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a numeric string.`,
129
+ tool,
130
+ phase: "parse_arguments",
131
+ details: { received: raw },
132
+ retryable: false
133
+ });
134
+ }
135
+ return n;
136
+ }
137
+ throw new RelayError({
138
+ code: "invalid_arguments",
139
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a number or numeric string.`,
140
+ tool,
141
+ phase: "parse_arguments",
142
+ details: { received: raw },
143
+ retryable: false
144
+ });
47
145
  }
48
- function optBool(obj, key) {
146
+ function optBool(obj, key, tool) {
49
147
  const v = obj[key];
50
- return typeof v === "boolean" ? v : void 0;
148
+ if (v === void 0 || v === null)
149
+ return void 0;
150
+ if (typeof v !== "boolean")
151
+ rejectWrongType(obj, key, "a boolean", tool);
152
+ return v;
51
153
  }
52
- function parseTargetArgs(obj) {
154
+ function parseTargetArgs(obj, tool) {
53
155
  const out = {};
54
- if (typeof obj.tabId === "number")
156
+ if (obj.tabId !== void 0 && obj.tabId !== null) {
157
+ if (typeof obj.tabId !== "number" || !Number.isFinite(obj.tabId)) {
158
+ rejectWrongType(obj, "tabId", "a finite number", tool);
159
+ }
55
160
  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;
161
+ }
162
+ if (obj.workspaceName !== void 0 && obj.workspaceName !== null) {
163
+ if (typeof obj.workspaceName !== "string")
164
+ rejectWrongType(obj, "workspaceName", "a string", tool);
165
+ if (obj.workspaceName)
166
+ out.workspaceName = obj.workspaceName;
167
+ }
168
+ if (obj.groupName !== void 0 && obj.groupName !== null) {
169
+ if (typeof obj.groupName !== "string")
170
+ rejectWrongType(obj, "groupName", "a string", tool);
171
+ if (obj.groupName)
172
+ out.groupName = obj.groupName;
173
+ }
60
174
  return out;
61
175
  }
62
176
  var init_shared = __esm({
@@ -69,10 +183,7 @@ var init_shared = __esm({
69
183
  // ../protocol/dist/args/navigate.js
70
184
  function parseChromeNavigateArgs(input) {
71
185
  const obj = asObject(input, TOOL_NAMES.NAVIGATE);
72
- const out = {
73
- url: requireString(obj, "url", TOOL_NAMES.NAVIGATE),
74
- ...parseTargetArgs(obj)
75
- };
186
+ const out = { url: requireString(obj, "url", TOOL_NAMES.NAVIGATE) };
76
187
  if (typeof obj.tabId === "string" && obj.tabId) {
77
188
  const n = Number(obj.tabId);
78
189
  if (!Number.isFinite(n)) {
@@ -87,17 +198,23 @@ function parseChromeNavigateArgs(input) {
87
198
  }
88
199
  out.tabId = n;
89
200
  } else {
90
- const n = optNumber(obj, "tabId");
201
+ const n = optNumber(obj, "tabId", TOOL_NAMES.NAVIGATE);
91
202
  if (n !== void 0)
92
203
  out.tabId = n;
93
204
  }
94
- const newTab = optBool(obj, "newTab");
205
+ const { tabId: _, ...rest } = obj;
206
+ const target = parseTargetArgs(rest, TOOL_NAMES.NAVIGATE);
207
+ if (target.workspaceName)
208
+ out.workspaceName = target.workspaceName;
209
+ if (target.groupName)
210
+ out.groupName = target.groupName;
211
+ const newTab = optBool(obj, "newTab", TOOL_NAMES.NAVIGATE);
95
212
  if (newTab !== void 0)
96
213
  out.newTab = newTab;
97
- const active = optBool(obj, "active");
214
+ const active = optBool(obj, "active", TOOL_NAMES.NAVIGATE);
98
215
  if (active !== void 0)
99
216
  out.active = active;
100
- const allowPartial = optBool(obj, "allowPartial");
217
+ const allowPartial = optBool(obj, "allowPartial", TOOL_NAMES.NAVIGATE);
101
218
  if (allowPartial !== void 0)
102
219
  out.allowPartial = allowPartial;
103
220
  void optString;
@@ -114,13 +231,13 @@ var init_navigate = __esm({
114
231
  // ../protocol/dist/args/hover.js
115
232
  function parseChromeHoverArgs(input) {
116
233
  const obj = asObject(input, TOOL_NAMES.HOVER);
117
- const target = parseTargetArgs(obj);
118
- const x = optNumber(obj, "x");
119
- const y = optNumber(obj, "y");
234
+ const target = parseTargetArgs(obj, TOOL_NAMES.HOVER);
235
+ const x = optNumber(obj, "x", TOOL_NAMES.HOVER);
236
+ const y = optNumber(obj, "y", TOOL_NAMES.HOVER);
120
237
  if (x !== void 0 && y !== void 0) {
121
238
  return { ...target, kind: "coords", x, y };
122
239
  }
123
- const selector = optString(obj, "selector");
240
+ const selector = optString(obj, "selector", TOOL_NAMES.HOVER);
124
241
  if (selector) {
125
242
  return { ...target, kind: "selector", selector };
126
243
  }
@@ -197,7 +314,7 @@ function parseChromeNetworkArgs(input) {
197
314
  const full = optBool(obj, "full");
198
315
  if (full !== void 0)
199
316
  out.full = full;
200
- const head = optNumber(obj, "head");
317
+ const head = optPositiveNumber(obj, "head", TOOL_NAMES.NETWORK);
201
318
  if (head !== void 0)
202
319
  out.head = head;
203
320
  return out;
@@ -236,10 +353,14 @@ var init_network = __esm({
236
353
  });
237
354
 
238
355
  // ../protocol/dist/args/simple.js
239
- function parseGetWindowsAndTabsArgs(_input) {
356
+ function parseGetWindowsAndTabsArgs(input) {
357
+ if (input !== void 0 && input !== null)
358
+ asObject(input, TOOL_NAMES.GET_WINDOWS_AND_TABS);
240
359
  return {};
241
360
  }
242
- function parseChromeSelfReloadArgs(_input) {
361
+ function parseChromeSelfReloadArgs(input) {
362
+ if (input !== void 0 && input !== null)
363
+ asObject(input, TOOL_NAMES.SELF_RELOAD);
243
364
  return {};
244
365
  }
245
366
  function parseChromeReadPageArgs(input) {
@@ -297,27 +418,25 @@ function parseChromeEvaluateArgs(input) {
297
418
  const obj = asObject(input, TOOL_NAMES.EVALUATE);
298
419
  const out = {
299
420
  code: requireString(obj, "code", TOOL_NAMES.EVALUATE),
300
- ...parseTargetArgs(obj)
421
+ ...parseTargetArgs(obj, TOOL_NAMES.EVALUATE)
301
422
  };
302
- const t = optNumber(obj, "timeoutMs");
423
+ const t = optPositiveNumber(obj, "timeoutMs", TOOL_NAMES.EVALUATE);
303
424
  if (t !== void 0)
304
425
  out.timeoutMs = t;
305
426
  return out;
306
427
  }
307
428
  function parseChromeSwitchTabArgs(input) {
308
429
  const obj = asObject(input, TOOL_NAMES.SWITCH_TAB);
309
- const tabId = Number(obj.tabId);
310
- if (!Number.isFinite(tabId)) {
430
+ if (obj.tabId === void 0 || obj.tabId === null) {
311
431
  throw new RelayError({
312
432
  code: "invalid_arguments",
313
433
  message: `${TOOL_NAMES.SWITCH_TAB} requires a numeric tabId.`,
314
434
  tool: TOOL_NAMES.SWITCH_TAB,
315
435
  phase: "parse_arguments",
316
- details: { received: obj.tabId },
317
436
  retryable: false
318
437
  });
319
438
  }
320
- return { tabId };
439
+ return { tabId: coerceTabId(obj.tabId, TOOL_NAMES.SWITCH_TAB) };
321
440
  }
322
441
  function parseChromeCloseTabsArgs(input) {
323
442
  const obj = asObject(input, TOOL_NAMES.CLOSE_TABS);
@@ -331,18 +450,17 @@ function parseChromeCloseTabsArgs(input) {
331
450
  retryable: false
332
451
  });
333
452
  }
334
- const tabIds = obj.tabIds.map((v) => Number(v));
335
- if (tabIds.length === 0 || tabIds.some((n) => !Number.isFinite(n))) {
453
+ if (obj.tabIds.length === 0) {
336
454
  throw new RelayError({
337
455
  code: "invalid_arguments",
338
- message: `${TOOL_NAMES.CLOSE_TABS} requires a non-empty array of numeric tab IDs.`,
456
+ message: `${TOOL_NAMES.CLOSE_TABS} requires a non-empty array of tab IDs.`,
339
457
  tool: TOOL_NAMES.CLOSE_TABS,
340
458
  phase: "parse_arguments",
341
459
  details: { received: obj.tabIds },
342
460
  retryable: false
343
461
  });
344
462
  }
345
- return { tabIds };
463
+ return { tabIds: obj.tabIds.map((v) => coerceTabId(v, TOOL_NAMES.CLOSE_TABS)) };
346
464
  }
347
465
  function parseChromeAxArgs(input) {
348
466
  const obj = asObject(input, TOOL_NAMES.AX);
@@ -375,21 +493,21 @@ function parseChromeClickAxArgs(input) {
375
493
  }
376
494
  function parseChromeScreenshotArgs(input) {
377
495
  const obj = asObject(input, TOOL_NAMES.SCREENSHOT);
378
- const out = { ...parseTargetArgs(obj) };
379
- const fp = optBool(obj, "fullPage");
496
+ const out = { ...parseTargetArgs(obj, TOOL_NAMES.SCREENSHOT) };
497
+ const fp = optBool(obj, "fullPage", TOOL_NAMES.SCREENSHOT);
380
498
  if (fp !== void 0)
381
499
  out.fullPage = fp;
382
- const bbox = optString(obj, "bbox");
500
+ const bbox = optString(obj, "bbox", TOOL_NAMES.SCREENSHOT);
383
501
  if (bbox)
384
502
  out.bbox = bbox;
385
- const sel = optString(obj, "selector");
503
+ const sel = optString(obj, "selector", TOOL_NAMES.SCREENSHOT);
386
504
  if (sel)
387
505
  out.selector = sel;
388
- const pad = optNumber(obj, "padding");
506
+ const pad = optNonNegativeNumber(obj, "padding", TOOL_NAMES.SCREENSHOT);
389
507
  if (pad !== void 0)
390
508
  out.padding = pad;
391
- const me = optNumber(obj, "maxEdge");
392
- if (me !== void 0 && me > 0)
509
+ const me = optPositiveNumber(obj, "maxEdge", TOOL_NAMES.SCREENSHOT);
510
+ if (me !== void 0)
393
511
  out.maxEdge = me;
394
512
  return out;
395
513
  }
@@ -532,28 +650,12 @@ function parseChromeWorkspaceArgs(input) {
532
650
  return out;
533
651
  }
534
652
  function parseTabIds(raw) {
535
- const reject = (bad) => {
536
- throw new RelayError({
537
- code: "invalid_arguments",
538
- message: `${TOOL_NAMES.GROUP}: invalid tabId ${JSON.stringify(bad)}. Expected a number or a comma-separated list of numbers.`,
539
- tool: TOOL_NAMES.GROUP,
540
- phase: "parse_tab_ids",
541
- details: { received: bad },
542
- retryable: false
543
- });
544
- };
545
- const coerce = (v) => {
546
- const n = Number(typeof v === "string" ? v.trim() : v);
547
- if (!Number.isFinite(n))
548
- reject(v);
549
- return n;
550
- };
551
653
  if (Array.isArray(raw))
552
- return raw.map(coerce);
654
+ return raw.map((v) => coerceTabId(v, TOOL_NAMES.GROUP));
553
655
  if (typeof raw === "string")
554
- return raw.split(",").map(coerce);
656
+ return raw.split(",").map((s) => coerceTabId(s, TOOL_NAMES.GROUP));
555
657
  if (typeof raw === "number")
556
- return [raw];
658
+ return [coerceTabId(raw, TOOL_NAMES.GROUP)];
557
659
  return [];
558
660
  }
559
661
  function parseColor(raw) {
@@ -673,16 +775,27 @@ function parseChromeScreencastArgs(input) {
673
775
  }
674
776
  out.format = obj.format;
675
777
  }
676
- const q = optNumber(obj, "quality");
677
- if (q !== void 0)
778
+ const q = optNonNegativeNumber(obj, "quality", TOOL_NAMES.SCREENCAST);
779
+ if (q !== void 0) {
780
+ if (q > 100) {
781
+ throw new RelayError({
782
+ code: "invalid_arguments",
783
+ message: `${TOOL_NAMES.SCREENCAST}: quality must be 0-100 (got ${q}).`,
784
+ tool: TOOL_NAMES.SCREENCAST,
785
+ phase: "parse_arguments",
786
+ details: { field: "quality", received: q, range: [0, 100] },
787
+ retryable: false
788
+ });
789
+ }
678
790
  out.quality = q;
679
- const mw = optNumber(obj, "maxWidth");
791
+ }
792
+ const mw = optPositiveNumber(obj, "maxWidth", TOOL_NAMES.SCREENCAST);
680
793
  if (mw !== void 0)
681
794
  out.maxWidth = mw;
682
- const mh = optNumber(obj, "maxHeight");
795
+ const mh = optPositiveNumber(obj, "maxHeight", TOOL_NAMES.SCREENCAST);
683
796
  if (mh !== void 0)
684
797
  out.maxHeight = mh;
685
- const en = optNumber(obj, "everyNthFrame");
798
+ const en = optPositiveNumber(obj, "everyNthFrame", TOOL_NAMES.SCREENCAST);
686
799
  if (en !== void 0)
687
800
  out.everyNthFrame = en;
688
801
  return out;
@@ -717,21 +830,50 @@ var init_args = __esm({
717
830
  }
718
831
  });
719
832
 
833
+ // ../protocol/dist/limits.js
834
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS, DEFAULT_PING_TIMEOUT_MS, DEFAULT_READY_TIMEOUT_MS, DEFAULT_EVAL_TIMEOUT_MS, DEFAULT_BODY_PREVIEW_BYTES, NETWORK_BUFFER_MAX_ENTRIES, NETWORK_BUFFER_MAX_BYTES, CONSOLE_BUFFER_MAX_ENTRIES, CONSOLE_BUFFER_MAX_BYTES;
835
+ var init_limits = __esm({
836
+ "../protocol/dist/limits.js"() {
837
+ "use strict";
838
+ DEFAULT_TOOL_CALL_TIMEOUT_MS = 3e4;
839
+ DEFAULT_PING_TIMEOUT_MS = 2e3;
840
+ DEFAULT_READY_TIMEOUT_MS = 15e3;
841
+ DEFAULT_EVAL_TIMEOUT_MS = 15e3;
842
+ DEFAULT_BODY_PREVIEW_BYTES = 8 * 1024;
843
+ NETWORK_BUFFER_MAX_ENTRIES = 200;
844
+ NETWORK_BUFFER_MAX_BYTES = 512 * 1024;
845
+ CONSOLE_BUFFER_MAX_ENTRIES = 200;
846
+ CONSOLE_BUFFER_MAX_BYTES = 256 * 1024;
847
+ }
848
+ });
849
+
720
850
  // ../protocol/dist/index.js
721
851
  var dist_exports = {};
722
852
  __export(dist_exports, {
723
853
  CHROME_WEB_STORE_EXTENSION_ID: () => CHROME_WEB_STORE_EXTENSION_ID,
854
+ CONSOLE_BUFFER_MAX_BYTES: () => CONSOLE_BUFFER_MAX_BYTES,
855
+ CONSOLE_BUFFER_MAX_ENTRIES: () => CONSOLE_BUFFER_MAX_ENTRIES,
856
+ DEFAULT_BODY_PREVIEW_BYTES: () => DEFAULT_BODY_PREVIEW_BYTES,
857
+ DEFAULT_EVAL_TIMEOUT_MS: () => DEFAULT_EVAL_TIMEOUT_MS,
724
858
  DEFAULT_EXTENSION_ID: () => DEFAULT_EXTENSION_ID,
725
859
  DEFAULT_EXTENSION_IDS: () => DEFAULT_EXTENSION_IDS,
726
860
  DEFAULT_HTTP_PORT: () => DEFAULT_HTTP_PORT,
861
+ DEFAULT_PING_TIMEOUT_MS: () => DEFAULT_PING_TIMEOUT_MS,
862
+ DEFAULT_READY_TIMEOUT_MS: () => DEFAULT_READY_TIMEOUT_MS,
863
+ DEFAULT_TOOL_CALL_TIMEOUT_MS: () => DEFAULT_TOOL_CALL_TIMEOUT_MS,
727
864
  LEGACY_DEV_EXTENSION_ID: () => LEGACY_DEV_EXTENSION_ID,
728
865
  LOCAL_UNPACKED_EXTENSION_ID: () => LOCAL_UNPACKED_EXTENSION_ID,
729
866
  NATIVE_HOST_NAME: () => NATIVE_HOST_NAME,
867
+ NETWORK_BUFFER_MAX_BYTES: () => NETWORK_BUFFER_MAX_BYTES,
868
+ NETWORK_BUFFER_MAX_ENTRIES: () => NETWORK_BUFFER_MAX_ENTRIES,
730
869
  RelayError: () => RelayError,
731
870
  TOOL_NAMES: () => TOOL_NAMES,
732
871
  asObject: () => asObject,
872
+ coerceTabId: () => coerceTabId,
733
873
  optBool: () => optBool,
874
+ optNonNegativeNumber: () => optNonNegativeNumber,
734
875
  optNumber: () => optNumber,
876
+ optPositiveNumber: () => optPositiveNumber,
735
877
  optString: () => optString,
736
878
  parseChromeAxArgs: () => parseChromeAxArgs,
737
879
  parseChromeClickArgs: () => parseChromeClickArgs,
@@ -775,6 +917,7 @@ var init_dist = __esm({
775
917
  "../protocol/dist/index.js"() {
776
918
  "use strict";
777
919
  init_args();
920
+ init_limits();
778
921
  NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
779
922
  DEFAULT_HTTP_PORT = 12122;
780
923
  CHROME_WEB_STORE_EXTENSION_ID = "cpdiapbifblhlcpnmlmfpgfjlacebokb";
@@ -866,7 +1009,7 @@ var init_dist = __esm({
866
1009
  import { Command } from "commander";
867
1010
 
868
1011
  // src/index.ts
869
- var CHROME_RELAY_VERSION = true ? "0.5.13" : "0.0.0-dev";
1012
+ var CHROME_RELAY_VERSION = true ? "0.5.15" : "0.0.0-dev";
870
1013
 
871
1014
  // src/commands/shared.ts
872
1015
  init_dist();
@@ -914,6 +1057,11 @@ async function callTool(name, args) {
914
1057
  }
915
1058
 
916
1059
  // src/commands/shared.ts
1060
+ function makeWithBase(baseArgs) {
1061
+ return function withBase(opts, extras) {
1062
+ return { ...baseArgs(opts), ...extras ?? {} };
1063
+ };
1064
+ }
917
1065
  function tabOpt(cmd) {
918
1066
  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
1067
  }
@@ -1100,6 +1248,23 @@ async function runDoctor() {
1100
1248
 
1101
1249
  // src/release-notes.ts
1102
1250
  var RELEASE_NOTES = {
1251
+ "0.5.15": [
1252
+ "Tab-id coercion is now strict against blank strings. `--tabs '1,,3'` (or `[\"\"]`, or `[' ']`) used to silently become `[1, 0, 3]` because Number('') === 0 \u2014 and tab 0 is a real Chrome target. Now throws RelayError(invalid_arguments). Affects chrome_group create/add/remove, chrome_switch_tab, chrome_close_tabs.",
1253
+ "Numeric range validation added across the parsers. `optPositiveNumber` (> 0) and `optNonNegativeNumber` (>= 0) helpers reject out-of-range values at the parser boundary instead of letting nonsense through to CDP / handler logic. Affects: chrome_evaluate timeoutMs, chrome_screenshot maxEdge + padding, chrome_screencast quality/maxWidth/maxHeight/everyNthFrame, chrome_network body head.",
1254
+ "chrome_screenshot maxEdge <= 0 now throws instead of being silently ignored (was the one remaining `if (x > 0)` silent-drop in the parsers).",
1255
+ "No-args parser comment + behavior aligned. parseGetWindowsAndTabsArgs / parseChromeSelfReloadArgs validate the input is at least an object (rejects strings/arrays) but accept any extra fields. Both handlers now call their parser at the top for consistency with every other tool.",
1256
+ "New @chrome-relay/protocol exports: shared numeric limits in `packages/protocol/src/limits.ts`. Constants (DEFAULT_TOOL_CALL_TIMEOUT_MS, DEFAULT_EVAL_TIMEOUT_MS, DEFAULT_BODY_PREVIEW_BYTES, NETWORK_BUFFER_MAX_ENTRIES/BYTES, CONSOLE_BUFFER_MAX_ENTRIES/BYTES, etc.) now flow from one source \u2014 bridge.ts, console-buffer.ts, network-buffer.ts, and the network/evaluate handlers all import. Future doc/help drift is structurally prevented.",
1257
+ "Tests: +9 in args-all.test.ts covering blank-string rejection, range validation across screenshot/evaluate/screencast/network. Total 416 (was 407)."
1258
+ ],
1259
+ "0.5.14": [
1260
+ "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.",
1261
+ "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.",
1262
+ "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].",
1263
+ "Deleted the dead duplicate parser module at apps/extension/src/browser/parsers.ts and its test file. The protocol parsers replace them; behavior identical.",
1264
+ "HAR creator.version now reads from chrome.runtime.getManifest() instead of the hardcoded stale '0.2.x'. Drift-proof.",
1265
+ "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.",
1266
+ "Tests: +2 strict-optional cases. Total 407 (was 433 \u2014 28 dropped were duplicates of protocol parsers)."
1267
+ ],
1103
1268
  "0.5.13": [
1104
1269
  "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
1270
  "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 +1464,7 @@ function registerInstallUpdate(program) {
1299
1464
 
1300
1465
  // src/commands/navigation.ts
1301
1466
  function registerNavigation(ctx) {
1302
- const { program, baseArgs, run } = ctx;
1467
+ const { program, withBase, run } = ctx;
1303
1468
  program.command("tabs [verb]").description("List open Chrome windows and tabs. (verb 'list' is accepted as alias)").action(async (verb) => {
1304
1469
  if (verb && verb !== "list") {
1305
1470
  process.stderr.write(`unknown tabs verb: ${verb}. Use 'tabs' or 'tabs list'.
@@ -1328,11 +1493,10 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1328
1493
  );
1329
1494
  process.exit(1);
1330
1495
  }
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);
1496
+ const extras = { url };
1497
+ if (opts.new) extras.newTab = true;
1498
+ if (opts.inactive) extras.active = false;
1499
+ await run("chrome_navigate", withBase(opts, extras));
1336
1500
  });
1337
1501
  program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
1338
1502
  await run("chrome_switch_tab", { tabId: Number(tabId) });
@@ -1348,20 +1512,16 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1348
1512
 
1349
1513
  // src/commands/input.ts
1350
1514
  function registerInput(ctx) {
1351
- const { program, baseArgs, run } = ctx;
1515
+ const { program, withBase, run } = ctx;
1352
1516
  tabOpt(
1353
1517
  program.command("click <selector>").description("Click an element by CSS selector.")
1354
1518
  ).action(async (selector, opts) => {
1355
- const args = { selector };
1356
- Object.assign(args, baseArgs(opts));
1357
- await run("chrome_click_element", args);
1519
+ await run("chrome_click_element", withBase(opts, { selector }));
1358
1520
  });
1359
1521
  tabOpt(
1360
1522
  program.command("fill <selector> <value>").description("Fill an input or textarea.")
1361
1523
  ).action(async (selector, value, opts) => {
1362
- const args = { selector, value };
1363
- Object.assign(args, baseArgs(opts));
1364
- await run("chrome_fill_or_select", args);
1524
+ await run("chrome_fill_or_select", withBase(opts, { selector, value }));
1365
1525
  });
1366
1526
  tabOpt(
1367
1527
  program.command("keys <keys>").description("Press a single key or chord via trusted CDP input (e.g. Enter, Cmd+K).").addHelpText(
@@ -1378,9 +1538,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
1378
1538
  `
1379
1539
  )
1380
1540
  ).action(async (keys, opts) => {
1381
- const args = { keys };
1382
- Object.assign(args, baseArgs(opts));
1383
- await run("chrome_keyboard", args);
1541
+ await run("chrome_keyboard", withBase(opts, { keys }));
1384
1542
  });
1385
1543
  tabOpt(
1386
1544
  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 +1557,9 @@ When to pick which:
1399
1557
  `
1400
1558
  )
1401
1559
  ).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);
1560
+ const extras = { text };
1561
+ if (opts.selector) extras.selector = opts.selector;
1562
+ await run("chrome_type", withBase(opts, extras));
1406
1563
  });
1407
1564
  tabOpt(
1408
1565
  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 +1578,9 @@ Notes:
1421
1578
  `
1422
1579
  )
1423
1580
  ).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);
1581
+ const extras = { code };
1582
+ if (typeof opts.timeoutMs === "number") extras.timeoutMs = opts.timeoutMs;
1583
+ await run("chrome_evaluate", withBase(opts, extras));
1428
1584
  });
1429
1585
  tabOpt(
1430
1586
  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 +1596,20 @@ tooltip appearance, etc.) that a bare click would skip past too quickly.
1440
1596
  `
1441
1597
  )
1442
1598
  ).action(async (selector, opts) => {
1443
- const args = {};
1444
- Object.assign(args, baseArgs(opts));
1445
- if (selector) args.selector = selector;
1599
+ const extras = {};
1600
+ if (selector) extras.selector = selector;
1446
1601
  if (typeof opts.x === "number" && typeof opts.y === "number") {
1447
- args.x = opts.x;
1448
- args.y = opts.y;
1602
+ extras.x = opts.x;
1603
+ extras.y = opts.y;
1449
1604
  }
1450
- await run("chrome_hover", args);
1605
+ await run("chrome_hover", withBase(opts, extras));
1451
1606
  });
1452
1607
  }
1453
1608
 
1454
1609
  // src/commands/capture.ts
1455
1610
  import { writeFileSync } from "fs";
1456
1611
  function registerCapture(ctx) {
1457
- const { program, baseArgs, run } = ctx;
1612
+ const { program, withBase, run } = ctx;
1458
1613
  tabOpt(
1459
1614
  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
1615
  "after",
@@ -1473,13 +1628,13 @@ full-tab screenshot when an agent only needs to see one component.
1473
1628
  `
1474
1629
  )
1475
1630
  ).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;
1631
+ const extras = {};
1632
+ if (opts.full) extras.fullPage = true;
1633
+ if (opts.bbox) extras.bbox = opts.bbox;
1634
+ if (opts.selector) extras.selector = opts.selector;
1635
+ if (typeof opts.padding === "number") extras.padding = opts.padding;
1636
+ if (typeof opts.maxEdge === "number") extras.maxEdge = opts.maxEdge;
1637
+ const args = withBase(opts, extras);
1483
1638
  try {
1484
1639
  const result = await callTool("chrome_screenshot", args);
1485
1640
  if (opts.out && result && typeof result === "object") {
@@ -1503,10 +1658,9 @@ full-tab screenshot when an agent only needs to see one component.
1503
1658
  tabOpt(
1504
1659
  program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
1505
1660
  ).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);
1661
+ const extras = {};
1662
+ if (opts.interactive) extras.interactiveOnly = true;
1663
+ await run("chrome_read_page", withBase(opts, extras));
1510
1664
  });
1511
1665
  tabOpt(
1512
1666
  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 +1678,11 @@ Notes:
1524
1678
  `
1525
1679
  )
1526
1680
  ).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);
1681
+ const extras = {};
1682
+ if (opts.interactiveOnly) extras.interactiveOnly = true;
1683
+ if (opts.root) extras.rootRole = opts.root;
1684
+ if (opts.includeSubframes) extras.includeSubframes = true;
1685
+ await run("chrome_ax", withBase(opts, extras));
1532
1686
  });
1533
1687
  tabOpt(
1534
1688
  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 +1698,7 @@ Notes:
1544
1698
  `
1545
1699
  )
1546
1700
  ).action(async (opts) => {
1547
- const args = baseArgs(opts);
1548
- args.node = opts.node;
1549
- await run("chrome_click_ax", args);
1701
+ await run("chrome_click_ax", withBase(opts, { node: opts.node }));
1550
1702
  });
1551
1703
  const screencast = program.command("screencast").description("Record a tab via CDP (paint-driven). Requires an active tab.").addHelpText(
1552
1704
  "after",
@@ -1571,20 +1723,18 @@ Notes:
1571
1723
  tabOpt(
1572
1724
  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
1725
  ).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);
1726
+ const extras = { action: "start" };
1727
+ if (opts.format) extras.format = opts.format;
1728
+ if (typeof opts.quality === "number") extras.quality = opts.quality;
1729
+ if (typeof opts.maxWidth === "number") extras.maxWidth = opts.maxWidth;
1730
+ if (typeof opts.maxHeight === "number") extras.maxHeight = opts.maxHeight;
1731
+ if (typeof opts.everyNth === "number") extras.everyNthFrame = opts.everyNth;
1732
+ await run("chrome_screencast", withBase(opts, extras));
1582
1733
  });
1583
1734
  tabOpt(
1584
1735
  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
1736
  ).action(async (opts) => {
1586
- const args = { action: "stop" };
1587
- Object.assign(args, baseArgs(opts));
1737
+ const args = withBase(opts, { action: "stop" });
1588
1738
  try {
1589
1739
  const result = await callTool("chrome_screencast", args);
1590
1740
  if (!opts.out) {
@@ -1719,7 +1869,7 @@ function netFilterArgs(opts) {
1719
1869
  return a;
1720
1870
  }
1721
1871
  function registerSessions(ctx) {
1722
- const { program, baseArgs, run } = ctx;
1872
+ const { program, withBase, run } = ctx;
1723
1873
  const viewport = program.command("viewport").description("Emulate device viewport, DPR, mobile flag, touch, and user agent.").addHelpText(
1724
1874
  "after",
1725
1875
  `
@@ -1740,27 +1890,22 @@ Notes:
1740
1890
  tabOpt(
1741
1891
  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
1892
  ).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);
1893
+ const extras = { action: "set", width: opts.width, height: opts.height };
1894
+ if (opts.dpr !== void 0) extras.dpr = opts.dpr;
1895
+ if (opts.mobile) extras.mobile = true;
1896
+ if (opts.touch) extras.hasTouch = true;
1897
+ if (opts.userAgent) extras.userAgent = opts.userAgent;
1898
+ await run("chrome_viewport", withBase(opts, extras));
1750
1899
  });
1751
1900
  tabOpt(
1752
1901
  viewport.command("preset <name>").description("Apply a named device preset (iphone-14, pixel-7, desktop-1440, etc).")
1753
1902
  ).action(async (name, opts) => {
1754
- const args = { action: "preset", name };
1755
- Object.assign(args, baseArgs(opts));
1756
- await run("chrome_viewport", args);
1903
+ await run("chrome_viewport", withBase(opts, { action: "preset", name }));
1757
1904
  });
1758
1905
  tabOpt(
1759
1906
  viewport.command("clear").description("Drop the viewport override and return the tab to its native size.")
1760
1907
  ).action(async (opts) => {
1761
- const args = { action: "clear" };
1762
- Object.assign(args, baseArgs(opts));
1763
- await run("chrome_viewport", args);
1908
+ await run("chrome_viewport", withBase(opts, { action: "clear" }));
1764
1909
  });
1765
1910
  viewport.command("list").description("List available presets.").action(async () => {
1766
1911
  await run("chrome_viewport", { action: "list" });
@@ -1821,8 +1966,7 @@ Notes:
1821
1966
  `
1822
1967
  );
1823
1968
  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);
1969
+ const args = { action: "create", name, tabIds: String(opts.tabs) };
1826
1970
  if (opts.color) args.color = opts.color;
1827
1971
  if (opts.collapsed) args.collapsed = true;
1828
1972
  await run("chrome_group", args);
@@ -1834,12 +1978,10 @@ Notes:
1834
1978
  await run("chrome_group", { action: "close", name });
1835
1979
  });
1836
1980
  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 });
1981
+ await run("chrome_group", { action: "add", name, tabIds: String(opts.tabs) });
1839
1982
  });
1840
1983
  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 });
1984
+ await run("chrome_group", { action: "remove", tabIds: String(opts.tabs) });
1843
1985
  });
1844
1986
  const network = tabOpt(netFilterOpts(
1845
1987
  program.command("network").description("Capture HTTP request/response metadata. Ring buffer, last 200 per tab.")
@@ -1869,41 +2011,38 @@ Notes:
1869
2011
  WebSocket frames and SSE streams are out of scope.
1870
2012
  `
1871
2013
  ).action(async (opts) => {
1872
- const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
1873
- await run("chrome_network", args);
2014
+ await run("chrome_network", withBase(opts, netFilterArgs(opts)));
1874
2015
  });
1875
2016
  tabOpt(netFilterOpts(
1876
2017
  network.command("read").description("(alias) list captured network entries.")
1877
2018
  )).action(async (opts) => {
1878
- const args = { ...baseArgs(opts), ...netFilterArgs(opts) };
1879
- await run("chrome_network", args);
2019
+ await run("chrome_network", withBase(opts, netFilterArgs(opts)));
1880
2020
  });
1881
2021
  tabOpt(
1882
2022
  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
2023
  ).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);
2024
+ const extras = { action: "body", requestId };
2025
+ if (opts.full) extras.full = true;
2026
+ if (typeof opts.head === "number") extras.head = opts.head;
2027
+ await run("chrome_network", withBase(opts, extras));
1888
2028
  });
1889
2029
  tabOpt(netFilterOpts(
1890
2030
  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
2031
  )).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;
2032
+ const extras = { ...netFilterArgs(opts), action: "har" };
2033
+ if (opts.withBodies) extras.withBodies = true;
2034
+ if (opts.bestEffortBodies) extras.bestEffortBodies = true;
1895
2035
  if (!opts.withBodies) {
1896
2036
  process.stderr.write(
1897
2037
  "[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
2038
  );
1899
2039
  }
1900
- await run("chrome_network", args);
2040
+ await run("chrome_network", withBase(opts, extras));
1901
2041
  });
1902
2042
  tabOpt(
1903
2043
  network.command("clear").description("Wipe the network buffer for this tab.")
1904
2044
  ).action(async (opts) => {
1905
- const args = { ...baseArgs(opts), action: "clear" };
1906
- await run("chrome_network", args);
2045
+ await run("chrome_network", withBase(opts, { action: "clear" }));
1907
2046
  });
1908
2047
  tabOpt(
1909
2048
  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 +2062,12 @@ Notes:
1923
2062
  `
1924
2063
  )
1925
2064
  ).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);
2065
+ const extras = {};
2066
+ if (opts.clear) extras.action = "clear";
2067
+ if (opts.level) extras.levels = opts.level;
2068
+ if (typeof opts.since === "number") extras.since = opts.since;
2069
+ if (typeof opts.limit === "number") extras.limit = opts.limit;
2070
+ await run("chrome_console", withBase(opts, extras));
1932
2071
  });
1933
2072
  }
1934
2073
 
@@ -1955,9 +2094,11 @@ Notes:
1955
2094
  Tools attach via CDP and run on backgrounded tabs without stealing focus.
1956
2095
  `
1957
2096
  );
2097
+ const baseArgs = makeBaseArgs(program);
1958
2098
  const ctx = {
1959
2099
  program,
1960
- baseArgs: makeBaseArgs(program),
2100
+ baseArgs,
2101
+ withBase: makeWithBase(baseArgs),
1961
2102
  run: runTool
1962
2103
  };
1963
2104
  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.15" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -6,6 +6,14 @@ import process from "process";
6
6
  // src/http/server.ts
7
7
  import Fastify from "fastify";
8
8
 
9
+ // ../protocol/dist/limits.js
10
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS = 3e4;
11
+ var DEFAULT_PING_TIMEOUT_MS = 2e3;
12
+ var DEFAULT_READY_TIMEOUT_MS = 15e3;
13
+ var DEFAULT_BODY_PREVIEW_BYTES = 8 * 1024;
14
+ var NETWORK_BUFFER_MAX_BYTES = 512 * 1024;
15
+ var CONSOLE_BUFFER_MAX_BYTES = 256 * 1024;
16
+
9
17
  // ../protocol/dist/index.js
10
18
  var DEFAULT_HTTP_PORT = 12122;
11
19
  var RelayError = class extends Error {
@@ -48,7 +56,7 @@ function toBridgeError(unknownErr, fallbackTool) {
48
56
  }
49
57
 
50
58
  // src/index.ts
51
- var CHROME_RELAY_VERSION = true ? "0.5.13" : "0.0.0-dev";
59
+ var CHROME_RELAY_VERSION = true ? "0.5.15" : "0.0.0-dev";
52
60
 
53
61
  // src/release-notes.ts
54
62
  function compareSemver(a, b) {
@@ -197,7 +205,7 @@ var ExtensionBridge = class {
197
205
  pending.reject(new Error(message.payload.error));
198
206
  }
199
207
  }
200
- async waitUntilReady(timeoutMs = 15e3) {
208
+ async waitUntilReady(timeoutMs = DEFAULT_READY_TIMEOUT_MS) {
201
209
  if (this.ready) {
202
210
  return;
203
211
  }
@@ -213,7 +221,7 @@ var ExtensionBridge = class {
213
221
  this.readyWaiters.add(onReady);
214
222
  });
215
223
  }
216
- async ping(timeoutMs = 2e3) {
224
+ async ping(timeoutMs = DEFAULT_PING_TIMEOUT_MS) {
217
225
  const id = randomUUID();
218
226
  const message = { type: "bridge.ping", id };
219
227
  return new Promise((resolve) => {
@@ -229,7 +237,7 @@ var ExtensionBridge = class {
229
237
  this.send(message);
230
238
  });
231
239
  }
232
- async callTool(name, args, timeoutMs = 3e4) {
240
+ async callTool(name, args, timeoutMs = DEFAULT_TOOL_CALL_TIMEOUT_MS) {
233
241
  await this.waitUntilReady();
234
242
  const id = randomUUID();
235
243
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",