@browserbridge/bbx 1.0.1 → 1.1.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/README.md +4 -4
- package/package.json +11 -13
- package/packages/agent-client/src/cli-helpers.js +33 -0
- package/packages/agent-client/src/cli.js +116 -41
- package/packages/agent-client/src/client.js +29 -4
- package/packages/agent-client/src/command-registry.js +3 -0
- package/packages/agent-client/src/detect.js +159 -48
- package/packages/agent-client/src/install.js +24 -1
- package/packages/agent-client/src/mcp-config.js +29 -10
- package/packages/agent-client/src/setup-status.js +12 -4
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers.js +28 -7
- package/packages/mcp-server/src/server.js +12 -2
- package/packages/native-host/bin/bridge-daemon.js +33 -4
- package/packages/native-host/bin/install-manifest.js +24 -2
- package/packages/native-host/src/config.js +131 -6
- package/packages/native-host/src/daemon-process.js +396 -0
- package/packages/native-host/src/daemon.js +217 -68
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +121 -7
- package/packages/native-host/src/native-host.js +110 -73
- package/packages/protocol/src/capabilities.js +3 -0
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +4 -0
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +143 -7
- package/packages/protocol/src/registry.js +11 -0
- package/packages/protocol/src/summary.js +18 -10
- package/packages/protocol/src/types.js +28 -3
- package/skills/browser-bridge/SKILL.md +2 -1
- package/skills/browser-bridge/references/interaction.md +1 -0
- package/skills/browser-bridge/references/protocol.md +2 -1
- package/CHANGELOG.md +0 -55
- package/assets/banner.jpg +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +0 -65
- package/docs/api-reference.md +0 -157
- package/docs/cli-guide.md +0 -128
- package/docs/index.md +0 -25
- package/docs/manual-setup.md +0 -140
- package/docs/mcp-vs-cli.md +0 -258
- package/docs/publishing.md +0 -112
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/unpacked-extension.md +0 -72
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -38
- package/packages/extension/assets/icon-128.png +0 -0
- package/packages/extension/assets/icon-16.png +0 -0
- package/packages/extension/assets/icon-32.png +0 -0
- package/packages/extension/assets/icon-48.png +0 -0
- package/packages/extension/src/background-helpers.js +0 -474
- package/packages/extension/src/background-routing.js +0 -89
- package/packages/extension/src/background.js +0 -3490
- package/packages/extension/src/content-script-helpers.js +0 -282
- package/packages/extension/src/content-script.js +0 -2043
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -104
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -298
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1771
- package/packages/extension/ui/ui.css +0 -1160
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Browser Bridge
|
|
1
|
+
# Browser Bridge (BBX)
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
|
-
> **Chrome Web Store
|
|
5
|
+
> **Chrome Web Store:** Install `Browser Bridge (BBX)` from the [Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie). For local or custom builds, use the unpacked install flow in [docs/unpacked-extension.md](https://github.com/koltyakov/browser-bridge/blob/main/docs/unpacked-extension.md).
|
|
6
6
|
|
|
7
7
|
A local bridge between your coding agent and a real Chrome tab. Browser Bridge gives the agent structured access to DOM, styles, layout, console, network, and reversible patches - starting from the actual tab you already have open, with all its real state intact.
|
|
8
8
|
|
|
@@ -105,7 +105,7 @@ Browser Bridge is optimized for the opposite starting point: **inspect the state
|
|
|
105
105
|
|
|
106
106
|
## Setup
|
|
107
107
|
|
|
108
|
-
1. Install [Browser Bridge from the Chrome Web Store](https://
|
|
108
|
+
1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie)
|
|
109
109
|
2. `npm install -g @browserbridge/bbx` - installs the CLI and native host
|
|
110
110
|
3. In the extension side panel, install MCP or CLI (skill) for your agent of choice
|
|
111
111
|
4. Enable Browser Bridge for the Chrome window you want to inspect/control with the AI agent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserbridge/bbx",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-tools",
|
|
@@ -32,21 +32,17 @@
|
|
|
32
32
|
"bbx-mcp": "packages/mcp-server/src/bin.js"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"assets",
|
|
36
|
-
"CHANGELOG.md",
|
|
37
35
|
"LICENSE",
|
|
38
36
|
"README.md",
|
|
39
|
-
"docs",
|
|
40
|
-
"manifest.json",
|
|
41
37
|
"packages/agent-client/src",
|
|
42
|
-
"packages/extension/assets",
|
|
43
|
-
"packages/extension/src",
|
|
44
|
-
"packages/extension/ui",
|
|
45
38
|
"packages/mcp-server/src",
|
|
46
39
|
"packages/native-host/bin",
|
|
47
40
|
"packages/native-host/src",
|
|
48
41
|
"packages/protocol/src",
|
|
49
|
-
"skills/browser-bridge"
|
|
42
|
+
"skills/browser-bridge",
|
|
43
|
+
"!**/test/**",
|
|
44
|
+
"!**/*.test.*",
|
|
45
|
+
"!**/*.spec.*"
|
|
50
46
|
],
|
|
51
47
|
"type": "module",
|
|
52
48
|
"publishConfig": {
|
|
@@ -56,16 +52,17 @@
|
|
|
56
52
|
"lint": "oxlint . && oxfmt --check .",
|
|
57
53
|
"format": "oxfmt .",
|
|
58
54
|
"format:check": "oxfmt --check .",
|
|
59
|
-
"test:runtime": "node --test
|
|
60
|
-
"test": "c8 node --test
|
|
55
|
+
"test:runtime": "node --test",
|
|
56
|
+
"test": "c8 node --test",
|
|
61
57
|
"coverage": "c8 report --reporter=text --reporter=text-summary",
|
|
62
|
-
"coverage:
|
|
58
|
+
"coverage:review": "c8 report --reporter=json-summary && node scripts/review-coverage.mjs",
|
|
59
|
+
"coverage:check": "c8 check-coverage --lines 80 --branches 75",
|
|
63
60
|
"typecheck": "tsc --noEmit",
|
|
64
61
|
"postinstall": "node packages/native-host/bin/postinstall.js",
|
|
65
62
|
"package:extension": "node scripts/package-extension.mjs",
|
|
66
63
|
"check:extension-zip": "node scripts/check-extension-zip.mjs",
|
|
67
64
|
"release:check": "npm run lint && npm run typecheck && npm test && npm run coverage:check && npm run package:extension && npm pack --dry-run",
|
|
68
|
-
"prepublishOnly": "npm run lint && npm run typecheck && npm test",
|
|
65
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run coverage:check",
|
|
69
66
|
"status": "node packages/agent-client/src/cli.js status",
|
|
70
67
|
"daemon": "node packages/native-host/bin/bridge-daemon.js",
|
|
71
68
|
"install-manifest": "node packages/native-host/bin/install-manifest.js"
|
|
@@ -78,6 +75,7 @@
|
|
|
78
75
|
"@types/chrome": "^0.1.40",
|
|
79
76
|
"@types/node": "^25.5.2",
|
|
80
77
|
"c8": "^11.0.0",
|
|
78
|
+
"linkedom": "^0.18.12",
|
|
81
79
|
"oxfmt": "^0.47.0",
|
|
82
80
|
"oxlint": "^1.62.0",
|
|
83
81
|
"typescript": "^6.0.3"
|
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import { bridgeMethodNeedsTab } from '../../protocol/src/index.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Strip ANSI escape sequences from a string to prevent terminal injection
|
|
8
|
+
* from malicious page content (e.g. DOM text, console output, eval results).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} str
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function stripAnsi(str) {
|
|
14
|
+
// oxlint-disable-next-line no-control-regex
|
|
15
|
+
return str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\x1b[^[]/g, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Recursively sanitize all string values in a value tree by stripping ANSI
|
|
20
|
+
* escape sequences. Only strings are touched; structure is preserved.
|
|
21
|
+
*
|
|
22
|
+
* @param {unknown} value
|
|
23
|
+
* @returns {unknown}
|
|
24
|
+
*/
|
|
25
|
+
export function sanitizeOutput(value) {
|
|
26
|
+
if (typeof value === 'string') return stripAnsi(value);
|
|
27
|
+
if (Array.isArray(value)) return value.map(sanitizeOutput);
|
|
28
|
+
if (value !== null && typeof value === 'object') {
|
|
29
|
+
return Object.fromEntries(
|
|
30
|
+
Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [
|
|
31
|
+
k,
|
|
32
|
+
sanitizeOutput(v),
|
|
33
|
+
])
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
/**
|
|
7
40
|
* @param {string[]} values
|
|
8
41
|
* @returns {Record<string, string>}
|
|
@@ -6,7 +6,11 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
applyWindowsTcpTransportDefaults,
|
|
11
|
+
SUPPORTED_BROWSERS,
|
|
12
|
+
} from '../../native-host/src/config.js';
|
|
13
|
+
import { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
|
|
10
14
|
import { uninstallNativeManifest } from '../../native-host/src/install-manifest.js';
|
|
11
15
|
import {
|
|
12
16
|
createRuntimeContext,
|
|
@@ -23,8 +27,8 @@ import {
|
|
|
23
27
|
methodNeedsTab,
|
|
24
28
|
parseIntArg,
|
|
25
29
|
parseJsonObject,
|
|
30
|
+
sanitizeOutput,
|
|
26
31
|
} from './cli-helpers.js';
|
|
27
|
-
import { detectMcpClients, detectSkillTargets } from './detect.js';
|
|
28
32
|
import {
|
|
29
33
|
findInstalledManagedTargets,
|
|
30
34
|
installAgentFiles,
|
|
@@ -50,39 +54,8 @@ import { annotateBridgeSummary, summarizeBridgeResponse } from './subagent.js';
|
|
|
50
54
|
/** @typedef {{ image: string, rect: Record<string, unknown> }} ScreenshotResult */
|
|
51
55
|
|
|
52
56
|
const REQUEST_SOURCE = 'cli';
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* Strip ANSI escape sequences from a string to prevent terminal injection
|
|
56
|
-
* from malicious page content (e.g. DOM text, console output, eval results).
|
|
57
|
-
*
|
|
58
|
-
* @param {string} str
|
|
59
|
-
* @returns {string}
|
|
60
|
-
*/
|
|
61
|
-
function stripAnsi(str) {
|
|
62
|
-
// oxlint-disable-next-line no-control-regex
|
|
63
|
-
return str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\x1b[^[]/g, '');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Recursively sanitize all string values in a value tree by stripping ANSI
|
|
68
|
-
* escape sequences. Only strings are touched; structure is preserved.
|
|
69
|
-
*
|
|
70
|
-
* @param {unknown} value
|
|
71
|
-
* @returns {unknown}
|
|
72
|
-
*/
|
|
73
|
-
function sanitizeOutput(value) {
|
|
74
|
-
if (typeof value === 'string') return stripAnsi(value);
|
|
75
|
-
if (Array.isArray(value)) return value.map(sanitizeOutput);
|
|
76
|
-
if (value !== null && typeof value === 'object') {
|
|
77
|
-
return Object.fromEntries(
|
|
78
|
-
Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [
|
|
79
|
-
k,
|
|
80
|
-
sanitizeOutput(v),
|
|
81
|
-
])
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
57
|
+
const TEST_TIMEOUT_ENV = 'BBX_CLIENT_REQUEST_TIMEOUT_MS';
|
|
58
|
+
const TEST_DETECTED_MCP_CLIENTS_ENV = 'BBX_TEST_DETECTED_MCP_CLIENTS';
|
|
86
59
|
|
|
87
60
|
/**
|
|
88
61
|
* Read all of stdin as UTF-8 text. Resolves once stdin closes.
|
|
@@ -164,9 +137,12 @@ if (command === 'install-skill') {
|
|
|
164
137
|
global: isGlobal,
|
|
165
138
|
cwd: process.cwd(),
|
|
166
139
|
projectPath: isGlobal ? os.homedir() : process.cwd(),
|
|
140
|
+
...getSetupStatusTestOverrides(),
|
|
167
141
|
});
|
|
168
142
|
/** @type {import('./install.js').SupportedTarget[]} */
|
|
169
|
-
const detected =
|
|
143
|
+
const detected = /** @type {import('./install.js').SupportedTarget[]} */ (
|
|
144
|
+
setupStatus.skillTargets.filter((entry) => entry.detected).map((entry) => entry.key)
|
|
145
|
+
);
|
|
170
146
|
const installedManagedTargets = new Set(
|
|
171
147
|
setupStatus.skillTargets
|
|
172
148
|
.filter((entry) => entry.installed && entry.managed)
|
|
@@ -274,8 +250,11 @@ if (command === 'install-mcp') {
|
|
|
274
250
|
global: isGlobal,
|
|
275
251
|
cwd: process.cwd(),
|
|
276
252
|
projectPath: process.cwd(),
|
|
253
|
+
...getSetupStatusTestOverrides(),
|
|
277
254
|
});
|
|
278
|
-
const detected =
|
|
255
|
+
const detected = /** @type {import('./mcp-config.js').McpClientName[]} */ (
|
|
256
|
+
setupStatus.mcpClients.filter((entry) => entry.detected).map((entry) => entry.key)
|
|
257
|
+
);
|
|
279
258
|
const configuredClients = new Set(
|
|
280
259
|
setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
|
|
281
260
|
);
|
|
@@ -386,7 +365,11 @@ if (command === 'mcp') {
|
|
|
386
365
|
process.exit(1);
|
|
387
366
|
}
|
|
388
367
|
|
|
389
|
-
const
|
|
368
|
+
const clientTimeoutMs = getClientTimeoutOverride();
|
|
369
|
+
applyWindowsTcpTransportDefaults();
|
|
370
|
+
const client = new BridgeClient(
|
|
371
|
+
clientTimeoutMs ? { defaultTimeoutMs: clientTimeoutMs } : undefined
|
|
372
|
+
);
|
|
390
373
|
|
|
391
374
|
await main();
|
|
392
375
|
|
|
@@ -423,6 +406,18 @@ async function main() {
|
|
|
423
406
|
return;
|
|
424
407
|
}
|
|
425
408
|
|
|
409
|
+
if (command === 'restart') {
|
|
410
|
+
const result = await restartBridgeDaemon();
|
|
411
|
+
printJson({
|
|
412
|
+
ok: true,
|
|
413
|
+
summary: result.previouslyRunning
|
|
414
|
+
? 'Browser Bridge daemon restarted.'
|
|
415
|
+
: 'Browser Bridge daemon started.',
|
|
416
|
+
evidence: result,
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
426
421
|
if (command === 'logs') {
|
|
427
422
|
await printSummary(await requestBridge(client, 'log.tail', {}, { source: REQUEST_SOURCE }));
|
|
428
423
|
return;
|
|
@@ -470,7 +465,7 @@ async function main() {
|
|
|
470
465
|
tabId,
|
|
471
466
|
source: REQUEST_SOURCE,
|
|
472
467
|
});
|
|
473
|
-
|
|
468
|
+
printCallResponse(response);
|
|
474
469
|
return;
|
|
475
470
|
}
|
|
476
471
|
|
|
@@ -557,7 +552,7 @@ async function main() {
|
|
|
557
552
|
tabId,
|
|
558
553
|
source: REQUEST_SOURCE,
|
|
559
554
|
});
|
|
560
|
-
|
|
555
|
+
printCallResponse(response);
|
|
561
556
|
return;
|
|
562
557
|
}
|
|
563
558
|
|
|
@@ -597,6 +592,26 @@ async function main() {
|
|
|
597
592
|
return;
|
|
598
593
|
}
|
|
599
594
|
|
|
595
|
+
if (command === 'cdp-press-key') {
|
|
596
|
+
const parsed = extractTabFlag(rest);
|
|
597
|
+
const [key, code] = parsed.rest;
|
|
598
|
+
if (!key) throw new Error('Usage: cdp-press-key [--tab <tabId>] <key> [code]');
|
|
599
|
+
const response = await requestBridge(
|
|
600
|
+
client,
|
|
601
|
+
'cdp.dispatch_key_event',
|
|
602
|
+
{
|
|
603
|
+
key,
|
|
604
|
+
code,
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
tabId: parsed.tabId,
|
|
608
|
+
source: REQUEST_SOURCE,
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
await printSummary(response, 'cdp.dispatch_key_event');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
600
615
|
if (command === 'screenshot') {
|
|
601
616
|
const [refOrSelector, outputPath] = rest;
|
|
602
617
|
if (!refOrSelector) throw new Error('Usage: screenshot <ref|selector> [path]');
|
|
@@ -650,7 +665,7 @@ async function main() {
|
|
|
650
665
|
const message = error instanceof Error ? error.message : String(error);
|
|
651
666
|
const raw = error instanceof Error && 'code' in error ? /** @type {any} */ (error).code : '';
|
|
652
667
|
let code = 'ERROR';
|
|
653
|
-
if (raw === 'ENOENT' || raw === 'ECONNREFUSED') {
|
|
668
|
+
if (raw === 'ENOENT' || raw === 'ECONNREFUSED' || raw === 'EINVAL') {
|
|
654
669
|
code = 'DAEMON_OFFLINE';
|
|
655
670
|
} else if (raw === 'BRIDGE_TIMEOUT') {
|
|
656
671
|
code = 'BRIDGE_TIMEOUT';
|
|
@@ -670,6 +685,48 @@ async function main() {
|
|
|
670
685
|
}
|
|
671
686
|
}
|
|
672
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Allow tests to shrink request timeouts without changing the shared default.
|
|
690
|
+
*
|
|
691
|
+
* @returns {number | undefined}
|
|
692
|
+
*/
|
|
693
|
+
function getClientTimeoutOverride() {
|
|
694
|
+
const raw = process.env[TEST_TIMEOUT_ENV];
|
|
695
|
+
if (!raw) {
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const value = Number.parseInt(raw, 10);
|
|
700
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Allow CLI tests to provide deterministic MCP client detection without relying
|
|
705
|
+
* on whatever tools happen to be installed on the host machine.
|
|
706
|
+
*
|
|
707
|
+
* @returns {{
|
|
708
|
+
* mcpDetectors?: Record<string, () => boolean>,
|
|
709
|
+
* }}
|
|
710
|
+
*/
|
|
711
|
+
function getSetupStatusTestOverrides() {
|
|
712
|
+
if (!(TEST_DETECTED_MCP_CLIENTS_ENV in process.env)) {
|
|
713
|
+
return {};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const detectedClients = new Set(
|
|
717
|
+
(process.env[TEST_DETECTED_MCP_CLIENTS_ENV] || '')
|
|
718
|
+
.split(',')
|
|
719
|
+
.map((value) => value.trim().toLowerCase())
|
|
720
|
+
.filter(Boolean)
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
mcpDetectors: Object.fromEntries(
|
|
725
|
+
MCP_CLIENT_NAMES.map((clientName) => [clientName, () => detectedClients.has(clientName)])
|
|
726
|
+
),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
673
730
|
/**
|
|
674
731
|
* @param {{ detected: boolean, installed: boolean }} options
|
|
675
732
|
* @returns {string | undefined}
|
|
@@ -714,6 +771,24 @@ function printJson(value) {
|
|
|
714
771
|
);
|
|
715
772
|
}
|
|
716
773
|
|
|
774
|
+
/**
|
|
775
|
+
* @param {import('../../protocol/src/types.js').BridgeResponse} response
|
|
776
|
+
* @returns {void}
|
|
777
|
+
*/
|
|
778
|
+
function printCallResponse(response) {
|
|
779
|
+
if (response.ok) {
|
|
780
|
+
printJson(response.result);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
process.exitCode = 1;
|
|
785
|
+
const errorText = `${response.error.code}: ${response.error.message}`;
|
|
786
|
+
process.stderr.write(
|
|
787
|
+
`${process.stderr.isTTY ? `\u001b[31m${sanitizeOutput(errorText)}\u001b[0m` : sanitizeOutput(errorText)}\n`
|
|
788
|
+
);
|
|
789
|
+
printJson(response);
|
|
790
|
+
}
|
|
791
|
+
|
|
717
792
|
function printUsage() {
|
|
718
793
|
const blocks = ['Usage: bbx <command> [args]'];
|
|
719
794
|
for (const section of CLI_HELP_SECTIONS) {
|
|
@@ -10,7 +10,13 @@ import {
|
|
|
10
10
|
PROTOCOL_VERSION,
|
|
11
11
|
parseJsonLines,
|
|
12
12
|
} from '../../protocol/src/index.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createSocketBridgeTransport,
|
|
15
|
+
getBridgeTransport,
|
|
16
|
+
getSocketPath,
|
|
17
|
+
} from '../../native-host/src/config.js';
|
|
18
|
+
|
|
19
|
+
/** @typedef {import('../../native-host/src/config.js').BridgeTransport} BridgeTransport */
|
|
14
20
|
|
|
15
21
|
/** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
|
|
16
22
|
/** @typedef {import('../../protocol/src/types.js').BridgeMeta} BridgeMeta */
|
|
@@ -42,6 +48,16 @@ import { getSocketPath } from '../../native-host/src/config.js';
|
|
|
42
48
|
* }} PendingRequest
|
|
43
49
|
*/
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {{
|
|
53
|
+
* transport?: BridgeTransport,
|
|
54
|
+
* socketPath?: string,
|
|
55
|
+
* clientId?: string,
|
|
56
|
+
* defaultTimeoutMs?: number,
|
|
57
|
+
* autoReconnect?: boolean,
|
|
58
|
+
* }} BridgeClientOptions
|
|
59
|
+
*/
|
|
60
|
+
|
|
45
61
|
/**
|
|
46
62
|
* @param {string} method
|
|
47
63
|
* @param {number} timeoutMs
|
|
@@ -56,14 +72,20 @@ function createTimeoutError(method, timeoutMs) {
|
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
export class BridgeClient extends EventEmitter {
|
|
75
|
+
/**
|
|
76
|
+
* @param {BridgeClientOptions} [options={}]
|
|
77
|
+
*/
|
|
59
78
|
constructor({
|
|
60
|
-
|
|
79
|
+
transport = getBridgeTransport(),
|
|
80
|
+
socketPath = undefined,
|
|
61
81
|
clientId = `agent_${randomUUID()}`,
|
|
62
82
|
defaultTimeoutMs = DEFAULT_CLIENT_REQUEST_TIMEOUT_MS,
|
|
63
83
|
autoReconnect = false,
|
|
64
84
|
} = {}) {
|
|
65
85
|
super();
|
|
66
|
-
this.
|
|
86
|
+
this.transport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
|
|
87
|
+
this.socketPath =
|
|
88
|
+
this.transport.type === 'socket' ? this.transport.socketPath : getSocketPath();
|
|
67
89
|
this.clientId = clientId;
|
|
68
90
|
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
69
91
|
this.autoReconnect = autoReconnect;
|
|
@@ -83,7 +105,10 @@ export class BridgeClient extends EventEmitter {
|
|
|
83
105
|
if (this.socket) {
|
|
84
106
|
throw new Error('BridgeClient is already connected.');
|
|
85
107
|
}
|
|
86
|
-
const socket =
|
|
108
|
+
const socket =
|
|
109
|
+
this.transport.type === 'tcp'
|
|
110
|
+
? net.createConnection({ host: this.transport.host, port: this.transport.port })
|
|
111
|
+
: net.createConnection(this.transport.socketPath);
|
|
87
112
|
this.socket = socket;
|
|
88
113
|
try {
|
|
89
114
|
await new Promise((resolve, reject) => {
|
|
@@ -237,6 +237,7 @@ export const CLI_METHOD_BINDINGS = Object.freeze({
|
|
|
237
237
|
),
|
|
238
238
|
...Object.fromEntries(BRIDGE_METHODS.map((method) => [method, method])),
|
|
239
239
|
'press-key': 'input.press_key',
|
|
240
|
+
'cdp-press-key': 'cdp.dispatch_key_event',
|
|
240
241
|
screenshot: 'screenshot.capture_element',
|
|
241
242
|
eval: 'page.evaluate',
|
|
242
243
|
});
|
|
@@ -253,6 +254,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
253
254
|
'bbx install-mcp [client|all] [--local] Write MCP config for codex|claude|cursor|copilot|opencode|antigravity|windsurf',
|
|
254
255
|
'bbx status Check bridge connection',
|
|
255
256
|
'bbx doctor Diagnose install, daemon, extension, and access readiness',
|
|
257
|
+
'bbx restart Restart the local Browser Bridge daemon',
|
|
256
258
|
'bbx access-request Request Browser Bridge access for the focused window',
|
|
257
259
|
'bbx logs Recent bridge logs',
|
|
258
260
|
'bbx tabs List available tabs',
|
|
@@ -330,6 +332,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
330
332
|
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
331
333
|
),
|
|
332
334
|
'bbx press-key <key> [ref|selector] Send key event',
|
|
335
|
+
'bbx cdp-press-key [--tab <tabId>] <key> [code] Dispatch CDP keyDown/keyUp without focusing the tab',
|
|
333
336
|
],
|
|
334
337
|
},
|
|
335
338
|
{
|