@browserbridge/bbx 1.0.0 → 1.1.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 +6 -4
- package/package.json +53 -53
- package/packages/agent-client/src/cli-helpers.js +43 -5
- package/packages/agent-client/src/cli.js +176 -171
- package/packages/agent-client/src/client.js +66 -21
- package/packages/agent-client/src/command-registry.js +104 -69
- package/packages/agent-client/src/detect.js +162 -54
- package/packages/agent-client/src/install.js +34 -28
- package/packages/agent-client/src/mcp-config.js +40 -40
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +23 -30
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers.js +573 -256
- package/packages/mcp-server/src/server.js +568 -257
- package/packages/native-host/bin/bridge-daemon.js +39 -6
- package/packages/native-host/bin/install-manifest.js +26 -4
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +142 -13
- package/packages/native-host/src/daemon-process.js +396 -0
- package/packages/native-host/src/daemon.js +350 -150
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +194 -29
- package/packages/native-host/src/native-host.js +154 -102
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +6 -3
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +15 -11
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +242 -73
- package/packages/protocol/src/registry.js +311 -45
- package/packages/protocol/src/summary.js +260 -109
- package/packages/protocol/src/types.js +29 -4
- package/skills/browser-bridge/SKILL.md +3 -2
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +34 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +127 -71
- 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/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 -114
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -52
- 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 -459
- package/packages/extension/src/background-routing.js +0 -91
- package/packages/extension/src/background.js +0 -3227
- package/packages/extension/src/content-script-helpers.js +0 -281
- package/packages/extension/src/content-script.js +0 -1977
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -102
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -279
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1854
- package/packages/extension/ui/ui.css +0 -1159
|
@@ -1,23 +1,39 @@
|
|
|
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}
|
|
16
33
|
*/
|
|
17
34
|
function getVsCodeUserDataDir() {
|
|
18
35
|
if (platform === 'win32') {
|
|
19
|
-
const appData =
|
|
20
|
-
process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
36
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
21
37
|
return path.join(appData, 'Code');
|
|
22
38
|
}
|
|
23
39
|
if (platform === 'linux') {
|
|
@@ -27,12 +43,12 @@ function getVsCodeUserDataDir() {
|
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
/**
|
|
30
|
-
* @param {string}
|
|
31
|
-
* @returns {boolean}
|
|
46
|
+
* @param {string} targetPath
|
|
47
|
+
* @returns {Promise<boolean>}
|
|
32
48
|
*/
|
|
33
|
-
function fsExists(
|
|
49
|
+
async function fsExists(targetPath) {
|
|
34
50
|
try {
|
|
35
|
-
fs.
|
|
51
|
+
await fs.promises.access(targetPath, fs.constants.F_OK);
|
|
36
52
|
return true;
|
|
37
53
|
} catch {
|
|
38
54
|
return false;
|
|
@@ -40,75 +56,151 @@ function fsExists(p) {
|
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
/**
|
|
43
|
-
* @param {string}
|
|
44
|
-
* @returns {
|
|
59
|
+
* @param {string} command
|
|
60
|
+
* @returns {string}
|
|
45
61
|
*/
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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;
|
|
54
78
|
}
|
|
79
|
+
return entryName.slice(0, -extension.length).toLowerCase();
|
|
55
80
|
}
|
|
56
81
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
155
|
+
if (platform === 'darwin') return fsExists('/Applications/Visual Studio Code.app');
|
|
63
156
|
if (platform === 'win32') {
|
|
64
|
-
const localAppData =
|
|
65
|
-
process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
157
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
66
158
|
return fsExists(path.join(localAppData, 'Programs', 'Microsoft VS Code'));
|
|
67
159
|
}
|
|
68
160
|
return commandExists('code');
|
|
69
161
|
}
|
|
70
162
|
|
|
71
|
-
/** @returns {boolean} */
|
|
72
|
-
function detectCursor() {
|
|
73
|
-
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;
|
|
74
166
|
if (platform === 'darwin') return fsExists('/Applications/Cursor.app');
|
|
75
167
|
return commandExists('cursor');
|
|
76
168
|
}
|
|
77
169
|
|
|
78
|
-
/** @returns {boolean} */
|
|
79
|
-
function detectWindsurf() {
|
|
80
|
-
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;
|
|
81
173
|
if (platform === 'darwin') return fsExists('/Applications/Windsurf.app');
|
|
82
174
|
return commandExists('windsurf');
|
|
83
175
|
}
|
|
84
176
|
|
|
85
|
-
/** @returns {boolean} */
|
|
86
|
-
function detectClaude() {
|
|
87
|
-
if (fsExists(path.join(home, '.claude'))) return true;
|
|
88
|
-
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;
|
|
89
181
|
return commandExists('claude');
|
|
90
182
|
}
|
|
91
183
|
|
|
92
|
-
/** @returns {boolean} */
|
|
93
|
-
function detectCodex() {
|
|
94
|
-
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;
|
|
95
187
|
return commandExists('codex');
|
|
96
188
|
}
|
|
97
189
|
|
|
98
|
-
/** @returns {boolean} */
|
|
99
|
-
function detectOpencode() {
|
|
100
|
-
if (fsExists(path.join(home, '.config', 'opencode'))) return true;
|
|
101
|
-
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;
|
|
102
194
|
return commandExists('opencode');
|
|
103
195
|
}
|
|
104
196
|
|
|
105
|
-
/** @returns {boolean} */
|
|
106
|
-
function detectAntigravity() {
|
|
107
|
-
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;
|
|
108
200
|
return commandExists('agy');
|
|
109
201
|
}
|
|
110
202
|
|
|
111
|
-
/** @type {Record<string,
|
|
203
|
+
/** @type {Record<string, Detector>} */
|
|
112
204
|
const DETECTORS = {
|
|
113
205
|
codex: detectCodex,
|
|
114
206
|
claude: detectClaude,
|
|
@@ -141,26 +233,42 @@ const SKILL_TARGET_KEYS = [
|
|
|
141
233
|
'windsurf',
|
|
142
234
|
];
|
|
143
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
|
+
|
|
144
252
|
/**
|
|
145
253
|
* Detect which MCP clients are installed on this machine.
|
|
146
254
|
*
|
|
147
|
-
* @param {Record<string,
|
|
148
|
-
* @returns {McpClientName[]}
|
|
255
|
+
* @param {Record<string, Detector>} [detectors=DETECTORS]
|
|
256
|
+
* @returns {Promise<McpClientName[]>}
|
|
149
257
|
*/
|
|
150
|
-
export function detectMcpClients(detectors = DETECTORS) {
|
|
151
|
-
return MCP_CLIENT_KEYS
|
|
258
|
+
export async function detectMcpClients(detectors = DETECTORS) {
|
|
259
|
+
return detectTargets(MCP_CLIENT_KEYS, detectors);
|
|
152
260
|
}
|
|
153
261
|
|
|
154
262
|
/**
|
|
155
263
|
* Detect which skill targets are installed on this machine.
|
|
156
264
|
* Always includes 'agents' as a generic fallback.
|
|
157
265
|
*
|
|
158
|
-
* @param {Record<string,
|
|
159
|
-
* @returns {SupportedTarget[]}
|
|
266
|
+
* @param {Record<string, Detector>} [detectors=DETECTORS]
|
|
267
|
+
* @returns {Promise<SupportedTarget[]>}
|
|
160
268
|
*/
|
|
161
|
-
export function detectSkillTargets(detectors = DETECTORS) {
|
|
269
|
+
export async function detectSkillTargets(detectors = DETECTORS) {
|
|
162
270
|
/** @type {SupportedTarget[]} */
|
|
163
|
-
const detected = SKILL_TARGET_KEYS
|
|
271
|
+
const detected = await detectTargets(SKILL_TARGET_KEYS, detectors);
|
|
164
272
|
detected.push('agents');
|
|
165
273
|
return detected;
|
|
166
274
|
}
|
|
@@ -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;
|
|
@@ -163,6 +157,8 @@ export function parseInstallAgentArgs(args, cwd = process.cwd()) {
|
|
|
163
157
|
export async function installAgentFiles(options) {
|
|
164
158
|
/** @type {string[]} */
|
|
165
159
|
const created = [];
|
|
160
|
+
/** @type {Array<{ path: string, existedBefore: boolean }>} */
|
|
161
|
+
const attempted = [];
|
|
166
162
|
/** @type {Set<string>} */
|
|
167
163
|
const seenTargets = new Set();
|
|
168
164
|
|
|
@@ -174,7 +170,14 @@ export async function installAgentFiles(options) {
|
|
|
174
170
|
continue;
|
|
175
171
|
}
|
|
176
172
|
seenTargets.add(skillTargetDir);
|
|
177
|
-
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
|
+
}
|
|
178
181
|
created.push(skillTargetDir);
|
|
179
182
|
}
|
|
180
183
|
}
|
|
@@ -182,6 +185,20 @@ export async function installAgentFiles(options) {
|
|
|
182
185
|
return created;
|
|
183
186
|
}
|
|
184
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
|
+
|
|
185
202
|
/**
|
|
186
203
|
* Write MCP config for the given clients.
|
|
187
204
|
*
|
|
@@ -204,7 +221,7 @@ export async function installMcpClientSetup(clients, options) {
|
|
|
204
221
|
global: options.global,
|
|
205
222
|
cwd: options.projectPath,
|
|
206
223
|
stdout: options.stdout,
|
|
207
|
-
})
|
|
224
|
+
})
|
|
208
225
|
);
|
|
209
226
|
}
|
|
210
227
|
|
|
@@ -289,7 +306,7 @@ function parseTargetList(raw) {
|
|
|
289
306
|
);
|
|
290
307
|
if (!canonical) {
|
|
291
308
|
throw new Error(
|
|
292
|
-
`Unknown install-skill target "${value}". Supported targets: ${supportedTargets.join(', ')}, all. Aliases: openai -> codex, google -> antigravity
|
|
309
|
+
`Unknown install-skill target "${value}". Supported targets: ${supportedTargets.join(', ')}, all. Aliases: openai -> codex, google -> antigravity.`
|
|
293
310
|
);
|
|
294
311
|
}
|
|
295
312
|
parsed.add(canonical);
|
|
@@ -380,7 +397,7 @@ export function formatManagedSkillSentinel(skillName) {
|
|
|
380
397
|
version: managedPackageVersion,
|
|
381
398
|
},
|
|
382
399
|
null,
|
|
383
|
-
2
|
|
400
|
+
2
|
|
384
401
|
)}\n`;
|
|
385
402
|
}
|
|
386
403
|
|
|
@@ -414,10 +431,7 @@ export function parseManagedSkillSentinel(raw) {
|
|
|
414
431
|
* @param {string | null} [currentVersion=managedPackageVersion]
|
|
415
432
|
* @returns {boolean}
|
|
416
433
|
*/
|
|
417
|
-
export function isManagedVersionOutdated(
|
|
418
|
-
installedVersion,
|
|
419
|
-
currentVersion = managedPackageVersion,
|
|
420
|
-
) {
|
|
434
|
+
export function isManagedVersionOutdated(installedVersion, currentVersion = managedPackageVersion) {
|
|
421
435
|
if (!currentVersion) {
|
|
422
436
|
return false;
|
|
423
437
|
}
|
|
@@ -439,19 +453,13 @@ async function installManagedSkill(skillName, target, targetDir) {
|
|
|
439
453
|
const targetExists = await pathExists(targetDir);
|
|
440
454
|
|
|
441
455
|
if (targetExists && !(await pathExists(sentinelPath))) {
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Refusing to overwrite unmanaged skill directory: ${targetDir}`,
|
|
444
|
-
);
|
|
456
|
+
throw new Error(`Refusing to overwrite unmanaged skill directory: ${targetDir}`);
|
|
445
457
|
}
|
|
446
458
|
|
|
447
459
|
await fs.promises.rm(targetDir, { recursive: true, force: true });
|
|
448
460
|
await copyDir(sourceDir, targetDir);
|
|
449
461
|
await applyManagedSkillPatches(skillName, target, targetDir);
|
|
450
|
-
await fs.promises.writeFile(
|
|
451
|
-
sentinelPath,
|
|
452
|
-
formatManagedSkillSentinel(skillName),
|
|
453
|
-
'utf8',
|
|
454
|
-
);
|
|
462
|
+
await fs.promises.writeFile(sentinelPath, formatManagedSkillSentinel(skillName), 'utf8');
|
|
455
463
|
}
|
|
456
464
|
|
|
457
465
|
/**
|
|
@@ -532,9 +540,7 @@ async function applyManagedSkillPatches(skillName, target, targetDir) {
|
|
|
532
540
|
*/
|
|
533
541
|
function loadPackageManifest() {
|
|
534
542
|
try {
|
|
535
|
-
return JSON.parse(
|
|
536
|
-
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
537
|
-
);
|
|
543
|
+
return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
538
544
|
} catch {
|
|
539
545
|
return {};
|
|
540
546
|
}
|
|
@@ -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}
|
|
@@ -48,8 +51,7 @@ const BROWSER_BRIDGE_SERVER_NAME = 'browser-bridge';
|
|
|
48
51
|
function getVsCodeUserDataDir() {
|
|
49
52
|
const home = os.homedir();
|
|
50
53
|
if (process.platform === 'win32') {
|
|
51
|
-
const appData =
|
|
52
|
-
process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
54
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
53
55
|
return path.join(appData, 'Code');
|
|
54
56
|
}
|
|
55
57
|
if (process.platform === 'linux') {
|
|
@@ -79,9 +81,7 @@ function getLegacyCopilotVsCodeConfigPath() {
|
|
|
79
81
|
* @returns {{ key: string, includeType: boolean, legacyKeys?: string[], keepEmptyBlock?: boolean }}
|
|
80
82
|
*/
|
|
81
83
|
export function getMcpConfigShape(clientName) {
|
|
82
|
-
return
|
|
83
|
-
MCP_CONFIG_SHAPES[clientName] ?? { key: 'mcpServers', includeType: false }
|
|
84
|
-
);
|
|
84
|
+
return MCP_CONFIG_SHAPES[clientName] ?? { key: 'mcpServers', includeType: false };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -96,17 +96,29 @@ export function getMcpConfigShape(clientName) {
|
|
|
96
96
|
* }}
|
|
97
97
|
*/
|
|
98
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
|
+
|
|
99
112
|
if (clientName === 'opencode') {
|
|
100
113
|
return {
|
|
101
114
|
type: 'local',
|
|
102
|
-
command:
|
|
115
|
+
command:
|
|
116
|
+
process.platform === 'win32'
|
|
117
|
+
? [process.execPath, mcpServerBinPath]
|
|
118
|
+
: ['bbx', 'mcp', 'serve'],
|
|
103
119
|
};
|
|
104
120
|
}
|
|
105
|
-
return
|
|
106
|
-
command: 'bbx',
|
|
107
|
-
args: ['mcp', 'serve'],
|
|
108
|
-
env: {},
|
|
109
|
-
};
|
|
121
|
+
return windowsCommand;
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
/** @type {Record<McpClientName, { key: string, includeType: boolean, legacyKeys?: string[], keepEmptyBlock?: boolean }>} */
|
|
@@ -132,20 +144,20 @@ const MCP_CONFIG_SHAPES = {
|
|
|
132
144
|
*/
|
|
133
145
|
export function buildMcpConfig(clientName) {
|
|
134
146
|
if (clientName === 'codex') {
|
|
147
|
+
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
148
|
+
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
135
149
|
return {
|
|
136
150
|
mcp_servers: {
|
|
137
151
|
[BROWSER_BRIDGE_SERVER_NAME]: {
|
|
138
|
-
command
|
|
139
|
-
args
|
|
152
|
+
command,
|
|
153
|
+
args,
|
|
140
154
|
},
|
|
141
155
|
},
|
|
142
156
|
};
|
|
143
157
|
}
|
|
144
158
|
const serverConfig = createBaseServerConfig(clientName);
|
|
145
159
|
const shape = getMcpConfigShape(clientName);
|
|
146
|
-
const entry = shape.includeType
|
|
147
|
-
? { type: 'stdio', ...serverConfig }
|
|
148
|
-
: serverConfig;
|
|
160
|
+
const entry = shape.includeType ? { type: 'stdio', ...serverConfig } : serverConfig;
|
|
149
161
|
return { [shape.key]: { [BROWSER_BRIDGE_SERVER_NAME]: entry } };
|
|
150
162
|
}
|
|
151
163
|
|
|
@@ -169,10 +181,7 @@ export function formatMcpConfig(clientName) {
|
|
|
169
181
|
* @param {{ global: boolean, cwd?: string }} options
|
|
170
182
|
* @returns {string}
|
|
171
183
|
*/
|
|
172
|
-
export function getMcpConfigPath(
|
|
173
|
-
clientName,
|
|
174
|
-
{ global: isGlobal, cwd = process.cwd() },
|
|
175
|
-
) {
|
|
184
|
+
export function getMcpConfigPath(clientName, { global: isGlobal, cwd = process.cwd() }) {
|
|
176
185
|
const home = os.homedir();
|
|
177
186
|
|
|
178
187
|
if (!isGlobal) {
|
|
@@ -252,7 +261,9 @@ export async function getMcpConfigPaths(clientName, options) {
|
|
|
252
261
|
const entries = await readdir(profilesDir, { withFileTypes: true });
|
|
253
262
|
const profilePaths = entries
|
|
254
263
|
.filter((/** @type {import('node:fs').Dirent} */ entry) => entry.isDirectory())
|
|
255
|
-
.map((/** @type {import('node:fs').Dirent} */ entry) =>
|
|
264
|
+
.map((/** @type {import('node:fs').Dirent} */ entry) =>
|
|
265
|
+
path.join(profilesDir, entry.name, 'mcp.json')
|
|
266
|
+
);
|
|
256
267
|
for (const profilePath of profilePaths) {
|
|
257
268
|
if (!paths.includes(profilePath)) {
|
|
258
269
|
paths.push(profilePath);
|
|
@@ -268,10 +279,12 @@ export async function getMcpConfigPaths(clientName, options) {
|
|
|
268
279
|
* @returns {string}
|
|
269
280
|
*/
|
|
270
281
|
function formatCodexServerBlock() {
|
|
282
|
+
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
283
|
+
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
271
284
|
return [
|
|
272
285
|
`[mcp_servers."${BROWSER_BRIDGE_SERVER_NAME}"]`,
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
`command = ${JSON.stringify(command)}`,
|
|
287
|
+
`args = ${JSON.stringify(args)}`,
|
|
275
288
|
'',
|
|
276
289
|
].join('\n');
|
|
277
290
|
}
|
|
@@ -533,11 +546,7 @@ async function installJsonMcpConfig(clientName, configPath, stdout) {
|
|
|
533
546
|
}
|
|
534
547
|
|
|
535
548
|
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
|
-
);
|
|
549
|
+
await fs.promises.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
541
550
|
stdout.write(`Wrote ${configPath}\n`);
|
|
542
551
|
}
|
|
543
552
|
|
|
@@ -600,11 +609,7 @@ async function removeJsonMcpConfig(clientName, configPath, stdout) {
|
|
|
600
609
|
}
|
|
601
610
|
|
|
602
611
|
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
|
-
);
|
|
612
|
+
await fs.promises.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
608
613
|
stdout.write(`Removed ${configPath}\n`);
|
|
609
614
|
return true;
|
|
610
615
|
}
|
|
@@ -647,16 +652,11 @@ export function parseInstalledMcpConfig(clientName, raw) {
|
|
|
647
652
|
const parsed = JSON.parse(raw);
|
|
648
653
|
const block =
|
|
649
654
|
parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
650
|
-
? getMergedJsonMcpBlock(
|
|
651
|
-
/** @type {Record<string, unknown>} */ (parsed),
|
|
652
|
-
clientName,
|
|
653
|
-
)
|
|
655
|
+
? getMergedJsonMcpBlock(/** @type {Record<string, unknown>} */ (parsed), clientName)
|
|
654
656
|
: {};
|
|
655
657
|
return {
|
|
656
658
|
configured: Boolean(
|
|
657
|
-
block &&
|
|
658
|
-
typeof block === 'object' &&
|
|
659
|
-
Object.hasOwn(block, BROWSER_BRIDGE_SERVER_NAME),
|
|
659
|
+
block && typeof block === 'object' && Object.hasOwn(block, BROWSER_BRIDGE_SERVER_NAME)
|
|
660
660
|
),
|
|
661
661
|
};
|
|
662
662
|
} catch {
|