@browserbridge/bbx 1.2.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 +24 -16
- package/packages/agent-client/src/client.js +7 -45
- package/packages/agent-client/src/command-registry.js +3 -13
- 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 +17 -5
- package/packages/mcp-server/src/handlers-dom.js +11 -4
- package/packages/mcp-server/src/handlers-page.js +2 -2
- package/packages/mcp-server/src/handlers-utils.js +22 -0
- package/packages/mcp-server/src/server.js +1 -0
- package/packages/native-host/bin/postinstall.js +42 -21
- package/packages/native-host/src/daemon-process.js +1 -2
- package/packages/native-host/src/daemon.js +28 -3
- package/packages/native-host/src/framing.js +13 -0
- package/packages/native-host/src/native-host.js +7 -5
- package/packages/protocol/src/protocol.js +40 -0
- package/packages/protocol/src/registry.js +3 -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') {
|
|
@@ -847,10 +852,13 @@ async function uninstallBrowserBridge() {
|
|
|
847
852
|
*/
|
|
848
853
|
async function parseCallCommand(args) {
|
|
849
854
|
const parsed = extractTabFlag(args);
|
|
850
|
-
const [first, second] = parsed.rest;
|
|
855
|
+
const [first, second, ...extra] = parsed.rest;
|
|
851
856
|
if (!first) {
|
|
852
857
|
throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
|
|
853
858
|
}
|
|
859
|
+
if (extra.length > 0) {
|
|
860
|
+
throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
|
|
861
|
+
}
|
|
854
862
|
|
|
855
863
|
if (first.includes('.')) {
|
|
856
864
|
const method = /** @type {BridgeMethod} */ (first);
|
|
@@ -17,51 +17,13 @@ import {
|
|
|
17
17
|
} from '../../native-host/src/config.js';
|
|
18
18
|
import { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
|
|
19
19
|
|
|
20
|
-
/** @typedef {import('
|
|
21
|
-
|
|
22
|
-
/** @typedef {import('
|
|
23
|
-
/** @typedef {import('
|
|
24
|
-
/** @typedef {import('
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
* extensionConnected?: boolean,
|
|
28
|
-
* supported_versions?: string[],
|
|
29
|
-
* daemon_supported_versions?: string[],
|
|
30
|
-
* deprecated_since?: string,
|
|
31
|
-
* migration_hint?: string
|
|
32
|
-
* }} ProtocolHealthResult
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @typedef {{
|
|
37
|
-
* type: 'registered',
|
|
38
|
-
* role: 'agent' | 'extension',
|
|
39
|
-
* clientId?: string
|
|
40
|
-
* } | {
|
|
41
|
-
* type: 'agent.response',
|
|
42
|
-
* response: BridgeResponse
|
|
43
|
-
* }} ClientMessage
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @typedef {{
|
|
48
|
-
* resolve: (value: any) => void,
|
|
49
|
-
* reject: (error: Error) => void,
|
|
50
|
-
* timeoutId: NodeJS.Timeout
|
|
51
|
-
* }} PendingRequest
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @typedef {{
|
|
56
|
-
* transport?: BridgeTransport,
|
|
57
|
-
* socketPath?: string,
|
|
58
|
-
* clientId?: string,
|
|
59
|
-
* defaultTimeoutMs?: number,
|
|
60
|
-
* autoReconnect?: boolean,
|
|
61
|
-
* restartDaemonOnVersionMismatch?: boolean,
|
|
62
|
-
* restartDaemonFn?: typeof restartBridgeDaemon,
|
|
63
|
-
* }} BridgeClientOptions
|
|
64
|
-
*/
|
|
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 */
|
|
65
27
|
|
|
66
28
|
/**
|
|
67
29
|
* @param {string} left
|
|
@@ -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',
|
|
@@ -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.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { BridgeTransport } from '../../native-host/src/config.js';
|
|
2
|
+
import type {
|
|
3
|
+
BridgeMeta,
|
|
4
|
+
BridgeMethod,
|
|
5
|
+
BridgeRequestSource,
|
|
6
|
+
BridgeResponse,
|
|
7
|
+
} from '../../protocol/src/types.js';
|
|
8
|
+
import type { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
|
|
9
|
+
|
|
10
|
+
export type { BridgeMeta, BridgeMethod, BridgeRequestSource, BridgeResponse, BridgeTransport };
|
|
11
|
+
|
|
12
|
+
export type McpClientName =
|
|
13
|
+
| 'codex'
|
|
14
|
+
| 'claude'
|
|
15
|
+
| 'cursor'
|
|
16
|
+
| 'copilot'
|
|
17
|
+
| 'opencode'
|
|
18
|
+
| 'antigravity'
|
|
19
|
+
| 'windsurf'
|
|
20
|
+
| 'agents';
|
|
21
|
+
|
|
22
|
+
export type SupportedTarget = McpClientName;
|
|
23
|
+
|
|
24
|
+
export type Detector = () => boolean | Promise<boolean>;
|
|
25
|
+
|
|
26
|
+
export interface InstallAgentOptions {
|
|
27
|
+
targets: SupportedTarget[];
|
|
28
|
+
projectPath: string;
|
|
29
|
+
global: boolean;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SetupStatusOptions {
|
|
34
|
+
global?: boolean;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
projectPath?: string;
|
|
37
|
+
mcpDetectors?: Record<string, Detector>;
|
|
38
|
+
skillDetectors?: Record<string, Detector>;
|
|
39
|
+
access?: (targetPath: string) => Promise<void>;
|
|
40
|
+
readFile?: (targetPath: string, encoding: BufferEncoding) => Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProtocolHealthResult {
|
|
44
|
+
extensionConnected?: boolean;
|
|
45
|
+
supported_versions?: string[];
|
|
46
|
+
daemon_supported_versions?: string[];
|
|
47
|
+
deprecated_since?: string;
|
|
48
|
+
migration_hint?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ClientMessage =
|
|
52
|
+
| {
|
|
53
|
+
type: 'registered';
|
|
54
|
+
role: 'agent' | 'extension';
|
|
55
|
+
clientId?: string;
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
type: 'agent.response';
|
|
59
|
+
response: BridgeResponse;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export interface PendingRequest {
|
|
63
|
+
resolve: (value: any) => void;
|
|
64
|
+
reject: (error: Error) => void;
|
|
65
|
+
timeoutId: NodeJS.Timeout;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BridgeClientOptions {
|
|
69
|
+
transport?: BridgeTransport;
|
|
70
|
+
socketPath?: string;
|
|
71
|
+
clientId?: string;
|
|
72
|
+
defaultTimeoutMs?: number;
|
|
73
|
+
autoReconnect?: boolean;
|
|
74
|
+
restartDaemonOnVersionMismatch?: boolean;
|
|
75
|
+
restartDaemonFn?: typeof restartBridgeDaemon;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ShortcutCommand {
|
|
79
|
+
method: BridgeMethod;
|
|
80
|
+
resolve?: boolean;
|
|
81
|
+
printMethod?: string;
|
|
82
|
+
usage: string;
|
|
83
|
+
description: string;
|
|
84
|
+
build: (r: string[], ref?: string) => Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface BrowserManifestStatus {
|
|
88
|
+
browser: string;
|
|
89
|
+
manifestPath: string;
|
|
90
|
+
installed: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface DoctorReport {
|
|
94
|
+
manifestInstalled: boolean;
|
|
95
|
+
manifestPath: string;
|
|
96
|
+
allowedOrigins: string[];
|
|
97
|
+
defaultExtensionId: string | null;
|
|
98
|
+
defaultExtensionIdSource: string;
|
|
99
|
+
daemonReachable: boolean;
|
|
100
|
+
extensionConnected: boolean;
|
|
101
|
+
accessEnabled: boolean;
|
|
102
|
+
enabledWindowId: number | null;
|
|
103
|
+
routeTabId: number | null;
|
|
104
|
+
routeReady: boolean;
|
|
105
|
+
routeReason: string;
|
|
106
|
+
issues: string[];
|
|
107
|
+
nextSteps: string[];
|
|
108
|
+
browserManifests: BrowserManifestStatus[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface DoctorReportOptions {
|
|
112
|
+
loadManifest?: () => Promise<{ allowed_origins?: string[] } | null>;
|
|
113
|
+
manifestPath?: string;
|
|
114
|
+
defaultExtensionIdInfo?: { extensionId: string | null; source: string };
|
|
115
|
+
bridgeClientRunner?: <T>(
|
|
116
|
+
callback: (client: { request: BridgeClientRequest }) => Promise<T>
|
|
117
|
+
) => Promise<T>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type BridgeClientRequest = (options: {
|
|
121
|
+
method: BridgeMethod;
|
|
122
|
+
tabId?: number | null;
|
|
123
|
+
params?: Record<string, unknown>;
|
|
124
|
+
meta?: BridgeMeta;
|
|
125
|
+
timeoutMs?: number;
|
|
126
|
+
}) => Promise<BridgeResponse>;
|
|
127
|
+
|
|
128
|
+
export interface ScreenshotResult {
|
|
129
|
+
image: string;
|
|
130
|
+
rect: Record<string, unknown>;
|
|
131
|
+
}
|
|
@@ -40,22 +40,34 @@ export const CAPTURE_ACTIONS = {
|
|
|
40
40
|
params: () => ({}),
|
|
41
41
|
},
|
|
42
42
|
cdp_box_model: {
|
|
43
|
-
ref:
|
|
43
|
+
ref: false,
|
|
44
44
|
method: 'cdp.get_box_model',
|
|
45
|
-
params: (
|
|
45
|
+
params: (a) => ({ nodeId: a.nodeId }),
|
|
46
46
|
},
|
|
47
47
|
cdp_computed_styles: {
|
|
48
|
-
ref:
|
|
48
|
+
ref: false,
|
|
49
49
|
method: 'cdp.get_computed_styles_for_node',
|
|
50
|
-
params: (
|
|
50
|
+
params: (a) => ({ nodeId: a.nodeId }),
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/** @param {Record<string, unknown>} args */
|
|
55
|
+
function isCdpNodeCapture(args) {
|
|
56
|
+
return args.action === 'cdp_box_model' || args.action === 'cdp_computed_styles';
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
/**
|
|
55
|
-
* @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
60
|
+
* @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, nodeId?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
56
61
|
* @returns {Promise<ToolResult>}
|
|
57
62
|
*/
|
|
58
63
|
export async function handleCaptureTool(args) {
|
|
64
|
+
if (
|
|
65
|
+
isCdpNodeCapture(args) &&
|
|
66
|
+
(typeof args.nodeId !== 'number' || !Number.isFinite(args.nodeId))
|
|
67
|
+
) {
|
|
68
|
+
return summarizeToolError('nodeId must be a finite number.');
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
return dispatchToolAction(CAPTURE_ACTIONS, args, 'capture');
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -148,6 +148,7 @@ export const PATCH_ACTIONS = {
|
|
|
148
148
|
target: { elementRef: r },
|
|
149
149
|
declarations: a.declarations,
|
|
150
150
|
important: a.important,
|
|
151
|
+
patchId: a.patchId,
|
|
151
152
|
verify: a.verify,
|
|
152
153
|
}),
|
|
153
154
|
},
|
|
@@ -160,16 +161,22 @@ export const PATCH_ACTIONS = {
|
|
|
160
161
|
const opMap = {
|
|
161
162
|
setAttribute: 'set_attribute',
|
|
162
163
|
removeAttribute: 'remove_attribute',
|
|
163
|
-
addClass: '
|
|
164
|
-
removeClass: '
|
|
164
|
+
addClass: 'add_class',
|
|
165
|
+
removeClass: 'remove_class',
|
|
165
166
|
setTextContent: 'set_text',
|
|
166
167
|
setProperty: 'set_attribute',
|
|
167
168
|
};
|
|
169
|
+
const normalizedOperation = opMap[operation] || operation;
|
|
170
|
+
const value =
|
|
171
|
+
normalizedOperation === 'add_class' || normalizedOperation === 'remove_class'
|
|
172
|
+
? (a.value ?? a.name)
|
|
173
|
+
: a.value;
|
|
168
174
|
return {
|
|
169
175
|
target: { elementRef: r },
|
|
170
|
-
operation:
|
|
171
|
-
value
|
|
176
|
+
operation: normalizedOperation,
|
|
177
|
+
value,
|
|
172
178
|
name: a.name,
|
|
179
|
+
patchId: a.patchId,
|
|
173
180
|
verify: a.verify,
|
|
174
181
|
};
|
|
175
182
|
},
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
annotateBridgeSummary,
|
|
10
10
|
applyLimitBudgetPreset,
|
|
11
|
-
|
|
11
|
+
applyPageTextBudgetPreset,
|
|
12
12
|
bridgeMethodNeedsTab,
|
|
13
13
|
callBridgeTool,
|
|
14
14
|
createToolResult,
|
|
@@ -72,7 +72,7 @@ export const PAGE_ACTIONS = {
|
|
|
72
72
|
export async function handlePageTool(args) {
|
|
73
73
|
let normalizedArgs = args;
|
|
74
74
|
if (args.action === 'text') {
|
|
75
|
-
normalizedArgs =
|
|
75
|
+
normalizedArgs = applyPageTextBudgetPreset(args);
|
|
76
76
|
} else if (args.action === 'console') {
|
|
77
77
|
normalizedArgs = applyLimitBudgetPreset(args, {
|
|
78
78
|
quick: 10,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
bridgeMethodNeedsTab,
|
|
5
5
|
DEFAULT_MAX_HTML_LENGTH,
|
|
6
|
+
DEFAULT_PAGE_TEXT_BUDGET,
|
|
6
7
|
estimateJsonPayloadCost,
|
|
7
8
|
getBudgetPreset,
|
|
8
9
|
getErrorRecovery,
|
|
@@ -176,6 +177,27 @@ export function applyTextBudgetPreset(args) {
|
|
|
176
177
|
});
|
|
177
178
|
}
|
|
178
179
|
|
|
180
|
+
/**
|
|
181
|
+
* @template {{ budgetPreset?: unknown, textBudget?: unknown }} T
|
|
182
|
+
* @param {T} args
|
|
183
|
+
* @returns {T}
|
|
184
|
+
*/
|
|
185
|
+
export function applyPageTextBudgetPreset(args) {
|
|
186
|
+
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
187
|
+
if (!presetName) {
|
|
188
|
+
return args;
|
|
189
|
+
}
|
|
190
|
+
const textBudgetByPreset = {
|
|
191
|
+
quick: 2000,
|
|
192
|
+
normal: DEFAULT_PAGE_TEXT_BUDGET,
|
|
193
|
+
deep: DEFAULT_PAGE_TEXT_BUDGET * 2,
|
|
194
|
+
};
|
|
195
|
+
return /** @type {T} */ ({
|
|
196
|
+
...args,
|
|
197
|
+
textBudget: args.textBudget ?? textBudgetByPreset[presetName],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
179
201
|
/**
|
|
180
202
|
* @template {{ budgetPreset?: unknown, limit?: unknown }} T
|
|
181
203
|
* @param {T} args
|