chrome-relay 0.5.21 → 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 +598 -213
- package/dist/index.js +1 -1
- package/dist/native-host.js +1 -1
- package/package.json +1 -1
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
|
|
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.
|
|
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();
|
|
@@ -1255,23 +1398,114 @@ function getDefaultAllowedOrigins() {
|
|
|
1255
1398
|
function formatKnownExtensionIds() {
|
|
1256
1399
|
return KNOWN_EXTENSION_IDS.map(([label, id]) => `${label}: ${id}`).join(", ");
|
|
1257
1400
|
}
|
|
1258
|
-
function
|
|
1401
|
+
function getChromiumBrowserTargets() {
|
|
1402
|
+
const home = os.homedir();
|
|
1259
1403
|
if (process.platform === "darwin") {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
"
|
|
1263
|
-
|
|
1404
|
+
const appSupport = path.join(home, "Library/Application Support");
|
|
1405
|
+
return [
|
|
1406
|
+
{ label: "Google Chrome", installRoot: path.join(appSupport, "Google/Chrome"), manifestDir: path.join(appSupport, "Google/Chrome/NativeMessagingHosts") },
|
|
1407
|
+
{ label: "Google Chrome Canary", installRoot: path.join(appSupport, "Google/Chrome Canary"), manifestDir: path.join(appSupport, "Google/Chrome Canary/NativeMessagingHosts") },
|
|
1408
|
+
{ label: "Chromium", installRoot: path.join(appSupport, "Chromium"), manifestDir: path.join(appSupport, "Chromium/NativeMessagingHosts") },
|
|
1409
|
+
{ label: "Microsoft Edge", installRoot: path.join(appSupport, "Microsoft Edge"), manifestDir: path.join(appSupport, "Microsoft Edge/NativeMessagingHosts") },
|
|
1410
|
+
{ label: "Brave", installRoot: path.join(appSupport, "BraveSoftware/Brave-Browser"), manifestDir: path.join(appSupport, "BraveSoftware/Brave-Browser/NativeMessagingHosts") },
|
|
1411
|
+
{ label: "Vivaldi", installRoot: path.join(appSupport, "Vivaldi"), manifestDir: path.join(appSupport, "Vivaldi/NativeMessagingHosts") },
|
|
1412
|
+
{ label: "Arc", installRoot: path.join(appSupport, "Arc/User Data"), manifestDir: path.join(appSupport, "Arc/User Data/NativeMessagingHosts") },
|
|
1413
|
+
{ label: "Opera", installRoot: path.join(appSupport, "com.operasoftware.Opera"), manifestDir: path.join(appSupport, "com.operasoftware.Opera/NativeMessagingHosts") }
|
|
1414
|
+
];
|
|
1264
1415
|
}
|
|
1265
1416
|
if (process.platform === "linux") {
|
|
1266
|
-
|
|
1417
|
+
const config = path.join(home, ".config");
|
|
1418
|
+
return [
|
|
1419
|
+
{ label: "Google Chrome", installRoot: path.join(config, "google-chrome"), manifestDir: path.join(config, "google-chrome/NativeMessagingHosts") },
|
|
1420
|
+
{ label: "Chromium", installRoot: path.join(config, "chromium"), manifestDir: path.join(config, "chromium/NativeMessagingHosts") },
|
|
1421
|
+
{ label: "Microsoft Edge", installRoot: path.join(config, "microsoft-edge"), manifestDir: path.join(config, "microsoft-edge/NativeMessagingHosts") },
|
|
1422
|
+
{ label: "Brave", installRoot: path.join(config, "BraveSoftware/Brave-Browser"), manifestDir: path.join(config, "BraveSoftware/Brave-Browser/NativeMessagingHosts") },
|
|
1423
|
+
{ label: "Vivaldi", installRoot: path.join(config, "vivaldi"), manifestDir: path.join(config, "vivaldi/NativeMessagingHosts") },
|
|
1424
|
+
{ label: "Opera", installRoot: path.join(config, "opera"), manifestDir: path.join(config, "opera/NativeMessagingHosts") }
|
|
1425
|
+
];
|
|
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
|
+
];
|
|
1267
1475
|
}
|
|
1268
1476
|
throw new Error(`Unsupported platform for install: ${process.platform}`);
|
|
1269
1477
|
}
|
|
1478
|
+
async function pathExists(p) {
|
|
1479
|
+
try {
|
|
1480
|
+
await stat(p);
|
|
1481
|
+
return true;
|
|
1482
|
+
} catch {
|
|
1483
|
+
return false;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
async function getInstalledBrowsers() {
|
|
1487
|
+
const all = getChromiumBrowserTargets();
|
|
1488
|
+
const installed = [];
|
|
1489
|
+
for (const target of all) {
|
|
1490
|
+
if (await pathExists(target.installRoot)) {
|
|
1491
|
+
installed.push(target);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return installed;
|
|
1495
|
+
}
|
|
1270
1496
|
function getDistDir() {
|
|
1271
1497
|
return path.dirname(fileURLToPath(import.meta.url));
|
|
1272
1498
|
}
|
|
1273
1499
|
async function writeWrapperScript(hostPath) {
|
|
1274
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
|
+
}
|
|
1275
1509
|
const wrapperPath = path.join(APP_DIR, "run-host.sh");
|
|
1276
1510
|
const content = `#!/bin/sh
|
|
1277
1511
|
exec "${process.execPath}" "${hostPath}"
|
|
@@ -1280,10 +1514,32 @@ exec "${process.execPath}" "${hostPath}"
|
|
|
1280
1514
|
await chmod(wrapperPath, 493);
|
|
1281
1515
|
return wrapperPath;
|
|
1282
1516
|
}
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
+
}
|
|
1542
|
+
async function writeManifestsForBrowsers(wrapperPath, browsers) {
|
|
1287
1543
|
const manifest = {
|
|
1288
1544
|
name: NATIVE_HOST_NAME,
|
|
1289
1545
|
description: "Native host for Chrome Relay",
|
|
@@ -1291,9 +1547,20 @@ async function writeManifest(wrapperPath) {
|
|
|
1291
1547
|
type: "stdio",
|
|
1292
1548
|
allowed_origins: getDefaultAllowedOrigins()
|
|
1293
1549
|
};
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1550
|
+
const body = `${JSON.stringify(manifest, null, 2)}
|
|
1551
|
+
`;
|
|
1552
|
+
const written = [];
|
|
1553
|
+
for (const target of browsers) {
|
|
1554
|
+
await mkdir(target.manifestDir, { recursive: true });
|
|
1555
|
+
const manifestPath = path.join(target.manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
1556
|
+
await writeFile(manifestPath, body, "utf8");
|
|
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 });
|
|
1562
|
+
}
|
|
1563
|
+
return written;
|
|
1297
1564
|
}
|
|
1298
1565
|
function killStaleNativeHosts() {
|
|
1299
1566
|
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
@@ -1322,28 +1589,77 @@ async function runInstall() {
|
|
|
1322
1589
|
const distDir = getDistDir();
|
|
1323
1590
|
const hostPath = path.join(distDir, "native-host.js");
|
|
1324
1591
|
const wrapperPath = await writeWrapperScript(hostPath);
|
|
1325
|
-
const
|
|
1592
|
+
const installed = await getInstalledBrowsers();
|
|
1593
|
+
if (installed.length === 0) {
|
|
1594
|
+
const all = getChromiumBrowserTargets();
|
|
1595
|
+
const fallback = all.find((t) => t.label === "Google Chrome");
|
|
1596
|
+
if (fallback) installed.push(fallback);
|
|
1597
|
+
}
|
|
1598
|
+
const writtenManifests = await writeManifestsForBrowsers(wrapperPath, installed);
|
|
1326
1599
|
const { killed } = killStaleNativeHosts();
|
|
1327
1600
|
console.log(`Installed Chrome Relay native host.`);
|
|
1328
1601
|
console.log(`Wrapper: ${wrapperPath}`);
|
|
1329
|
-
console.log(`
|
|
1602
|
+
console.log(`Manifests written:`);
|
|
1603
|
+
for (const m of writtenManifests) {
|
|
1604
|
+
console.log(` \u2022 ${m.browser}: ${m.manifestPath}`);
|
|
1605
|
+
if (m.registryKey) {
|
|
1606
|
+
console.log(` registry: ${m.registryKey}`);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1330
1609
|
console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
|
|
1331
1610
|
console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
|
|
1332
1611
|
if (killed > 0) {
|
|
1333
|
-
console.log(`Reaped ${killed} stale native-host process${killed === 1 ? "" : "es"};
|
|
1612
|
+
console.log(`Reaped ${killed} stale native-host process${killed === 1 ? "" : "es"}; browsers will respawn from the new manifest.`);
|
|
1334
1613
|
}
|
|
1335
1614
|
}
|
|
1336
1615
|
async function runDoctor() {
|
|
1337
1616
|
try {
|
|
1338
|
-
const wrapperPath = path.join(APP_DIR, "run-host.sh");
|
|
1339
|
-
const manifestPath = path.join(getChromeManifestDir(), `${NATIVE_HOST_NAME}.json`);
|
|
1617
|
+
const wrapperPath = path.join(APP_DIR, process.platform === "win32" ? "run-host.cmd" : "run-host.sh");
|
|
1340
1618
|
await stat(wrapperPath);
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
(
|
|
1346
|
-
|
|
1619
|
+
console.log(`Wrapper present: yes`);
|
|
1620
|
+
const installed = await getInstalledBrowsers();
|
|
1621
|
+
if (installed.length === 0) {
|
|
1622
|
+
console.log(`No Chromium-based browsers detected.`);
|
|
1623
|
+
console.log(`Tip: install Chrome / Arc / Brave / Edge / Chromium / Vivaldi / Opera then re-run "chrome-relay install".`);
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
const required = getDefaultAllowedOrigins();
|
|
1627
|
+
let allHealthy = true;
|
|
1628
|
+
console.log(`Detected browsers (${installed.length}):`);
|
|
1629
|
+
for (const target of installed) {
|
|
1630
|
+
const manifestPath = path.join(target.manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
1631
|
+
const exists = await pathExists(manifestPath);
|
|
1632
|
+
if (!exists) {
|
|
1633
|
+
allHealthy = false;
|
|
1634
|
+
console.log(` \u2022 ${target.label}: manifest MISSING (${manifestPath})`);
|
|
1635
|
+
continue;
|
|
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
|
+
}
|
|
1645
|
+
try {
|
|
1646
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1647
|
+
const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins : [];
|
|
1648
|
+
const missingOrigins = required.filter((o) => !allowedOrigins.includes(o));
|
|
1649
|
+
if (missingOrigins.length > 0) {
|
|
1650
|
+
allHealthy = false;
|
|
1651
|
+
console.log(` \u2022 ${target.label}: manifest present but missing origins: ${missingOrigins.join(", ")}`);
|
|
1652
|
+
} else {
|
|
1653
|
+
console.log(` \u2022 ${target.label}: ok`);
|
|
1654
|
+
}
|
|
1655
|
+
} catch (e) {
|
|
1656
|
+
allHealthy = false;
|
|
1657
|
+
console.log(` \u2022 ${target.label}: manifest unreadable (${e instanceof Error ? e.message : String(e)})`);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (!allHealthy) {
|
|
1661
|
+
console.log(`Tip: run "chrome-relay install" to refresh manifests for every detected browser.`);
|
|
1662
|
+
}
|
|
1347
1663
|
let serverReachable = false;
|
|
1348
1664
|
try {
|
|
1349
1665
|
const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/ping`);
|
|
@@ -1351,19 +1667,12 @@ async function runDoctor() {
|
|
|
1351
1667
|
} catch {
|
|
1352
1668
|
serverReachable = false;
|
|
1353
1669
|
}
|
|
1354
|
-
console.log(`Wrapper present: yes`);
|
|
1355
|
-
console.log(`Manifest present: yes`);
|
|
1356
1670
|
console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
|
|
1357
|
-
console.log(`Allowed origins: ${(manifest.allowed_origins ?? ["missing"]).join(", ")}`);
|
|
1358
|
-
if (missingOrigins.length > 0) {
|
|
1359
|
-
console.log(`Manifest missing origins: ${missingOrigins.join(", ")}`);
|
|
1360
|
-
console.log(`Tip: run "chrome-relay install" to refresh the native host manifest.`);
|
|
1361
|
-
}
|
|
1362
1671
|
console.log(`Local bridge reachable: ${serverReachable ? "yes" : "no"}`);
|
|
1363
1672
|
if (!serverReachable) {
|
|
1364
|
-
console.log(`Tip: load the extension so it can launch the native host.`);
|
|
1673
|
+
console.log(`Tip: load the extension in one of the detected browsers so it can launch the native host.`);
|
|
1365
1674
|
}
|
|
1366
|
-
return
|
|
1675
|
+
return allHealthy;
|
|
1367
1676
|
} catch (error) {
|
|
1368
1677
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1369
1678
|
return false;
|
|
@@ -1372,6 +1681,27 @@ async function runDoctor() {
|
|
|
1372
1681
|
|
|
1373
1682
|
// src/release-notes.ts
|
|
1374
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
|
+
],
|
|
1699
|
+
"0.5.22": [
|
|
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.",
|
|
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.",
|
|
1702
|
+
"`chrome-relay doctor` now reports per-browser manifest status, so when a refresh is needed the failure points at the specific browser whose manifest drifted.",
|
|
1703
|
+
"Fallback: if no Chromium browser is detected on the machine, we still drop the manifest at Chrome's path so a later Chrome install picks it up."
|
|
1704
|
+
],
|
|
1375
1705
|
"0.5.21": [
|
|
1376
1706
|
"Fix: `chrome-relay update` and `chrome-relay install` now SIGTERM any running native-host.js process before exiting, and `update` re-runs `install` from the freshly-installed binary. Chrome respawns the host from the new manifest on its next native-messaging request.",
|
|
1377
1707
|
"Why this matters: Chrome's native messaging keeps the host process alive for the session. Pre-0.5.21, `chrome-relay update` refreshed the on-disk package but Chrome kept routing through the OLD host. The HTTP bridge served by that old host then reported its own embedded `CHROME_RELAY_VERSION`, which falsely tripped the cli-outdated nudge against the newer extension. Users running the very command the nudge told them to run found the nudge still firing afterwards \u2014 the worst kind of UX bug.",
|
|
@@ -1686,34 +2016,48 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
|
|
|
1686
2016
|
}
|
|
1687
2017
|
|
|
1688
2018
|
// src/commands/input.ts
|
|
2019
|
+
init_dist();
|
|
2020
|
+
function addressArg(value) {
|
|
2021
|
+
const ref = parseRefToken(value);
|
|
2022
|
+
return ref ? { ref } : { selector: value };
|
|
2023
|
+
}
|
|
1689
2024
|
function registerInput(ctx) {
|
|
1690
2025
|
const { program, withBase, run } = ctx;
|
|
1691
2026
|
tabOpt(
|
|
1692
|
-
program.command("click [
|
|
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(
|
|
1693
2028
|
"after",
|
|
1694
2029
|
`
|
|
1695
2030
|
|
|
1696
2031
|
Examples:
|
|
2032
|
+
chrome-relay click @e12
|
|
1697
2033
|
chrome-relay click 'button[aria-label="Save"]'
|
|
1698
2034
|
chrome-relay click --tab 123 --x 1327 --y 771
|
|
1699
2035
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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.
|
|
1704
2040
|
`
|
|
1705
2041
|
)
|
|
1706
|
-
).action(async (
|
|
2042
|
+
).action(async (target, opts) => {
|
|
1707
2043
|
const extras = {};
|
|
1708
|
-
if (
|
|
2044
|
+
if (target) Object.assign(extras, addressArg(target));
|
|
1709
2045
|
if (typeof opts.x === "number") extras.x = opts.x;
|
|
1710
2046
|
if (typeof opts.y === "number") extras.y = opts.y;
|
|
1711
2047
|
await run("chrome_click_element", withBase(opts, extras));
|
|
1712
2048
|
});
|
|
1713
2049
|
tabOpt(
|
|
1714
|
-
program.command("fill <
|
|
1715
|
-
|
|
1716
|
-
|
|
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 }));
|
|
1717
2061
|
});
|
|
1718
2062
|
tabOpt(
|
|
1719
2063
|
program.command("keys <keys>").description("Press a single key or chord via trusted CDP input (e.g. Enter, Cmd+K).").addHelpText(
|
|
@@ -1733,7 +2077,7 @@ For typing text into a field, use \`chrome-relay type\` instead.
|
|
|
1733
2077
|
await run("chrome_keyboard", withBase(opts, { keys }));
|
|
1734
2078
|
});
|
|
1735
2079
|
tabOpt(
|
|
1736
|
-
program.command("type <text>").description("Insert text via trusted CDP input. Works in contenteditable / Draft.js / Lexical.").option("-s, --selector <
|
|
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(
|
|
1737
2081
|
"after",
|
|
1738
2082
|
`
|
|
1739
2083
|
|
|
@@ -1750,7 +2094,7 @@ When to pick which:
|
|
|
1750
2094
|
)
|
|
1751
2095
|
).action(async (text, opts) => {
|
|
1752
2096
|
const extras = { text };
|
|
1753
|
-
if (opts.selector) extras
|
|
2097
|
+
if (opts.selector) Object.assign(extras, addressArg(opts.selector));
|
|
1754
2098
|
await run("chrome_type", withBase(opts, extras));
|
|
1755
2099
|
});
|
|
1756
2100
|
tabOpt(
|
|
@@ -1775,7 +2119,7 @@ Notes:
|
|
|
1775
2119
|
await run("chrome_evaluate", withBase(opts, extras));
|
|
1776
2120
|
});
|
|
1777
2121
|
tabOpt(
|
|
1778
|
-
program.command("hover [
|
|
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(
|
|
1779
2123
|
"after",
|
|
1780
2124
|
`
|
|
1781
2125
|
|
|
@@ -1787,9 +2131,9 @@ Use before screencast to capture hover-driven micro-states (button glow,
|
|
|
1787
2131
|
tooltip appearance, etc.) that a bare click would skip past too quickly.
|
|
1788
2132
|
`
|
|
1789
2133
|
)
|
|
1790
|
-
).action(async (
|
|
2134
|
+
).action(async (target, opts) => {
|
|
1791
2135
|
const extras = {};
|
|
1792
|
-
if (
|
|
2136
|
+
if (target) Object.assign(extras, addressArg(target));
|
|
1793
2137
|
if (typeof opts.x === "number") extras.x = opts.x;
|
|
1794
2138
|
if (typeof opts.y === "number") extras.y = opts.y;
|
|
1795
2139
|
await run("chrome_hover", withBase(opts, extras));
|
|
@@ -1797,9 +2141,56 @@ tooltip appearance, etc.) that a bare click would skip past too quickly.
|
|
|
1797
2141
|
}
|
|
1798
2142
|
|
|
1799
2143
|
// src/commands/capture.ts
|
|
2144
|
+
init_dist();
|
|
1800
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
|
+
}
|
|
1801
2162
|
function registerCapture(ctx) {
|
|
1802
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
|
+
});
|
|
1803
2194
|
tabOpt(
|
|
1804
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(
|
|
1805
2196
|
"after",
|
|
@@ -1839,43 +2230,37 @@ full-tab screenshot when an agent only needs to see one component.
|
|
|
1839
2230
|
}
|
|
1840
2231
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1841
2232
|
} catch (error) {
|
|
1842
|
-
|
|
1843
|
-
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
1844
|
-
);
|
|
1845
|
-
process.exit(1);
|
|
2233
|
+
exitWithError(error);
|
|
1846
2234
|
}
|
|
1847
2235
|
});
|
|
1848
2236
|
tabOpt(
|
|
1849
|
-
program.command("read").description("
|
|
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")
|
|
1850
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");
|
|
1851
2240
|
const extras = {};
|
|
1852
2241
|
if (opts.interactive) extras.interactiveOnly = true;
|
|
1853
|
-
|
|
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
|
+
}
|
|
1854
2248
|
});
|
|
1855
2249
|
tabOpt(
|
|
1856
|
-
program.command("ax").description("
|
|
1857
|
-
"after",
|
|
1858
|
-
`
|
|
1859
|
-
|
|
1860
|
-
Examples:
|
|
1861
|
-
chrome-relay ax --tab 123
|
|
1862
|
-
chrome-relay ax --tab 123 --interactive-only
|
|
1863
|
-
chrome-relay ax --tab 123 --root main --interactive-only
|
|
1864
|
-
|
|
1865
|
-
Notes:
|
|
1866
|
-
Each node carries an "id" \u2014 that's the backendDOMNodeId. Pass it to
|
|
1867
|
-
\`chrome-relay click-ax --node <id>\` to click without a CSS selector.
|
|
1868
|
-
`
|
|
1869
|
-
)
|
|
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")
|
|
1870
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");
|
|
1871
2253
|
const extras = {};
|
|
1872
2254
|
if (opts.interactiveOnly) extras.interactiveOnly = true;
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
+
}
|
|
1876
2261
|
});
|
|
1877
2262
|
tabOpt(
|
|
1878
|
-
program.command("click-ax").description("
|
|
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(
|
|
1879
2264
|
"after",
|
|
1880
2265
|
`
|
|
1881
2266
|
|
package/dist/index.js
CHANGED
package/dist/native-host.js
CHANGED
|
@@ -56,7 +56,7 @@ function toBridgeError(unknownErr, fallbackTool) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// src/index.ts
|
|
59
|
-
var CHROME_RELAY_VERSION = true ? "0.
|
|
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) {
|