@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
|
@@ -4,7 +4,12 @@ import { EventEmitter, once } from 'node:events';
|
|
|
4
4
|
import net from 'node:net';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createRequest,
|
|
9
|
+
DEFAULT_CLIENT_REQUEST_TIMEOUT_MS,
|
|
10
|
+
PROTOCOL_VERSION,
|
|
11
|
+
parseJsonLines,
|
|
12
|
+
} from '../../protocol/src/index.js';
|
|
8
13
|
import { getSocketPath } from '../../native-host/src/config.js';
|
|
9
14
|
|
|
10
15
|
/** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
|
|
@@ -55,7 +60,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
55
60
|
socketPath = getSocketPath(),
|
|
56
61
|
clientId = `agent_${randomUUID()}`,
|
|
57
62
|
defaultTimeoutMs = DEFAULT_CLIENT_REQUEST_TIMEOUT_MS,
|
|
58
|
-
autoReconnect = false
|
|
63
|
+
autoReconnect = false,
|
|
59
64
|
} = {}) {
|
|
60
65
|
super();
|
|
61
66
|
this.socketPath = socketPath;
|
|
@@ -128,7 +133,9 @@ export class BridgeClient extends EventEmitter {
|
|
|
128
133
|
// 'close' fires after 'error'; reconnect is triggered there.
|
|
129
134
|
});
|
|
130
135
|
|
|
131
|
-
this.socket.write(
|
|
136
|
+
this.socket.write(
|
|
137
|
+
`${JSON.stringify({ type: 'register', role: 'agent', clientId: this.clientId })}\n`
|
|
138
|
+
);
|
|
132
139
|
await new Promise((resolve, reject) => {
|
|
133
140
|
const timeoutId = setTimeout(() => {
|
|
134
141
|
this.waiting.delete('registered');
|
|
@@ -137,13 +144,13 @@ export class BridgeClient extends EventEmitter {
|
|
|
137
144
|
this.waiting.set('registered', {
|
|
138
145
|
resolve,
|
|
139
146
|
reject,
|
|
140
|
-
timeoutId
|
|
147
|
+
timeoutId,
|
|
141
148
|
});
|
|
142
149
|
});
|
|
143
150
|
|
|
144
151
|
try {
|
|
145
152
|
const healthResponse = await this.request({
|
|
146
|
-
method: 'health.ping'
|
|
153
|
+
method: 'health.ping',
|
|
147
154
|
});
|
|
148
155
|
if (healthResponse.ok) {
|
|
149
156
|
this.protocolCompatibility = BridgeClient.checkProtocolVersion(
|
|
@@ -167,9 +174,17 @@ export class BridgeClient extends EventEmitter {
|
|
|
167
174
|
* }} options
|
|
168
175
|
* @returns {Promise<BridgeResponse>}
|
|
169
176
|
*/
|
|
170
|
-
async request({
|
|
177
|
+
async request({
|
|
178
|
+
method,
|
|
179
|
+
params = {},
|
|
180
|
+
tabId = null,
|
|
181
|
+
meta = {},
|
|
182
|
+
timeoutMs = this.defaultTimeoutMs,
|
|
183
|
+
}) {
|
|
171
184
|
if (!this.socket || this.socket.destroyed || !this.socket.writable) {
|
|
172
|
-
const err = /** @type {Error & { code: string }} */ (
|
|
185
|
+
const err = /** @type {Error & { code: string }} */ (
|
|
186
|
+
new Error('BridgeClient is not connected.')
|
|
187
|
+
);
|
|
173
188
|
err.code = 'ENOTCONN';
|
|
174
189
|
throw err;
|
|
175
190
|
}
|
|
@@ -179,7 +194,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
179
194
|
method,
|
|
180
195
|
params,
|
|
181
196
|
tabId,
|
|
182
|
-
meta
|
|
197
|
+
meta,
|
|
183
198
|
});
|
|
184
199
|
|
|
185
200
|
const responsePromise = new Promise((resolve, reject) => {
|
|
@@ -191,14 +206,16 @@ export class BridgeClient extends EventEmitter {
|
|
|
191
206
|
this.waiting.set(request.id, {
|
|
192
207
|
resolve,
|
|
193
208
|
reject,
|
|
194
|
-
timeoutId
|
|
209
|
+
timeoutId,
|
|
195
210
|
});
|
|
196
211
|
});
|
|
197
212
|
|
|
198
213
|
if (!this.socket.write(`${JSON.stringify({ type: 'agent.request', request })}\n`)) {
|
|
199
214
|
await Promise.race([
|
|
200
215
|
once(this.socket, 'drain'),
|
|
201
|
-
once(this.socket, 'close').then(() => {
|
|
216
|
+
once(this.socket, 'close').then(() => {
|
|
217
|
+
throw new Error('Bridge socket closed while writing.');
|
|
218
|
+
}),
|
|
202
219
|
]);
|
|
203
220
|
}
|
|
204
221
|
const response = /** @type {BridgeResponse} */ (await responsePromise);
|
|
@@ -275,7 +292,11 @@ export class BridgeClient extends EventEmitter {
|
|
|
275
292
|
? healthResult.supported_versions
|
|
276
293
|
: [];
|
|
277
294
|
if (remoteVersions.length === 0) {
|
|
278
|
-
return {
|
|
295
|
+
return {
|
|
296
|
+
compatible: true,
|
|
297
|
+
localVersion: PROTOCOL_VERSION,
|
|
298
|
+
remoteVersions,
|
|
299
|
+
};
|
|
279
300
|
}
|
|
280
301
|
const compatible = remoteVersions.includes(PROTOCOL_VERSION);
|
|
281
302
|
return {
|
|
@@ -284,11 +305,10 @@ export class BridgeClient extends EventEmitter {
|
|
|
284
305
|
remoteVersions,
|
|
285
306
|
...(!compatible && {
|
|
286
307
|
warning:
|
|
287
|
-
typeof healthResult?.migration_hint === 'string' &&
|
|
288
|
-
healthResult.migration_hint
|
|
308
|
+
typeof healthResult?.migration_hint === 'string' && healthResult.migration_hint
|
|
289
309
|
? healthResult.migration_hint
|
|
290
|
-
: `Protocol mismatch: client speaks ${PROTOCOL_VERSION} but remote supports [${remoteVersions.join(', ')}]. Update the ${remoteVersions[0] > PROTOCOL_VERSION ? 'client (npm)' : 'extension'} to match
|
|
291
|
-
})
|
|
310
|
+
: `Protocol mismatch: client speaks ${PROTOCOL_VERSION} but remote supports [${remoteVersions.join(', ')}]. Update the ${remoteVersions[0] > PROTOCOL_VERSION ? 'client (npm)' : 'extension'} to match.`,
|
|
311
|
+
}),
|
|
292
312
|
};
|
|
293
313
|
}
|
|
294
314
|
|
|
@@ -316,8 +336,8 @@ export class BridgeClient extends EventEmitter {
|
|
|
316
336
|
...response,
|
|
317
337
|
meta: {
|
|
318
338
|
...response.meta,
|
|
319
|
-
protocol_warning: this.protocolWarning
|
|
320
|
-
}
|
|
339
|
+
protocol_warning: this.protocolWarning,
|
|
340
|
+
},
|
|
321
341
|
};
|
|
322
342
|
}
|
|
323
343
|
}
|
|
@@ -27,21 +27,20 @@ function createShortcutCommand(method, usage, build, options = {}) {
|
|
|
27
27
|
return {
|
|
28
28
|
method,
|
|
29
29
|
usage,
|
|
30
|
-
description:
|
|
30
|
+
description:
|
|
31
|
+
options.description ?? BRIDGE_METHOD_REGISTRY[method].description.replace(/\.$/, ''),
|
|
31
32
|
build,
|
|
32
33
|
...(options.resolve ? { resolve: true } : {}),
|
|
33
|
-
...(options.printMethod ? { printMethod: options.printMethod } : {})
|
|
34
|
+
...(options.printMethod ? { printMethod: options.printMethod } : {}),
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/** @type {Record<string, ShortcutCommand>} */
|
|
38
39
|
export const SHORTCUT_COMMANDS = {
|
|
39
40
|
'access-request': createShortcutCommand('access.request', 'bbx access-request', () => ({})),
|
|
40
|
-
'dom-query': createShortcutCommand(
|
|
41
|
-
'
|
|
42
|
-
|
|
43
|
-
(r) => ({ selector: r[0] || 'body' })
|
|
44
|
-
),
|
|
41
|
+
'dom-query': createShortcutCommand('dom.query', 'bbx dom-query [selector]', (r) => ({
|
|
42
|
+
selector: r[0] || 'body',
|
|
43
|
+
})),
|
|
45
44
|
describe: createShortcutCommand(
|
|
46
45
|
'dom.describe',
|
|
47
46
|
'bbx describe <ref|selector>',
|
|
@@ -51,7 +50,10 @@ export const SHORTCUT_COMMANDS = {
|
|
|
51
50
|
text: createShortcutCommand(
|
|
52
51
|
'dom.get_text',
|
|
53
52
|
'bbx text <ref|selector> [budget]',
|
|
54
|
-
(r, ref) => ({
|
|
53
|
+
(r, ref) => ({
|
|
54
|
+
elementRef: ref,
|
|
55
|
+
textBudget: r[1] ? parseIntArg(r[1], 'budget') : undefined,
|
|
56
|
+
}),
|
|
55
57
|
{ resolve: true, printMethod: 'dom.get_text' }
|
|
56
58
|
),
|
|
57
59
|
styles: createShortcutCommand(
|
|
@@ -93,27 +95,34 @@ export const SHORTCUT_COMMANDS = {
|
|
|
93
95
|
html: createShortcutCommand(
|
|
94
96
|
'dom.get_html',
|
|
95
97
|
'bbx html <ref|selector> [maxLen]',
|
|
96
|
-
(r, ref) => ({
|
|
98
|
+
(r, ref) => ({
|
|
99
|
+
elementRef: ref,
|
|
100
|
+
maxLength: r[1] ? parseIntArg(r[1], 'maxLen') : undefined,
|
|
101
|
+
}),
|
|
97
102
|
{ resolve: true }
|
|
98
103
|
),
|
|
99
104
|
'patch-style': createShortcutCommand(
|
|
100
105
|
'patch.apply_styles',
|
|
101
106
|
'bbx patch-style <ref|sel> prop=val',
|
|
102
|
-
(r, ref) => ({
|
|
107
|
+
(r, ref) => ({
|
|
108
|
+
target: { elementRef: ref },
|
|
109
|
+
declarations: parsePropertyAssignments(r.slice(1)),
|
|
110
|
+
}),
|
|
103
111
|
{ resolve: true }
|
|
104
112
|
),
|
|
105
113
|
'patch-text': createShortcutCommand(
|
|
106
114
|
'patch.apply_dom',
|
|
107
115
|
'bbx patch-text <ref|sel> <text...>',
|
|
108
|
-
(r, ref) => ({
|
|
116
|
+
(r, ref) => ({
|
|
117
|
+
target: { elementRef: ref },
|
|
118
|
+
operation: 'set_text',
|
|
119
|
+
value: r.slice(1).join(' '),
|
|
120
|
+
}),
|
|
109
121
|
{ resolve: true, description: 'Apply a reversible DOM text patch' }
|
|
110
122
|
),
|
|
111
|
-
patches: createShortcutCommand(
|
|
112
|
-
'patch.list',
|
|
113
|
-
|
|
114
|
-
() => ({}),
|
|
115
|
-
{ printMethod: 'patch.list' }
|
|
116
|
-
),
|
|
123
|
+
patches: createShortcutCommand('patch.list', 'bbx patches', () => ({}), {
|
|
124
|
+
printMethod: 'patch.list',
|
|
125
|
+
}),
|
|
117
126
|
rollback: createShortcutCommand('patch.rollback', 'bbx rollback <patchId>', (r) => {
|
|
118
127
|
if (!r[0]) throw new Error('Usage: rollback <patchId>');
|
|
119
128
|
return { patchId: r[0] };
|
|
@@ -122,11 +131,17 @@ export const SHORTCUT_COMMANDS = {
|
|
|
122
131
|
'page.get_console',
|
|
123
132
|
'bbx console [level]',
|
|
124
133
|
(r) => ({ level: r[0] || 'all', clear: false }),
|
|
125
|
-
{
|
|
134
|
+
{
|
|
135
|
+
printMethod: 'page.get_console',
|
|
136
|
+
description: 'Read buffered console output (log|warn|error|all)',
|
|
137
|
+
}
|
|
126
138
|
),
|
|
127
139
|
wait: createShortcutCommand('dom.wait_for', 'bbx wait <selector> [timeoutMs]', (r) => {
|
|
128
140
|
if (!r[0]) throw new Error('Usage: wait <selector> [timeoutMs]');
|
|
129
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
selector: r[0],
|
|
143
|
+
timeoutMs: r[1] ? parseIntArg(r[1], 'timeoutMs') : 5000,
|
|
144
|
+
};
|
|
130
145
|
}),
|
|
131
146
|
find: createShortcutCommand(
|
|
132
147
|
'dom.find_by_text',
|
|
@@ -138,19 +153,23 @@ export const SHORTCUT_COMMANDS = {
|
|
|
138
153
|
},
|
|
139
154
|
{ printMethod: 'dom.find_by_text' }
|
|
140
155
|
),
|
|
141
|
-
'find-role': createShortcutCommand(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
'find-role': createShortcutCommand(
|
|
157
|
+
'dom.find_by_role',
|
|
158
|
+
'bbx find-role <role> [name]',
|
|
159
|
+
(r) => {
|
|
160
|
+
if (!r[0]) throw new Error('Usage: find-role <role> [name]');
|
|
161
|
+
return { role: r[0], name: r.slice(1).join(' ') || undefined };
|
|
162
|
+
},
|
|
163
|
+
{ printMethod: 'dom.find_by_role' }
|
|
164
|
+
),
|
|
145
165
|
navigate: createShortcutCommand('navigation.navigate', 'bbx navigate <url>', (r) => {
|
|
146
166
|
if (!r[0]) throw new Error('Usage: navigate <url>');
|
|
147
167
|
return { url: r[0] };
|
|
148
168
|
}),
|
|
149
|
-
storage: createShortcutCommand(
|
|
150
|
-
'
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
),
|
|
169
|
+
storage: createShortcutCommand('page.get_storage', 'bbx storage [local|session] [keys]', (r) => ({
|
|
170
|
+
type: r[0] === 'session' ? 'session' : 'local',
|
|
171
|
+
keys: r.slice(1).length ? r.slice(1) : undefined,
|
|
172
|
+
})),
|
|
154
173
|
'page-text': createShortcutCommand(
|
|
155
174
|
'page.get_text',
|
|
156
175
|
'bbx page-text [textBudget]',
|
|
@@ -161,21 +180,33 @@ export const SHORTCUT_COMMANDS = {
|
|
|
161
180
|
'page.get_network',
|
|
162
181
|
'bbx network [limit]',
|
|
163
182
|
(r) => ({ limit: r[0] ? parseIntArg(r[0], 'limit') : undefined }),
|
|
164
|
-
{
|
|
183
|
+
{
|
|
184
|
+
printMethod: 'page.get_network',
|
|
185
|
+
description: 'Read buffered network requests (fetch/XHR)',
|
|
186
|
+
}
|
|
165
187
|
),
|
|
166
188
|
'a11y-tree': createShortcutCommand(
|
|
167
189
|
'dom.get_accessibility_tree',
|
|
168
190
|
'bbx a11y-tree [maxNodes] [maxDepth]',
|
|
169
|
-
(r) => ({
|
|
191
|
+
(r) => ({
|
|
192
|
+
maxNodes: r[0] ? parseIntArg(r[0], 'maxNodes') : undefined,
|
|
193
|
+
maxDepth: r[1] ? parseIntArg(r[1], 'maxDepth') : undefined,
|
|
194
|
+
})
|
|
170
195
|
),
|
|
171
196
|
perf: createShortcutCommand('performance.get_metrics', 'bbx perf', () => ({})),
|
|
172
197
|
scroll: createShortcutCommand('viewport.scroll', 'bbx scroll <top> [left]', (r) => {
|
|
173
198
|
if (!r[0] && !r[1]) throw new Error('Usage: scroll <top> [left]');
|
|
174
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
top: r[0] ? parseIntArg(r[0], 'top') : undefined,
|
|
201
|
+
left: r[1] ? parseIntArg(r[1], 'left') : undefined,
|
|
202
|
+
};
|
|
175
203
|
}),
|
|
176
204
|
resize: createShortcutCommand('viewport.resize', 'bbx resize <width> <height>', (r) => {
|
|
177
205
|
if (!r[0] || !r[1]) throw new Error('Usage: resize <width> <height>');
|
|
178
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
width: parseIntArg(r[0], 'width'),
|
|
208
|
+
height: parseIntArg(r[1], 'height'),
|
|
209
|
+
};
|
|
179
210
|
}),
|
|
180
211
|
reload: createShortcutCommand('navigation.reload', 'bbx reload', () => ({})),
|
|
181
212
|
back: createShortcutCommand('navigation.go_back', 'bbx back', () => ({})),
|
|
@@ -191,7 +222,7 @@ export const SHORTCUT_COMMANDS = {
|
|
|
191
222
|
'bbx matched-rules <ref|selector>',
|
|
192
223
|
(_r, ref) => ({ elementRef: ref }),
|
|
193
224
|
{ resolve: true, printMethod: 'styles.get_matched_rules' }
|
|
194
|
-
)
|
|
225
|
+
),
|
|
195
226
|
};
|
|
196
227
|
|
|
197
228
|
/** @type {Readonly<Record<string, BridgeMethod>>} */
|
|
@@ -207,7 +238,7 @@ export const CLI_METHOD_BINDINGS = Object.freeze({
|
|
|
207
238
|
...Object.fromEntries(BRIDGE_METHODS.map((method) => [method, method])),
|
|
208
239
|
'press-key': 'input.press_key',
|
|
209
240
|
screenshot: 'screenshot.capture_element',
|
|
210
|
-
eval: 'page.evaluate'
|
|
241
|
+
eval: 'page.evaluate',
|
|
211
242
|
});
|
|
212
243
|
|
|
213
244
|
/** @type {ReadonlyArray<{ title: string, lines: readonly string[] }>} */
|
|
@@ -228,18 +259,18 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
228
259
|
'bbx tab-create [url] Create a new tab',
|
|
229
260
|
'bbx tab-close <tabId> Close a tab',
|
|
230
261
|
'bbx skill Runtime budget presets and method groups',
|
|
231
|
-
'bbx mcp serve Start Browser Bridge as an MCP stdio server'
|
|
232
|
-
]
|
|
262
|
+
'bbx mcp serve Start Browser Bridge as an MCP stdio server',
|
|
263
|
+
],
|
|
233
264
|
},
|
|
234
265
|
{
|
|
235
266
|
title: 'Generic RPC',
|
|
236
267
|
lines: [
|
|
237
268
|
'bbx call [--tab <tabId>] <method> [paramsJson|-] Call any bridge method (- reads JSON from stdin)',
|
|
238
269
|
'bbx <method> [--tab <tabId>] [paramsJson|-] Direct alias for exact bridge methods such as page.get_state',
|
|
239
|
-
|
|
270
|
+
"bbx batch '[{method,params,tabId?},...]' Parallel method calls",
|
|
240
271
|
'Advanced bridge params stay available through `bbx call`, even when shortcuts expose only the common case.',
|
|
241
|
-
'For open-ended investigation, start with `bbx batch` on `page.get_state`, `dom.query`, and `page.get_text` before any screenshot or CDP call.'
|
|
242
|
-
]
|
|
272
|
+
'For open-ended investigation, start with `bbx batch` on `page.get_state`, `dom.query`, and `page.get_text` before any screenshot or CDP call.',
|
|
273
|
+
],
|
|
243
274
|
},
|
|
244
275
|
{
|
|
245
276
|
title: 'Inspect',
|
|
@@ -253,19 +284,21 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
253
284
|
'attrs',
|
|
254
285
|
'matched-rules',
|
|
255
286
|
'box',
|
|
256
|
-
'a11y-tree'
|
|
257
|
-
].map(
|
|
258
|
-
|
|
287
|
+
'a11y-tree',
|
|
288
|
+
].map(
|
|
289
|
+
(command) =>
|
|
290
|
+
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
291
|
+
),
|
|
292
|
+
],
|
|
259
293
|
},
|
|
260
294
|
{
|
|
261
295
|
title: 'Find',
|
|
262
296
|
lines: [
|
|
263
|
-
...[
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
]
|
|
297
|
+
...['find', 'find-role', 'wait'].map(
|
|
298
|
+
(command) =>
|
|
299
|
+
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
300
|
+
),
|
|
301
|
+
],
|
|
269
302
|
},
|
|
270
303
|
{
|
|
271
304
|
title: 'Page',
|
|
@@ -282,37 +315,36 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
282
315
|
'forward',
|
|
283
316
|
'perf',
|
|
284
317
|
'scroll',
|
|
285
|
-
'resize'
|
|
286
|
-
].map(
|
|
287
|
-
|
|
318
|
+
'resize',
|
|
319
|
+
].map(
|
|
320
|
+
(command) =>
|
|
321
|
+
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
322
|
+
),
|
|
323
|
+
],
|
|
288
324
|
},
|
|
289
325
|
{
|
|
290
326
|
title: 'Interact',
|
|
291
327
|
lines: [
|
|
292
|
-
...[
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
'bbx press-key <key> [ref|selector] Send key event'
|
|
299
|
-
]
|
|
328
|
+
...['click', 'focus', 'type', 'hover'].map(
|
|
329
|
+
(command) =>
|
|
330
|
+
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
331
|
+
),
|
|
332
|
+
'bbx press-key <key> [ref|selector] Send key event',
|
|
333
|
+
],
|
|
300
334
|
},
|
|
301
335
|
{
|
|
302
336
|
title: 'Patch',
|
|
303
337
|
lines: [
|
|
304
|
-
...[
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
].map((command) => `${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`)
|
|
310
|
-
]
|
|
338
|
+
...['patch-style', 'patch-text', 'patches', 'rollback'].map(
|
|
339
|
+
(command) =>
|
|
340
|
+
`${SHORTCUT_COMMANDS[command].usage.padEnd(64)} ${SHORTCUT_COMMANDS[command].description}`
|
|
341
|
+
),
|
|
342
|
+
],
|
|
311
343
|
},
|
|
312
344
|
{
|
|
313
345
|
title: 'Capture',
|
|
314
346
|
lines: [
|
|
315
|
-
'bbx screenshot <ref|selector> [path] Capture partial element screenshot'
|
|
316
|
-
]
|
|
317
|
-
}
|
|
347
|
+
'bbx screenshot <ref|selector> [path] Capture partial element screenshot',
|
|
348
|
+
],
|
|
349
|
+
},
|
|
318
350
|
]);
|
|
@@ -16,8 +16,7 @@ const platform = process.platform;
|
|
|
16
16
|
*/
|
|
17
17
|
function getVsCodeUserDataDir() {
|
|
18
18
|
if (platform === 'win32') {
|
|
19
|
-
const appData =
|
|
20
|
-
process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
19
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
21
20
|
return path.join(appData, 'Code');
|
|
22
21
|
}
|
|
23
22
|
if (platform === 'linux') {
|
|
@@ -58,11 +57,9 @@ function commandExists(cmd) {
|
|
|
58
57
|
function detectCopilot() {
|
|
59
58
|
if (fsExists(path.join(getVsCodeUserDataDir(), 'User'))) return true;
|
|
60
59
|
if (fsExists(path.join(home, '.vscode'))) return true;
|
|
61
|
-
if (platform === 'darwin')
|
|
62
|
-
return fsExists('/Applications/Visual Studio Code.app');
|
|
60
|
+
if (platform === 'darwin') return fsExists('/Applications/Visual Studio Code.app');
|
|
63
61
|
if (platform === 'win32') {
|
|
64
|
-
const localAppData =
|
|
65
|
-
process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
62
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
66
63
|
return fsExists(path.join(localAppData, 'Programs', 'Microsoft VS Code'));
|
|
67
64
|
}
|
|
68
65
|
return commandExists('code');
|
|
@@ -29,9 +29,7 @@ const targetAliases = /** @type {const} */ ({
|
|
|
29
29
|
|
|
30
30
|
const packageManifest = loadPackageManifest();
|
|
31
31
|
const managedPackageName =
|
|
32
|
-
typeof packageManifest.name === 'string'
|
|
33
|
-
? packageManifest.name
|
|
34
|
-
: '@browserbridge/bbx';
|
|
32
|
+
typeof packageManifest.name === 'string' ? packageManifest.name : '@browserbridge/bbx';
|
|
35
33
|
const managedPackageVersion =
|
|
36
34
|
typeof packageManifest.version === 'string' ? packageManifest.version : null;
|
|
37
35
|
const copilotBrowserBridgeNote = [
|
|
@@ -91,9 +89,7 @@ export function parseInstallAgentArgs(args, cwd = process.cwd()) {
|
|
|
91
89
|
if (arg === '--agents' || arg === '--agent') {
|
|
92
90
|
const value = args[index + 1];
|
|
93
91
|
if (!value) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
'Usage: install-skill [targets|all] [--project <path>]',
|
|
96
|
-
);
|
|
92
|
+
throw new Error('Usage: install-skill [targets|all] [--project <path>]');
|
|
97
93
|
}
|
|
98
94
|
targets = parseTargetList(value);
|
|
99
95
|
index += 1;
|
|
@@ -113,9 +109,7 @@ export function parseInstallAgentArgs(args, cwd = process.cwd()) {
|
|
|
113
109
|
if (arg === '--project') {
|
|
114
110
|
const value = args[index + 1];
|
|
115
111
|
if (!value) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
'Usage: install-skill [targets|all] [--project <path>] [--global]',
|
|
118
|
-
);
|
|
112
|
+
throw new Error('Usage: install-skill [targets|all] [--project <path>] [--global]');
|
|
119
113
|
}
|
|
120
114
|
projectPath = path.resolve(cwd, value);
|
|
121
115
|
isGlobal = false;
|
|
@@ -204,7 +198,7 @@ export async function installMcpClientSetup(clients, options) {
|
|
|
204
198
|
global: options.global,
|
|
205
199
|
cwd: options.projectPath,
|
|
206
200
|
stdout: options.stdout,
|
|
207
|
-
})
|
|
201
|
+
})
|
|
208
202
|
);
|
|
209
203
|
}
|
|
210
204
|
|
|
@@ -289,7 +283,7 @@ function parseTargetList(raw) {
|
|
|
289
283
|
);
|
|
290
284
|
if (!canonical) {
|
|
291
285
|
throw new Error(
|
|
292
|
-
`Unknown install-skill target "${value}". Supported targets: ${supportedTargets.join(', ')}, all. Aliases: openai -> codex, google -> antigravity
|
|
286
|
+
`Unknown install-skill target "${value}". Supported targets: ${supportedTargets.join(', ')}, all. Aliases: openai -> codex, google -> antigravity.`
|
|
293
287
|
);
|
|
294
288
|
}
|
|
295
289
|
parsed.add(canonical);
|
|
@@ -380,7 +374,7 @@ export function formatManagedSkillSentinel(skillName) {
|
|
|
380
374
|
version: managedPackageVersion,
|
|
381
375
|
},
|
|
382
376
|
null,
|
|
383
|
-
2
|
|
377
|
+
2
|
|
384
378
|
)}\n`;
|
|
385
379
|
}
|
|
386
380
|
|
|
@@ -414,10 +408,7 @@ export function parseManagedSkillSentinel(raw) {
|
|
|
414
408
|
* @param {string | null} [currentVersion=managedPackageVersion]
|
|
415
409
|
* @returns {boolean}
|
|
416
410
|
*/
|
|
417
|
-
export function isManagedVersionOutdated(
|
|
418
|
-
installedVersion,
|
|
419
|
-
currentVersion = managedPackageVersion,
|
|
420
|
-
) {
|
|
411
|
+
export function isManagedVersionOutdated(installedVersion, currentVersion = managedPackageVersion) {
|
|
421
412
|
if (!currentVersion) {
|
|
422
413
|
return false;
|
|
423
414
|
}
|
|
@@ -439,19 +430,13 @@ async function installManagedSkill(skillName, target, targetDir) {
|
|
|
439
430
|
const targetExists = await pathExists(targetDir);
|
|
440
431
|
|
|
441
432
|
if (targetExists && !(await pathExists(sentinelPath))) {
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Refusing to overwrite unmanaged skill directory: ${targetDir}`,
|
|
444
|
-
);
|
|
433
|
+
throw new Error(`Refusing to overwrite unmanaged skill directory: ${targetDir}`);
|
|
445
434
|
}
|
|
446
435
|
|
|
447
436
|
await fs.promises.rm(targetDir, { recursive: true, force: true });
|
|
448
437
|
await copyDir(sourceDir, targetDir);
|
|
449
438
|
await applyManagedSkillPatches(skillName, target, targetDir);
|
|
450
|
-
await fs.promises.writeFile(
|
|
451
|
-
sentinelPath,
|
|
452
|
-
formatManagedSkillSentinel(skillName),
|
|
453
|
-
'utf8',
|
|
454
|
-
);
|
|
439
|
+
await fs.promises.writeFile(sentinelPath, formatManagedSkillSentinel(skillName), 'utf8');
|
|
455
440
|
}
|
|
456
441
|
|
|
457
442
|
/**
|
|
@@ -532,9 +517,7 @@ async function applyManagedSkillPatches(skillName, target, targetDir) {
|
|
|
532
517
|
*/
|
|
533
518
|
function loadPackageManifest() {
|
|
534
519
|
try {
|
|
535
|
-
return JSON.parse(
|
|
536
|
-
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
537
|
-
);
|
|
520
|
+
return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
538
521
|
} catch {
|
|
539
522
|
return {};
|
|
540
523
|
}
|
|
@@ -48,8 +48,7 @@ const BROWSER_BRIDGE_SERVER_NAME = 'browser-bridge';
|
|
|
48
48
|
function getVsCodeUserDataDir() {
|
|
49
49
|
const home = os.homedir();
|
|
50
50
|
if (process.platform === 'win32') {
|
|
51
|
-
const appData =
|
|
52
|
-
process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
51
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
53
52
|
return path.join(appData, 'Code');
|
|
54
53
|
}
|
|
55
54
|
if (process.platform === 'linux') {
|
|
@@ -79,9 +78,7 @@ function getLegacyCopilotVsCodeConfigPath() {
|
|
|
79
78
|
* @returns {{ key: string, includeType: boolean, legacyKeys?: string[], keepEmptyBlock?: boolean }}
|
|
80
79
|
*/
|
|
81
80
|
export function getMcpConfigShape(clientName) {
|
|
82
|
-
return
|
|
83
|
-
MCP_CONFIG_SHAPES[clientName] ?? { key: 'mcpServers', includeType: false }
|
|
84
|
-
);
|
|
81
|
+
return MCP_CONFIG_SHAPES[clientName] ?? { key: 'mcpServers', includeType: false };
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
/**
|
|
@@ -143,9 +140,7 @@ export function buildMcpConfig(clientName) {
|
|
|
143
140
|
}
|
|
144
141
|
const serverConfig = createBaseServerConfig(clientName);
|
|
145
142
|
const shape = getMcpConfigShape(clientName);
|
|
146
|
-
const entry = shape.includeType
|
|
147
|
-
? { type: 'stdio', ...serverConfig }
|
|
148
|
-
: serverConfig;
|
|
143
|
+
const entry = shape.includeType ? { type: 'stdio', ...serverConfig } : serverConfig;
|
|
149
144
|
return { [shape.key]: { [BROWSER_BRIDGE_SERVER_NAME]: entry } };
|
|
150
145
|
}
|
|
151
146
|
|
|
@@ -169,10 +164,7 @@ export function formatMcpConfig(clientName) {
|
|
|
169
164
|
* @param {{ global: boolean, cwd?: string }} options
|
|
170
165
|
* @returns {string}
|
|
171
166
|
*/
|
|
172
|
-
export function getMcpConfigPath(
|
|
173
|
-
clientName,
|
|
174
|
-
{ global: isGlobal, cwd = process.cwd() },
|
|
175
|
-
) {
|
|
167
|
+
export function getMcpConfigPath(clientName, { global: isGlobal, cwd = process.cwd() }) {
|
|
176
168
|
const home = os.homedir();
|
|
177
169
|
|
|
178
170
|
if (!isGlobal) {
|
|
@@ -252,7 +244,9 @@ export async function getMcpConfigPaths(clientName, options) {
|
|
|
252
244
|
const entries = await readdir(profilesDir, { withFileTypes: true });
|
|
253
245
|
const profilePaths = entries
|
|
254
246
|
.filter((/** @type {import('node:fs').Dirent} */ entry) => entry.isDirectory())
|
|
255
|
-
.map((/** @type {import('node:fs').Dirent} */ entry) =>
|
|
247
|
+
.map((/** @type {import('node:fs').Dirent} */ entry) =>
|
|
248
|
+
path.join(profilesDir, entry.name, 'mcp.json')
|
|
249
|
+
);
|
|
256
250
|
for (const profilePath of profilePaths) {
|
|
257
251
|
if (!paths.includes(profilePath)) {
|
|
258
252
|
paths.push(profilePath);
|
|
@@ -533,11 +527,7 @@ async function installJsonMcpConfig(clientName, configPath, stdout) {
|
|
|
533
527
|
}
|
|
534
528
|
|
|
535
529
|
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
536
|
-
await fs.promises.writeFile(
|
|
537
|
-
configPath,
|
|
538
|
-
`${JSON.stringify(merged, null, 2)}\n`,
|
|
539
|
-
'utf8',
|
|
540
|
-
);
|
|
530
|
+
await fs.promises.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
541
531
|
stdout.write(`Wrote ${configPath}\n`);
|
|
542
532
|
}
|
|
543
533
|
|
|
@@ -600,11 +590,7 @@ async function removeJsonMcpConfig(clientName, configPath, stdout) {
|
|
|
600
590
|
}
|
|
601
591
|
|
|
602
592
|
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
603
|
-
await fs.promises.writeFile(
|
|
604
|
-
configPath,
|
|
605
|
-
`${JSON.stringify(merged, null, 2)}\n`,
|
|
606
|
-
'utf8',
|
|
607
|
-
);
|
|
593
|
+
await fs.promises.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
608
594
|
stdout.write(`Removed ${configPath}\n`);
|
|
609
595
|
return true;
|
|
610
596
|
}
|
|
@@ -647,16 +633,11 @@ export function parseInstalledMcpConfig(clientName, raw) {
|
|
|
647
633
|
const parsed = JSON.parse(raw);
|
|
648
634
|
const block =
|
|
649
635
|
parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
650
|
-
? getMergedJsonMcpBlock(
|
|
651
|
-
/** @type {Record<string, unknown>} */ (parsed),
|
|
652
|
-
clientName,
|
|
653
|
-
)
|
|
636
|
+
? getMergedJsonMcpBlock(/** @type {Record<string, unknown>} */ (parsed), clientName)
|
|
654
637
|
: {};
|
|
655
638
|
return {
|
|
656
639
|
configured: Boolean(
|
|
657
|
-
block &&
|
|
658
|
-
typeof block === 'object' &&
|
|
659
|
-
Object.hasOwn(block, BROWSER_BRIDGE_SERVER_NAME),
|
|
640
|
+
block && typeof block === 'object' && Object.hasOwn(block, BROWSER_BRIDGE_SERVER_NAME)
|
|
660
641
|
),
|
|
661
642
|
};
|
|
662
643
|
} catch {
|