@browserbridge/bbx 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +6 -5
  2. package/package.json +1 -1
  3. package/packages/agent-client/src/cli.js +30 -20
  4. package/packages/agent-client/src/client.js +105 -42
  5. package/packages/agent-client/src/command-registry.js +4 -14
  6. package/packages/agent-client/src/detect.js +3 -3
  7. package/packages/agent-client/src/install.js +3 -7
  8. package/packages/agent-client/src/mcp-config.js +1 -3
  9. package/packages/agent-client/src/runtime.js +7 -41
  10. package/packages/agent-client/src/setup-status.js +3 -13
  11. package/packages/agent-client/src/types.ts +131 -0
  12. package/packages/mcp-server/src/handlers-capture.js +291 -0
  13. package/packages/mcp-server/src/handlers-dom.js +203 -0
  14. package/packages/mcp-server/src/handlers-navigation.js +79 -0
  15. package/packages/mcp-server/src/handlers-page.js +365 -0
  16. package/packages/mcp-server/src/handlers-utils.js +318 -0
  17. package/packages/mcp-server/src/handlers.js +59 -1176
  18. package/packages/mcp-server/src/server.js +2 -1
  19. package/packages/native-host/bin/bridge-daemon.js +2 -1
  20. package/packages/native-host/bin/install-manifest.js +8 -0
  21. package/packages/native-host/bin/postinstall.js +46 -9
  22. package/packages/native-host/src/daemon-logger.js +157 -0
  23. package/packages/native-host/src/daemon-process.js +43 -18
  24. package/packages/native-host/src/daemon.js +133 -12
  25. package/packages/native-host/src/framing.js +13 -0
  26. package/packages/native-host/src/native-host.js +7 -5
  27. package/packages/protocol/src/capabilities.js +1 -0
  28. package/packages/protocol/src/protocol.js +40 -0
  29. package/packages/protocol/src/registry.js +5 -9
  30. package/packages/protocol/src/types.ts +572 -0
  31. package/packages/protocol/src/types.js +0 -626
package/README.md CHANGED
@@ -99,17 +99,18 @@ Managed installs support OpenAI Codex, Claude Code, Cursor, GitHub Copilot, Open
99
99
 
100
100
  ## Why Browser Bridge
101
101
 
102
- Most adjacent tools optimize for different goals. [Playwright](https://playwright.dev/) and headless automation stacks are excellent for deterministic tests and CI - but they start from a clean browser context by design. [Claude in Chrome](https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome) is great for integrated Claude workflows, but is vendor-specific. Generic MCP browser servers offer broad control without the developer-focused depth.
102
+ Most adjacent tools optimize for different goals. [Playwright](https://playwright.dev/) and headless automation stacks are excellent for deterministic tests and CI - but they start from a clean browser context by design. [Claude in Chrome](https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome) is great for integrated Claude workflows, and the [Codex extension](https://chromewebstore.google.com/detail/codex/hehggadaopoacecdllhhajmbjkdcmajg) is a great option if you use Codex, but both are vendor-specific. Generic MCP browser servers offer broad control without the developer-focused depth.
103
103
 
104
104
  Browser Bridge is optimized for the opposite starting point: **inspect the state that already exists** in a real tab - logged-in sessions, feature flags, seeded storage, SPA state - use structured reads to understand it, test a patch in place, then fix the source. It's open-source, agent-agnostic, and scoped to explicit tab sessions rather than ambient browser control.
105
105
 
106
106
  ## Setup
107
107
 
108
- 1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie)
108
+ 1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie) in Chrome or another Chromium-based browser
109
109
  2. `npm install -g @browserbridge/bbx` - installs the CLI and native host
110
- 3. In the extension side panel, install MCP or CLI (skill) for your agent of choice
111
- 4. Enable Browser Bridge for the Chrome window you want to inspect/control with the AI agent
112
- 5. Ask your agent to use Browser Bridge via MCP (`BB MCP` or `Browser Bridge MCP`), or invoke the `browser-bridge` / `$bbx` skill in CLI mode
110
+ 3. Run `bbx install`, or target a specific browser with `bbx install --browser edge`, `bbx install --browser brave`, `bbx install --browser chromium`, or `bbx install --browser arc`
111
+ 4. In the extension side panel, install MCP or CLI (skill) for your agent of choice
112
+ 5. Enable Browser Bridge for the browser window you want to inspect/control with the AI agent
113
+ 6. Ask your agent to use Browser Bridge via MCP (`BB MCP` or `Browser Bridge MCP`), or invoke the `browser-bridge` / `$bbx` skill in CLI mode
113
114
 
114
115
  ## How it works
115
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbridge/bbx",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "agent-tools",
@@ -50,8 +50,8 @@ import { getDoctorReport, requestBridge, resolveRef } from './runtime.js';
50
50
  import { collectSetupStatus } from './setup-status.js';
51
51
  import { annotateBridgeSummary, summarizeBridgeResponse } from './subagent.js';
52
52
 
53
- /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
54
- /** @typedef {{ image: string, rect: Record<string, unknown> }} ScreenshotResult */
53
+ /** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
54
+ /** @typedef {import('./types.js').ScreenshotResult} ScreenshotResult */
55
55
 
56
56
  const REQUEST_SOURCE = 'cli';
57
57
  const TEST_TIMEOUT_ENV = 'BBX_CLIENT_REQUEST_TIMEOUT_MS';
@@ -139,8 +139,8 @@ if (command === 'install-skill') {
139
139
  projectPath: isGlobal ? os.homedir() : process.cwd(),
140
140
  ...getSetupStatusTestOverrides(),
141
141
  });
142
- /** @type {import('./install.js').SupportedTarget[]} */
143
- const detected = /** @type {import('./install.js').SupportedTarget[]} */ (
142
+ /** @type {import('./types.js').SupportedTarget[]} */
143
+ const detected = /** @type {import('./types.js').SupportedTarget[]} */ (
144
144
  setupStatus.skillTargets.filter((entry) => entry.detected).map((entry) => entry.key)
145
145
  );
146
146
  const installedManagedTargets = new Set(
@@ -149,7 +149,7 @@ if (command === 'install-skill') {
149
149
  .map((entry) => entry.key)
150
150
  );
151
151
  const installedManagedTargetList =
152
- /** @type {import('./install.js').SupportedTarget[]} */ ([...installedManagedTargets]);
152
+ /** @type {import('./types.js').SupportedTarget[]} */ ([...installedManagedTargets]);
153
153
 
154
154
  // Aliases like 'openai' and 'google' map to canonical targets and stay omitted.
155
155
  const items = SUPPORTED_TARGETS.map((t) => ({
@@ -167,19 +167,19 @@ if (command === 'install-skill') {
167
167
  items
168
168
  );
169
169
 
170
- /** @type {import('./install.js').SupportedTarget[]} */
170
+ /** @type {import('./types.js').SupportedTarget[]} */
171
171
  let targets;
172
172
  if (selected === null) {
173
173
  // Non-TTY: prefer managed installs, then detected targets (always includes 'agents').
174
174
  targets = installedManagedTargets.size > 0 ? installedManagedTargetList : detected;
175
175
  } else {
176
- targets = /** @type {import('./install.js').SupportedTarget[]} */ (selected);
176
+ targets = /** @type {import('./types.js').SupportedTarget[]} */ (selected);
177
177
  }
178
178
 
179
179
  const projectPath = isGlobal ? os.homedir() : process.cwd();
180
180
  if (selected !== null) {
181
181
  const deselectedTargets =
182
- /** @type {import('./install.js').SupportedTarget[]} */ (
182
+ /** @type {import('./types.js').SupportedTarget[]} */ (
183
183
  installedManagedTargetList.filter((target) => !targets.includes(target))
184
184
  );
185
185
  const removableTargets = await findInstalledManagedTargets({
@@ -241,7 +241,7 @@ if (command === 'install-mcp') {
241
241
 
242
242
  const clientArg = argsLeft[0];
243
243
 
244
- /** @type {import('./mcp-config.js').McpClientName[]} */
244
+ /** @type {import('./types.js').McpClientName[]} */
245
245
  let clients;
246
246
 
247
247
  if (!clientArg) {
@@ -252,14 +252,14 @@ if (command === 'install-mcp') {
252
252
  projectPath: process.cwd(),
253
253
  ...getSetupStatusTestOverrides(),
254
254
  });
255
- const detected = /** @type {import('./mcp-config.js').McpClientName[]} */ (
255
+ const detected = /** @type {import('./types.js').McpClientName[]} */ (
256
256
  setupStatus.mcpClients.filter((entry) => entry.detected).map((entry) => entry.key)
257
257
  );
258
258
  const configuredClients = new Set(
259
259
  setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
260
260
  );
261
261
  const configuredClientList =
262
- /** @type {import('./mcp-config.js').McpClientName[]} */ ([...configuredClients]);
262
+ /** @type {import('./types.js').McpClientName[]} */ ([...configuredClients]);
263
263
  const items = MCP_CLIENT_NAMES.map((c) => ({
264
264
  value: c,
265
265
  label: `${c.padEnd(10)} ${MCP_CLIENT_LABELS[c]}`,
@@ -284,12 +284,12 @@ if (command === 'install-mcp') {
284
284
  ? detected
285
285
  : [...MCP_CLIENT_NAMES];
286
286
  } else {
287
- clients = /** @type {import('./mcp-config.js').McpClientName[]} */ (selected);
287
+ clients = /** @type {import('./types.js').McpClientName[]} */ (selected);
288
288
  }
289
289
 
290
290
  if (selected !== null) {
291
291
  const deselectedClients =
292
- /** @type {import('./mcp-config.js').McpClientName[]} */ (
292
+ /** @type {import('./types.js').McpClientName[]} */ (
293
293
  configuredClientList.filter((clientName) => !clients.includes(clientName))
294
294
  );
295
295
  const removableClients = await findConfiguredMcpClients({
@@ -470,15 +470,20 @@ async function main() {
470
470
  }
471
471
 
472
472
  if (command === 'batch') {
473
- await ensureClientConnection();
474
473
  const input = rest[0];
475
474
  if (!input) {
476
475
  throw new Error('Usage: batch \'[{"method":"...","params":{...}}, ...]\'');
477
476
  }
478
- const calls = JSON.parse(input);
477
+ let calls;
478
+ try {
479
+ calls = JSON.parse(input);
480
+ } catch {
481
+ throw new Error('Invalid JSON syntax. Expected a JSON array of bridge calls.');
482
+ }
479
483
  if (!Array.isArray(calls)) {
480
484
  throw new Error('Batch input must be a JSON array.');
481
485
  }
486
+ await ensureClientConnection();
482
487
  const results = await Promise.all(
483
488
  calls.map(async (call) => {
484
489
  if (!call || typeof call !== 'object' || typeof call.method !== 'string') {
@@ -613,16 +618,18 @@ async function main() {
613
618
  }
614
619
 
615
620
  if (command === 'screenshot') {
616
- const [refOrSelector, outputPath] = rest;
617
- if (!refOrSelector) throw new Error('Usage: screenshot <ref|selector> [path]');
618
- const elementRef = await resolveRef(client, refOrSelector, null, REQUEST_SOURCE);
621
+ const parsed = extractTabFlag(rest);
622
+ const [refOrSelector, outputPath] = parsed.rest;
623
+ if (!refOrSelector)
624
+ throw new Error('Usage: screenshot [--tab <tabId>] <ref|selector> [path]');
625
+ const elementRef = await resolveRef(client, refOrSelector, parsed.tabId, REQUEST_SOURCE);
619
626
  const response = await requestBridge(
620
627
  client,
621
628
  'screenshot.capture_element',
622
629
  {
623
630
  elementRef,
624
631
  },
625
- { source: REQUEST_SOURCE }
632
+ { tabId: parsed.tabId, source: REQUEST_SOURCE }
626
633
  );
627
634
  if (!response.ok) {
628
635
  await printSummary(response);
@@ -845,10 +852,13 @@ async function uninstallBrowserBridge() {
845
852
  */
846
853
  async function parseCallCommand(args) {
847
854
  const parsed = extractTabFlag(args);
848
- const [first, second] = parsed.rest;
855
+ const [first, second, ...extra] = parsed.rest;
849
856
  if (!first) {
850
857
  throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
851
858
  }
859
+ if (extra.length > 0) {
860
+ throw new Error('Usage: call [--tab <tabId>] <method> [paramsJson]');
861
+ }
852
862
 
853
863
  if (first.includes('.')) {
854
864
  const method = /** @type {BridgeMethod} */ (first);
@@ -15,48 +15,33 @@ import {
15
15
  getBridgeTransport,
16
16
  getSocketPath,
17
17
  } from '../../native-host/src/config.js';
18
+ import { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
18
19
 
19
- /** @typedef {import('../../native-host/src/config.js').BridgeTransport} BridgeTransport */
20
+ /** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
21
+ /** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
22
+ /** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
23
+ /** @typedef {import('./types.js').BridgeClientOptions} BridgeClientOptions */
24
+ /** @typedef {import('./types.js').ClientMessage} ClientMessage */
25
+ /** @typedef {import('./types.js').PendingRequest} PendingRequest */
26
+ /** @typedef {import('./types.js').ProtocolHealthResult} ProtocolHealthResult */
20
27
 
21
- /** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
22
- /** @typedef {import('../../protocol/src/types.js').BridgeMeta} BridgeMeta */
23
- /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
24
28
  /**
25
- * @typedef {{
26
- * supported_versions?: string[],
27
- * deprecated_since?: string,
28
- * migration_hint?: string
29
- * }} ProtocolHealthResult
30
- */
31
-
32
- /**
33
- * @typedef {{
34
- * type: 'registered',
35
- * role: 'agent' | 'extension',
36
- * clientId?: string
37
- * } | {
38
- * type: 'agent.response',
39
- * response: BridgeResponse
40
- * }} ClientMessage
41
- */
42
-
43
- /**
44
- * @typedef {{
45
- * resolve: (value: any) => void,
46
- * reject: (error: Error) => void,
47
- * timeoutId: NodeJS.Timeout
48
- * }} PendingRequest
49
- */
50
-
51
- /**
52
- * @typedef {{
53
- * transport?: BridgeTransport,
54
- * socketPath?: string,
55
- * clientId?: string,
56
- * defaultTimeoutMs?: number,
57
- * autoReconnect?: boolean,
58
- * }} BridgeClientOptions
29
+ * @param {string} left
30
+ * @param {string} right
31
+ * @returns {number}
59
32
  */
33
+ function compareProtocolVersions(left, right) {
34
+ const leftParts = left.split('.').map((part) => Number(part) || 0);
35
+ const rightParts = right.split('.').map((part) => Number(part) || 0);
36
+ const length = Math.max(leftParts.length, rightParts.length);
37
+ for (let index = 0; index < length; index += 1) {
38
+ const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
39
+ if (delta !== 0) {
40
+ return delta > 0 ? 1 : -1;
41
+ }
42
+ }
43
+ return 0;
44
+ }
60
45
 
61
46
  /**
62
47
  * @param {string} method
@@ -81,6 +66,8 @@ export class BridgeClient extends EventEmitter {
81
66
  clientId = `agent_${randomUUID()}`,
82
67
  defaultTimeoutMs = DEFAULT_CLIENT_REQUEST_TIMEOUT_MS,
83
68
  autoReconnect = false,
69
+ restartDaemonOnVersionMismatch = true,
70
+ restartDaemonFn = restartBridgeDaemon,
84
71
  } = {}) {
85
72
  super();
86
73
  this.transport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
@@ -89,6 +76,8 @@ export class BridgeClient extends EventEmitter {
89
76
  this.clientId = clientId;
90
77
  this.defaultTimeoutMs = defaultTimeoutMs;
91
78
  this.autoReconnect = autoReconnect;
79
+ this.restartDaemonOnVersionMismatch = restartDaemonOnVersionMismatch;
80
+ this.restartDaemonFn = restartDaemonFn;
92
81
  this.socket = null;
93
82
  this.connected = false;
94
83
  this.protocolCompatibility = null;
@@ -96,6 +85,7 @@ export class BridgeClient extends EventEmitter {
96
85
  /** @type {Map<string, PendingRequest>} */
97
86
  this.waiting = new Map();
98
87
  this._reconnecting = false;
88
+ this._attemptedVersionMismatchRestart = false;
99
89
  }
100
90
 
101
91
  /**
@@ -173,19 +163,38 @@ export class BridgeClient extends EventEmitter {
173
163
  });
174
164
  });
175
165
 
166
+ this.protocolCompatibility = null;
167
+ this.protocolWarning = null;
168
+
169
+ /** @type {ProtocolHealthResult | null} */
170
+ let healthResult = null;
176
171
  try {
177
172
  const healthResponse = await this.request({
178
173
  method: 'health.ping',
179
174
  });
180
175
  if (healthResponse.ok) {
181
- this.protocolCompatibility = BridgeClient.checkProtocolVersion(
182
- /** @type {ProtocolHealthResult} */ (healthResponse.result)
183
- );
184
- this.protocolWarning = this.protocolCompatibility.warning ?? null;
176
+ healthResult = /** @type {ProtocolHealthResult} */ (healthResponse.result);
185
177
  }
186
178
  } catch {
187
179
  this.protocolCompatibility = null;
188
180
  this.protocolWarning = null;
181
+ return;
182
+ }
183
+
184
+ if (!healthResult) {
185
+ return;
186
+ }
187
+
188
+ this.protocolCompatibility = BridgeClient.checkProtocolVersion(healthResult);
189
+ this.protocolWarning = this.protocolCompatibility.warning ?? null;
190
+ if (this.protocolCompatibility.compatible) {
191
+ this._attemptedVersionMismatchRestart = false;
192
+ }
193
+ if (this.shouldRestartDaemonForProtocolMismatch(healthResult)) {
194
+ this._attemptedVersionMismatchRestart = true;
195
+ await this.disconnectForDaemonRestart();
196
+ await this.restartDaemonFn({ transport: this.transport });
197
+ await this.connect();
189
198
  }
190
199
  }
191
200
 
@@ -305,6 +314,60 @@ export class BridgeClient extends EventEmitter {
305
314
  this._reconnecting = false;
306
315
  }
307
316
 
317
+ /**
318
+ * @param {ProtocolHealthResult} healthResult
319
+ * @returns {boolean}
320
+ */
321
+ shouldRestartDaemonForProtocolMismatch(healthResult) {
322
+ if (
323
+ !this.restartDaemonOnVersionMismatch ||
324
+ this._attemptedVersionMismatchRestart ||
325
+ !this.protocolCompatibility ||
326
+ this.protocolCompatibility.compatible
327
+ ) {
328
+ return false;
329
+ }
330
+
331
+ const remoteVersions = Array.isArray(healthResult?.daemon_supported_versions)
332
+ ? healthResult.daemon_supported_versions
333
+ : healthResult?.extensionConnected === true
334
+ ? []
335
+ : Array.isArray(healthResult?.supported_versions)
336
+ ? healthResult.supported_versions
337
+ : [];
338
+ const latestRemote = remoteVersions[0];
339
+ return (
340
+ typeof latestRemote === 'string' &&
341
+ compareProtocolVersions(latestRemote, PROTOCOL_VERSION) < 0
342
+ );
343
+ }
344
+
345
+ /**
346
+ * Drop the current socket before forcing a daemon restart so the next
347
+ * connect() call observes a fresh local process rather than the existing one.
348
+ *
349
+ * @returns {Promise<void>}
350
+ */
351
+ async disconnectForDaemonRestart() {
352
+ const socket = this.socket;
353
+ if (!socket) {
354
+ return;
355
+ }
356
+
357
+ const previousAutoReconnect = this.autoReconnect;
358
+ this.autoReconnect = false;
359
+ this.connected = false;
360
+ this.socket = null;
361
+
362
+ if (!socket.destroyed) {
363
+ const closed = once(socket, 'close').catch(() => {});
364
+ socket.destroy();
365
+ await closed;
366
+ }
367
+
368
+ this.autoReconnect = previousAutoReconnect;
369
+ }
370
+
308
371
  /**
309
372
  * Check whether the remote side supports our protocol version.
310
373
  * Call after a successful health.ping to get early warnings about version drift.
@@ -3,18 +3,8 @@
3
3
  import { BRIDGE_METHOD_REGISTRY, BRIDGE_METHODS } from '../../protocol/src/index.js';
4
4
  import { parseCommaList, parseIntArg, parsePropertyAssignments } from './cli-helpers.js';
5
5
 
6
- /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
7
-
8
- /**
9
- * @typedef {{
10
- * method: BridgeMethod,
11
- * resolve?: boolean,
12
- * printMethod?: string,
13
- * usage: string,
14
- * description: string,
15
- * build: (r: string[], ref?: string) => Record<string, unknown>
16
- * }} ShortcutCommand
17
- */
6
+ /** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
7
+ /** @typedef {import('./types.js').ShortcutCommand} ShortcutCommand */
18
8
 
19
9
  /**
20
10
  * @param {BridgeMethod} method
@@ -247,7 +237,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
247
237
  {
248
238
  title: 'Setup',
249
239
  lines: [
250
- 'bbx install [--browser chrome|edge|brave|chromium] [extension-id] Install native messaging manifest',
240
+ 'bbx install [--browser chrome|edge|brave|chromium|arc] [extension-id] Install native messaging manifest',
251
241
  'bbx uninstall Remove native host manifests, Browser Bridge runtime files, and managed MCP/skill installs',
252
242
  'bbx install [--all] [--browser <name>] [extension-id] Install native host manifest (--all for all supported browsers)',
253
243
  'bbx install-skill [targets|all] [--global] [--project <path>] Install/update the managed Browser Bridge CLI skill',
@@ -347,7 +337,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
347
337
  {
348
338
  title: 'Capture',
349
339
  lines: [
350
- 'bbx screenshot <ref|selector> [path] Capture partial element screenshot',
340
+ 'bbx screenshot [--tab <tabId>] <ref|selector> [path] Capture partial element screenshot',
351
341
  ],
352
342
  },
353
343
  ]);
@@ -4,9 +4,9 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
 
7
- /** @typedef {import('./mcp-config.js').McpClientName} McpClientName */
8
- /** @typedef {import('./install.js').SupportedTarget} SupportedTarget */
9
- /** @typedef {() => boolean | Promise<boolean>} Detector */
7
+ /** @typedef {import('./types.js').McpClientName} McpClientName */
8
+ /** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
9
+ /** @typedef {import('./types.js').Detector} Detector */
10
10
 
11
11
  const home = os.homedir();
12
12
  const platform = process.platform;
@@ -41,9 +41,7 @@ const copilotBrowserBridgeNote = [
41
41
  '',
42
42
  ].join('\n');
43
43
 
44
- /**
45
- * @typedef {'codex' | 'claude' | 'cursor' | 'copilot' | 'opencode' | 'antigravity' | 'windsurf' | 'agents'} SupportedTarget
46
- */
44
+ /** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
47
45
 
48
46
  /** @type {SupportedTarget[]} */
49
47
  export const SUPPORTED_TARGETS = [...supportedTargets];
@@ -68,9 +66,7 @@ export function isSupportedTarget(value) {
68
66
  return supportedTargets.includes(/** @type {SupportedTarget} */ (value));
69
67
  }
70
68
 
71
- /**
72
- * @typedef {{targets: SupportedTarget[], projectPath: string, global: boolean}} InstallAgentOptions
73
- */
69
+ /** @typedef {import('./types.js').InstallAgentOptions} InstallAgentOptions */
74
70
 
75
71
  /**
76
72
  * @param {string[]} args
@@ -202,7 +198,7 @@ async function rollbackInstalledSkillDirs(attempted) {
202
198
  /**
203
199
  * Write MCP config for the given clients.
204
200
  *
205
- * @param {import('./mcp-config.js').McpClientName[]} clients
201
+ * @param {import('./types.js').McpClientName[]} clients
206
202
  * @param {{
207
203
  * global: boolean,
208
204
  * projectPath: string,
@@ -5,9 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- /**
9
- * @typedef {'codex' | 'claude' | 'cursor' | 'copilot' | 'opencode' | 'antigravity' | 'windsurf' | 'agents'} McpClientName
10
- */
8
+ /** @typedef {import('./types.js').McpClientName} McpClientName */
11
9
 
12
10
  /** @type {McpClientName[]} */
13
11
  export const MCP_CLIENT_NAMES = [
@@ -12,39 +12,14 @@ import { resolveDefaultExtensionId } from '../../native-host/src/install-manifes
12
12
  import { methodNeedsTab } from './cli-helpers.js';
13
13
  import { BridgeClient } from './client.js';
14
14
 
15
- /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
16
- /** @typedef {import('../../protocol/src/types.js').BridgeMeta} BridgeMeta */
17
- /** @typedef {import('../../protocol/src/types.js').BridgeRequestSource} BridgeRequestSource */
18
- /** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
15
+ /** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
16
+ /** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
17
+ /** @typedef {import('./types.js').BridgeRequestSource} BridgeRequestSource */
18
+ /** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
19
19
  /** @typedef {import('../../native-host/src/config.js').SupportedBrowser} SupportedBrowser */
20
-
21
- /**
22
- * @typedef {{
23
- * browser: string,
24
- * manifestPath: string,
25
- * installed: boolean
26
- * }} BrowserManifestStatus
27
- */
28
-
29
- /**
30
- * @typedef {{
31
- * manifestInstalled: boolean,
32
- * manifestPath: string,
33
- * allowedOrigins: string[],
34
- * defaultExtensionId: string | null,
35
- * defaultExtensionIdSource: string,
36
- * daemonReachable: boolean,
37
- * extensionConnected: boolean,
38
- * accessEnabled: boolean,
39
- * enabledWindowId: number | null,
40
- * routeTabId: number | null,
41
- * routeReady: boolean,
42
- * routeReason: string,
43
- * issues: string[],
44
- * nextSteps: string[],
45
- * browserManifests: BrowserManifestStatus[]
46
- * }} DoctorReport
47
- */
20
+ /** @typedef {import('./types.js').BrowserManifestStatus} BrowserManifestStatus */
21
+ /** @typedef {import('./types.js').DoctorReport} DoctorReport */
22
+ /** @typedef {import('./types.js').DoctorReportOptions} DoctorReportOptions */
48
23
 
49
24
  /**
50
25
  * @param {BridgeClient} client
@@ -178,15 +153,6 @@ export async function checkBrowserManifests() {
178
153
  );
179
154
  }
180
155
 
181
- /**
182
- * @typedef {{
183
- * loadManifest?: () => Promise<{allowed_origins?: string[]} | null>,
184
- * manifestPath?: string,
185
- * defaultExtensionIdInfo?: { extensionId: string | null, source: string },
186
- * bridgeClientRunner?: <T>(callback: (client: BridgeClient) => Promise<T>) => Promise<T>
187
- * }} DoctorReportOptions
188
- */
189
-
190
156
  /**
191
157
  * @param {DoctorReportOptions} [options={}]
192
158
  * @returns {Promise<DoctorReport>}
@@ -21,8 +21,8 @@ import {
21
21
  SUPPORTED_TARGETS,
22
22
  } from './install.js';
23
23
 
24
- /** @typedef {import('./mcp-config.js').McpClientName} McpClientName */
25
- /** @typedef {import('./install.js').SupportedTarget} SupportedTarget */
24
+ /** @typedef {import('./types.js').McpClientName} McpClientName */
25
+ /** @typedef {import('./types.js').SupportedTarget} SupportedTarget */
26
26
  /** @typedef {import('../../protocol/src/types.js').SetupStatus} SetupStatus */
27
27
  /** @typedef {import('../../protocol/src/types.js').McpClientStatus} McpClientStatus */
28
28
  /** @typedef {import('../../protocol/src/types.js').SkillTargetStatus} SkillTargetStatus */
@@ -52,17 +52,7 @@ const SKILL_TARGET_LABELS = {
52
52
  agents: 'Generic agents',
53
53
  };
54
54
 
55
- /**
56
- * @typedef {{
57
- * global?: boolean,
58
- * cwd?: string,
59
- * projectPath?: string,
60
- * mcpDetectors?: Record<string, () => boolean | Promise<boolean>>,
61
- * skillDetectors?: Record<string, () => boolean | Promise<boolean>>,
62
- * access?: (targetPath: string) => Promise<void>,
63
- * readFile?: (targetPath: string, encoding: BufferEncoding) => Promise<string>
64
- * }} SetupStatusOptions
65
- */
55
+ /** @typedef {import('./types.js').SetupStatusOptions} SetupStatusOptions */
66
56
 
67
57
  /**
68
58
  * Return Browser Bridge MCP and skill installation status for supported clients.