chrome-relay 0.5.22 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -228,140 +228,6 @@ var init_navigate = __esm({
228
228
  }
229
229
  });
230
230
 
231
- // ../protocol/dist/args/hover.js
232
- function parseChromeHoverArgs(input) {
233
- const obj = asObject(input, TOOL_NAMES.HOVER);
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);
237
- if (x !== void 0 !== (y !== void 0)) {
238
- throw new RelayError({
239
- code: "invalid_arguments",
240
- message: "chrome_hover: pass BOTH x and y, or neither (selector mode).",
241
- tool: TOOL_NAMES.HOVER,
242
- phase: "parse_arguments",
243
- details: { received: { x: obj.x, y: obj.y } },
244
- retryable: false
245
- });
246
- }
247
- if (x !== void 0 && y !== void 0) {
248
- return { ...target, kind: "coords", x, y };
249
- }
250
- const selector = optString(obj, "selector", TOOL_NAMES.HOVER);
251
- if (selector) {
252
- return { ...target, kind: "selector", selector };
253
- }
254
- throw new RelayError({
255
- code: "invalid_arguments",
256
- message: "chrome_hover requires either a selector or x AND y.",
257
- tool: TOOL_NAMES.HOVER,
258
- phase: "parse_arguments",
259
- details: { received: { selector: obj.selector, x: obj.x, y: obj.y } },
260
- retryable: false
261
- });
262
- }
263
- var init_hover = __esm({
264
- "../protocol/dist/args/hover.js"() {
265
- "use strict";
266
- init_dist();
267
- init_shared();
268
- }
269
- });
270
-
271
- // ../protocol/dist/args/network.js
272
- function parseFilter(obj) {
273
- const out = {};
274
- const filter = optString(obj, "filter");
275
- if (filter)
276
- out.filter = filter;
277
- const method = optString(obj, "method");
278
- if (method)
279
- out.method = method;
280
- const limit = optNumber(obj, "limit");
281
- if (limit !== void 0)
282
- out.limit = limit;
283
- const status = obj.status;
284
- if (status !== void 0 && status !== null) {
285
- if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
286
- throw new RelayError({
287
- code: "invalid_arguments",
288
- message: `chrome_network: invalid status ${JSON.stringify(status)}. Expected one of: ${VALID_STATUSES.join(", ")}.`,
289
- tool: TOOL_NAMES.NETWORK,
290
- phase: "parse_status",
291
- details: { received: status, validChoices: VALID_STATUSES },
292
- retryable: false
293
- });
294
- }
295
- out.status = status;
296
- }
297
- return out;
298
- }
299
- function parseChromeNetworkArgs(input) {
300
- const obj = asObject(input, TOOL_NAMES.NETWORK);
301
- const target = parseTargetArgs(obj);
302
- const rawAction = obj.action;
303
- const action = typeof rawAction === "string" ? rawAction : "read";
304
- if (action === "clear") {
305
- return { ...target, action: "clear" };
306
- }
307
- if (action === "body") {
308
- const requestId = optString(obj, "requestId");
309
- if (!requestId) {
310
- throw new RelayError({
311
- code: "invalid_arguments",
312
- message: "chrome_network body requires `requestId` (a non-empty string).",
313
- tool: TOOL_NAMES.NETWORK,
314
- phase: "parse_arguments",
315
- details: { field: "requestId", received: obj.requestId },
316
- retryable: false
317
- });
318
- }
319
- const out = {
320
- ...target,
321
- action: "body",
322
- requestId
323
- };
324
- const full = optBool(obj, "full");
325
- if (full !== void 0)
326
- out.full = full;
327
- const head = optPositiveNumber(obj, "head", TOOL_NAMES.NETWORK);
328
- if (head !== void 0)
329
- out.head = head;
330
- return out;
331
- }
332
- if (action === "har") {
333
- const withBodies = optBool(obj, "withBodies");
334
- const bestEffortBodies = optBool(obj, "bestEffortBodies");
335
- return {
336
- ...target,
337
- action: "har",
338
- ...withBodies !== void 0 ? { withBodies } : {},
339
- ...bestEffortBodies !== void 0 ? { bestEffortBodies } : {},
340
- ...parseFilter(obj)
341
- };
342
- }
343
- if (action === "read") {
344
- return { ...target, action: "read", ...parseFilter(obj) };
345
- }
346
- throw new RelayError({
347
- code: "invalid_arguments",
348
- message: `chrome_network: unknown action "${action}". Expected read | clear | har | body.`,
349
- tool: TOOL_NAMES.NETWORK,
350
- phase: "parse_action",
351
- details: { received: action, validChoices: ["read", "clear", "har", "body"] },
352
- retryable: false
353
- });
354
- }
355
- var VALID_STATUSES;
356
- var init_network = __esm({
357
- "../protocol/dist/args/network.js"() {
358
- "use strict";
359
- init_dist();
360
- init_shared();
361
- VALID_STATUSES = ["ok", "redirect", "client_error", "server_error", "failed"];
362
- }
363
- });
364
-
365
231
  // ../protocol/dist/args/simple.js
366
232
  function parseGetWindowsAndTabsArgs(input) {
367
233
  if (input !== void 0 && input !== null)
@@ -381,6 +247,25 @@ function parseChromeReadPageArgs(input) {
381
247
  out.interactiveOnly = io;
382
248
  return out;
383
249
  }
250
+ function rejectMixedAddressing(tool, obj, modes) {
251
+ const present = [];
252
+ if (modes.ref)
253
+ present.push("ref");
254
+ if (modes.selector)
255
+ present.push("selector");
256
+ if (modes.coords)
257
+ present.push("x/y");
258
+ if (present.length > 1) {
259
+ throw new RelayError({
260
+ code: "invalid_arguments",
261
+ message: `${tool}: ${present.join(" + ")} are mutually exclusive \u2014 pass exactly one addressing mode.`,
262
+ tool,
263
+ phase: "parse_arguments",
264
+ details: { received: present },
265
+ retryable: false
266
+ });
267
+ }
268
+ }
384
269
  function parseChromeClickArgs(input) {
385
270
  const obj = asObject(input, TOOL_NAMES.CLICK);
386
271
  const target = parseTargetArgs(obj, TOOL_NAMES.CLICK);
@@ -396,19 +281,24 @@ function parseChromeClickArgs(input) {
396
281
  retryable: false
397
282
  });
398
283
  }
284
+ const ref = optString(obj, "ref", TOOL_NAMES.CLICK);
285
+ const selector = optString(obj, "selector", TOOL_NAMES.CLICK);
286
+ rejectMixedAddressing(TOOL_NAMES.CLICK, obj, { ref, selector, coords: x !== void 0 });
287
+ if (ref) {
288
+ return { ...target, kind: "ref", ref };
289
+ }
399
290
  if (x !== void 0 && y !== void 0) {
400
291
  return { ...target, kind: "coords", x, y };
401
292
  }
402
- const selector = optString(obj, "selector", TOOL_NAMES.CLICK);
403
293
  if (selector) {
404
294
  return { ...target, kind: "selector", selector };
405
295
  }
406
296
  throw new RelayError({
407
297
  code: "invalid_arguments",
408
- message: "chrome_click_element requires either a selector or x AND y.",
298
+ message: "chrome_click_element requires a @ref, a selector, or x AND y.",
409
299
  tool: TOOL_NAMES.CLICK,
410
300
  phase: "parse_arguments",
411
- details: { received: { selector: obj.selector, x: obj.x, y: obj.y } },
301
+ details: { received: { ref: obj.ref, selector: obj.selector, x: obj.x, y: obj.y } },
412
302
  retryable: false
413
303
  });
414
304
  }
@@ -424,10 +314,18 @@ function parseChromeFillArgs(input) {
424
314
  retryable: false
425
315
  });
426
316
  }
317
+ const target = parseTargetArgs(obj);
318
+ const ref = optString(obj, "ref", TOOL_NAMES.FILL);
319
+ const selector = optString(obj, "selector", TOOL_NAMES.FILL);
320
+ rejectMixedAddressing(TOOL_NAMES.FILL, obj, { ref, selector });
321
+ if (ref) {
322
+ return { ...target, kind: "ref", ref, value: obj.value };
323
+ }
427
324
  return {
325
+ ...target,
326
+ kind: "selector",
428
327
  selector: requireString(obj, "selector", TOOL_NAMES.FILL),
429
- value: obj.value,
430
- ...parseTargetArgs(obj)
328
+ value: obj.value
431
329
  };
432
330
  }
433
331
  function parseChromeKeyboardArgs(input) {
@@ -444,8 +342,12 @@ function parseChromeTypeArgs(input) {
444
342
  ...parseTargetArgs(obj)
445
343
  };
446
344
  const selector = optString(obj, "selector");
345
+ const ref = optString(obj, "ref", TOOL_NAMES.TYPE);
346
+ rejectMixedAddressing(TOOL_NAMES.TYPE, obj, { ref, selector });
447
347
  if (selector)
448
348
  out.selector = selector;
349
+ if (ref)
350
+ out.ref = ref;
449
351
  return out;
450
352
  }
451
353
  function parseChromeEvaluateArgs(input) {
@@ -525,6 +427,23 @@ function parseChromeClickAxArgs(input) {
525
427
  }
526
428
  return { node, ...parseTargetArgs(obj) };
527
429
  }
430
+ function parseChromeSnapshotArgs(input) {
431
+ const obj = asObject(input, TOOL_NAMES.SNAPSHOT);
432
+ const out = { ...parseTargetArgs(obj, TOOL_NAMES.SNAPSHOT) };
433
+ const io = optBool(obj, "interactiveOnly", TOOL_NAMES.SNAPSHOT);
434
+ if (io !== void 0)
435
+ out.interactiveOnly = io;
436
+ const depth = optPositiveNumber(obj, "depth", TOOL_NAMES.SNAPSHOT);
437
+ if (depth !== void 0)
438
+ out.depth = depth;
439
+ const scope = optString(obj, "scope", TOOL_NAMES.SNAPSHOT);
440
+ if (scope)
441
+ out.scope = scope;
442
+ const urls = optBool(obj, "urls", TOOL_NAMES.SNAPSHOT);
443
+ if (urls !== void 0)
444
+ out.urls = urls;
445
+ return out;
446
+ }
528
447
  function parseChromeScreenshotArgs(input) {
529
448
  const obj = asObject(input, TOOL_NAMES.SCREENSHOT);
530
449
  const out = { ...parseTargetArgs(obj, TOOL_NAMES.SCREENSHOT) };
@@ -553,6 +472,146 @@ var init_simple = __esm({
553
472
  }
554
473
  });
555
474
 
475
+ // ../protocol/dist/args/hover.js
476
+ function parseChromeHoverArgs(input) {
477
+ const obj = asObject(input, TOOL_NAMES.HOVER);
478
+ const target = parseTargetArgs(obj, TOOL_NAMES.HOVER);
479
+ const x = optNumber(obj, "x", TOOL_NAMES.HOVER);
480
+ const y = optNumber(obj, "y", TOOL_NAMES.HOVER);
481
+ if (x !== void 0 !== (y !== void 0)) {
482
+ throw new RelayError({
483
+ code: "invalid_arguments",
484
+ message: "chrome_hover: pass BOTH x and y, or neither (selector mode).",
485
+ tool: TOOL_NAMES.HOVER,
486
+ phase: "parse_arguments",
487
+ details: { received: { x: obj.x, y: obj.y } },
488
+ retryable: false
489
+ });
490
+ }
491
+ const ref = optString(obj, "ref", TOOL_NAMES.HOVER);
492
+ const selector = optString(obj, "selector", TOOL_NAMES.HOVER);
493
+ rejectMixedAddressing(TOOL_NAMES.HOVER, obj, { ref, selector, coords: x !== void 0 });
494
+ if (ref) {
495
+ return { ...target, kind: "ref", ref };
496
+ }
497
+ if (x !== void 0 && y !== void 0) {
498
+ return { ...target, kind: "coords", x, y };
499
+ }
500
+ if (selector) {
501
+ return { ...target, kind: "selector", selector };
502
+ }
503
+ throw new RelayError({
504
+ code: "invalid_arguments",
505
+ message: "chrome_hover requires a @ref, a selector, or x AND y.",
506
+ tool: TOOL_NAMES.HOVER,
507
+ phase: "parse_arguments",
508
+ details: { received: { ref: obj.ref, selector: obj.selector, x: obj.x, y: obj.y } },
509
+ retryable: false
510
+ });
511
+ }
512
+ var init_hover = __esm({
513
+ "../protocol/dist/args/hover.js"() {
514
+ "use strict";
515
+ init_dist();
516
+ init_shared();
517
+ init_simple();
518
+ }
519
+ });
520
+
521
+ // ../protocol/dist/args/network.js
522
+ function parseFilter(obj) {
523
+ const out = {};
524
+ const filter = optString(obj, "filter");
525
+ if (filter)
526
+ out.filter = filter;
527
+ const method = optString(obj, "method");
528
+ if (method)
529
+ out.method = method;
530
+ const limit = optNumber(obj, "limit");
531
+ if (limit !== void 0)
532
+ out.limit = limit;
533
+ const status = obj.status;
534
+ if (status !== void 0 && status !== null) {
535
+ if (typeof status !== "string" || !VALID_STATUSES.includes(status)) {
536
+ throw new RelayError({
537
+ code: "invalid_arguments",
538
+ message: `chrome_network: invalid status ${JSON.stringify(status)}. Expected one of: ${VALID_STATUSES.join(", ")}.`,
539
+ tool: TOOL_NAMES.NETWORK,
540
+ phase: "parse_status",
541
+ details: { received: status, validChoices: VALID_STATUSES },
542
+ retryable: false
543
+ });
544
+ }
545
+ out.status = status;
546
+ }
547
+ return out;
548
+ }
549
+ function parseChromeNetworkArgs(input) {
550
+ const obj = asObject(input, TOOL_NAMES.NETWORK);
551
+ const target = parseTargetArgs(obj);
552
+ const rawAction = obj.action;
553
+ const action = typeof rawAction === "string" ? rawAction : "read";
554
+ if (action === "clear") {
555
+ return { ...target, action: "clear" };
556
+ }
557
+ if (action === "body") {
558
+ const requestId = optString(obj, "requestId");
559
+ if (!requestId) {
560
+ throw new RelayError({
561
+ code: "invalid_arguments",
562
+ message: "chrome_network body requires `requestId` (a non-empty string).",
563
+ tool: TOOL_NAMES.NETWORK,
564
+ phase: "parse_arguments",
565
+ details: { field: "requestId", received: obj.requestId },
566
+ retryable: false
567
+ });
568
+ }
569
+ const out = {
570
+ ...target,
571
+ action: "body",
572
+ requestId
573
+ };
574
+ const full = optBool(obj, "full");
575
+ if (full !== void 0)
576
+ out.full = full;
577
+ const head = optPositiveNumber(obj, "head", TOOL_NAMES.NETWORK);
578
+ if (head !== void 0)
579
+ out.head = head;
580
+ return out;
581
+ }
582
+ if (action === "har") {
583
+ const withBodies = optBool(obj, "withBodies");
584
+ const bestEffortBodies = optBool(obj, "bestEffortBodies");
585
+ return {
586
+ ...target,
587
+ action: "har",
588
+ ...withBodies !== void 0 ? { withBodies } : {},
589
+ ...bestEffortBodies !== void 0 ? { bestEffortBodies } : {},
590
+ ...parseFilter(obj)
591
+ };
592
+ }
593
+ if (action === "read") {
594
+ return { ...target, action: "read", ...parseFilter(obj) };
595
+ }
596
+ throw new RelayError({
597
+ code: "invalid_arguments",
598
+ message: `chrome_network: unknown action "${action}". Expected read | clear | har | body.`,
599
+ tool: TOOL_NAMES.NETWORK,
600
+ phase: "parse_action",
601
+ details: { received: action, validChoices: ["read", "clear", "har", "body"] },
602
+ retryable: false
603
+ });
604
+ }
605
+ var VALID_STATUSES;
606
+ var init_network = __esm({
607
+ "../protocol/dist/args/network.js"() {
608
+ "use strict";
609
+ init_dist();
610
+ init_shared();
611
+ VALID_STATUSES = ["ok", "redirect", "client_error", "server_error", "failed"];
612
+ }
613
+ });
614
+
556
615
  // ../protocol/dist/args/multi.js
557
616
  function invalidAction(tool, received, expected) {
558
617
  throw new RelayError({
@@ -896,6 +955,8 @@ function parseToolArgs(name, input) {
896
955
  return parseChromeHoverArgs(input);
897
956
  case "chrome_screencast":
898
957
  return parseChromeScreencastArgs(input);
958
+ case "chrome_snapshot":
959
+ return parseChromeSnapshotArgs(input);
899
960
  }
900
961
  const exhaustive = name;
901
962
  return exhaustive;
@@ -936,6 +997,78 @@ var init_limits = __esm({
936
997
  }
937
998
  });
938
999
 
1000
+ // ../protocol/dist/snapshot.js
1001
+ function parseRefToken(input) {
1002
+ const m = REF_TOKEN.exec(input.trim());
1003
+ return m ? m[1] : null;
1004
+ }
1005
+ function formatRefToken(ref) {
1006
+ return `@${ref}`;
1007
+ }
1008
+ function renderAttrs(node) {
1009
+ const parts = [];
1010
+ const attrs = node.attrs;
1011
+ if (attrs) {
1012
+ for (const key of ATTR_ORDER) {
1013
+ if (key === "url")
1014
+ continue;
1015
+ const v = attrs[key];
1016
+ if (v === void 0)
1017
+ continue;
1018
+ if (v === true)
1019
+ parts.push(key);
1020
+ else
1021
+ parts.push(`${key}=${v}`);
1022
+ }
1023
+ }
1024
+ if (node.ref)
1025
+ parts.push(`ref=${node.ref}`);
1026
+ if (attrs?.url)
1027
+ parts.push(`url=${attrs.url}`);
1028
+ return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
1029
+ }
1030
+ function renderNode(node, depth, out) {
1031
+ const indent = " ".repeat(depth);
1032
+ let line = `${indent}- ${node.role}`;
1033
+ if (node.name)
1034
+ line += ` ${JSON.stringify(node.name)}`;
1035
+ line += renderAttrs(node);
1036
+ if (node.value !== void 0 && node.value !== node.name) {
1037
+ line += `: ${node.value}`;
1038
+ }
1039
+ out.push(line);
1040
+ for (const child of node.children ?? []) {
1041
+ renderNode(child, depth + 1, out);
1042
+ }
1043
+ }
1044
+ function renderSnapshot(data) {
1045
+ const nodes = data.nodes ?? [];
1046
+ const out = [`Page: ${data.title ?? ""}`, `URL: ${data.url ?? ""}`, `Tab: ${data.tabId ?? "?"}`, ""];
1047
+ for (const node of nodes)
1048
+ renderNode(node, 0, out);
1049
+ if (nodes.length === 0)
1050
+ out.push("(empty snapshot \u2014 page may still be loading)");
1051
+ return out.join("\n");
1052
+ }
1053
+ var REF_TOKEN, ATTR_ORDER;
1054
+ var init_snapshot = __esm({
1055
+ "../protocol/dist/snapshot.js"() {
1056
+ "use strict";
1057
+ REF_TOKEN = /^@(e\d+)$/;
1058
+ ATTR_ORDER = [
1059
+ "level",
1060
+ "checked",
1061
+ "expanded",
1062
+ "selected",
1063
+ "disabled",
1064
+ "required",
1065
+ "readonly",
1066
+ "pressed",
1067
+ "url"
1068
+ ];
1069
+ }
1070
+ });
1071
+
939
1072
  // ../protocol/dist/index.js
940
1073
  var dist_exports = {};
941
1074
  __export(dist_exports, {
@@ -961,6 +1094,7 @@ __export(dist_exports, {
961
1094
  TOOL_NAMES: () => TOOL_NAMES,
962
1095
  asObject: () => asObject,
963
1096
  coerceTabId: () => coerceTabId,
1097
+ formatRefToken: () => formatRefToken,
964
1098
  optBool: () => optBool,
965
1099
  optNonNegativeNumber: () => optNonNegativeNumber,
966
1100
  optNumber: () => optNumber,
@@ -982,13 +1116,17 @@ __export(dist_exports, {
982
1116
  parseChromeScreencastArgs: () => parseChromeScreencastArgs,
983
1117
  parseChromeScreenshotArgs: () => parseChromeScreenshotArgs,
984
1118
  parseChromeSelfReloadArgs: () => parseChromeSelfReloadArgs,
1119
+ parseChromeSnapshotArgs: () => parseChromeSnapshotArgs,
985
1120
  parseChromeSwitchTabArgs: () => parseChromeSwitchTabArgs,
986
1121
  parseChromeTypeArgs: () => parseChromeTypeArgs,
987
1122
  parseChromeViewportArgs: () => parseChromeViewportArgs,
988
1123
  parseChromeWorkspaceArgs: () => parseChromeWorkspaceArgs,
989
1124
  parseGetWindowsAndTabsArgs: () => parseGetWindowsAndTabsArgs,
1125
+ parseRefToken: () => parseRefToken,
990
1126
  parseTargetArgs: () => parseTargetArgs,
991
1127
  parseToolArgs: () => parseToolArgs,
1128
+ rejectMixedAddressing: () => rejectMixedAddressing,
1129
+ renderSnapshot: () => renderSnapshot,
992
1130
  requireString: () => requireString,
993
1131
  toBridgeError: () => toBridgeError
994
1132
  });
@@ -1010,6 +1148,7 @@ var init_dist = __esm({
1010
1148
  "use strict";
1011
1149
  init_args();
1012
1150
  init_limits();
1151
+ init_snapshot();
1013
1152
  NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
1014
1153
  DEFAULT_HTTP_PORT = 12122;
1015
1154
  CHROME_WEB_STORE_EXTENSION_ID = "cpdiapbifblhlcpnmlmfpgfjlacebokb";
@@ -1066,7 +1205,11 @@ var init_dist = __esm({
1066
1205
  // CSS transitions, fade-ins, focus-ring motion) — at the cost of requiring
1067
1206
  // the tab to be ACTIVE (Chrome doesn't paint backgrounded tabs). See
1068
1207
  // docs/recording.md for the active-tab matrix.
1069
- SCREENCAST: "chrome_screencast"
1208
+ SCREENCAST: "chrome_screencast",
1209
+ // Unified page snapshot (adoption-spec Change 1) — AX tree + cursor-
1210
+ // interactive sweep, one ref space, compact text rendered CLI-side.
1211
+ // Supersedes chrome_read_page and chrome_ax, which now alias to it.
1212
+ SNAPSHOT: "chrome_snapshot"
1070
1213
  };
1071
1214
  RelayError = class extends Error {
1072
1215
  code;
@@ -1101,7 +1244,7 @@ var init_dist = __esm({
1101
1244
  import { Command } from "commander";
1102
1245
 
1103
1246
  // src/index.ts
1104
- var CHROME_RELAY_VERSION = true ? "0.5.22" : "0.0.0-dev";
1247
+ var CHROME_RELAY_VERSION = true ? "0.6.0" : "0.0.0-dev";
1105
1248
 
1106
1249
  // src/commands/shared.ts
1107
1250
  init_dist();
@@ -1281,6 +1424,55 @@ function getChromiumBrowserTargets() {
1281
1424
  { label: "Opera", installRoot: path.join(config, "opera"), manifestDir: path.join(config, "opera/NativeMessagingHosts") }
1282
1425
  ];
1283
1426
  }
1427
+ if (process.platform === "win32") {
1428
+ const local = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
1429
+ const roaming = process.env.APPDATA || path.join(home, "AppData", "Roaming");
1430
+ const manifestBase = path.join(APP_DIR, "NativeMessagingHosts");
1431
+ return [
1432
+ {
1433
+ label: "Google Chrome",
1434
+ installRoot: path.join(local, "Google", "Chrome", "User Data"),
1435
+ manifestDir: path.join(manifestBase, "Google Chrome"),
1436
+ registryKey: `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1437
+ },
1438
+ {
1439
+ label: "Google Chrome Canary",
1440
+ installRoot: path.join(local, "Google", "Chrome SxS", "User Data"),
1441
+ manifestDir: path.join(manifestBase, "Google Chrome Canary"),
1442
+ registryKey: `HKCU\\Software\\Google\\Chrome SxS\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1443
+ },
1444
+ {
1445
+ label: "Chromium",
1446
+ installRoot: path.join(local, "Chromium", "User Data"),
1447
+ manifestDir: path.join(manifestBase, "Chromium"),
1448
+ registryKey: `HKCU\\Software\\Chromium\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1449
+ },
1450
+ {
1451
+ label: "Microsoft Edge",
1452
+ installRoot: path.join(local, "Microsoft", "Edge", "User Data"),
1453
+ manifestDir: path.join(manifestBase, "Microsoft Edge"),
1454
+ registryKey: `HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1455
+ },
1456
+ {
1457
+ label: "Brave",
1458
+ installRoot: path.join(local, "BraveSoftware", "Brave-Browser", "User Data"),
1459
+ manifestDir: path.join(manifestBase, "Brave"),
1460
+ registryKey: `HKCU\\Software\\BraveSoftware\\Brave-Browser\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1461
+ },
1462
+ {
1463
+ label: "Vivaldi",
1464
+ installRoot: path.join(local, "Vivaldi", "User Data"),
1465
+ manifestDir: path.join(manifestBase, "Vivaldi"),
1466
+ registryKey: `HKCU\\Software\\Vivaldi\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1467
+ },
1468
+ {
1469
+ label: "Opera",
1470
+ installRoot: path.join(roaming, "Opera Software", "Opera Stable"),
1471
+ manifestDir: path.join(manifestBase, "Opera"),
1472
+ registryKey: `HKCU\\Software\\Opera Software\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`
1473
+ }
1474
+ ];
1475
+ }
1284
1476
  throw new Error(`Unsupported platform for install: ${process.platform}`);
1285
1477
  }
1286
1478
  async function pathExists(p) {
@@ -1306,6 +1498,14 @@ function getDistDir() {
1306
1498
  }
1307
1499
  async function writeWrapperScript(hostPath) {
1308
1500
  await mkdir(APP_DIR, { recursive: true });
1501
+ if (process.platform === "win32") {
1502
+ const wrapperPath2 = path.join(APP_DIR, "run-host.cmd");
1503
+ const content2 = `@echo off\r
1504
+ "${process.execPath}" "${hostPath}"\r
1505
+ `;
1506
+ await writeFile(wrapperPath2, content2, "utf8");
1507
+ return wrapperPath2;
1508
+ }
1309
1509
  const wrapperPath = path.join(APP_DIR, "run-host.sh");
1310
1510
  const content = `#!/bin/sh
1311
1511
  exec "${process.execPath}" "${hostPath}"
@@ -1314,6 +1514,31 @@ exec "${process.execPath}" "${hostPath}"
1314
1514
  await chmod(wrapperPath, 493);
1315
1515
  return wrapperPath;
1316
1516
  }
1517
+ function registerWindowsNativeHost(registryKey, manifestPath) {
1518
+ const res = spawnSync("reg.exe", [
1519
+ "ADD",
1520
+ registryKey,
1521
+ "/ve",
1522
+ "/t",
1523
+ "REG_SZ",
1524
+ "/d",
1525
+ manifestPath,
1526
+ "/f"
1527
+ ], { encoding: "utf8" });
1528
+ if (res.status !== 0) {
1529
+ const detail = (res.stderr || res.stdout || "").trim();
1530
+ throw new Error(`failed to register ${registryKey}${detail ? `: ${detail}` : ""}`);
1531
+ }
1532
+ }
1533
+ function readWindowsNativeHostRegistry(registryKey) {
1534
+ const res = spawnSync("reg.exe", ["QUERY", registryKey, "/ve"], { encoding: "utf8" });
1535
+ if (res.status !== 0 || !res.stdout) return null;
1536
+ for (const line of res.stdout.split("\n")) {
1537
+ const match = line.match(/REG_SZ\s+(.+?)\s*$/);
1538
+ if (match?.[1]) return match[1].trim();
1539
+ }
1540
+ return null;
1541
+ }
1317
1542
  async function writeManifestsForBrowsers(wrapperPath, browsers) {
1318
1543
  const manifest = {
1319
1544
  name: NATIVE_HOST_NAME,
@@ -1329,7 +1554,11 @@ async function writeManifestsForBrowsers(wrapperPath, browsers) {
1329
1554
  await mkdir(target.manifestDir, { recursive: true });
1330
1555
  const manifestPath = path.join(target.manifestDir, `${NATIVE_HOST_NAME}.json`);
1331
1556
  await writeFile(manifestPath, body, "utf8");
1332
- written.push({ browser: target.label, manifestPath });
1557
+ if (process.platform === "win32") {
1558
+ if (!target.registryKey) throw new Error(`missing registry key for ${target.label}`);
1559
+ registerWindowsNativeHost(target.registryKey, manifestPath);
1560
+ }
1561
+ written.push({ browser: target.label, manifestPath, registryKey: target.registryKey });
1333
1562
  }
1334
1563
  return written;
1335
1564
  }
@@ -1373,6 +1602,9 @@ async function runInstall() {
1373
1602
  console.log(`Manifests written:`);
1374
1603
  for (const m of writtenManifests) {
1375
1604
  console.log(` \u2022 ${m.browser}: ${m.manifestPath}`);
1605
+ if (m.registryKey) {
1606
+ console.log(` registry: ${m.registryKey}`);
1607
+ }
1376
1608
  }
1377
1609
  console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
1378
1610
  console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
@@ -1382,7 +1614,7 @@ async function runInstall() {
1382
1614
  }
1383
1615
  async function runDoctor() {
1384
1616
  try {
1385
- const wrapperPath = path.join(APP_DIR, "run-host.sh");
1617
+ const wrapperPath = path.join(APP_DIR, process.platform === "win32" ? "run-host.cmd" : "run-host.sh");
1386
1618
  await stat(wrapperPath);
1387
1619
  console.log(`Wrapper present: yes`);
1388
1620
  const installed = await getInstalledBrowsers();
@@ -1402,6 +1634,14 @@ async function runDoctor() {
1402
1634
  console.log(` \u2022 ${target.label}: manifest MISSING (${manifestPath})`);
1403
1635
  continue;
1404
1636
  }
1637
+ if (process.platform === "win32" && target.registryKey) {
1638
+ const registered = readWindowsNativeHostRegistry(target.registryKey);
1639
+ if (path.normalize(registered || "") !== path.normalize(manifestPath)) {
1640
+ allHealthy = false;
1641
+ console.log(` \u2022 ${target.label}: registry MISSING/STALE (${target.registryKey})`);
1642
+ continue;
1643
+ }
1644
+ }
1405
1645
  try {
1406
1646
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1407
1647
  const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins : [];
@@ -1441,6 +1681,21 @@ async function runDoctor() {
1441
1681
 
1442
1682
  // src/release-notes.ts
1443
1683
  var RELEASE_NOTES = {
1684
+ "0.6.0": [
1685
+ "Unified `snapshot` with actionable @refs. `chrome-relay snapshot -i` renders the page as compact text (~4-5x smaller than the old `read -i`; 14 KB on the HN front page) \u2014 accessibility tree merged with a cursor-interactive sweep that catches div-soup clickables (cursor:pointer, onclick, tabindex, contenteditable) the AX tree misses. Every element gets a browser-unique @eN ref.",
1686
+ 'Refs are actionable everywhere: `click @e12`, `fill @e14 "v"`, `hover @e3`, `type -s @e7`. A ref carries its tab \u2014 no --tab needed, a contradicting --tab is target_conflict, so an agent can never click into the page the user is reading. Resolution is backendNodeId fast-path with role+name+nth healing on same-page DOM churn (healed clicks report `healed: true`). Refs reach inside shadow DOM, where CSS selectors can\'t.',
1687
+ "Refs die on real navigation, deliberately: Chromium reuses backendNodeId integers in the new document, so a stale ref could silently click an unrelated element. Dead refs return the new `error.code = stale_ref` with a re-snapshot hint. SPA route changes (pushState) keep refs alive.",
1688
+ "BREAKING: `read` and `ax` output changed. They are now aliases for `chrome_snapshot` and return the unified snapshot format (compact text / SnapshotData JSON) \u2014 the old `{elements: [{selector, bounds, ...}]}` and CompactAxNode shapes are gone, deliberately: keeping them would mean keeping the deleted walkers in parallel. Both print a deprecation notice; alias removal lands next minor. `click-ax` still works on raw backendNodeIds (now visible in `snapshot --json` refs).",
1689
+ "Ref clicks hit-test before dispatch: if an unrelated element (overlay, sticky header, modal) owns the click point, the click fails with the new `error.code = click_intercepted` naming the interceptor \u2014 instead of reporting success while the overlay ate the click. Same-lineage hits (inner text, wrapping label) pass; fill/type skip the check (writing to a covered input is legitimate).",
1690
+ "`snapshot --scope <css>` now bounds the cursor-interactive sweep too \u2014 a scoped snapshot never returns actionable refs for elements outside the scope subtree.",
1691
+ "Error hygiene: in-page failures (bad selector, malformed CSS, zero-size element, unfocusable element) now map to structured codes (element_not_found, invalid_arguments) instead of internal_error with a raw JS stack.",
1692
+ "Snapshot flags: -i (ref-bearing only), -d <n> (depth cap), -s <css> (scope to subtree), -u (include hrefs), --json (structured envelope incl. the refs map with backendNodeIds)."
1693
+ ],
1694
+ "0.5.23": [
1695
+ "Windows native-host install. `chrome-relay install` now supports `win32`: it writes a `run-host.cmd` wrapper, writes browser-specific native-messaging manifests under `~/.chrome-relay/NativeMessagingHosts`, and registers each manifest through HKCU registry keys.",
1696
+ "Detected Windows browsers: Chrome, Chrome Canary, Chromium, Edge, Brave, Vivaldi, and Opera. Detection uses the browser profile directory; if none are detected, install falls back to Chrome so a later Chrome install can find the host after re-running install.",
1697
+ "`chrome-relay doctor` now checks the Windows wrapper and registry entries, so stale or missing native-host registration points at the failing browser instead of only showing a generic extension connection failure."
1698
+ ],
1444
1699
  "0.5.22": [
1445
1700
  "Multi-browser install. `chrome-relay install` now writes the native-messaging manifest into every detected Chromium-fork browser's NativeMessagingHosts dir, not just Google Chrome's. Detected: Chrome, Chrome Canary, Chromium, Edge, Brave, Vivaldi, Arc, Opera (macOS + Linux paths). Detection is parent-dir existence \u2014 we never speculatively create profile dirs for browsers that aren't installed.",
1446
1701
  "Why this matters: the extension installs fine via Chrome Web Store in any Chromium fork, but the bridge silently failed because the host manifest was only at Chrome's path. Arc + Brave users hit `connectNative()` errors with no obvious cause.",
@@ -1761,34 +2016,48 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1761
2016
  }
1762
2017
 
1763
2018
  // src/commands/input.ts
2019
+ init_dist();
2020
+ function addressArg(value) {
2021
+ const ref = parseRefToken(value);
2022
+ return ref ? { ref } : { selector: value };
2023
+ }
1764
2024
  function registerInput(ctx) {
1765
2025
  const { program, withBase, run } = ctx;
1766
2026
  tabOpt(
1767
- program.command("click [selector]").description("Click an element. Pass a CSS selector OR --x and --y for a coordinate click.").option("--x <px>", "explicit x coordinate (CSS pixels); requires --y", (v) => Number(v)).option("--y <px>", "explicit y coordinate (CSS pixels); requires --x", (v) => Number(v)).addHelpText(
2027
+ program.command("click [target]").description("Click an element. Pass a @ref from `snapshot`, a CSS selector, OR --x/--y coordinates.").option("--x <px>", "explicit x coordinate (CSS pixels); requires --y", (v) => Number(v)).option("--y <px>", "explicit y coordinate (CSS pixels); requires --x", (v) => Number(v)).addHelpText(
1768
2028
  "after",
1769
2029
  `
1770
2030
 
1771
2031
  Examples:
2032
+ chrome-relay click @e12
1772
2033
  chrome-relay click 'button[aria-label="Save"]'
1773
2034
  chrome-relay click --tab 123 --x 1327 --y 771
1774
2035
 
1775
- Pick selector mode when the element has a stable CSS query. Pick
1776
- coordinate mode when the page wraps content in unmarked divs (Cloudflare
1777
- dashboard, Vercel dashboard, etc.) and you got the rect from a prior
1778
- \`chrome-relay js\` call or a screenshot. See docs/clicking-strategies.md.
2036
+ Prefer @refs from \`chrome-relay snapshot\` \u2014 they carry their own tab and
2037
+ survive DOM churn (backendNodeId + role/name heal). CSS selectors for
2038
+ elements you know statically. Coordinates for canvas/SVG chart internals
2039
+ where no DOM handle exists. See docs/clicking-strategies.md.
1779
2040
  `
1780
2041
  )
1781
- ).action(async (selector, opts) => {
2042
+ ).action(async (target, opts) => {
1782
2043
  const extras = {};
1783
- if (selector) extras.selector = selector;
2044
+ if (target) Object.assign(extras, addressArg(target));
1784
2045
  if (typeof opts.x === "number") extras.x = opts.x;
1785
2046
  if (typeof opts.y === "number") extras.y = opts.y;
1786
2047
  await run("chrome_click_element", withBase(opts, extras));
1787
2048
  });
1788
2049
  tabOpt(
1789
- program.command("fill <selector> <value>").description("Fill an input or textarea.")
1790
- ).action(async (selector, value, opts) => {
1791
- await run("chrome_fill_or_select", withBase(opts, { selector, value }));
2050
+ program.command("fill <target> <value>").description("Fill an input or textarea. Target is a @ref from `snapshot` or a CSS selector.").addHelpText(
2051
+ "after",
2052
+ `
2053
+
2054
+ Examples:
2055
+ chrome-relay fill @e4 "kushal@example.com"
2056
+ chrome-relay fill 'input[name="email"]' "kushal@example.com"
2057
+ `
2058
+ )
2059
+ ).action(async (target, value, opts) => {
2060
+ await run("chrome_fill_or_select", withBase(opts, { ...addressArg(target), value }));
1792
2061
  });
1793
2062
  tabOpt(
1794
2063
  program.command("keys <keys>").description("Press a single key or chord via trusted CDP input (e.g. Enter, Cmd+K).").addHelpText(
@@ -1808,7 +2077,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
1808
2077
  await run("chrome_keyboard", withBase(opts, { keys }));
1809
2078
  });
1810
2079
  tabOpt(
1811
- 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(
2080
+ program.command("type <text>").description("Insert text via trusted CDP input. Works in contenteditable / Draft.js / Lexical.").option("-s, --selector <target>", "focus this element first (@ref or CSS selector)").addHelpText(
1812
2081
  "after",
1813
2082
  `
1814
2083
 
@@ -1825,7 +2094,7 @@ When to pick which:
1825
2094
  )
1826
2095
  ).action(async (text, opts) => {
1827
2096
  const extras = { text };
1828
- if (opts.selector) extras.selector = opts.selector;
2097
+ if (opts.selector) Object.assign(extras, addressArg(opts.selector));
1829
2098
  await run("chrome_type", withBase(opts, extras));
1830
2099
  });
1831
2100
  tabOpt(
@@ -1850,7 +2119,7 @@ Notes:
1850
2119
  await run("chrome_evaluate", withBase(opts, extras));
1851
2120
  });
1852
2121
  tabOpt(
1853
- 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(
2122
+ program.command("hover [target]").description("Move the pointer over an element (@ref or CSS selector) 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(
1854
2123
  "after",
1855
2124
  `
1856
2125
 
@@ -1862,9 +2131,9 @@ Use before screencast to capture hover-driven micro-states (button glow,
1862
2131
  tooltip appearance, etc.) that a bare click would skip past too quickly.
1863
2132
  `
1864
2133
  )
1865
- ).action(async (selector, opts) => {
2134
+ ).action(async (target, opts) => {
1866
2135
  const extras = {};
1867
- if (selector) extras.selector = selector;
2136
+ if (target) Object.assign(extras, addressArg(target));
1868
2137
  if (typeof opts.x === "number") extras.x = opts.x;
1869
2138
  if (typeof opts.y === "number") extras.y = opts.y;
1870
2139
  await run("chrome_hover", withBase(opts, extras));
@@ -1872,9 +2141,56 @@ tooltip appearance, etc.) that a bare click would skip past too quickly.
1872
2141
  }
1873
2142
 
1874
2143
  // src/commands/capture.ts
2144
+ init_dist();
1875
2145
  import { writeFileSync } from "fs";
2146
+ function printSnapshot(result, asJson) {
2147
+ if (asJson) {
2148
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2149
+ return;
2150
+ }
2151
+ process.stdout.write(renderSnapshot(result) + "\n");
2152
+ }
2153
+ function exitWithError(error) {
2154
+ if (error instanceof RelayError) {
2155
+ process.stderr.write(error.message + "\n");
2156
+ process.stderr.write(JSON.stringify({ relayError: error.toBridgeError() }, null, 2) + "\n");
2157
+ } else {
2158
+ process.stderr.write((error instanceof Error ? error.message : String(error)) + "\n");
2159
+ }
2160
+ process.exit(1);
2161
+ }
1876
2162
  function registerCapture(ctx) {
1877
2163
  const { program, withBase, run } = ctx;
2164
+ tabOpt(
2165
+ program.command("snapshot").description("Page snapshot with actionable @refs \u2014 accessibility tree + cursor-interactive sweep, compact text.").option("-i, --interactive", "only ref-bearing elements (buttons, links, inputs, named content, clickables)").option("-d, --depth <n>", "truncate the tree at this depth", (v) => Number(v)).option("-s, --scope <css>", "restrict to the subtree of the first CSS match").option("-u, --urls", "include link hrefs as url= attrs").option("--json", "structured output: { title, url, tabId, nodes, refs }").addHelpText(
2166
+ "after",
2167
+ `
2168
+
2169
+ Examples:
2170
+ chrome-relay snapshot -i # see the page, get @refs
2171
+ chrome-relay click @e12 # act on a ref \u2014 no --tab needed
2172
+ chrome-relay snapshot -i -s "#main" # scope to a subtree
2173
+ chrome-relay snapshot --json # machine-readable envelope
2174
+
2175
+ The core loop: snapshot -i \u2192 click/fill @eN \u2192 snapshot -i again after the
2176
+ page changes. Refs carry their own tab and heal across DOM churn
2177
+ (backendNodeId fast path + role/name re-find); a dead ref returns
2178
+ error.code = stale_ref, which means: re-run snapshot.
2179
+ `
2180
+ )
2181
+ ).action(async (opts) => {
2182
+ const extras = {};
2183
+ if (opts.interactive) extras.interactiveOnly = true;
2184
+ if (typeof opts.depth === "number") extras.depth = opts.depth;
2185
+ if (opts.scope) extras.scope = opts.scope;
2186
+ if (opts.urls) extras.urls = true;
2187
+ try {
2188
+ const result = await callTool("chrome_snapshot", withBase(opts, extras));
2189
+ printSnapshot(result, opts.json === true);
2190
+ } catch (error) {
2191
+ exitWithError(error);
2192
+ }
2193
+ });
1878
2194
  tabOpt(
1879
2195
  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(
1880
2196
  "after",
@@ -1914,43 +2230,37 @@ full-tab screenshot when an agent only needs to see one component.
1914
2230
  }
1915
2231
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1916
2232
  } catch (error) {
1917
- process.stderr.write(
1918
- (error instanceof Error ? error.message : String(error)) + "\n"
1919
- );
1920
- process.exit(1);
2233
+ exitWithError(error);
1921
2234
  }
1922
2235
  });
1923
2236
  tabOpt(
1924
- program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
2237
+ program.command("read").description("[deprecated \u2014 use `snapshot`] Alias for the unified snapshot.").option("-i, --interactive", "only ref-bearing elements").option("--json", "structured output")
1925
2238
  ).action(async (opts) => {
2239
+ process.stderr.write("[chrome-relay] deprecated: `read` is now an alias for `snapshot` (new output format). Use `chrome-relay snapshot`.\n");
1926
2240
  const extras = {};
1927
2241
  if (opts.interactive) extras.interactiveOnly = true;
1928
- await run("chrome_read_page", withBase(opts, extras));
2242
+ try {
2243
+ const result = await callTool("chrome_read_page", withBase(opts, extras));
2244
+ printSnapshot(result, opts.json === true);
2245
+ } catch (error) {
2246
+ exitWithError(error);
2247
+ }
1929
2248
  });
1930
2249
  tabOpt(
1931
- 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(
1932
- "after",
1933
- `
1934
-
1935
- Examples:
1936
- chrome-relay ax --tab 123
1937
- chrome-relay ax --tab 123 --interactive-only
1938
- chrome-relay ax --tab 123 --root main --interactive-only
1939
-
1940
- Notes:
1941
- Each node carries an "id" \u2014 that's the backendDOMNodeId. Pass it to
1942
- \`chrome-relay click-ax --node <id>\` to click without a CSS selector.
1943
- `
1944
- )
2250
+ program.command("ax").description("[deprecated \u2014 use `snapshot`] Alias for the unified snapshot.").option("-i, --interactive-only", "only ref-bearing elements").option("--root <role>", "(ignored \u2014 use `snapshot --scope <css>`)").option("--include-subframes", "(ignored \u2014 snapshot is top-frame only)").option("--json", "structured output")
1945
2251
  ).action(async (opts) => {
2252
+ process.stderr.write("[chrome-relay] deprecated: `ax` is now an alias for `snapshot` (new output format, one ref space). Use `chrome-relay snapshot`.\n");
1946
2253
  const extras = {};
1947
2254
  if (opts.interactiveOnly) extras.interactiveOnly = true;
1948
- if (opts.root) extras.rootRole = opts.root;
1949
- if (opts.includeSubframes) extras.includeSubframes = true;
1950
- await run("chrome_ax", withBase(opts, extras));
2255
+ try {
2256
+ const result = await callTool("chrome_ax", withBase(opts, extras));
2257
+ printSnapshot(result, opts.json === true);
2258
+ } catch (error) {
2259
+ exitWithError(error);
2260
+ }
1951
2261
  });
1952
2262
  tabOpt(
1953
- 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(
2263
+ program.command("click-ax").description("[deprecated \u2014 use `click @eN`] Click by raw backendDOMNodeId (from `snapshot --json` refs).").requiredOption("--node <id>", "backendDOMNodeId from `chrome-relay ax`", (v) => Number(v)).addHelpText(
1954
2264
  "after",
1955
2265
  `
1956
2266
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.22" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.6.0" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -56,7 +56,7 @@ function toBridgeError(unknownErr, fallbackTool) {
56
56
  }
57
57
 
58
58
  // src/index.ts
59
- var CHROME_RELAY_VERSION = true ? "0.5.22" : "0.0.0-dev";
59
+ var CHROME_RELAY_VERSION = true ? "0.6.0" : "0.0.0-dev";
60
60
 
61
61
  // src/release-notes.ts
62
62
  function compareSemver(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.22",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",