@browserbridge/bbx 1.0.0 → 1.0.1
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 +3 -1
- package/docs/api-reference.md +33 -33
- package/docs/mcp-vs-cli.md +104 -104
- package/docs/publishing.md +1 -3
- package/docs/quickstart.md +6 -6
- package/docs/unpacked-extension.md +72 -0
- package/manifest.json +3 -17
- package/package.json +44 -42
- package/packages/agent-client/src/cli-helpers.js +10 -5
- package/packages/agent-client/src/cli.js +65 -135
- package/packages/agent-client/src/client.js +37 -17
- package/packages/agent-client/src/command-registry.js +101 -69
- package/packages/agent-client/src/detect.js +3 -6
- package/packages/agent-client/src/install.js +10 -27
- package/packages/agent-client/src/mcp-config.js +11 -30
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +13 -28
- package/packages/extension/src/background-helpers.js +51 -36
- package/packages/extension/src/background-routing.js +11 -13
- package/packages/extension/src/background.js +562 -299
- package/packages/extension/src/content-script-helpers.js +17 -16
- package/packages/extension/src/content-script.js +175 -109
- package/packages/extension/src/sidepanel-helpers.js +3 -1
- package/packages/extension/ui/popup.js +39 -20
- package/packages/extension/ui/sidepanel.js +108 -191
- package/packages/extension/ui/ui.css +2 -1
- package/packages/mcp-server/src/handlers.js +546 -250
- package/packages/mcp-server/src/server.js +558 -257
- package/packages/native-host/bin/bridge-daemon.js +6 -2
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +11 -7
- package/packages/native-host/src/daemon.js +143 -92
- package/packages/native-host/src/install-manifest.js +73 -22
- package/packages/native-host/src/native-host.js +55 -40
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +3 -3
- package/packages/protocol/src/errors.js +11 -11
- package/packages/protocol/src/protocol.js +104 -71
- package/packages/protocol/src/registry.js +300 -45
- package/packages/protocol/src/summary.js +249 -106
- package/packages/protocol/src/types.js +1 -1
- package/skills/browser-bridge/SKILL.md +1 -1
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +33 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +125 -70
- 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/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
package/package.json
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserbridge/bbx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-tools",
|
|
7
|
+
"ai-agent",
|
|
8
|
+
"browser",
|
|
9
|
+
"browser-automation",
|
|
10
|
+
"browser-bridge",
|
|
11
|
+
"chrome",
|
|
12
|
+
"chrome-extension",
|
|
13
|
+
"developer-tools",
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"native-messaging",
|
|
17
|
+
"ui-debugging"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/koltyakov/browser-bridge#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/koltyakov/browser-bridge/issues"
|
|
22
|
+
},
|
|
6
23
|
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/koltyakov/browser-bridge.git"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"bbx": "packages/agent-client/src/cli.js",
|
|
30
|
+
"bbx-daemon": "packages/native-host/bin/bridge-daemon.js",
|
|
31
|
+
"bbx-install": "packages/native-host/bin/install-manifest.js",
|
|
32
|
+
"bbx-mcp": "packages/mcp-server/src/bin.js"
|
|
33
|
+
},
|
|
7
34
|
"files": [
|
|
8
35
|
"assets",
|
|
9
36
|
"CHANGELOG.md",
|
|
@@ -21,39 +48,15 @@
|
|
|
21
48
|
"packages/protocol/src",
|
|
22
49
|
"skills/browser-bridge"
|
|
23
50
|
],
|
|
24
|
-
"
|
|
25
|
-
"bbx": "packages/agent-client/src/cli.js",
|
|
26
|
-
"bbx-mcp": "packages/mcp-server/src/bin.js",
|
|
27
|
-
"bbx-daemon": "packages/native-host/bin/bridge-daemon.js",
|
|
28
|
-
"bbx-install": "packages/native-host/bin/install-manifest.js"
|
|
29
|
-
},
|
|
51
|
+
"type": "module",
|
|
30
52
|
"publishConfig": {
|
|
31
53
|
"access": "public"
|
|
32
54
|
},
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/koltyakov/browser-bridge.git"
|
|
36
|
-
},
|
|
37
|
-
"homepage": "https://github.com/koltyakov/browser-bridge#readme",
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/koltyakov/browser-bridge/issues"
|
|
40
|
-
},
|
|
41
|
-
"keywords": [
|
|
42
|
-
"browser-bridge",
|
|
43
|
-
"browser",
|
|
44
|
-
"chrome",
|
|
45
|
-
"chrome-extension",
|
|
46
|
-
"native-messaging",
|
|
47
|
-
"mcp",
|
|
48
|
-
"model-context-protocol",
|
|
49
|
-
"ai-agent",
|
|
50
|
-
"agent-tools",
|
|
51
|
-
"developer-tools",
|
|
52
|
-
"ui-debugging",
|
|
53
|
-
"browser-automation"
|
|
54
|
-
],
|
|
55
55
|
"scripts": {
|
|
56
|
-
"lint": "
|
|
56
|
+
"lint": "oxlint . && oxfmt --check .",
|
|
57
|
+
"format": "oxfmt .",
|
|
58
|
+
"format:check": "oxfmt --check .",
|
|
59
|
+
"test:runtime": "node --test packages/protocol/test/*.test.js packages/native-host/test/*.test.js packages/agent-client/test/*.test.js packages/mcp-server/test/*.test.js packages/extension/test/*.test.js",
|
|
57
60
|
"test": "c8 node --test packages/protocol/test/*.test.js packages/native-host/test/*.test.js packages/agent-client/test/*.test.js packages/mcp-server/test/*.test.js packages/extension/test/*.test.js",
|
|
58
61
|
"coverage": "c8 report --reporter=text --reporter=text-summary",
|
|
59
62
|
"coverage:check": "c8 check-coverage --lines 70",
|
|
@@ -67,20 +70,19 @@
|
|
|
67
70
|
"daemon": "node packages/native-host/bin/bridge-daemon.js",
|
|
68
71
|
"install-manifest": "node packages/native-host/bin/install-manifest.js"
|
|
69
72
|
},
|
|
70
|
-
"
|
|
71
|
-
"
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
75
|
+
"zod": "^4.3.6"
|
|
72
76
|
},
|
|
73
77
|
"devDependencies": {
|
|
74
|
-
"@
|
|
75
|
-
"@types/
|
|
76
|
-
"@types/node": "^25.4.0",
|
|
78
|
+
"@types/chrome": "^0.1.40",
|
|
79
|
+
"@types/node": "^25.5.2",
|
|
77
80
|
"c8": "^11.0.0",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"typescript": "^6.0.
|
|
81
|
+
"oxfmt": "^0.47.0",
|
|
82
|
+
"oxlint": "^1.62.0",
|
|
83
|
+
"typescript": "^6.0.3"
|
|
81
84
|
},
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"zod": "^4.3.6"
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=18"
|
|
85
87
|
}
|
|
86
88
|
}
|
|
@@ -30,7 +30,10 @@ export function parseCommaList(value) {
|
|
|
30
30
|
if (!value) {
|
|
31
31
|
return [];
|
|
32
32
|
}
|
|
33
|
-
return value
|
|
33
|
+
return value
|
|
34
|
+
.split(',')
|
|
35
|
+
.map((item) => item.trim())
|
|
36
|
+
.filter(Boolean);
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -50,7 +53,9 @@ export function parseJsonObject(value) {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
53
|
-
throw new Error(
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Expected a JSON object but got ${Array.isArray(parsed) ? 'array' : typeof parsed}. Wrap your input in {}.`
|
|
58
|
+
);
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
return /** @type {Record<string, unknown>} */ (parsed);
|
|
@@ -131,7 +136,7 @@ export async function interactiveCheckbox(title, items) {
|
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
process.stdout.write('\x1b[?25l'); // hide cursor
|
|
134
|
-
if (typeof /** @type {any} */ (process.stdin).setRawMode === 'function') {
|
|
139
|
+
if (typeof (/** @type {any} */ (process.stdin).setRawMode) === 'function') {
|
|
135
140
|
/** @type {any} */ (process.stdin).setRawMode(true);
|
|
136
141
|
}
|
|
137
142
|
process.stdin.resume();
|
|
@@ -143,7 +148,7 @@ export async function interactiveCheckbox(title, items) {
|
|
|
143
148
|
*/
|
|
144
149
|
function cleanup(result) {
|
|
145
150
|
process.stdin.removeListener('keypress', onKeypress);
|
|
146
|
-
if (typeof /** @type {any} */ (process.stdin).setRawMode === 'function') {
|
|
151
|
+
if (typeof (/** @type {any} */ (process.stdin).setRawMode) === 'function') {
|
|
147
152
|
/** @type {any} */ (process.stdin).setRawMode(false);
|
|
148
153
|
}
|
|
149
154
|
process.stdin.pause();
|
|
@@ -204,7 +209,7 @@ export async function interactiveConfirm(prompt, options = {}) {
|
|
|
204
209
|
const suffix = defaultValue ? ' [Y/n] ' : ' [y/N] ';
|
|
205
210
|
const rl = readline.createInterface({
|
|
206
211
|
input: process.stdin,
|
|
207
|
-
output: process.stdout
|
|
212
|
+
output: process.stdout,
|
|
208
213
|
});
|
|
209
214
|
|
|
210
215
|
try {
|
|
@@ -42,16 +42,9 @@ import {
|
|
|
42
42
|
MCP_CLIENT_NAMES,
|
|
43
43
|
removeMcpConfig,
|
|
44
44
|
} from './mcp-config.js';
|
|
45
|
-
import {
|
|
46
|
-
getDoctorReport,
|
|
47
|
-
requestBridge,
|
|
48
|
-
resolveRef,
|
|
49
|
-
} from './runtime.js';
|
|
45
|
+
import { getDoctorReport, requestBridge, resolveRef } from './runtime.js';
|
|
50
46
|
import { collectSetupStatus } from './setup-status.js';
|
|
51
|
-
import {
|
|
52
|
-
annotateBridgeSummary,
|
|
53
|
-
summarizeBridgeResponse,
|
|
54
|
-
} from './subagent.js';
|
|
47
|
+
import { annotateBridgeSummary, summarizeBridgeResponse } from './subagent.js';
|
|
55
48
|
|
|
56
49
|
/** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
|
|
57
50
|
/** @typedef {{ image: string, rect: Record<string, unknown> }} ScreenshotResult */
|
|
@@ -66,7 +59,7 @@ const REQUEST_SOURCE = 'cli';
|
|
|
66
59
|
* @returns {string}
|
|
67
60
|
*/
|
|
68
61
|
function stripAnsi(str) {
|
|
69
|
-
//
|
|
62
|
+
// oxlint-disable-next-line no-control-regex
|
|
70
63
|
return str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\x1b[^[]/g, '');
|
|
71
64
|
}
|
|
72
65
|
|
|
@@ -82,7 +75,10 @@ function sanitizeOutput(value) {
|
|
|
82
75
|
if (Array.isArray(value)) return value.map(sanitizeOutput);
|
|
83
76
|
if (value !== null && typeof value === 'object') {
|
|
84
77
|
return Object.fromEntries(
|
|
85
|
-
Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [
|
|
78
|
+
Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [
|
|
79
|
+
k,
|
|
80
|
+
sanitizeOutput(v),
|
|
81
|
+
])
|
|
86
82
|
);
|
|
87
83
|
}
|
|
88
84
|
return value;
|
|
@@ -98,9 +94,7 @@ function readStdin() {
|
|
|
98
94
|
const chunks = /** @type {Buffer[]} */ ([]);
|
|
99
95
|
process.stdin.setEncoding('utf8');
|
|
100
96
|
process.stdin.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
101
|
-
process.stdin.on('end', () =>
|
|
102
|
-
resolve(Buffer.concat(chunks).toString('utf8').trim()),
|
|
103
|
-
);
|
|
97
|
+
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim()));
|
|
104
98
|
process.stdin.on('error', reject);
|
|
105
99
|
// If stdin is a TTY and nothing is piped, read nothing
|
|
106
100
|
if (process.stdin.isTTY) {
|
|
@@ -117,7 +111,10 @@ if (!command || ['help', '--help', '-h'].includes(command)) {
|
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
if (['--version', '-v'].includes(command)) {
|
|
120
|
-
const pkgPath = path.resolve(
|
|
114
|
+
const pkgPath = path.resolve(
|
|
115
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
116
|
+
'../../../package.json'
|
|
117
|
+
);
|
|
121
118
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
122
119
|
process.stdout.write(`${pkg.version}\n`);
|
|
123
120
|
process.exit(0);
|
|
@@ -125,7 +122,7 @@ if (['--version', '-v'].includes(command)) {
|
|
|
125
122
|
|
|
126
123
|
if (command === 'skill') {
|
|
127
124
|
process.stdout.write(
|
|
128
|
-
`${JSON.stringify(createRuntimeContext(), null, process.stdout.isTTY ? 2 : undefined)}\n
|
|
125
|
+
`${JSON.stringify(createRuntimeContext(), null, process.stdout.isTTY ? 2 : undefined)}\n`
|
|
129
126
|
);
|
|
130
127
|
process.exit(0);
|
|
131
128
|
}
|
|
@@ -135,7 +132,7 @@ if (command === 'install') {
|
|
|
135
132
|
const { fileURLToPath } = await import('node:url');
|
|
136
133
|
const installScript = path.resolve(
|
|
137
134
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
138
|
-
'../../native-host/bin/install-manifest.js'
|
|
135
|
+
'../../native-host/bin/install-manifest.js'
|
|
139
136
|
);
|
|
140
137
|
execFileSync(process.execPath, [installScript, ...rest], {
|
|
141
138
|
stdio: 'inherit',
|
|
@@ -173,12 +170,10 @@ if (command === 'install-skill') {
|
|
|
173
170
|
const installedManagedTargets = new Set(
|
|
174
171
|
setupStatus.skillTargets
|
|
175
172
|
.filter((entry) => entry.installed && entry.managed)
|
|
176
|
-
.map((entry) => entry.key)
|
|
173
|
+
.map((entry) => entry.key)
|
|
177
174
|
);
|
|
178
175
|
const installedManagedTargetList =
|
|
179
|
-
/** @type {import('./install.js').SupportedTarget[]} */ ([
|
|
180
|
-
...installedManagedTargets,
|
|
181
|
-
]);
|
|
176
|
+
/** @type {import('./install.js').SupportedTarget[]} */ ([...installedManagedTargets]);
|
|
182
177
|
|
|
183
178
|
// Aliases like 'openai' and 'google' map to canonical targets and stay omitted.
|
|
184
179
|
const items = SUPPORTED_TARGETS.map((t) => ({
|
|
@@ -193,30 +188,23 @@ if (command === 'install-skill') {
|
|
|
193
188
|
|
|
194
189
|
const selected = await interactiveCheckbox(
|
|
195
190
|
'Select agents to install skill for (↑↓ move · space toggle · a all · enter confirm)',
|
|
196
|
-
items
|
|
191
|
+
items
|
|
197
192
|
);
|
|
198
193
|
|
|
199
194
|
/** @type {import('./install.js').SupportedTarget[]} */
|
|
200
195
|
let targets;
|
|
201
196
|
if (selected === null) {
|
|
202
197
|
// Non-TTY: prefer managed installs, then detected targets (always includes 'agents').
|
|
203
|
-
targets =
|
|
204
|
-
installedManagedTargets.size > 0
|
|
205
|
-
? installedManagedTargetList
|
|
206
|
-
: detected;
|
|
198
|
+
targets = installedManagedTargets.size > 0 ? installedManagedTargetList : detected;
|
|
207
199
|
} else {
|
|
208
|
-
targets = /** @type {import('./install.js').SupportedTarget[]} */ (
|
|
209
|
-
selected
|
|
210
|
-
);
|
|
200
|
+
targets = /** @type {import('./install.js').SupportedTarget[]} */ (selected);
|
|
211
201
|
}
|
|
212
202
|
|
|
213
203
|
const projectPath = isGlobal ? os.homedir() : process.cwd();
|
|
214
204
|
if (selected !== null) {
|
|
215
205
|
const deselectedTargets =
|
|
216
206
|
/** @type {import('./install.js').SupportedTarget[]} */ (
|
|
217
|
-
installedManagedTargetList.filter(
|
|
218
|
-
(target) => !targets.includes(target),
|
|
219
|
-
)
|
|
207
|
+
installedManagedTargetList.filter((target) => !targets.includes(target))
|
|
220
208
|
);
|
|
221
209
|
const removableTargets = await findInstalledManagedTargets({
|
|
222
210
|
targets: deselectedTargets,
|
|
@@ -225,7 +213,7 @@ if (command === 'install-skill') {
|
|
|
225
213
|
});
|
|
226
214
|
if (removableTargets.length > 0) {
|
|
227
215
|
const confirmed = await interactiveConfirm(
|
|
228
|
-
`Remove Browser Bridge skill from deselected targets: ${removableTargets.join(', ')}
|
|
216
|
+
`Remove Browser Bridge skill from deselected targets: ${removableTargets.join(', ')}?`
|
|
229
217
|
);
|
|
230
218
|
if (confirmed) {
|
|
231
219
|
const removedPaths = await removeAgentFiles({
|
|
@@ -289,14 +277,10 @@ if (command === 'install-mcp') {
|
|
|
289
277
|
});
|
|
290
278
|
const detected = detectMcpClients();
|
|
291
279
|
const configuredClients = new Set(
|
|
292
|
-
setupStatus.mcpClients
|
|
293
|
-
.filter((entry) => entry.configured)
|
|
294
|
-
.map((entry) => entry.key),
|
|
280
|
+
setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
|
|
295
281
|
);
|
|
296
282
|
const configuredClientList =
|
|
297
|
-
/** @type {import('./mcp-config.js').McpClientName[]} */ ([
|
|
298
|
-
...configuredClients,
|
|
299
|
-
]);
|
|
283
|
+
/** @type {import('./mcp-config.js').McpClientName[]} */ ([...configuredClients]);
|
|
300
284
|
const items = MCP_CLIENT_NAMES.map((c) => ({
|
|
301
285
|
value: c,
|
|
302
286
|
label: `${c.padEnd(10)} ${MCP_CLIENT_LABELS[c]}`,
|
|
@@ -309,7 +293,7 @@ if (command === 'install-mcp') {
|
|
|
309
293
|
|
|
310
294
|
const selected = await interactiveCheckbox(
|
|
311
295
|
'Select clients to configure (↑↓ move · space toggle · a all · enter confirm)',
|
|
312
|
-
items
|
|
296
|
+
items
|
|
313
297
|
);
|
|
314
298
|
|
|
315
299
|
if (selected === null) {
|
|
@@ -321,17 +305,13 @@ if (command === 'install-mcp') {
|
|
|
321
305
|
? detected
|
|
322
306
|
: [...MCP_CLIENT_NAMES];
|
|
323
307
|
} else {
|
|
324
|
-
clients = /** @type {import('./mcp-config.js').McpClientName[]} */ (
|
|
325
|
-
selected
|
|
326
|
-
);
|
|
308
|
+
clients = /** @type {import('./mcp-config.js').McpClientName[]} */ (selected);
|
|
327
309
|
}
|
|
328
310
|
|
|
329
311
|
if (selected !== null) {
|
|
330
312
|
const deselectedClients =
|
|
331
313
|
/** @type {import('./mcp-config.js').McpClientName[]} */ (
|
|
332
|
-
configuredClientList.filter(
|
|
333
|
-
(clientName) => !clients.includes(clientName),
|
|
334
|
-
)
|
|
314
|
+
configuredClientList.filter((clientName) => !clients.includes(clientName))
|
|
335
315
|
);
|
|
336
316
|
const removableClients = await findConfiguredMcpClients({
|
|
337
317
|
clients: deselectedClients,
|
|
@@ -340,7 +320,7 @@ if (command === 'install-mcp') {
|
|
|
340
320
|
});
|
|
341
321
|
if (removableClients.length > 0) {
|
|
342
322
|
const confirmed = await interactiveConfirm(
|
|
343
|
-
`Remove Browser Bridge MCP config from deselected clients: ${removableClients.join(', ')}
|
|
323
|
+
`Remove Browser Bridge MCP config from deselected clients: ${removableClients.join(', ')}?`
|
|
344
324
|
);
|
|
345
325
|
if (confirmed) {
|
|
346
326
|
for (const clientName of removableClients) {
|
|
@@ -371,7 +351,7 @@ if (command === 'install-mcp') {
|
|
|
371
351
|
for (const part of parts) {
|
|
372
352
|
if (!isMcpClientName(part)) {
|
|
373
353
|
process.stderr.write(
|
|
374
|
-
`Unknown client "${part}". Supported: ${MCP_CLIENT_NAMES.join(', ')}, all\n
|
|
354
|
+
`Unknown client "${part}". Supported: ${MCP_CLIENT_NAMES.join(', ')}, all\n`
|
|
375
355
|
);
|
|
376
356
|
process.exit(1);
|
|
377
357
|
}
|
|
@@ -396,9 +376,7 @@ if (command === 'mcp') {
|
|
|
396
376
|
}
|
|
397
377
|
if (subcommand === 'config') {
|
|
398
378
|
if (!clientName || !isMcpClientName(clientName)) {
|
|
399
|
-
process.stderr.write(
|
|
400
|
-
`Usage: bbx mcp config <${MCP_CLIENT_NAMES.join('|')}>\n`,
|
|
401
|
-
);
|
|
379
|
+
process.stderr.write(`Usage: bbx mcp config <${MCP_CLIENT_NAMES.join('|')}>\n`);
|
|
402
380
|
process.exit(1);
|
|
403
381
|
}
|
|
404
382
|
process.stdout.write(formatMcpConfig(clientName));
|
|
@@ -419,7 +397,7 @@ async function main() {
|
|
|
419
397
|
client,
|
|
420
398
|
'health.ping',
|
|
421
399
|
{},
|
|
422
|
-
{ source: REQUEST_SOURCE }
|
|
400
|
+
{ source: REQUEST_SOURCE }
|
|
423
401
|
);
|
|
424
402
|
await printSummary(healthResponse);
|
|
425
403
|
return;
|
|
@@ -427,12 +405,7 @@ async function main() {
|
|
|
427
405
|
|
|
428
406
|
if (command === 'access-request') {
|
|
429
407
|
await printSummary(
|
|
430
|
-
await requestBridge(
|
|
431
|
-
client,
|
|
432
|
-
'access.request',
|
|
433
|
-
{},
|
|
434
|
-
{ source: REQUEST_SOURCE },
|
|
435
|
-
),
|
|
408
|
+
await requestBridge(client, 'access.request', {}, { source: REQUEST_SOURCE })
|
|
436
409
|
);
|
|
437
410
|
return;
|
|
438
411
|
}
|
|
@@ -444,28 +417,19 @@ async function main() {
|
|
|
444
417
|
summary:
|
|
445
418
|
report.issues.length === 0
|
|
446
419
|
? 'Browser Bridge is ready.'
|
|
447
|
-
: `Browser Bridge has ${report.issues.length}
|
|
420
|
+
: `Browser Bridge has ${report.issues.length} readiness issue(s).`,
|
|
448
421
|
evidence: report,
|
|
449
422
|
});
|
|
450
423
|
return;
|
|
451
424
|
}
|
|
452
425
|
|
|
453
426
|
if (command === 'logs') {
|
|
454
|
-
await printSummary(
|
|
455
|
-
await requestBridge(client, 'log.tail', {}, { source: REQUEST_SOURCE }),
|
|
456
|
-
);
|
|
427
|
+
await printSummary(await requestBridge(client, 'log.tail', {}, { source: REQUEST_SOURCE }));
|
|
457
428
|
return;
|
|
458
429
|
}
|
|
459
430
|
|
|
460
431
|
if (command === 'tabs') {
|
|
461
|
-
await printSummary(
|
|
462
|
-
await requestBridge(
|
|
463
|
-
client,
|
|
464
|
-
'tabs.list',
|
|
465
|
-
{},
|
|
466
|
-
{ source: REQUEST_SOURCE },
|
|
467
|
-
),
|
|
468
|
-
);
|
|
432
|
+
await printSummary(await requestBridge(client, 'tabs.list', {}, { source: REQUEST_SOURCE }));
|
|
469
433
|
return;
|
|
470
434
|
}
|
|
471
435
|
|
|
@@ -477,7 +441,7 @@ async function main() {
|
|
|
477
441
|
{
|
|
478
442
|
url: url || undefined,
|
|
479
443
|
},
|
|
480
|
-
{ source: REQUEST_SOURCE }
|
|
444
|
+
{ source: REQUEST_SOURCE }
|
|
481
445
|
);
|
|
482
446
|
await printSummary(response);
|
|
483
447
|
return;
|
|
@@ -494,7 +458,7 @@ async function main() {
|
|
|
494
458
|
{
|
|
495
459
|
tabId: parseIntArg(tabId, 'tabId'),
|
|
496
460
|
},
|
|
497
|
-
{ source: REQUEST_SOURCE }
|
|
461
|
+
{ source: REQUEST_SOURCE }
|
|
498
462
|
);
|
|
499
463
|
await printSummary(response);
|
|
500
464
|
return;
|
|
@@ -514,9 +478,7 @@ async function main() {
|
|
|
514
478
|
await ensureClientConnection();
|
|
515
479
|
const input = rest[0];
|
|
516
480
|
if (!input) {
|
|
517
|
-
throw new Error(
|
|
518
|
-
'Usage: batch \'[{"method":"...","params":{...}}, ...]\'',
|
|
519
|
-
);
|
|
481
|
+
throw new Error('Usage: batch \'[{"method":"...","params":{...}}, ...]\'');
|
|
520
482
|
}
|
|
521
483
|
const calls = JSON.parse(input);
|
|
522
484
|
if (!Array.isArray(calls)) {
|
|
@@ -534,7 +496,10 @@ async function main() {
|
|
|
534
496
|
durationMs: 0,
|
|
535
497
|
approxTokens: 0,
|
|
536
498
|
meta: { protocol_version: '1.0' },
|
|
537
|
-
error: {
|
|
499
|
+
error: {
|
|
500
|
+
code: 'INVALID_REQUEST',
|
|
501
|
+
message: 'Each batch call needs a method.',
|
|
502
|
+
},
|
|
538
503
|
response: null,
|
|
539
504
|
};
|
|
540
505
|
}
|
|
@@ -548,14 +513,16 @@ async function main() {
|
|
|
548
513
|
durationMs: 0,
|
|
549
514
|
approxTokens: 0,
|
|
550
515
|
meta: { protocol_version: '1.0' },
|
|
551
|
-
error: {
|
|
516
|
+
error: {
|
|
517
|
+
code: 'INVALID_REQUEST',
|
|
518
|
+
message: `Unknown bridge method "${call.method}".`,
|
|
519
|
+
},
|
|
552
520
|
response: null,
|
|
553
521
|
};
|
|
554
522
|
}
|
|
555
523
|
const method = /** @type {BridgeMethod} */ (call.method);
|
|
556
|
-
const tabId =
|
|
557
|
-
? call.tabId
|
|
558
|
-
: null;
|
|
524
|
+
const tabId =
|
|
525
|
+
methodNeedsTab(call.method) && typeof call.tabId === 'number' ? call.tabId : null;
|
|
559
526
|
const startTime = Date.now();
|
|
560
527
|
try {
|
|
561
528
|
const response = await client.request({
|
|
@@ -578,20 +545,14 @@ async function main() {
|
|
|
578
545
|
durationMs: Date.now() - startTime,
|
|
579
546
|
});
|
|
580
547
|
}
|
|
581
|
-
})
|
|
548
|
+
})
|
|
582
549
|
);
|
|
583
550
|
printJson(results);
|
|
584
551
|
return;
|
|
585
552
|
}
|
|
586
553
|
|
|
587
|
-
if (
|
|
588
|
-
command
|
|
589
|
-
&& METHODS.includes(/** @type {BridgeMethod} */ (command))
|
|
590
|
-
) {
|
|
591
|
-
const { tabId, method, params } = await parseCallCommand([
|
|
592
|
-
command,
|
|
593
|
-
...rest,
|
|
594
|
-
]);
|
|
554
|
+
if (command.includes('.') && METHODS.includes(/** @type {BridgeMethod} */ (command))) {
|
|
555
|
+
const { tabId, method, params } = await parseCallCommand([command, ...rest]);
|
|
595
556
|
const response = await requestBridge(client, method, params, {
|
|
596
557
|
tabId,
|
|
597
558
|
source: REQUEST_SOURCE,
|
|
@@ -605,18 +566,13 @@ async function main() {
|
|
|
605
566
|
let elementRef;
|
|
606
567
|
if (shortcutCmd.resolve) {
|
|
607
568
|
if (!rest[0]) throw new Error(`Usage: ${command} <ref|selector>`);
|
|
608
|
-
elementRef = await resolveRef(
|
|
609
|
-
client,
|
|
610
|
-
rest[0],
|
|
611
|
-
null,
|
|
612
|
-
REQUEST_SOURCE,
|
|
613
|
-
);
|
|
569
|
+
elementRef = await resolveRef(client, rest[0], null, REQUEST_SOURCE);
|
|
614
570
|
}
|
|
615
571
|
const response = await requestBridge(
|
|
616
572
|
client,
|
|
617
573
|
shortcutCmd.method,
|
|
618
574
|
shortcutCmd.build(rest, elementRef),
|
|
619
|
-
{ source: REQUEST_SOURCE }
|
|
575
|
+
{ source: REQUEST_SOURCE }
|
|
620
576
|
);
|
|
621
577
|
await printSummary(response, shortcutCmd.printMethod);
|
|
622
578
|
return;
|
|
@@ -626,12 +582,7 @@ async function main() {
|
|
|
626
582
|
const [key, refOrSelector] = rest;
|
|
627
583
|
if (!key) throw new Error('Usage: press-key <key> [ref|selector]');
|
|
628
584
|
const elementRef = refOrSelector
|
|
629
|
-
? await resolveRef(
|
|
630
|
-
client,
|
|
631
|
-
refOrSelector,
|
|
632
|
-
null,
|
|
633
|
-
REQUEST_SOURCE,
|
|
634
|
-
)
|
|
585
|
+
? await resolveRef(client, refOrSelector, null, REQUEST_SOURCE)
|
|
635
586
|
: undefined;
|
|
636
587
|
const response = await requestBridge(
|
|
637
588
|
client,
|
|
@@ -640,7 +591,7 @@ async function main() {
|
|
|
640
591
|
key,
|
|
641
592
|
target: elementRef ? { elementRef } : undefined,
|
|
642
593
|
},
|
|
643
|
-
{ source: REQUEST_SOURCE }
|
|
594
|
+
{ source: REQUEST_SOURCE }
|
|
644
595
|
);
|
|
645
596
|
await printSummary(response);
|
|
646
597
|
return;
|
|
@@ -649,33 +600,22 @@ async function main() {
|
|
|
649
600
|
if (command === 'screenshot') {
|
|
650
601
|
const [refOrSelector, outputPath] = rest;
|
|
651
602
|
if (!refOrSelector) throw new Error('Usage: screenshot <ref|selector> [path]');
|
|
652
|
-
const elementRef = await resolveRef(
|
|
653
|
-
client,
|
|
654
|
-
refOrSelector,
|
|
655
|
-
null,
|
|
656
|
-
REQUEST_SOURCE,
|
|
657
|
-
);
|
|
603
|
+
const elementRef = await resolveRef(client, refOrSelector, null, REQUEST_SOURCE);
|
|
658
604
|
const response = await requestBridge(
|
|
659
605
|
client,
|
|
660
606
|
'screenshot.capture_element',
|
|
661
607
|
{
|
|
662
608
|
elementRef,
|
|
663
609
|
},
|
|
664
|
-
{ source: REQUEST_SOURCE }
|
|
610
|
+
{ source: REQUEST_SOURCE }
|
|
665
611
|
);
|
|
666
612
|
if (!response.ok) {
|
|
667
613
|
await printSummary(response);
|
|
668
614
|
return;
|
|
669
615
|
}
|
|
670
|
-
const screenshotResult = /** @type {ScreenshotResult} */ (
|
|
671
|
-
|
|
672
|
-
);
|
|
673
|
-
const filePath =
|
|
674
|
-
outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.png`);
|
|
675
|
-
const data = screenshotResult.image.replace(
|
|
676
|
-
/^data:image\/png;base64,/,
|
|
677
|
-
'',
|
|
678
|
-
);
|
|
616
|
+
const screenshotResult = /** @type {ScreenshotResult} */ (response.result);
|
|
617
|
+
const filePath = outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.png`);
|
|
618
|
+
const data = screenshotResult.image.replace(/^data:image\/png;base64,/, '');
|
|
679
619
|
await fs.promises.writeFile(filePath, Buffer.from(data, 'base64'));
|
|
680
620
|
printJson({
|
|
681
621
|
ok: true,
|
|
@@ -689,9 +629,7 @@ async function main() {
|
|
|
689
629
|
let expression = rest.join(' ');
|
|
690
630
|
if (!expression || expression === '-') expression = await readStdin();
|
|
691
631
|
if (!expression)
|
|
692
|
-
throw new Error(
|
|
693
|
-
'Usage: eval <expression> (or pipe via stdin: echo "expr" | bbx eval -)',
|
|
694
|
-
);
|
|
632
|
+
throw new Error('Usage: eval <expression> (or pipe via stdin: echo "expr" | bbx eval -)');
|
|
695
633
|
const response = await requestBridge(
|
|
696
634
|
client,
|
|
697
635
|
'page.evaluate',
|
|
@@ -699,7 +637,7 @@ async function main() {
|
|
|
699
637
|
expression,
|
|
700
638
|
returnByValue: true,
|
|
701
639
|
},
|
|
702
|
-
{ source: REQUEST_SOURCE }
|
|
640
|
+
{ source: REQUEST_SOURCE }
|
|
703
641
|
);
|
|
704
642
|
await printSummary(response);
|
|
705
643
|
return;
|
|
@@ -710,10 +648,7 @@ async function main() {
|
|
|
710
648
|
process.exitCode = 1;
|
|
711
649
|
} catch (error) {
|
|
712
650
|
const message = error instanceof Error ? error.message : String(error);
|
|
713
|
-
const raw =
|
|
714
|
-
error instanceof Error && 'code' in error
|
|
715
|
-
? /** @type {any} */ (error).code
|
|
716
|
-
: '';
|
|
651
|
+
const raw = error instanceof Error && 'code' in error ? /** @type {any} */ (error).code : '';
|
|
717
652
|
let code = 'ERROR';
|
|
718
653
|
if (raw === 'ENOENT' || raw === 'ECONNREFUSED') {
|
|
719
654
|
code = 'DAEMON_OFFLINE';
|
|
@@ -766,10 +701,7 @@ async function ensureClientConnection() {
|
|
|
766
701
|
* @returns {Promise<void>}
|
|
767
702
|
*/
|
|
768
703
|
async function printSummary(response, method) {
|
|
769
|
-
printJson(annotateBridgeSummary(
|
|
770
|
-
summarizeBridgeResponse(response, method),
|
|
771
|
-
response,
|
|
772
|
-
));
|
|
704
|
+
printJson(annotateBridgeSummary(summarizeBridgeResponse(response, method), response));
|
|
773
705
|
}
|
|
774
706
|
|
|
775
707
|
/**
|
|
@@ -778,7 +710,7 @@ async function printSummary(response, method) {
|
|
|
778
710
|
*/
|
|
779
711
|
function printJson(value) {
|
|
780
712
|
process.stdout.write(
|
|
781
|
-
`${JSON.stringify(sanitizeOutput(value), null, process.stdout.isTTY ? 2 : undefined)}\n
|
|
713
|
+
`${JSON.stringify(sanitizeOutput(value), null, process.stdout.isTTY ? 2 : undefined)}\n`
|
|
782
714
|
);
|
|
783
715
|
}
|
|
784
716
|
|
|
@@ -846,9 +778,7 @@ async function parseCallCommand(args) {
|
|
|
846
778
|
if (first.includes('.')) {
|
|
847
779
|
const method = /** @type {BridgeMethod} */ (first);
|
|
848
780
|
if (!METHODS.includes(method)) {
|
|
849
|
-
throw new Error(
|
|
850
|
-
`Unknown method "${first}". Run bbx skill to see available methods.`,
|
|
851
|
-
);
|
|
781
|
+
throw new Error(`Unknown method "${first}". Run bbx skill to see available methods.`);
|
|
852
782
|
}
|
|
853
783
|
let rawParams = second;
|
|
854
784
|
// Support piped stdin: `echo '{"key":"val"}' | bbx call method -`
|