@browserbridge/bbx 1.7.4 → 1.7.6

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.
Files changed (34) hide show
  1. package/package.json +6 -6
  2. package/packages/agent-client/src/atomic-write.js +99 -0
  3. package/packages/agent-client/src/cli-args.js +152 -0
  4. package/packages/agent-client/src/cli-batch.js +140 -0
  5. package/packages/agent-client/src/cli-helpers.js +20 -3
  6. package/packages/agent-client/src/cli-output.js +57 -0
  7. package/packages/agent-client/src/cli-proxy-remote.js +350 -0
  8. package/packages/agent-client/src/cli-setup-commands.js +404 -0
  9. package/packages/agent-client/src/cli.js +63 -906
  10. package/packages/agent-client/src/command-registry.js +12 -6
  11. package/packages/agent-client/src/install.js +39 -4
  12. package/packages/agent-client/src/mcp-config.js +28 -14
  13. package/packages/agent-client/src/remotes.js +93 -15
  14. package/packages/agent-client/src/runtime.js +6 -4
  15. package/packages/mcp-server/src/bin.js +10 -1
  16. package/packages/mcp-server/src/guidance.js +1 -1
  17. package/packages/mcp-server/src/handlers-capture.js +8 -5
  18. package/packages/mcp-server/src/handlers-dom.js +2 -2
  19. package/packages/mcp-server/src/handlers-navigation.js +20 -9
  20. package/packages/mcp-server/src/handlers-page.js +111 -111
  21. package/packages/mcp-server/src/handlers-utils.js +268 -8
  22. package/packages/mcp-server/src/handlers.js +31 -10
  23. package/packages/mcp-server/src/lifecycle.js +413 -0
  24. package/packages/mcp-server/src/server.js +27 -19
  25. package/packages/native-host/src/auth-token.js +1 -1
  26. package/packages/native-host/src/config.js +9 -4
  27. package/packages/native-host/src/daemon-process.js +8 -1
  28. package/packages/protocol/src/capabilities.js +33 -110
  29. package/packages/protocol/src/capability-values.js +45 -0
  30. package/packages/protocol/src/defaults.js +19 -2
  31. package/packages/protocol/src/protocol.js +106 -5
  32. package/packages/protocol/src/registry.js +299 -86
  33. package/packages/protocol/src/types.ts +19 -1
  34. package/skills/browser-bridge/SKILL.md +1 -1
@@ -0,0 +1,404 @@
1
+ // @ts-check
2
+
3
+ import os from 'node:os';
4
+
5
+ import { SUPPORTED_BROWSERS } from '../../native-host/src/config.js';
6
+ import { uninstallNativeManifest } from '../../native-host/src/install-manifest.js';
7
+ import { interactiveCheckbox, interactiveConfirm } from './cli-helpers.js';
8
+ import {
9
+ findInstalledManagedTargets,
10
+ installAgentFiles,
11
+ installMcpClientSetup,
12
+ parseInstallAgentArgs,
13
+ removeAgentFiles,
14
+ SUPPORTED_TARGETS,
15
+ TARGET_LABELS,
16
+ } from './install.js';
17
+ import {
18
+ findConfiguredMcpClients,
19
+ isMcpClientName,
20
+ MCP_CLIENT_LABELS,
21
+ MCP_CLIENT_NAMES,
22
+ removeMcpConfig,
23
+ } from './mcp-config.js';
24
+ import { collectSetupStatus } from './setup-status.js';
25
+
26
+ const TEST_DETECTED_MCP_CLIENTS_ENV = 'BBX_TEST_DETECTED_MCP_CLIENTS';
27
+ const TEST_DETECTED_SKILL_TARGETS_ENV = 'BBX_TEST_DETECTED_SKILL_TARGETS';
28
+
29
+ /**
30
+ * Handle `bbx install-skill [targets] [--global|--project <path>]`.
31
+ * Exits the process when finished.
32
+ *
33
+ * @param {string[]} args
34
+ * @returns {Promise<never>}
35
+ */
36
+ export async function runInstallSkillCommand(args) {
37
+ // When no positional target is given, detect installed agents and prompt.
38
+ if (!hasExplicitInstallSkillTarget(args)) {
39
+ let scopeOptions;
40
+ try {
41
+ scopeOptions = parseInstallAgentArgs(args);
42
+ } catch (error) {
43
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
44
+ process.exit(1);
45
+ }
46
+
47
+ const isGlobal = scopeOptions.global !== false;
48
+ const projectPath = isGlobal ? os.homedir() : scopeOptions.projectPath;
49
+
50
+ const setupStatus = await collectSetupStatus({
51
+ global: isGlobal,
52
+ cwd: process.cwd(),
53
+ projectPath,
54
+ ...getSetupStatusTestOverrides(),
55
+ });
56
+ /** @type {import('./types.js').SupportedTarget[]} */
57
+ const detected = /** @type {import('./types.js').SupportedTarget[]} */ (
58
+ setupStatus.skillTargets.filter((entry) => entry.detected).map((entry) => entry.key)
59
+ );
60
+ const installedManagedTargets = new Set(
61
+ setupStatus.skillTargets
62
+ .filter((entry) => entry.installed && entry.managed)
63
+ .map((entry) => entry.key)
64
+ );
65
+ const installedManagedTargetList = /** @type {import('./types.js').SupportedTarget[]} */ ([
66
+ ...installedManagedTargets,
67
+ ]);
68
+
69
+ // Aliases like 'openai' and 'google' map to canonical targets and stay omitted.
70
+ const items = SUPPORTED_TARGETS.map((t) => ({
71
+ value: t,
72
+ label: `${t.padEnd(10)} ${TARGET_LABELS[t]}`,
73
+ hint: formatSelectionHint({
74
+ detected: detected.includes(t),
75
+ installed: installedManagedTargets.has(t),
76
+ }),
77
+ checked: installedManagedTargets.has(t),
78
+ }));
79
+
80
+ const selected = await interactiveCheckbox(
81
+ 'Select agents to install skill for (↑↓ move · space toggle · a all · enter confirm)',
82
+ items
83
+ );
84
+
85
+ /** @type {import('./types.js').SupportedTarget[]} */
86
+ let targets;
87
+ if (selected === null) {
88
+ // Non-TTY: prefer managed installs, then detected targets (always includes 'agents').
89
+ targets = installedManagedTargets.size > 0 ? installedManagedTargetList : detected;
90
+ } else {
91
+ targets = /** @type {import('./types.js').SupportedTarget[]} */ (selected);
92
+ }
93
+
94
+ if (selected !== null) {
95
+ const deselectedTargets = /** @type {import('./types.js').SupportedTarget[]} */ (
96
+ installedManagedTargetList.filter((target) => !targets.includes(target))
97
+ );
98
+ const removableTargets = await findInstalledManagedTargets({
99
+ targets: deselectedTargets,
100
+ projectPath,
101
+ global: isGlobal,
102
+ });
103
+ if (removableTargets.length > 0) {
104
+ const confirmed = await interactiveConfirm(
105
+ `Remove Browser Bridge skill from deselected targets: ${removableTargets.join(', ')}?`
106
+ );
107
+ if (confirmed) {
108
+ const removedPaths = await removeAgentFiles({
109
+ targets: removableTargets,
110
+ projectPath,
111
+ global: isGlobal,
112
+ });
113
+ for (const p of removedPaths) process.stdout.write(`Removed ${p}\n`);
114
+ }
115
+ }
116
+ }
117
+
118
+ if (targets.length === 0) {
119
+ process.stdout.write('No targets selected.\n');
120
+ process.exit(0);
121
+ }
122
+
123
+ const installedPaths = await installAgentFiles({
124
+ targets,
125
+ projectPath,
126
+ global: isGlobal,
127
+ });
128
+ for (const p of installedPaths) process.stdout.write(`Installed ${p}\n`);
129
+ process.exit(0);
130
+ }
131
+
132
+ // Explicit targets or 'all' provided - use existing arg-parsing logic.
133
+ const options = parseInstallAgentArgs(args);
134
+ const installedPaths = await installAgentFiles(options);
135
+ for (const installedPath of installedPaths) {
136
+ process.stdout.write(`Installed ${installedPath}\n`);
137
+ }
138
+ process.exit(0);
139
+ }
140
+
141
+ /**
142
+ * Handle `bbx install-mcp [client] [--global|--local]`.
143
+ * Exits the process when finished.
144
+ *
145
+ * @param {string[]} args
146
+ * @returns {Promise<never>}
147
+ */
148
+ export async function runInstallMcpCommand(args) {
149
+ let isGlobal = true;
150
+ /** @type {string[]} */
151
+ const positionals = [];
152
+
153
+ for (const arg of args) {
154
+ if (arg === '--local') {
155
+ isGlobal = false;
156
+ continue;
157
+ }
158
+ if (arg === '--global') {
159
+ isGlobal = true;
160
+ continue;
161
+ }
162
+ if (arg.startsWith('--')) {
163
+ process.stderr.write(`Unknown install-mcp option "${arg}".\n`);
164
+ process.exit(1);
165
+ }
166
+ positionals.push(arg);
167
+ }
168
+
169
+ if (positionals.length > 1) {
170
+ process.stderr.write(`Unexpected extra argument "${positionals[1]}".\n`);
171
+ process.exit(1);
172
+ }
173
+
174
+ const clientArg = positionals[0];
175
+
176
+ /** @type {import('./types.js').McpClientName[]} */
177
+ let clients;
178
+
179
+ if (!clientArg) {
180
+ // No client specified: inspect current MCP config and prompt interactively.
181
+ const setupStatus = await collectSetupStatus({
182
+ global: isGlobal,
183
+ cwd: process.cwd(),
184
+ projectPath: process.cwd(),
185
+ ...getSetupStatusTestOverrides(),
186
+ });
187
+ const detected = /** @type {import('./types.js').McpClientName[]} */ (
188
+ setupStatus.mcpClients.filter((entry) => entry.detected).map((entry) => entry.key)
189
+ );
190
+ const configuredClients = new Set(
191
+ setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
192
+ );
193
+ const configuredClientList = /** @type {import('./types.js').McpClientName[]} */ ([
194
+ ...configuredClients,
195
+ ]);
196
+ const items = MCP_CLIENT_NAMES.map((c) => ({
197
+ value: c,
198
+ label: `${c.padEnd(10)} ${MCP_CLIENT_LABELS[c]}`,
199
+ hint: formatSelectionHint({
200
+ detected: detected.includes(c),
201
+ installed: configuredClients.has(c),
202
+ }),
203
+ checked: configuredClients.has(c),
204
+ }));
205
+
206
+ const selected = await interactiveCheckbox(
207
+ 'Select clients to configure (↑↓ move · space toggle · a all · enter confirm)',
208
+ items
209
+ );
210
+
211
+ if (selected === null) {
212
+ // Non-TTY: prefer configured clients, then detected clients, then all.
213
+ clients =
214
+ configuredClients.size > 0
215
+ ? configuredClientList
216
+ : detected.length > 0
217
+ ? detected
218
+ : [...MCP_CLIENT_NAMES];
219
+ } else {
220
+ clients = /** @type {import('./types.js').McpClientName[]} */ (selected);
221
+ }
222
+
223
+ if (selected !== null) {
224
+ const deselectedClients = /** @type {import('./types.js').McpClientName[]} */ (
225
+ configuredClientList.filter((clientName) => !clients.includes(clientName))
226
+ );
227
+ const removableClients = await findConfiguredMcpClients({
228
+ clients: deselectedClients,
229
+ global: isGlobal,
230
+ cwd: process.cwd(),
231
+ });
232
+ if (removableClients.length > 0) {
233
+ const confirmed = await interactiveConfirm(
234
+ `Remove Browser Bridge MCP config from deselected clients: ${removableClients.join(', ')}?`
235
+ );
236
+ if (confirmed) {
237
+ for (const clientName of removableClients) {
238
+ await removeMcpConfig(clientName, {
239
+ global: isGlobal,
240
+ cwd: process.cwd(),
241
+ });
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ if (clients.length === 0) {
248
+ process.stdout.write('No clients selected.\n');
249
+ process.exit(0);
250
+ }
251
+ } else if (clientArg === 'all') {
252
+ clients = [...MCP_CLIENT_NAMES];
253
+ } else {
254
+ const parts = clientArg
255
+ .split(',')
256
+ .map((s) => s.trim().toLowerCase())
257
+ .filter(Boolean);
258
+ if (parts.includes('all')) {
259
+ clients = [...MCP_CLIENT_NAMES];
260
+ } else {
261
+ clients = [];
262
+ for (const part of parts) {
263
+ if (!isMcpClientName(part)) {
264
+ process.stderr.write(
265
+ `Unknown client "${part}". Supported: ${MCP_CLIENT_NAMES.join(', ')}, all\n`
266
+ );
267
+ process.exit(1);
268
+ }
269
+ clients.push(part);
270
+ }
271
+ }
272
+ }
273
+
274
+ await installMcpClientSetup(clients, {
275
+ global: isGlobal,
276
+ projectPath: process.cwd(),
277
+ stdout: process.stdout,
278
+ });
279
+ process.exit(0);
280
+ }
281
+
282
+ /**
283
+ * Remove all Browser Bridge MCP configs, skills, and native manifests.
284
+ *
285
+ * @returns {Promise<void>}
286
+ */
287
+ export async function uninstallBrowserBridge() {
288
+ const cwd = process.cwd();
289
+ const globalProjectPath = os.homedir();
290
+
291
+ for (const clientName of MCP_CLIENT_NAMES) {
292
+ await removeMcpConfig(clientName, { global: true, cwd });
293
+ }
294
+
295
+ const removedGlobalSkillPaths = await removeAgentFiles({
296
+ targets: SUPPORTED_TARGETS,
297
+ projectPath: globalProjectPath,
298
+ global: true,
299
+ });
300
+ for (const removedPath of removedGlobalSkillPaths) {
301
+ process.stdout.write(`Removed ${removedPath}\n`);
302
+ }
303
+
304
+ for (const clientName of MCP_CLIENT_NAMES) {
305
+ await removeMcpConfig(clientName, { global: false, cwd });
306
+ }
307
+
308
+ const removedLocalSkillPaths = await removeAgentFiles({
309
+ targets: SUPPORTED_TARGETS,
310
+ projectPath: cwd,
311
+ global: false,
312
+ });
313
+ for (const removedPath of removedLocalSkillPaths) {
314
+ process.stdout.write(`Removed ${removedPath}\n`);
315
+ }
316
+
317
+ for (const [index, browser] of SUPPORTED_BROWSERS.entries()) {
318
+ await uninstallNativeManifest({
319
+ browser,
320
+ removeBridgeDir: index === SUPPORTED_BROWSERS.length - 1,
321
+ });
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Determine whether install-skill includes an explicit target without treating
327
+ * option values such as `--project <path>` as positional targets.
328
+ *
329
+ * @param {string[]} args
330
+ * @returns {boolean}
331
+ */
332
+ function hasExplicitInstallSkillTarget(args) {
333
+ for (let index = 0; index < args.length; index += 1) {
334
+ const arg = args[index];
335
+ if (arg === '--project') {
336
+ index += 1;
337
+ continue;
338
+ }
339
+ if (arg === '--agents' || arg === '--agent') {
340
+ return true;
341
+ }
342
+ if (arg.startsWith('--agents=') || arg.startsWith('--agent=')) {
343
+ return true;
344
+ }
345
+ if (!arg.startsWith('--')) {
346
+ return true;
347
+ }
348
+ }
349
+ return false;
350
+ }
351
+
352
+ /**
353
+ * Allow CLI tests to provide deterministic MCP client detection without relying
354
+ * on whatever tools happen to be installed on the host machine.
355
+ *
356
+ * @returns {{
357
+ * mcpDetectors?: Record<string, () => boolean>,
358
+ * skillDetectors?: Record<string, () => boolean>,
359
+ * }}
360
+ */
361
+ function getSetupStatusTestOverrides() {
362
+ /** @type {{ mcpDetectors?: Record<string, () => boolean>, skillDetectors?: Record<string, () => boolean> }} */
363
+ const overrides = {};
364
+ if (TEST_DETECTED_MCP_CLIENTS_ENV in process.env) {
365
+ const detectedClients = new Set(
366
+ (process.env[TEST_DETECTED_MCP_CLIENTS_ENV] || '')
367
+ .split(',')
368
+ .map((value) => value.trim().toLowerCase())
369
+ .filter(Boolean)
370
+ );
371
+ overrides.mcpDetectors = Object.fromEntries(
372
+ MCP_CLIENT_NAMES.map((clientName) => [clientName, () => detectedClients.has(clientName)])
373
+ );
374
+ }
375
+
376
+ if (TEST_DETECTED_SKILL_TARGETS_ENV in process.env) {
377
+ const detectedTargets = new Set(
378
+ (process.env[TEST_DETECTED_SKILL_TARGETS_ENV] || '')
379
+ .split(',')
380
+ .map((value) => value.trim().toLowerCase())
381
+ .filter(Boolean)
382
+ );
383
+ overrides.skillDetectors = Object.fromEntries(
384
+ SUPPORTED_TARGETS.map((target) => [target, () => detectedTargets.has(target)])
385
+ );
386
+ }
387
+ return overrides;
388
+ }
389
+
390
+ /**
391
+ * @param {{ detected: boolean, installed: boolean }} options
392
+ * @returns {string | undefined}
393
+ */
394
+ function formatSelectionHint(options) {
395
+ /** @type {string[]} */
396
+ const parts = [];
397
+ if (options.detected) {
398
+ parts.push('● detected');
399
+ }
400
+ if (options.installed) {
401
+ parts.push('installed');
402
+ }
403
+ return parts.length > 0 ? parts.join(' · ') : undefined;
404
+ }