@browserbridge/bbx 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/package.json +11 -13
- package/packages/agent-client/src/cli-helpers.js +33 -0
- package/packages/agent-client/src/cli.js +122 -45
- package/packages/agent-client/src/client.js +134 -8
- package/packages/agent-client/src/command-registry.js +4 -1
- package/packages/agent-client/src/detect.js +159 -48
- package/packages/agent-client/src/install.js +24 -1
- package/packages/agent-client/src/mcp-config.js +29 -10
- package/packages/agent-client/src/setup-status.js +12 -4
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers-capture.js +279 -0
- package/packages/mcp-server/src/handlers-dom.js +196 -0
- package/packages/mcp-server/src/handlers-navigation.js +79 -0
- package/packages/mcp-server/src/handlers-page.js +365 -0
- package/packages/mcp-server/src/handlers-utils.js +296 -0
- package/packages/mcp-server/src/handlers.js +63 -1159
- package/packages/mcp-server/src/server.js +13 -3
- package/packages/native-host/bin/bridge-daemon.js +34 -4
- package/packages/native-host/bin/install-manifest.js +32 -2
- package/packages/native-host/bin/postinstall.js +16 -0
- package/packages/native-host/src/config.js +131 -6
- package/packages/native-host/src/daemon-logger.js +157 -0
- package/packages/native-host/src/daemon-process.js +422 -0
- package/packages/native-host/src/daemon.js +322 -77
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +121 -7
- package/packages/native-host/src/native-host.js +110 -73
- package/packages/protocol/src/capabilities.js +4 -0
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +4 -0
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +143 -7
- package/packages/protocol/src/registry.js +13 -0
- package/packages/protocol/src/summary.js +18 -10
- package/packages/protocol/src/types.js +28 -3
- package/skills/browser-bridge/SKILL.md +2 -1
- package/skills/browser-bridge/references/interaction.md +1 -0
- package/skills/browser-bridge/references/protocol.md +2 -1
- package/CHANGELOG.md +0 -55
- package/assets/banner.jpg +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +0 -65
- package/docs/api-reference.md +0 -157
- package/docs/cli-guide.md +0 -128
- package/docs/index.md +0 -25
- package/docs/manual-setup.md +0 -140
- package/docs/mcp-vs-cli.md +0 -258
- package/docs/publishing.md +0 -112
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/unpacked-extension.md +0 -72
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -38
- package/packages/extension/assets/icon-128.png +0 -0
- package/packages/extension/assets/icon-16.png +0 -0
- package/packages/extension/assets/icon-32.png +0 -0
- package/packages/extension/assets/icon-48.png +0 -0
- package/packages/extension/src/background-helpers.js +0 -474
- package/packages/extension/src/background-routing.js +0 -89
- package/packages/extension/src/background.js +0 -3490
- package/packages/extension/src/content-script-helpers.js +0 -282
- package/packages/extension/src/content-script.js +0 -2043
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -104
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -298
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1771
- package/packages/extension/ui/ui.css +0 -1160
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
3
|
import fs from 'node:fs';
|
|
5
4
|
import os from 'node:os';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
|
|
8
7
|
/** @typedef {import('./mcp-config.js').McpClientName} McpClientName */
|
|
9
8
|
/** @typedef {import('./install.js').SupportedTarget} SupportedTarget */
|
|
9
|
+
/** @typedef {() => boolean | Promise<boolean>} Detector */
|
|
10
10
|
|
|
11
11
|
const home = os.homedir();
|
|
12
12
|
const platform = process.platform;
|
|
13
|
+
const WINDOWS_EXECUTABLE_EXTENSIONS = new Set(
|
|
14
|
+
(process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
15
|
+
.split(';')
|
|
16
|
+
.map((extension) => extension.trim().toLowerCase())
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
);
|
|
19
|
+
const DEFAULT_COMMAND_NAMES =
|
|
20
|
+
platform === 'darwin'
|
|
21
|
+
? ['codex', 'claude', 'opencode', 'agy']
|
|
22
|
+
: platform === 'linux'
|
|
23
|
+
? ['codex', 'claude', 'cursor', 'code', 'opencode', 'agy', 'windsurf']
|
|
24
|
+
: ['codex', 'claude', 'cursor', 'opencode', 'agy', 'windsurf'];
|
|
25
|
+
|
|
26
|
+
const PATH_DELIMITER = platform === 'win32' ? ';' : ':';
|
|
27
|
+
|
|
28
|
+
/** @type {Promise<Set<string>> | null} */
|
|
29
|
+
let availableCommandsPromise = null;
|
|
13
30
|
|
|
14
31
|
/**
|
|
15
32
|
* @returns {string}
|
|
@@ -26,12 +43,12 @@ function getVsCodeUserDataDir() {
|
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
/**
|
|
29
|
-
* @param {string}
|
|
30
|
-
* @returns {boolean}
|
|
46
|
+
* @param {string} targetPath
|
|
47
|
+
* @returns {Promise<boolean>}
|
|
31
48
|
*/
|
|
32
|
-
function fsExists(
|
|
49
|
+
async function fsExists(targetPath) {
|
|
33
50
|
try {
|
|
34
|
-
fs.
|
|
51
|
+
await fs.promises.access(targetPath, fs.constants.F_OK);
|
|
35
52
|
return true;
|
|
36
53
|
} catch {
|
|
37
54
|
return false;
|
|
@@ -39,24 +56,102 @@ function fsExists(p) {
|
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
/**
|
|
42
|
-
* @param {string}
|
|
43
|
-
* @returns {
|
|
59
|
+
* @param {string} command
|
|
60
|
+
* @returns {string}
|
|
44
61
|
*/
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
function normalizeCommandName(command) {
|
|
63
|
+
return platform === 'win32' ? command.toLowerCase() : command;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} entryName
|
|
68
|
+
* @returns {string | null}
|
|
69
|
+
*/
|
|
70
|
+
function getCommandNameFromPathEntry(entryName) {
|
|
71
|
+
if (platform !== 'win32') {
|
|
72
|
+
return entryName;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const extension = path.extname(entryName).toLowerCase();
|
|
76
|
+
if (!WINDOWS_EXECUTABLE_EXTENSIONS.has(extension)) {
|
|
77
|
+
return null;
|
|
53
78
|
}
|
|
79
|
+
return entryName.slice(0, -extension.length).toLowerCase();
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
/**
|
|
83
|
+
* @param {readonly string[]} commands
|
|
84
|
+
* @returns {Promise<Set<string>>}
|
|
85
|
+
*/
|
|
86
|
+
async function resolveAvailableCommands(commands) {
|
|
87
|
+
const resolved = new Set();
|
|
88
|
+
const unresolved = new Set(commands.map((command) => normalizeCommandName(command)));
|
|
89
|
+
const pathEntries = (process.env.PATH || '').split(PATH_DELIMITER).filter(Boolean);
|
|
90
|
+
const executeAccessMode = platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK;
|
|
91
|
+
|
|
92
|
+
for (const directory of pathEntries) {
|
|
93
|
+
if (unresolved.size === 0) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let entries;
|
|
98
|
+
try {
|
|
99
|
+
entries = await fs.promises.readdir(directory);
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const matches = await Promise.all(
|
|
105
|
+
entries.map(async (entryName) => {
|
|
106
|
+
const commandName = getCommandNameFromPathEntry(entryName);
|
|
107
|
+
if (!commandName || !unresolved.has(commandName)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await fs.promises.access(path.join(directory, entryName), executeAccessMode);
|
|
113
|
+
return commandName;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
for (const commandName of matches) {
|
|
121
|
+
if (!commandName) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
unresolved.delete(commandName);
|
|
125
|
+
resolved.add(commandName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return resolved;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @returns {Promise<Set<string>>}
|
|
134
|
+
*/
|
|
135
|
+
function getAvailableCommands() {
|
|
136
|
+
if (!availableCommandsPromise) {
|
|
137
|
+
availableCommandsPromise = resolveAvailableCommands(DEFAULT_COMMAND_NAMES);
|
|
138
|
+
}
|
|
139
|
+
return availableCommandsPromise;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} command
|
|
144
|
+
* @returns {Promise<boolean>}
|
|
145
|
+
*/
|
|
146
|
+
async function commandExists(command) {
|
|
147
|
+
const availableCommands = await getAvailableCommands();
|
|
148
|
+
return availableCommands.has(normalizeCommandName(command));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @returns {Promise<boolean>} */
|
|
152
|
+
async function detectCopilot() {
|
|
153
|
+
if (await fsExists(path.join(getVsCodeUserDataDir(), 'User'))) return true;
|
|
154
|
+
if (await fsExists(path.join(home, '.vscode'))) return true;
|
|
60
155
|
if (platform === 'darwin') return fsExists('/Applications/Visual Studio Code.app');
|
|
61
156
|
if (platform === 'win32') {
|
|
62
157
|
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
@@ -65,47 +160,47 @@ function detectCopilot() {
|
|
|
65
160
|
return commandExists('code');
|
|
66
161
|
}
|
|
67
162
|
|
|
68
|
-
/** @returns {boolean} */
|
|
69
|
-
function detectCursor() {
|
|
70
|
-
if (fsExists(path.join(home, '.cursor'))) return true;
|
|
163
|
+
/** @returns {Promise<boolean>} */
|
|
164
|
+
async function detectCursor() {
|
|
165
|
+
if (await fsExists(path.join(home, '.cursor'))) return true;
|
|
71
166
|
if (platform === 'darwin') return fsExists('/Applications/Cursor.app');
|
|
72
167
|
return commandExists('cursor');
|
|
73
168
|
}
|
|
74
169
|
|
|
75
|
-
/** @returns {boolean} */
|
|
76
|
-
function detectWindsurf() {
|
|
77
|
-
if (fsExists(path.join(home, '.codeium', 'windsurf'))) return true;
|
|
170
|
+
/** @returns {Promise<boolean>} */
|
|
171
|
+
async function detectWindsurf() {
|
|
172
|
+
if (await fsExists(path.join(home, '.codeium', 'windsurf'))) return true;
|
|
78
173
|
if (platform === 'darwin') return fsExists('/Applications/Windsurf.app');
|
|
79
174
|
return commandExists('windsurf');
|
|
80
175
|
}
|
|
81
176
|
|
|
82
|
-
/** @returns {boolean} */
|
|
83
|
-
function detectClaude() {
|
|
84
|
-
if (fsExists(path.join(home, '.claude'))) return true;
|
|
85
|
-
if (fsExists(path.join(home, '.claude.json'))) return true;
|
|
177
|
+
/** @returns {Promise<boolean>} */
|
|
178
|
+
async function detectClaude() {
|
|
179
|
+
if (await fsExists(path.join(home, '.claude'))) return true;
|
|
180
|
+
if (await fsExists(path.join(home, '.claude.json'))) return true;
|
|
86
181
|
return commandExists('claude');
|
|
87
182
|
}
|
|
88
183
|
|
|
89
|
-
/** @returns {boolean} */
|
|
90
|
-
function detectCodex() {
|
|
91
|
-
if (fsExists(path.join(home, '.codex'))) return true;
|
|
184
|
+
/** @returns {Promise<boolean>} */
|
|
185
|
+
async function detectCodex() {
|
|
186
|
+
if (await fsExists(path.join(home, '.codex'))) return true;
|
|
92
187
|
return commandExists('codex');
|
|
93
188
|
}
|
|
94
189
|
|
|
95
|
-
/** @returns {boolean} */
|
|
96
|
-
function detectOpencode() {
|
|
97
|
-
if (fsExists(path.join(home, '.config', 'opencode'))) return true;
|
|
98
|
-
if (fsExists(path.join(home, '.opencode'))) return true;
|
|
190
|
+
/** @returns {Promise<boolean>} */
|
|
191
|
+
async function detectOpencode() {
|
|
192
|
+
if (await fsExists(path.join(home, '.config', 'opencode'))) return true;
|
|
193
|
+
if (await fsExists(path.join(home, '.opencode'))) return true;
|
|
99
194
|
return commandExists('opencode');
|
|
100
195
|
}
|
|
101
196
|
|
|
102
|
-
/** @returns {boolean} */
|
|
103
|
-
function detectAntigravity() {
|
|
104
|
-
if (fsExists(path.join(home, '.gemini', 'antigravity'))) return true;
|
|
197
|
+
/** @returns {Promise<boolean>} */
|
|
198
|
+
async function detectAntigravity() {
|
|
199
|
+
if (await fsExists(path.join(home, '.gemini', 'antigravity'))) return true;
|
|
105
200
|
return commandExists('agy');
|
|
106
201
|
}
|
|
107
202
|
|
|
108
|
-
/** @type {Record<string,
|
|
203
|
+
/** @type {Record<string, Detector>} */
|
|
109
204
|
const DETECTORS = {
|
|
110
205
|
codex: detectCodex,
|
|
111
206
|
claude: detectClaude,
|
|
@@ -138,26 +233,42 @@ const SKILL_TARGET_KEYS = [
|
|
|
138
233
|
'windsurf',
|
|
139
234
|
];
|
|
140
235
|
|
|
236
|
+
/**
|
|
237
|
+
* @template {string} T
|
|
238
|
+
* @param {readonly T[]} keys
|
|
239
|
+
* @param {Record<string, Detector>} detectors
|
|
240
|
+
* @returns {Promise<T[]>}
|
|
241
|
+
*/
|
|
242
|
+
async function detectTargets(keys, detectors) {
|
|
243
|
+
const detectionResults = await Promise.all(
|
|
244
|
+
keys.map(async (name) => ({
|
|
245
|
+
name,
|
|
246
|
+
detected: await (detectors[name]?.() ?? false),
|
|
247
|
+
}))
|
|
248
|
+
);
|
|
249
|
+
return detectionResults.filter((entry) => entry.detected).map((entry) => entry.name);
|
|
250
|
+
}
|
|
251
|
+
|
|
141
252
|
/**
|
|
142
253
|
* Detect which MCP clients are installed on this machine.
|
|
143
254
|
*
|
|
144
|
-
* @param {Record<string,
|
|
145
|
-
* @returns {McpClientName[]}
|
|
255
|
+
* @param {Record<string, Detector>} [detectors=DETECTORS]
|
|
256
|
+
* @returns {Promise<McpClientName[]>}
|
|
146
257
|
*/
|
|
147
|
-
export function detectMcpClients(detectors = DETECTORS) {
|
|
148
|
-
return MCP_CLIENT_KEYS
|
|
258
|
+
export async function detectMcpClients(detectors = DETECTORS) {
|
|
259
|
+
return detectTargets(MCP_CLIENT_KEYS, detectors);
|
|
149
260
|
}
|
|
150
261
|
|
|
151
262
|
/**
|
|
152
263
|
* Detect which skill targets are installed on this machine.
|
|
153
264
|
* Always includes 'agents' as a generic fallback.
|
|
154
265
|
*
|
|
155
|
-
* @param {Record<string,
|
|
156
|
-
* @returns {SupportedTarget[]}
|
|
266
|
+
* @param {Record<string, Detector>} [detectors=DETECTORS]
|
|
267
|
+
* @returns {Promise<SupportedTarget[]>}
|
|
157
268
|
*/
|
|
158
|
-
export function detectSkillTargets(detectors = DETECTORS) {
|
|
269
|
+
export async function detectSkillTargets(detectors = DETECTORS) {
|
|
159
270
|
/** @type {SupportedTarget[]} */
|
|
160
|
-
const detected = SKILL_TARGET_KEYS
|
|
271
|
+
const detected = await detectTargets(SKILL_TARGET_KEYS, detectors);
|
|
161
272
|
detected.push('agents');
|
|
162
273
|
return detected;
|
|
163
274
|
}
|
|
@@ -157,6 +157,8 @@ export function parseInstallAgentArgs(args, cwd = process.cwd()) {
|
|
|
157
157
|
export async function installAgentFiles(options) {
|
|
158
158
|
/** @type {string[]} */
|
|
159
159
|
const created = [];
|
|
160
|
+
/** @type {Array<{ path: string, existedBefore: boolean }>} */
|
|
161
|
+
const attempted = [];
|
|
160
162
|
/** @type {Set<string>} */
|
|
161
163
|
const seenTargets = new Set();
|
|
162
164
|
|
|
@@ -168,7 +170,14 @@ export async function installAgentFiles(options) {
|
|
|
168
170
|
continue;
|
|
169
171
|
}
|
|
170
172
|
seenTargets.add(skillTargetDir);
|
|
171
|
-
await
|
|
173
|
+
const existedBefore = await pathExists(skillTargetDir);
|
|
174
|
+
attempted.push({ path: skillTargetDir, existedBefore });
|
|
175
|
+
try {
|
|
176
|
+
await installManagedSkill(skillName, target, skillTargetDir);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
await rollbackInstalledSkillDirs(attempted);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
172
181
|
created.push(skillTargetDir);
|
|
173
182
|
}
|
|
174
183
|
}
|
|
@@ -176,6 +185,20 @@ export async function installAgentFiles(options) {
|
|
|
176
185
|
return created;
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Remove any newly created managed skill directories after a failed install so a
|
|
190
|
+
* later retry starts cleanly. Pre-existing directories are left untouched.
|
|
191
|
+
*
|
|
192
|
+
* @param {Array<{ path: string, existedBefore: boolean }>} attempted
|
|
193
|
+
* @returns {Promise<void>}
|
|
194
|
+
*/
|
|
195
|
+
async function rollbackInstalledSkillDirs(attempted) {
|
|
196
|
+
const rollbackPaths = attempted
|
|
197
|
+
.filter((entry) => !entry.existedBefore)
|
|
198
|
+
.map((entry) => fs.promises.rm(entry.path, { recursive: true, force: true }));
|
|
199
|
+
await Promise.allSettled(rollbackPaths);
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
/**
|
|
180
203
|
* Write MCP config for the given clients.
|
|
181
204
|
*
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {'codex' | 'claude' | 'cursor' | 'copilot' | 'opencode' | 'antigravity' | 'windsurf' | 'agents'} McpClientName
|
|
@@ -41,6 +42,8 @@ export function isMcpClientName(value) {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const BROWSER_BRIDGE_SERVER_NAME = 'browser-bridge';
|
|
45
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
46
|
+
const mcpServerBinPath = path.join(packageRoot, 'packages', 'mcp-server', 'src', 'bin.js');
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* @returns {string}
|
|
@@ -93,17 +96,29 @@ export function getMcpConfigShape(clientName) {
|
|
|
93
96
|
* }}
|
|
94
97
|
*/
|
|
95
98
|
function createBaseServerConfig(clientName) {
|
|
99
|
+
const windowsCommand =
|
|
100
|
+
process.platform === 'win32'
|
|
101
|
+
? {
|
|
102
|
+
command: process.execPath,
|
|
103
|
+
args: [mcpServerBinPath],
|
|
104
|
+
env: {},
|
|
105
|
+
}
|
|
106
|
+
: {
|
|
107
|
+
command: 'bbx',
|
|
108
|
+
args: ['mcp', 'serve'],
|
|
109
|
+
env: {},
|
|
110
|
+
};
|
|
111
|
+
|
|
96
112
|
if (clientName === 'opencode') {
|
|
97
113
|
return {
|
|
98
114
|
type: 'local',
|
|
99
|
-
command:
|
|
115
|
+
command:
|
|
116
|
+
process.platform === 'win32'
|
|
117
|
+
? [process.execPath, mcpServerBinPath]
|
|
118
|
+
: ['bbx', 'mcp', 'serve'],
|
|
100
119
|
};
|
|
101
120
|
}
|
|
102
|
-
return
|
|
103
|
-
command: 'bbx',
|
|
104
|
-
args: ['mcp', 'serve'],
|
|
105
|
-
env: {},
|
|
106
|
-
};
|
|
121
|
+
return windowsCommand;
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
/** @type {Record<McpClientName, { key: string, includeType: boolean, legacyKeys?: string[], keepEmptyBlock?: boolean }>} */
|
|
@@ -129,11 +144,13 @@ const MCP_CONFIG_SHAPES = {
|
|
|
129
144
|
*/
|
|
130
145
|
export function buildMcpConfig(clientName) {
|
|
131
146
|
if (clientName === 'codex') {
|
|
147
|
+
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
148
|
+
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
132
149
|
return {
|
|
133
150
|
mcp_servers: {
|
|
134
151
|
[BROWSER_BRIDGE_SERVER_NAME]: {
|
|
135
|
-
command
|
|
136
|
-
args
|
|
152
|
+
command,
|
|
153
|
+
args,
|
|
137
154
|
},
|
|
138
155
|
},
|
|
139
156
|
};
|
|
@@ -262,10 +279,12 @@ export async function getMcpConfigPaths(clientName, options) {
|
|
|
262
279
|
* @returns {string}
|
|
263
280
|
*/
|
|
264
281
|
function formatCodexServerBlock() {
|
|
282
|
+
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
283
|
+
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
265
284
|
return [
|
|
266
285
|
`[mcp_servers."${BROWSER_BRIDGE_SERVER_NAME}"]`,
|
|
267
|
-
|
|
268
|
-
|
|
286
|
+
`command = ${JSON.stringify(command)}`,
|
|
287
|
+
`args = ${JSON.stringify(args)}`,
|
|
269
288
|
'',
|
|
270
289
|
].join('\n');
|
|
271
290
|
}
|
|
@@ -57,8 +57,8 @@ const SKILL_TARGET_LABELS = {
|
|
|
57
57
|
* global?: boolean,
|
|
58
58
|
* cwd?: string,
|
|
59
59
|
* projectPath?: string,
|
|
60
|
-
* mcpDetectors?: Record<string, () => boolean
|
|
61
|
-
* skillDetectors?: Record<string, () => boolean
|
|
60
|
+
* mcpDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
61
|
+
* skillDetectors?: Record<string, () => boolean | Promise<boolean>>,
|
|
62
62
|
* access?: (targetPath: string) => Promise<void>,
|
|
63
63
|
* readFile?: (targetPath: string, encoding: BufferEncoding) => Promise<string>
|
|
64
64
|
* }} SetupStatusOptions
|
|
@@ -76,8 +76,16 @@ export async function collectSetupStatus(options = {}) {
|
|
|
76
76
|
const projectPath = options.projectPath || cwd;
|
|
77
77
|
const access = options.access || fs.promises.access.bind(fs.promises);
|
|
78
78
|
const readFile = options.readFile || fs.promises.readFile.bind(fs.promises);
|
|
79
|
-
const
|
|
80
|
-
|
|
79
|
+
const [detectedMcpClientResult, detectedSkillTargetResult] = await Promise.allSettled([
|
|
80
|
+
detectMcpClients(options.mcpDetectors),
|
|
81
|
+
detectSkillTargets(options.skillDetectors),
|
|
82
|
+
]);
|
|
83
|
+
const detectedMcpClientNames =
|
|
84
|
+
detectedMcpClientResult.status === 'fulfilled' ? detectedMcpClientResult.value : [];
|
|
85
|
+
const detectedSkillTargetNames =
|
|
86
|
+
detectedSkillTargetResult.status === 'fulfilled' ? detectedSkillTargetResult.value : [];
|
|
87
|
+
const detectedMcpClients = new Set(detectedMcpClientNames);
|
|
88
|
+
const detectedSkillTargets = new Set(detectedSkillTargetNames);
|
|
81
89
|
for (const clientName of detectedMcpClients) {
|
|
82
90
|
if (SUPPORTED_TARGETS.includes(/** @type {SupportedTarget} */ (clientName))) {
|
|
83
91
|
detectedSkillTargets.add(/** @type {SupportedTarget} */ (clientName));
|
|
@@ -1,10 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
4
7
|
import { startBridgeMcpServer } from './server.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* start?: () => Promise<void>,
|
|
12
|
+
* argv?: string[],
|
|
13
|
+
* stdout?: { write: (chunk: string) => unknown },
|
|
14
|
+
* stderr?: { write: (chunk: string) => unknown },
|
|
15
|
+
* exit?: (code: number) => unknown,
|
|
16
|
+
* }} BridgeMcpCliOptions
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const HELP_FLAGS = new Set(['help', '--help', '-h']);
|
|
20
|
+
|
|
21
|
+
const MCP_USAGE = [
|
|
22
|
+
'Usage: bbx-mcp [--help]',
|
|
23
|
+
'',
|
|
24
|
+
'Start the Browser Bridge MCP stdio server.',
|
|
25
|
+
].join('\n');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start the MCP server CLI and report startup failures to stderr.
|
|
29
|
+
*
|
|
30
|
+
* @param {BridgeMcpCliOptions} [options]
|
|
31
|
+
* @returns {Promise<number>}
|
|
32
|
+
*/
|
|
33
|
+
export async function runBridgeMcpCli(options = {}) {
|
|
34
|
+
const {
|
|
35
|
+
start = startBridgeMcpServer,
|
|
36
|
+
argv = process.argv.slice(2),
|
|
37
|
+
stdout = process.stdout,
|
|
38
|
+
stderr = process.stderr,
|
|
39
|
+
exit = process.exit,
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
if (argv.some((arg) => HELP_FLAGS.has(arg))) {
|
|
43
|
+
stdout.write(`${MCP_USAGE}\n`);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await start();
|
|
49
|
+
return 0;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
52
|
+
stderr.write(`${message}\n`);
|
|
53
|
+
exit(1);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const entryPointUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
|
|
59
|
+
|
|
60
|
+
if (entryPointUrl === import.meta.url) {
|
|
61
|
+
void runBridgeMcpCli();
|
|
62
|
+
}
|