@browserbridge/bbx 1.0.0 → 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 +6 -4
- package/package.json +53 -53
- package/packages/agent-client/src/cli-helpers.js +43 -5
- package/packages/agent-client/src/cli.js +176 -171
- package/packages/agent-client/src/client.js +66 -21
- package/packages/agent-client/src/command-registry.js +104 -69
- package/packages/agent-client/src/detect.js +162 -54
- package/packages/agent-client/src/install.js +34 -28
- package/packages/agent-client/src/mcp-config.js +40 -40
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +23 -30
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers.js +573 -256
- package/packages/mcp-server/src/server.js +568 -257
- package/packages/native-host/bin/bridge-daemon.js +39 -6
- package/packages/native-host/bin/install-manifest.js +26 -4
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +142 -13
- package/packages/native-host/src/daemon-process.js +396 -0
- package/packages/native-host/src/daemon.js +350 -150
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +194 -29
- package/packages/native-host/src/native-host.js +154 -102
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +6 -3
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +15 -11
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +242 -73
- package/packages/protocol/src/registry.js +311 -45
- package/packages/protocol/src/summary.js +260 -109
- package/packages/protocol/src/types.js +29 -4
- package/skills/browser-bridge/SKILL.md +3 -2
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +34 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +127 -71
- package/skills/browser-bridge/references/tailwind.md +12 -11
- package/skills/browser-bridge/references/token-efficiency.md +23 -22
- package/skills/browser-bridge/references/ui-workflows.md +8 -0
- 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 -114
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -52
- 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 -459
- package/packages/extension/src/background-routing.js +0 -91
- package/packages/extension/src/background.js +0 -3227
- package/packages/extension/src/content-script-helpers.js +0 -281
- package/packages/extension/src/content-script.js +0 -1977
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -102
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -279
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1854
- package/packages/extension/ui/ui.css +0 -1159
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
APP_NAME,
|
|
8
|
+
getManifestInstallDir,
|
|
9
|
+
SUPPORTED_BROWSERS,
|
|
10
|
+
} from '../../native-host/src/config.js';
|
|
7
11
|
import { resolveDefaultExtensionId } from '../../native-host/src/install-manifest.js';
|
|
8
12
|
import { methodNeedsTab } from './cli-helpers.js';
|
|
9
13
|
import { BridgeClient } from './client.js';
|
|
@@ -65,7 +69,7 @@ export async function requestBridge(client, method, params = {}, options = {}) {
|
|
|
65
69
|
method,
|
|
66
70
|
params,
|
|
67
71
|
tabId: methodNeedsTab(method) ? (options.tabId ?? null) : null,
|
|
68
|
-
meta: withRequestMeta(options.source, options.tokenBudget)
|
|
72
|
+
meta: withRequestMeta(options.source, options.tokenBudget),
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -81,9 +85,14 @@ export async function resolveRef(client, refOrSelector, tabId = null, source) {
|
|
|
81
85
|
return refOrSelector;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
const response = await requestBridge(
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
const response = await requestBridge(
|
|
89
|
+
client,
|
|
90
|
+
'dom.query',
|
|
91
|
+
{
|
|
92
|
+
selector: refOrSelector,
|
|
93
|
+
},
|
|
94
|
+
{ tabId, source }
|
|
95
|
+
);
|
|
87
96
|
|
|
88
97
|
if (!response.ok) {
|
|
89
98
|
throw new Error(response.error.message);
|
|
@@ -184,9 +193,7 @@ export async function checkBrowserManifests() {
|
|
|
184
193
|
*/
|
|
185
194
|
export async function getDoctorReport(options = {}) {
|
|
186
195
|
const manifest = await (options.loadManifest || loadInstalledManifest)();
|
|
187
|
-
const allowedOrigins = Array.isArray(manifest?.allowed_origins)
|
|
188
|
-
? manifest.allowed_origins
|
|
189
|
-
: [];
|
|
196
|
+
const allowedOrigins = Array.isArray(manifest?.allowed_origins) ? manifest.allowed_origins : [];
|
|
190
197
|
const manifestInstalled = Boolean(manifest);
|
|
191
198
|
const defaultExtensionId = options.defaultExtensionIdInfo || resolveDefaultExtensionId();
|
|
192
199
|
|
|
@@ -208,7 +215,7 @@ export async function getDoctorReport(options = {}) {
|
|
|
208
215
|
routeReason: 'access_disabled',
|
|
209
216
|
issues: [],
|
|
210
217
|
nextSteps: [],
|
|
211
|
-
browserManifests
|
|
218
|
+
browserManifests,
|
|
212
219
|
};
|
|
213
220
|
|
|
214
221
|
try {
|
|
@@ -217,7 +224,8 @@ export async function getDoctorReport(options = {}) {
|
|
|
217
224
|
if (!response.ok) {
|
|
218
225
|
throw new Error(response.error.message);
|
|
219
226
|
}
|
|
220
|
-
const result =
|
|
227
|
+
const result =
|
|
228
|
+
/** @type {{ daemon?: string, extensionConnected?: boolean, access?: {
|
|
221
229
|
enabled?: boolean,
|
|
222
230
|
windowId?: number | null,
|
|
223
231
|
routeTabId?: number | null,
|
|
@@ -227,10 +235,13 @@ export async function getDoctorReport(options = {}) {
|
|
|
227
235
|
report.daemonReachable = result.daemon === 'ok';
|
|
228
236
|
report.extensionConnected = result.extensionConnected === true;
|
|
229
237
|
report.accessEnabled = result.access?.enabled === true;
|
|
230
|
-
report.enabledWindowId =
|
|
231
|
-
|
|
238
|
+
report.enabledWindowId =
|
|
239
|
+
typeof result.access?.windowId === 'number' ? result.access.windowId : null;
|
|
240
|
+
report.routeTabId =
|
|
241
|
+
typeof result.access?.routeTabId === 'number' ? result.access.routeTabId : null;
|
|
232
242
|
report.routeReady = result.access?.routeReady === true;
|
|
233
|
-
report.routeReason =
|
|
243
|
+
report.routeReason =
|
|
244
|
+
typeof result.access?.reason === 'string' ? result.access.reason : 'access_disabled';
|
|
234
245
|
});
|
|
235
246
|
} catch {
|
|
236
247
|
report.daemonReachable = false;
|
|
@@ -241,13 +252,17 @@ export async function getDoctorReport(options = {}) {
|
|
|
241
252
|
|
|
242
253
|
if (!report.manifestInstalled) {
|
|
243
254
|
report.issues.push('native_host_manifest_missing');
|
|
244
|
-
report.nextSteps.push(
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
report.nextSteps.push(
|
|
256
|
+
defaultExtensionId.extensionId
|
|
257
|
+
? 'Run `bbx install` (or `bbx install --all` for all browsers) to install the native host manifest.'
|
|
258
|
+
: 'Run `bbx install <extension-id>` (or `bbx install --all`) to install the native host manifest.'
|
|
259
|
+
);
|
|
247
260
|
} else if (browsersWithoutManifest.length > 0) {
|
|
248
261
|
report.issues.push('native_host_manifest_partial');
|
|
249
262
|
const missing = browsersWithoutManifest.map((b) => b.browser).join(', ');
|
|
250
|
-
report.nextSteps.push(
|
|
263
|
+
report.nextSteps.push(
|
|
264
|
+
`Manifests missing for: ${missing}. Run \`bbx install --all\` to install for all supported browsers.`
|
|
265
|
+
);
|
|
251
266
|
}
|
|
252
267
|
if (!report.daemonReachable) {
|
|
253
268
|
report.issues.push('daemon_offline');
|
|
@@ -255,14 +270,20 @@ export async function getDoctorReport(options = {}) {
|
|
|
255
270
|
}
|
|
256
271
|
if (report.daemonReachable && !report.extensionConnected) {
|
|
257
272
|
report.issues.push('extension_disconnected');
|
|
258
|
-
report.nextSteps.push(
|
|
273
|
+
report.nextSteps.push(
|
|
274
|
+
'Open Chrome and make sure the Browser Bridge extension is installed and active.'
|
|
275
|
+
);
|
|
259
276
|
}
|
|
260
277
|
if (report.daemonReachable && report.extensionConnected && !report.accessEnabled) {
|
|
261
278
|
report.issues.push('access_disabled');
|
|
262
|
-
report.nextSteps.push(
|
|
279
|
+
report.nextSteps.push(
|
|
280
|
+
'If a Browser Bridge call returns ACCESS_DENIED, stop requesting access. Ask the user to click Enable for the needed window, then tell you when that window is ready.'
|
|
281
|
+
);
|
|
263
282
|
} else if (report.daemonReachable && report.extensionConnected && !report.routeReady) {
|
|
264
283
|
report.issues.push(report.routeReason || 'no_routable_active_tab');
|
|
265
|
-
report.nextSteps.push(
|
|
284
|
+
report.nextSteps.push(
|
|
285
|
+
'Switch to a supported page in the enabled window, or use an explicit tabId override.'
|
|
286
|
+
);
|
|
266
287
|
}
|
|
267
288
|
|
|
268
289
|
return report;
|
|
@@ -57,8 +57,8 @@ const SKILL_TARGET_LABELS = {
|
|
|
57
57
|
* global?: boolean,
|
|
58
58
|
* cwd?: string,
|
|
59
59
|
* projectPath?: string,
|
|
60
|
-
* mcpDetectors?: Record<string, () => boolean
|
|
61
|
-
* skillDetectors?: Record<string, () => boolean
|
|
60
|
+
* mcpDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
61
|
+
* skillDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
62
62
|
* access?: (targetPath: string) => Promise<void>,
|
|
63
63
|
* readFile?: (targetPath: string, encoding: BufferEncoding) => Promise<string>
|
|
64
64
|
* }} SetupStatusOptions
|
|
@@ -76,14 +76,18 @@ export async function collectSetupStatus(options = {}) {
|
|
|
76
76
|
const projectPath = options.projectPath || cwd;
|
|
77
77
|
const access = options.access || fs.promises.access.bind(fs.promises);
|
|
78
78
|
const readFile = options.readFile || fs.promises.readFile.bind(fs.promises);
|
|
79
|
-
const
|
|
80
|
-
|
|
79
|
+
const [detectedMcpClientResult, detectedSkillTargetResult] = await Promise.allSettled([
|
|
80
|
+
detectMcpClients(options.mcpDetectors),
|
|
81
81
|
detectSkillTargets(options.skillDetectors),
|
|
82
|
-
);
|
|
82
|
+
]);
|
|
83
|
+
const detectedMcpClientNames =
|
|
84
|
+
detectedMcpClientResult.status === 'fulfilled' ? detectedMcpClientResult.value : [];
|
|
85
|
+
const detectedSkillTargetNames =
|
|
86
|
+
detectedSkillTargetResult.status === 'fulfilled' ? detectedSkillTargetResult.value : [];
|
|
87
|
+
const detectedMcpClients = new Set(detectedMcpClientNames);
|
|
88
|
+
const detectedSkillTargets = new Set(detectedSkillTargetNames);
|
|
83
89
|
for (const clientName of detectedMcpClients) {
|
|
84
|
-
if (
|
|
85
|
-
SUPPORTED_TARGETS.includes(/** @type {SupportedTarget} */ (clientName))
|
|
86
|
-
) {
|
|
90
|
+
if (SUPPORTED_TARGETS.includes(/** @type {SupportedTarget} */ (clientName))) {
|
|
87
91
|
detectedSkillTargets.add(/** @type {SupportedTarget} */ (clientName));
|
|
88
92
|
}
|
|
89
93
|
}
|
|
@@ -96,7 +100,7 @@ export async function collectSetupStatus(options = {}) {
|
|
|
96
100
|
detected: detectedMcpClients.has(clientName),
|
|
97
101
|
readFile,
|
|
98
102
|
});
|
|
99
|
-
})
|
|
103
|
+
})
|
|
100
104
|
);
|
|
101
105
|
const skillTargets = await Promise.all(
|
|
102
106
|
SUPPORTED_TARGETS.map(async (target) => {
|
|
@@ -107,7 +111,7 @@ export async function collectSetupStatus(options = {}) {
|
|
|
107
111
|
access,
|
|
108
112
|
readFile,
|
|
109
113
|
});
|
|
110
|
-
})
|
|
114
|
+
})
|
|
111
115
|
);
|
|
112
116
|
|
|
113
117
|
return {
|
|
@@ -138,12 +142,8 @@ async function collectMcpClientStatus(clientName, options) {
|
|
|
138
142
|
});
|
|
139
143
|
const entries = await Promise.all(
|
|
140
144
|
configPaths.map(async (configPath) => {
|
|
141
|
-
return readBrowserBridgeMcpEntry(
|
|
142
|
-
|
|
143
|
-
configPath,
|
|
144
|
-
options.readFile,
|
|
145
|
-
);
|
|
146
|
-
}),
|
|
145
|
+
return readBrowserBridgeMcpEntry(clientName, configPath, options.readFile);
|
|
146
|
+
})
|
|
147
147
|
);
|
|
148
148
|
const preferredEntry =
|
|
149
149
|
entries.find((entry) => entry.configured) ||
|
|
@@ -188,20 +188,17 @@ async function collectSkillTargetStatus(target, options) {
|
|
|
188
188
|
skillName,
|
|
189
189
|
sentinelFilename,
|
|
190
190
|
options.access,
|
|
191
|
-
options.readFile
|
|
191
|
+
options.readFile
|
|
192
192
|
);
|
|
193
|
-
})
|
|
193
|
+
})
|
|
194
194
|
);
|
|
195
195
|
const skillByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
196
196
|
const coreSkill = skillByName.get(coreSkillName) || null;
|
|
197
197
|
const coreInstalled = Boolean(coreSkill?.exists);
|
|
198
198
|
const coreManaged = Boolean(coreSkill?.exists && coreSkill.managed);
|
|
199
|
-
const installedVersion = getInstalledSkillBundleVersion(
|
|
200
|
-
coreSkill ? [coreSkill] : [],
|
|
201
|
-
);
|
|
199
|
+
const installedVersion = getInstalledSkillBundleVersion(coreSkill ? [coreSkill] : []);
|
|
202
200
|
const updateAvailable =
|
|
203
|
-
coreManaged &&
|
|
204
|
-
isManagedVersionOutdated(coreSkill?.version || null, currentVersion);
|
|
201
|
+
coreManaged && isManagedVersionOutdated(coreSkill?.version || null, currentVersion);
|
|
205
202
|
|
|
206
203
|
return {
|
|
207
204
|
key: target,
|
|
@@ -230,15 +227,13 @@ async function collectInstalledSkillStatus(
|
|
|
230
227
|
skillName,
|
|
231
228
|
sentinelFilename,
|
|
232
229
|
access,
|
|
233
|
-
readFile
|
|
230
|
+
readFile
|
|
234
231
|
) {
|
|
235
232
|
const skillPath = path.join(basePath, skillName);
|
|
236
233
|
const exists = await pathExists(skillPath, access);
|
|
237
234
|
const sentinelPath = path.join(skillPath, sentinelFilename);
|
|
238
235
|
const managed = exists && (await pathExists(sentinelPath, access));
|
|
239
|
-
const version = managed
|
|
240
|
-
? await readManagedSkillVersion(sentinelPath, readFile)
|
|
241
|
-
: null;
|
|
236
|
+
const version = managed ? await readManagedSkillVersion(sentinelPath, readFile) : null;
|
|
242
237
|
|
|
243
238
|
return {
|
|
244
239
|
name: skillName,
|
|
@@ -261,9 +256,7 @@ function getInstalledSkillBundleVersion(skills) {
|
|
|
261
256
|
if (!first || typeof first.version !== 'string') {
|
|
262
257
|
return null;
|
|
263
258
|
}
|
|
264
|
-
return skills.every((skill) => skill.version === first.version)
|
|
265
|
-
? first.version
|
|
266
|
-
: null;
|
|
259
|
+
return skills.every((skill) => skill.version === first.version) ? first.version : null;
|
|
267
260
|
}
|
|
268
261
|
|
|
269
262
|
/**
|
|
@@ -1,10 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
4
7
|
import { startBridgeMcpServer } from './server.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* start?: () => Promise<void>,
|
|
12
|
+
* argv?: string[],
|
|
13
|
+
* stdout?: { write: (chunk: string) => unknown },
|
|
14
|
+
* stderr?: { write: (chunk: string) => unknown },
|
|
15
|
+
* exit?: (code: number) => unknown,
|
|
16
|
+
* }} BridgeMcpCliOptions
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const HELP_FLAGS = new Set(['help', '--help', '-h']);
|
|
20
|
+
|
|
21
|
+
const MCP_USAGE = [
|
|
22
|
+
'Usage: bbx-mcp [--help]',
|
|
23
|
+
'',
|
|
24
|
+
'Start the Browser Bridge MCP stdio server.',
|
|
25
|
+
].join('\n');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start the MCP server CLI and report startup failures to stderr.
|
|
29
|
+
*
|
|
30
|
+
* @param {BridgeMcpCliOptions} [options]
|
|
31
|
+
* @returns {Promise<number>}
|
|
32
|
+
*/
|
|
33
|
+
export async function runBridgeMcpCli(options = {}) {
|
|
34
|
+
const {
|
|
35
|
+
start = startBridgeMcpServer,
|
|
36
|
+
argv = process.argv.slice(2),
|
|
37
|
+
stdout = process.stdout,
|
|
38
|
+
stderr = process.stderr,
|
|
39
|
+
exit = process.exit,
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
if (argv.some((arg) => HELP_FLAGS.has(arg))) {
|
|
43
|
+
stdout.write(`${MCP_USAGE}\n`);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await start();
|
|
49
|
+
return 0;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
52
|
+
stderr.write(`${message}\n`);
|
|
53
|
+
exit(1);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const entryPointUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
|
|
59
|
+
|
|
60
|
+
if (entryPointUrl === import.meta.url) {
|
|
61
|
+
void runBridgeMcpCli();
|
|
62
|
+
}
|