@browserbridge/bbx 1.1.0 → 1.3.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 +6 -5
- package/package.json +1 -1
- package/packages/agent-client/src/cli.js +30 -20
- package/packages/agent-client/src/client.js +105 -42
- package/packages/agent-client/src/command-registry.js +4 -14
- package/packages/agent-client/src/detect.js +3 -3
- package/packages/agent-client/src/install.js +3 -7
- package/packages/agent-client/src/mcp-config.js +1 -3
- package/packages/agent-client/src/runtime.js +7 -41
- package/packages/agent-client/src/setup-status.js +3 -13
- package/packages/agent-client/src/types.ts +131 -0
- package/packages/mcp-server/src/handlers-capture.js +291 -0
- package/packages/mcp-server/src/handlers-dom.js +203 -0
- package/packages/mcp-server/src/handlers-navigation.js +79 -0
- package/packages/mcp-server/src/handlers-page.js +365 -0
- package/packages/mcp-server/src/handlers-utils.js +318 -0
- package/packages/mcp-server/src/handlers.js +59 -1176
- package/packages/mcp-server/src/server.js +2 -1
- package/packages/native-host/bin/bridge-daemon.js +2 -1
- package/packages/native-host/bin/install-manifest.js +8 -0
- package/packages/native-host/bin/postinstall.js +46 -9
- package/packages/native-host/src/daemon-logger.js +157 -0
- package/packages/native-host/src/daemon-process.js +43 -18
- package/packages/native-host/src/daemon.js +133 -12
- package/packages/native-host/src/framing.js +13 -0
- package/packages/native-host/src/native-host.js +7 -5
- package/packages/protocol/src/capabilities.js +1 -0
- package/packages/protocol/src/protocol.js +40 -0
- package/packages/protocol/src/registry.js +5 -9
- package/packages/protocol/src/types.ts +572 -0
- package/packages/protocol/src/types.js +0 -626
package/README.md
CHANGED
|
@@ -99,17 +99,18 @@ Managed installs support OpenAI Codex, Claude Code, Cursor, GitHub Copilot, Open
|
|
|
99
99
|
|
|
100
100
|
## Why Browser Bridge
|
|
101
101
|
|
|
102
|
-
Most adjacent tools optimize for different goals. [Playwright](https://playwright.dev/) and headless automation stacks are excellent for deterministic tests and CI - but they start from a clean browser context by design. [Claude in Chrome](https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome) is great for integrated Claude workflows,
|
|
102
|
+
Most adjacent tools optimize for different goals. [Playwright](https://playwright.dev/) and headless automation stacks are excellent for deterministic tests and CI - but they start from a clean browser context by design. [Claude in Chrome](https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome) is great for integrated Claude workflows, and the [Codex extension](https://chromewebstore.google.com/detail/codex/hehggadaopoacecdllhhajmbjkdcmajg) is a great option if you use Codex, but both are vendor-specific. Generic MCP browser servers offer broad control without the developer-focused depth.
|
|
103
103
|
|
|
104
104
|
Browser Bridge is optimized for the opposite starting point: **inspect the state that already exists** in a real tab - logged-in sessions, feature flags, seeded storage, SPA state - use structured reads to understand it, test a patch in place, then fix the source. It's open-source, agent-agnostic, and scoped to explicit tab sessions rather than ambient browser control.
|
|
105
105
|
|
|
106
106
|
## Setup
|
|
107
107
|
|
|
108
|
-
1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie)
|
|
108
|
+
1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie) in Chrome or another Chromium-based browser
|
|
109
109
|
2. `npm install -g @browserbridge/bbx` - installs the CLI and native host
|
|
110
|
-
3.
|
|
111
|
-
4.
|
|
112
|
-
5.
|
|
110
|
+
3. Run `bbx install`, or target a specific browser with `bbx install --browser edge`, `bbx install --browser brave`, `bbx install --browser chromium`, or `bbx install --browser arc`
|
|
111
|
+
4. In the extension side panel, install MCP or CLI (skill) for your agent of choice
|
|
112
|
+
5. Enable Browser Bridge for the browser window you want to inspect/control with the AI agent
|
|
113
|
+
6. Ask your agent to use Browser Bridge via MCP (`BB MCP` or `Browser Bridge MCP`), or invoke the `browser-bridge` / `$bbx` skill in CLI mode
|
|
113
114
|
|
|
114
115
|
## How it works
|
|
115
116
|
|
package/package.json
CHANGED
|
@@ -50,8 +50,8 @@ import { getDoctorReport, requestBridge, resolveRef } from './runtime.js';
|
|
|
50
50
|
import { collectSetupStatus } from './setup-status.js';
|
|
51
51
|
import { annotateBridgeSummary, summarizeBridgeResponse } from './subagent.js';
|
|
52
52
|
|
|
53
|
-
/** @typedef {import('
|
|
54
|
-
/** @typedef {
|
|
53
|
+
/** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
|
|
54
|
+
/** @typedef {import('./types.js').ScreenshotResult} ScreenshotResult */
|
|
55
55
|
|
|
56
56
|
const REQUEST_SOURCE = 'cli';
|
|
57
57
|
const TEST_TIMEOUT_ENV = 'BBX_CLIENT_REQUEST_TIMEOUT_MS';
|
|
@@ -139,8 +139,8 @@ if (command === 'install-skill') {
|
|
|
139
139
|
projectPath: isGlobal ? os.homedir() : process.cwd(),
|
|
140
140
|
...getSetupStatusTestOverrides(),
|
|
141
141
|
});
|
|
142
|
-
/** @type {import('./
|
|
143
|
-
const detected = /** @type {import('./
|
|
142
|
+
/** @type {import('./types.js').SupportedTarget[]} */
|
|
143
|
+
const detected = /** @type {import('./types.js').SupportedTarget[]} */ (
|
|
144
144
|
setupStatus.skillTargets.filter((entry) => entry.detected).map((entry) => entry.key)
|
|
145
145
|
);
|
|
146
146
|
const installedManagedTargets = new Set(
|
|
@@ -149,7 +149,7 @@ if (command === 'install-skill') {
|
|
|
149
149
|
.map((entry) => entry.key)
|
|
150
150
|
);
|
|
151
151
|
const installedManagedTargetList =
|
|
152
|
-
/** @type {import('./
|
|
152
|
+
/** @type {import('./types.js').SupportedTarget[]} */ ([...installedManagedTargets]);
|
|
153
153
|
|
|
154
154
|
// Aliases like 'openai' and 'google' map to canonical targets and stay omitted.
|
|
155
155
|
const items = SUPPORTED_TARGETS.map((t) => ({
|
|
@@ -167,19 +167,19 @@ if (command === 'install-skill') {
|
|
|
167
167
|
items
|
|
168
168
|
);
|
|
169
169
|
|
|
170
|
-
/** @type {import('./
|
|
170
|
+
/** @type {import('./types.js').SupportedTarget[]} */
|
|
171
171
|
let targets;
|
|
172
172
|
if (selected === null) {
|
|
173
173
|
// Non-TTY: prefer managed installs, then detected targets (always includes 'agents').
|
|
174
174
|
targets = installedManagedTargets.size > 0 ? installedManagedTargetList : detected;
|
|
175
175
|
} else {
|
|
176
|
-
targets = /** @type {import('./
|
|
176
|
+
targets = /** @type {import('./types.js').SupportedTarget[]} */ (selected);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const projectPath = isGlobal ? os.homedir() : process.cwd();
|
|
180
180
|
if (selected !== null) {
|
|
181
181
|
const deselectedTargets =
|
|
182
|
-
/** @type {import('./
|
|
182
|
+
/** @type {import('./types.js').SupportedTarget[]} */ (
|
|
183
183
|
installedManagedTargetList.filter((target) => !targets.includes(target))
|
|
184
184
|
);
|
|
185
185
|
const removableTargets = await findInstalledManagedTargets({
|
|
@@ -241,7 +241,7 @@ if (command === 'install-mcp') {
|
|
|
241
241
|
|
|
242
242
|
const clientArg = argsLeft[0];
|
|
243
243
|
|
|
244
|
-
/** @type {import('./
|
|
244
|
+
/** @type {import('./types.js').McpClientName[]} */
|
|
245
245
|
let clients;
|
|
246
246
|
|
|
247
247
|
if (!clientArg) {
|
|
@@ -252,14 +252,14 @@ if (command === 'install-mcp') {
|
|
|
252
252
|
projectPath: process.cwd(),
|
|
253
253
|
...getSetupStatusTestOverrides(),
|
|
254
254
|
});
|
|
255
|
-
const detected = /** @type {import('./
|
|
255
|
+
const detected = /** @type {import('./types.js').McpClientName[]} */ (
|
|
256
256
|
setupStatus.mcpClients.filter((entry) => entry.detected).map((entry) => entry.key)
|
|
257
257
|
);
|
|
258
258
|
const configuredClients = new Set(
|
|
259
259
|
setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
|
|
260
260
|
);
|
|
261
261
|
const configuredClientList =
|
|
262
|
-
/** @type {import('./
|
|
262
|
+
/** @type {import('./types.js').McpClientName[]} */ ([...configuredClients]);
|
|
263
263
|
const items = MCP_CLIENT_NAMES.map((c) => ({
|
|
264
264
|
value: c,
|
|
265
265
|
label: `${c.padEnd(10)} ${MCP_CLIENT_LABELS[c]}`,
|
|
@@ -284,12 +284,12 @@ if (command === 'install-mcp') {
|
|
|
284
284
|
? detected
|
|
285
285
|
: [...MCP_CLIENT_NAMES];
|
|
286
286
|
} else {
|
|
287
|
-
clients = /** @type {import('./
|
|
287
|
+
clients = /** @type {import('./types.js').McpClientName[]} */ (selected);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
if (selected !== null) {
|
|
291
291
|
const deselectedClients =
|
|
292
|
-
/** @type {import('./
|
|
292
|
+
/** @type {import('./types.js').McpClientName[]} */ (
|
|
293
293
|
configuredClientList.filter((clientName) => !clients.includes(clientName))
|
|
294
294
|
);
|
|
295
295
|
const removableClients = await findConfiguredMcpClients({
|
|
@@ -470,15 +470,20 @@ async function main() {
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
if (command === 'batch') {
|
|
473
|
-
await ensureClientConnection();
|
|
474
473
|
const input = rest[0];
|
|
475
474
|
if (!input) {
|
|
476
475
|
throw new Error('Usage: batch \'[{"method":"...","params":{...}}, ...]\'');
|
|
477
476
|
}
|
|
478
|
-
|
|
477
|
+
let calls;
|
|
478
|
+
try {
|
|
479
|
+
calls = JSON.parse(input);
|
|
480
|
+
} catch {
|
|
481
|
+
throw new Error('Invalid JSON syntax. Expected a JSON array of bridge calls.');
|
|
482
|
+
}
|
|
479
483
|
if (!Array.isArray(calls)) {
|
|
480
484
|
throw new Error('Batch input must be a JSON array.');
|
|
481
485
|
}
|
|
486
|
+
await ensureClientConnection();
|
|
482
487
|
const results = await Promise.all(
|
|
483
488
|
calls.map(async (call) => {
|
|
484
489
|
if (!call || typeof call !== 'object' || typeof call.method !== 'string') {
|
|
@@ -613,16 +618,18 @@ async function main() {
|
|
|
613
618
|
}
|
|
614
619
|
|
|
615
620
|
if (command === 'screenshot') {
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
621
|
+
const parsed = extractTabFlag(rest);
|
|
622
|
+
const [refOrSelector, outputPath] = parsed.rest;
|
|
623
|
+
if (!refOrSelector)
|
|
624
|
+
throw new Error('Usage: screenshot [--tab <tabId>] <ref|selector> [path]');
|
|
625
|
+
const elementRef = await resolveRef(client, refOrSelector, parsed.tabId, REQUEST_SOURCE);
|
|
619
626
|
const response = await requestBridge(
|
|
620
627
|
client,
|
|
621
628
|
'screenshot.capture_element',
|
|
622
629
|
{
|
|
623
630
|
elementRef,
|
|
624
631
|
},
|
|
625
|
-
{ source: REQUEST_SOURCE }
|
|
632
|
+
{ tabId: parsed.tabId, source: REQUEST_SOURCE }
|
|
626
633
|
);
|
|
627
634
|
if (!response.ok) {
|
|
628
635
|
await printSummary(response);
|
|
@@ -845,10 +852,13 @@ async function uninstallBrowserBridge() {
|
|
|
845
852
|
*/
|
|
846
853
|
async function parseCallCommand(args) {
|
|
847
854
|
const parsed = extractTabFlag(args);
|
|
848
|
-
const [first, second] = parsed.rest;
|
|
855
|
+
const [first, second, ...extra] = parsed.rest;
|
|
849
856
|
if (!first) {
|
|
850
857
|
throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
|
|
851
858
|
}
|
|
859
|
+
if (extra.length > 0) {
|
|
860
|
+
throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
|
|
861
|
+
}
|
|
852
862
|
|
|
853
863
|
if (first.includes('.')) {
|
|
854
864
|
const method = /** @type {BridgeMethod} */ (first);
|
|
@@ -15,48 +15,33 @@ import {
|
|
|
15
15
|
getBridgeTransport,
|
|
16
16
|
getSocketPath,
|
|
17
17
|
} from '../../native-host/src/config.js';
|
|
18
|
+
import { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
|
|
18
19
|
|
|
19
|
-
/** @typedef {import('
|
|
20
|
+
/** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
|
|
21
|
+
/** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
|
|
22
|
+
/** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
|
|
23
|
+
/** @typedef {import('./types.js').BridgeClientOptions} BridgeClientOptions */
|
|
24
|
+
/** @typedef {import('./types.js').ClientMessage} ClientMessage */
|
|
25
|
+
/** @typedef {import('./types.js').PendingRequest} PendingRequest */
|
|
26
|
+
/** @typedef {import('./types.js').ProtocolHealthResult} ProtocolHealthResult */
|
|
20
27
|
|
|
21
|
-
/** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
|
|
22
|
-
/** @typedef {import('../../protocol/src/types.js').BridgeMeta} BridgeMeta */
|
|
23
|
-
/** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
|
|
24
28
|
/**
|
|
25
|
-
* @
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* migration_hint?: string
|
|
29
|
-
* }} ProtocolHealthResult
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {{
|
|
34
|
-
* type: 'registered',
|
|
35
|
-
* role: 'agent' | 'extension',
|
|
36
|
-
* clientId?: string
|
|
37
|
-
* } | {
|
|
38
|
-
* type: 'agent.response',
|
|
39
|
-
* response: BridgeResponse
|
|
40
|
-
* }} ClientMessage
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @typedef {{
|
|
45
|
-
* resolve: (value: any) => void,
|
|
46
|
-
* reject: (error: Error) => void,
|
|
47
|
-
* timeoutId: NodeJS.Timeout
|
|
48
|
-
* }} PendingRequest
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @typedef {{
|
|
53
|
-
* transport?: BridgeTransport,
|
|
54
|
-
* socketPath?: string,
|
|
55
|
-
* clientId?: string,
|
|
56
|
-
* defaultTimeoutMs?: number,
|
|
57
|
-
* autoReconnect?: boolean,
|
|
58
|
-
* }} BridgeClientOptions
|
|
29
|
+
* @param {string} left
|
|
30
|
+
* @param {string} right
|
|
31
|
+
* @returns {number}
|
|
59
32
|
*/
|
|
33
|
+
function compareProtocolVersions(left, right) {
|
|
34
|
+
const leftParts = left.split('.').map((part) => Number(part) || 0);
|
|
35
|
+
const rightParts = right.split('.').map((part) => Number(part) || 0);
|
|
36
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
37
|
+
for (let index = 0; index < length; index += 1) {
|
|
38
|
+
const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
|
|
39
|
+
if (delta !== 0) {
|
|
40
|
+
return delta > 0 ? 1 : -1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
60
45
|
|
|
61
46
|
/**
|
|
62
47
|
* @param {string} method
|
|
@@ -81,6 +66,8 @@ export class BridgeClient extends EventEmitter {
|
|
|
81
66
|
clientId = `agent_${randomUUID()}`,
|
|
82
67
|
defaultTimeoutMs = DEFAULT_CLIENT_REQUEST_TIMEOUT_MS,
|
|
83
68
|
autoReconnect = false,
|
|
69
|
+
restartDaemonOnVersionMismatch = true,
|
|
70
|
+
restartDaemonFn = restartBridgeDaemon,
|
|
84
71
|
} = {}) {
|
|
85
72
|
super();
|
|
86
73
|
this.transport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
|
|
@@ -89,6 +76,8 @@ export class BridgeClient extends EventEmitter {
|
|
|
89
76
|
this.clientId = clientId;
|
|
90
77
|
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
91
78
|
this.autoReconnect = autoReconnect;
|
|
79
|
+
this.restartDaemonOnVersionMismatch = restartDaemonOnVersionMismatch;
|
|
80
|
+
this.restartDaemonFn = restartDaemonFn;
|
|
92
81
|
this.socket = null;
|
|
93
82
|
this.connected = false;
|
|
94
83
|
this.protocolCompatibility = null;
|
|
@@ -96,6 +85,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
96
85
|
/** @type {Map<string, PendingRequest>} */
|
|
97
86
|
this.waiting = new Map();
|
|
98
87
|
this._reconnecting = false;
|
|
88
|
+
this._attemptedVersionMismatchRestart = false;
|
|
99
89
|
}
|
|
100
90
|
|
|
101
91
|
/**
|
|
@@ -173,19 +163,38 @@ export class BridgeClient extends EventEmitter {
|
|
|
173
163
|
});
|
|
174
164
|
});
|
|
175
165
|
|
|
166
|
+
this.protocolCompatibility = null;
|
|
167
|
+
this.protocolWarning = null;
|
|
168
|
+
|
|
169
|
+
/** @type {ProtocolHealthResult | null} */
|
|
170
|
+
let healthResult = null;
|
|
176
171
|
try {
|
|
177
172
|
const healthResponse = await this.request({
|
|
178
173
|
method: 'health.ping',
|
|
179
174
|
});
|
|
180
175
|
if (healthResponse.ok) {
|
|
181
|
-
|
|
182
|
-
/** @type {ProtocolHealthResult} */ (healthResponse.result)
|
|
183
|
-
);
|
|
184
|
-
this.protocolWarning = this.protocolCompatibility.warning ?? null;
|
|
176
|
+
healthResult = /** @type {ProtocolHealthResult} */ (healthResponse.result);
|
|
185
177
|
}
|
|
186
178
|
} catch {
|
|
187
179
|
this.protocolCompatibility = null;
|
|
188
180
|
this.protocolWarning = null;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!healthResult) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.protocolCompatibility = BridgeClient.checkProtocolVersion(healthResult);
|
|
189
|
+
this.protocolWarning = this.protocolCompatibility.warning ?? null;
|
|
190
|
+
if (this.protocolCompatibility.compatible) {
|
|
191
|
+
this._attemptedVersionMismatchRestart = false;
|
|
192
|
+
}
|
|
193
|
+
if (this.shouldRestartDaemonForProtocolMismatch(healthResult)) {
|
|
194
|
+
this._attemptedVersionMismatchRestart = true;
|
|
195
|
+
await this.disconnectForDaemonRestart();
|
|
196
|
+
await this.restartDaemonFn({ transport: this.transport });
|
|
197
|
+
await this.connect();
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
200
|
|
|
@@ -305,6 +314,60 @@ export class BridgeClient extends EventEmitter {
|
|
|
305
314
|
this._reconnecting = false;
|
|
306
315
|
}
|
|
307
316
|
|
|
317
|
+
/**
|
|
318
|
+
* @param {ProtocolHealthResult} healthResult
|
|
319
|
+
* @returns {boolean}
|
|
320
|
+
*/
|
|
321
|
+
shouldRestartDaemonForProtocolMismatch(healthResult) {
|
|
322
|
+
if (
|
|
323
|
+
!this.restartDaemonOnVersionMismatch ||
|
|
324
|
+
this._attemptedVersionMismatchRestart ||
|
|
325
|
+
!this.protocolCompatibility ||
|
|
326
|
+
this.protocolCompatibility.compatible
|
|
327
|
+
) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const remoteVersions = Array.isArray(healthResult?.daemon_supported_versions)
|
|
332
|
+
? healthResult.daemon_supported_versions
|
|
333
|
+
: healthResult?.extensionConnected === true
|
|
334
|
+
? []
|
|
335
|
+
: Array.isArray(healthResult?.supported_versions)
|
|
336
|
+
? healthResult.supported_versions
|
|
337
|
+
: [];
|
|
338
|
+
const latestRemote = remoteVersions[0];
|
|
339
|
+
return (
|
|
340
|
+
typeof latestRemote === 'string' &&
|
|
341
|
+
compareProtocolVersions(latestRemote, PROTOCOL_VERSION) < 0
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Drop the current socket before forcing a daemon restart so the next
|
|
347
|
+
* connect() call observes a fresh local process rather than the existing one.
|
|
348
|
+
*
|
|
349
|
+
* @returns {Promise<void>}
|
|
350
|
+
*/
|
|
351
|
+
async disconnectForDaemonRestart() {
|
|
352
|
+
const socket = this.socket;
|
|
353
|
+
if (!socket) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const previousAutoReconnect = this.autoReconnect;
|
|
358
|
+
this.autoReconnect = false;
|
|
359
|
+
this.connected = false;
|
|
360
|
+
this.socket = null;
|
|
361
|
+
|
|
362
|
+
if (!socket.destroyed) {
|
|
363
|
+
const closed = once(socket, 'close').catch(() => {});
|
|
364
|
+
socket.destroy();
|
|
365
|
+
await closed;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this.autoReconnect = previousAutoReconnect;
|
|
369
|
+
}
|
|
370
|
+
|
|
308
371
|
/**
|
|
309
372
|
* Check whether the remote side supports our protocol version.
|
|
310
373
|
* Call after a successful health.ping to get early warnings about version drift.
|
|
@@ -3,18 +3,8 @@
|
|
|
3
3
|
import { BRIDGE_METHOD_REGISTRY, BRIDGE_METHODS } from '../../protocol/src/index.js';
|
|
4
4
|
import { parseCommaList, parseIntArg, parsePropertyAssignments } from './cli-helpers.js';
|
|
5
5
|
|
|
6
|
-
/** @typedef {import('
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {{
|
|
10
|
-
* method: BridgeMethod,
|
|
11
|
-
* resolve?: boolean,
|
|
12
|
-
* printMethod?: string,
|
|
13
|
-
* usage: string,
|
|
14
|
-
* description: string,
|
|
15
|
-
* build: (r: string[], ref?: string) => Record<string, unknown>
|
|
16
|
-
* }} ShortcutCommand
|
|
17
|
-
*/
|
|
6
|
+
/** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
|
|
7
|
+
/** @typedef {import('./types.js').ShortcutCommand} ShortcutCommand */
|
|
18
8
|
|
|
19
9
|
/**
|
|
20
10
|
* @param {BridgeMethod} method
|
|
@@ -247,7 +237,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
247
237
|
{
|
|
248
238
|
title: 'Setup',
|
|
249
239
|
lines: [
|
|
250
|
-
'bbx install [--browser chrome|edge|brave|chromium] [extension-id] Install native messaging manifest',
|
|
240
|
+
'bbx install [--browser chrome|edge|brave|chromium|arc] [extension-id] Install native messaging manifest',
|
|
251
241
|
'bbx uninstall Remove native host manifests, Browser Bridge runtime files, and managed MCP/skill installs',
|
|
252
242
|
'bbx install [--all] [--browser <name>] [extension-id] Install native host manifest (--all for all supported browsers)',
|
|
253
243
|
'bbx install-skill [targets|all] [--global] [--project <path>] Install/update the managed Browser Bridge CLI skill',
|
|
@@ -347,7 +337,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
347
337
|
{
|
|
348
338
|
title: 'Capture',
|
|
349
339
|
lines: [
|
|
350
|
-
'bbx screenshot <ref|selector> [path]
|
|
340
|
+
'bbx screenshot [--tab <tabId>] <ref|selector> [path] Capture partial element screenshot',
|
|
351
341
|
],
|
|
352
342
|
},
|
|
353
343
|
]);
|
|
@@ -4,9 +4,9 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
/** @typedef {import('./
|
|
8
|
-
/** @typedef {import('./
|
|
9
|
-
/** @typedef {()
|
|
7
|
+
/** @typedef {import('./types.js').McpClientName} McpClientName */
|
|
8
|
+
/** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
|
|
9
|
+
/** @typedef {import('./types.js').Detector} Detector */
|
|
10
10
|
|
|
11
11
|
const home = os.homedir();
|
|
12
12
|
const platform = process.platform;
|
|
@@ -41,9 +41,7 @@ const copilotBrowserBridgeNote = [
|
|
|
41
41
|
'',
|
|
42
42
|
].join('\n');
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* @typedef {'codex' | 'claude' | 'cursor' | 'copilot' | 'opencode' | 'antigravity' | 'windsurf' | 'agents'} SupportedTarget
|
|
46
|
-
*/
|
|
44
|
+
/** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
|
|
47
45
|
|
|
48
46
|
/** @type {SupportedTarget[]} */
|
|
49
47
|
export const SUPPORTED_TARGETS = [...supportedTargets];
|
|
@@ -68,9 +66,7 @@ export function isSupportedTarget(value) {
|
|
|
68
66
|
return supportedTargets.includes(/** @type {SupportedTarget} */ (value));
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
/**
|
|
72
|
-
* @typedef {{targets: SupportedTarget[], projectPath: string, global: boolean}} InstallAgentOptions
|
|
73
|
-
*/
|
|
69
|
+
/** @typedef {import('./types.js').InstallAgentOptions} InstallAgentOptions */
|
|
74
70
|
|
|
75
71
|
/**
|
|
76
72
|
* @param {string[]} args
|
|
@@ -202,7 +198,7 @@ async function rollbackInstalledSkillDirs(attempted) {
|
|
|
202
198
|
/**
|
|
203
199
|
* Write MCP config for the given clients.
|
|
204
200
|
*
|
|
205
|
-
* @param {import('./
|
|
201
|
+
* @param {import('./types.js').McpClientName[]} clients
|
|
206
202
|
* @param {{
|
|
207
203
|
* global: boolean,
|
|
208
204
|
* projectPath: string,
|
|
@@ -5,9 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {'codex' | 'claude' | 'cursor' | 'copilot' | 'opencode' | 'antigravity' | 'windsurf' | 'agents'} McpClientName
|
|
10
|
-
*/
|
|
8
|
+
/** @typedef {import('./types.js').McpClientName} McpClientName */
|
|
11
9
|
|
|
12
10
|
/** @type {McpClientName[]} */
|
|
13
11
|
export const MCP_CLIENT_NAMES = [
|
|
@@ -12,39 +12,14 @@ import { resolveDefaultExtensionId } from '../../native-host/src/install-manifes
|
|
|
12
12
|
import { methodNeedsTab } from './cli-helpers.js';
|
|
13
13
|
import { BridgeClient } from './client.js';
|
|
14
14
|
|
|
15
|
-
/** @typedef {import('
|
|
16
|
-
/** @typedef {import('
|
|
17
|
-
/** @typedef {import('
|
|
18
|
-
/** @typedef {import('
|
|
15
|
+
/** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
|
|
16
|
+
/** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
|
|
17
|
+
/** @typedef {import('./types.js').BridgeRequestSource} BridgeRequestSource */
|
|
18
|
+
/** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
|
|
19
19
|
/** @typedef {import('../../native-host/src/config.js').SupportedBrowser} SupportedBrowser */
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
* browser: string,
|
|
24
|
-
* manifestPath: string,
|
|
25
|
-
* installed: boolean
|
|
26
|
-
* }} BrowserManifestStatus
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @typedef {{
|
|
31
|
-
* manifestInstalled: boolean,
|
|
32
|
-
* manifestPath: string,
|
|
33
|
-
* allowedOrigins: string[],
|
|
34
|
-
* defaultExtensionId: string | null,
|
|
35
|
-
* defaultExtensionIdSource: string,
|
|
36
|
-
* daemonReachable: boolean,
|
|
37
|
-
* extensionConnected: boolean,
|
|
38
|
-
* accessEnabled: boolean,
|
|
39
|
-
* enabledWindowId: number | null,
|
|
40
|
-
* routeTabId: number | null,
|
|
41
|
-
* routeReady: boolean,
|
|
42
|
-
* routeReason: string,
|
|
43
|
-
* issues: string[],
|
|
44
|
-
* nextSteps: string[],
|
|
45
|
-
* browserManifests: BrowserManifestStatus[]
|
|
46
|
-
* }} DoctorReport
|
|
47
|
-
*/
|
|
20
|
+
/** @typedef {import('./types.js').BrowserManifestStatus} BrowserManifestStatus */
|
|
21
|
+
/** @typedef {import('./types.js').DoctorReport} DoctorReport */
|
|
22
|
+
/** @typedef {import('./types.js').DoctorReportOptions} DoctorReportOptions */
|
|
48
23
|
|
|
49
24
|
/**
|
|
50
25
|
* @param {BridgeClient} client
|
|
@@ -178,15 +153,6 @@ export async function checkBrowserManifests() {
|
|
|
178
153
|
);
|
|
179
154
|
}
|
|
180
155
|
|
|
181
|
-
/**
|
|
182
|
-
* @typedef {{
|
|
183
|
-
* loadManifest?: () => Promise<{allowed_origins?: string[]} | null>,
|
|
184
|
-
* manifestPath?: string,
|
|
185
|
-
* defaultExtensionIdInfo?: { extensionId: string | null, source: string },
|
|
186
|
-
* bridgeClientRunner?: <T>(callback: (client: BridgeClient) => Promise<T>) => Promise<T>
|
|
187
|
-
* }} DoctorReportOptions
|
|
188
|
-
*/
|
|
189
|
-
|
|
190
156
|
/**
|
|
191
157
|
* @param {DoctorReportOptions} [options={}]
|
|
192
158
|
* @returns {Promise<DoctorReport>}
|
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
SUPPORTED_TARGETS,
|
|
22
22
|
} from './install.js';
|
|
23
23
|
|
|
24
|
-
/** @typedef {import('./
|
|
25
|
-
/** @typedef {import('./
|
|
24
|
+
/** @typedef {import('./types.js').McpClientName} McpClientName */
|
|
25
|
+
/** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
|
|
26
26
|
/** @typedef {import('../../protocol/src/types.js').SetupStatus} SetupStatus */
|
|
27
27
|
/** @typedef {import('../../protocol/src/types.js').McpClientStatus} McpClientStatus */
|
|
28
28
|
/** @typedef {import('../../protocol/src/types.js').SkillTargetStatus} SkillTargetStatus */
|
|
@@ -52,17 +52,7 @@ const SKILL_TARGET_LABELS = {
|
|
|
52
52
|
agents: 'Generic agents',
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
* @typedef {{
|
|
57
|
-
* global?: boolean,
|
|
58
|
-
* cwd?: string,
|
|
59
|
-
* projectPath?: string,
|
|
60
|
-
* mcpDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
61
|
-
* skillDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
62
|
-
* access?: (targetPath: string) => Promise<void>,
|
|
63
|
-
* readFile?: (targetPath: string, encoding: BufferEncoding) => Promise<string>
|
|
64
|
-
* }} SetupStatusOptions
|
|
65
|
-
*/
|
|
55
|
+
/** @typedef {import('./types.js').SetupStatusOptions} SetupStatusOptions */
|
|
66
56
|
|
|
67
57
|
/**
|
|
68
58
|
* Return Browser Bridge MCP and skill installation status for supported clients.
|