@btraut/browser-bridge 0.2.0 → 0.4.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/CHANGELOG.md +51 -0
- package/README.md +37 -7
- package/dist/api.js +1652 -1528
- package/dist/api.js.map +4 -4
- package/dist/index.js +636 -22
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +96 -2
- package/extension/dist/background.js.map +3 -3
- package/package.json +4 -2
- package/skills/browser-bridge/SKILL.md +12 -0
- package/skills/browser-bridge/skill.json +4 -0
package/dist/index.js
CHANGED
|
@@ -142,6 +142,11 @@ var DiagnosticReportSchema = import_zod2.z.object({
|
|
|
142
142
|
ok: import_zod2.z.boolean(),
|
|
143
143
|
session_id: import_zod2.z.string().optional(),
|
|
144
144
|
checks: import_zod2.z.array(DiagnosticCheckSchema).optional(),
|
|
145
|
+
sessions: import_zod2.z.object({
|
|
146
|
+
count: import_zod2.z.number().finite().optional(),
|
|
147
|
+
max_age_ms: import_zod2.z.number().finite().optional(),
|
|
148
|
+
max_idle_ms: import_zod2.z.number().finite().optional()
|
|
149
|
+
}).optional(),
|
|
145
150
|
extension: import_zod2.z.object({
|
|
146
151
|
connected: import_zod2.z.boolean().optional(),
|
|
147
152
|
version: import_zod2.z.string().optional(),
|
|
@@ -673,7 +678,7 @@ var resolveTimeoutMs = (timeoutMs) => {
|
|
|
673
678
|
}
|
|
674
679
|
return Math.floor(parsed);
|
|
675
680
|
};
|
|
676
|
-
var normalizePath = (
|
|
681
|
+
var normalizePath = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
|
|
677
682
|
var createCoreClient = (options = {}) => {
|
|
678
683
|
const host = resolveHost(options.host);
|
|
679
684
|
const port = resolvePort(options.port);
|
|
@@ -682,11 +687,11 @@ var createCoreClient = (options = {}) => {
|
|
|
682
687
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
683
688
|
const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
|
|
684
689
|
const ensureDaemon = options.ensureDaemon ?? true;
|
|
685
|
-
const requestJson = async (method,
|
|
690
|
+
const requestJson = async (method, path9, body) => {
|
|
686
691
|
const controller = new AbortController();
|
|
687
692
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
688
693
|
try {
|
|
689
|
-
const response = await fetchImpl(`${baseUrl}${normalizePath(
|
|
694
|
+
const response = await fetchImpl(`${baseUrl}${normalizePath(path9)}`, {
|
|
690
695
|
method,
|
|
691
696
|
headers: {
|
|
692
697
|
"content-type": "application/json"
|
|
@@ -774,9 +779,9 @@ startCoreServer({ host: ${JSON.stringify(
|
|
|
774
779
|
}
|
|
775
780
|
await ensurePromise;
|
|
776
781
|
};
|
|
777
|
-
const post = async (
|
|
782
|
+
const post = async (path9, body) => {
|
|
778
783
|
await ensureReady();
|
|
779
|
-
return requestJson("POST",
|
|
784
|
+
return requestJson("POST", path9, body);
|
|
780
785
|
};
|
|
781
786
|
return { baseUrl, ensureReady, post };
|
|
782
787
|
};
|
|
@@ -1365,18 +1370,18 @@ var resolvePort2 = (port) => {
|
|
|
1365
1370
|
}
|
|
1366
1371
|
return parsed;
|
|
1367
1372
|
};
|
|
1368
|
-
var normalizePath2 = (
|
|
1373
|
+
var normalizePath2 = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
|
|
1369
1374
|
var createCoreClient2 = (options = {}) => {
|
|
1370
1375
|
const host = resolveHost2(options.host);
|
|
1371
1376
|
const port = resolvePort2(options.port);
|
|
1372
1377
|
const baseUrl = `http://${host}:${port}`;
|
|
1373
1378
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
1374
1379
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1375
|
-
const requestJson = async (
|
|
1380
|
+
const requestJson = async (path9, body) => {
|
|
1376
1381
|
const controller = new AbortController();
|
|
1377
1382
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1378
1383
|
try {
|
|
1379
|
-
const response = await fetchImpl(`${baseUrl}${normalizePath2(
|
|
1384
|
+
const response = await fetchImpl(`${baseUrl}${normalizePath2(path9)}`, {
|
|
1380
1385
|
method: "POST",
|
|
1381
1386
|
headers: {
|
|
1382
1387
|
"content-type": "application/json"
|
|
@@ -1398,8 +1403,8 @@ var createCoreClient2 = (options = {}) => {
|
|
|
1398
1403
|
clearTimeout(timeout);
|
|
1399
1404
|
}
|
|
1400
1405
|
};
|
|
1401
|
-
const post = async (
|
|
1402
|
-
return requestJson(
|
|
1406
|
+
const post = async (path9, body) => {
|
|
1407
|
+
return requestJson(path9, body);
|
|
1403
1408
|
};
|
|
1404
1409
|
return { baseUrl, post };
|
|
1405
1410
|
};
|
|
@@ -1837,9 +1842,186 @@ var startMcpServer = async (options = {}) => {
|
|
|
1837
1842
|
return { ...handle, transport };
|
|
1838
1843
|
};
|
|
1839
1844
|
|
|
1845
|
+
// packages/cli/src/tui.ts
|
|
1846
|
+
var requireTty = () => {
|
|
1847
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1848
|
+
throw new Error("Interactive install requires a TTY.");
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
var checkboxPrompt = async (options) => {
|
|
1852
|
+
const mod = await import("@inquirer/prompts");
|
|
1853
|
+
const { checkbox } = mod;
|
|
1854
|
+
const values = await checkbox({
|
|
1855
|
+
message: options.message,
|
|
1856
|
+
// Avoid wrapping from bottom -> top (and top -> bottom) when navigating long lists.
|
|
1857
|
+
loop: false,
|
|
1858
|
+
choices: options.choices.map((c) => ({
|
|
1859
|
+
value: c.value,
|
|
1860
|
+
name: c.label,
|
|
1861
|
+
checked: c.checked,
|
|
1862
|
+
disabled: c.disabled
|
|
1863
|
+
}))
|
|
1864
|
+
});
|
|
1865
|
+
return values;
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1868
|
+
// packages/cli/src/installer/mcp-install.ts
|
|
1869
|
+
var import_node_child_process2 = require("node:child_process");
|
|
1870
|
+
|
|
1871
|
+
// packages/cli/src/installer/cursor-mcp.ts
|
|
1872
|
+
var import_promises2 = __toESM(require("node:fs/promises"));
|
|
1873
|
+
var import_node_os = __toESM(require("node:os"));
|
|
1874
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
1875
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1876
|
+
var resolveCursorUserSettingsPath = (options) => {
|
|
1877
|
+
const platform = options?.platform ?? process.platform;
|
|
1878
|
+
const homeDir = options?.homeDir ?? import_node_os.default.homedir();
|
|
1879
|
+
const env = options?.env ?? process.env;
|
|
1880
|
+
if (platform === "darwin") {
|
|
1881
|
+
return import_node_path2.default.join(
|
|
1882
|
+
homeDir,
|
|
1883
|
+
"Library",
|
|
1884
|
+
"Application Support",
|
|
1885
|
+
"Cursor",
|
|
1886
|
+
"User",
|
|
1887
|
+
"settings.json"
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
if (platform === "win32") {
|
|
1891
|
+
const appData = env.APPDATA;
|
|
1892
|
+
if (!appData) {
|
|
1893
|
+
throw new Error(
|
|
1894
|
+
"APPDATA is not set; cannot resolve Cursor settings path."
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
return import_node_path2.default.join(appData, "Cursor", "User", "settings.json");
|
|
1898
|
+
}
|
|
1899
|
+
return import_node_path2.default.join(homeDir, ".config", "Cursor", "User", "settings.json");
|
|
1900
|
+
};
|
|
1901
|
+
var installCursorMcp = async (settingsPath) => {
|
|
1902
|
+
const filePath = settingsPath ?? resolveCursorUserSettingsPath();
|
|
1903
|
+
const dir = import_node_path2.default.dirname(filePath);
|
|
1904
|
+
await import_promises2.default.mkdir(dir, { recursive: true });
|
|
1905
|
+
let settings = {};
|
|
1906
|
+
try {
|
|
1907
|
+
const raw = await import_promises2.default.readFile(filePath, "utf8");
|
|
1908
|
+
const parsed = JSON.parse(raw);
|
|
1909
|
+
if (isObject(parsed)) {
|
|
1910
|
+
settings = parsed;
|
|
1911
|
+
} else {
|
|
1912
|
+
throw new Error("Cursor settings.json is not a JSON object.");
|
|
1913
|
+
}
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
1916
|
+
settings = {};
|
|
1917
|
+
} else {
|
|
1918
|
+
throw error;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const mcp = isObject(settings.mcp) ? settings.mcp : {};
|
|
1922
|
+
const servers = isObject(mcp.servers) ? mcp.servers : {};
|
|
1923
|
+
servers["browser-bridge"] = {
|
|
1924
|
+
command: "browser-bridge",
|
|
1925
|
+
args: ["mcp"]
|
|
1926
|
+
};
|
|
1927
|
+
mcp.servers = servers;
|
|
1928
|
+
settings.mcp = mcp;
|
|
1929
|
+
await import_promises2.default.writeFile(
|
|
1930
|
+
filePath,
|
|
1931
|
+
JSON.stringify(settings, null, 2) + "\n",
|
|
1932
|
+
"utf8"
|
|
1933
|
+
);
|
|
1934
|
+
return { settingsPath: filePath };
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
// packages/cli/src/installer/mcp-install.ts
|
|
1938
|
+
var runQuiet = async (cmd, args) => {
|
|
1939
|
+
await new Promise((resolve2, reject) => {
|
|
1940
|
+
const child = (0, import_node_child_process2.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1941
|
+
let stderr = "";
|
|
1942
|
+
child.stderr?.on("data", (chunk) => {
|
|
1943
|
+
stderr += String(chunk);
|
|
1944
|
+
});
|
|
1945
|
+
child.on("error", reject);
|
|
1946
|
+
child.on("exit", (code) => {
|
|
1947
|
+
if (code === 0) resolve2();
|
|
1948
|
+
else {
|
|
1949
|
+
const suffix = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
1950
|
+
reject(new Error(`${cmd} exited with ${code ?? "unknown"}${suffix}`));
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
});
|
|
1954
|
+
};
|
|
1955
|
+
var tryRun = async (cmd, args) => {
|
|
1956
|
+
try {
|
|
1957
|
+
await runQuiet(cmd, args);
|
|
1958
|
+
} catch {
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
var installMcp = async (harness) => {
|
|
1962
|
+
try {
|
|
1963
|
+
if (harness === "codex") {
|
|
1964
|
+
await tryRun("codex", ["mcp", "remove", "browser-bridge"]);
|
|
1965
|
+
await runQuiet("codex", [
|
|
1966
|
+
"mcp",
|
|
1967
|
+
"add",
|
|
1968
|
+
"browser-bridge",
|
|
1969
|
+
"--",
|
|
1970
|
+
"browser-bridge",
|
|
1971
|
+
"mcp"
|
|
1972
|
+
]);
|
|
1973
|
+
return { ok: true };
|
|
1974
|
+
}
|
|
1975
|
+
if (harness === "claude") {
|
|
1976
|
+
await tryRun("claude", [
|
|
1977
|
+
"mcp",
|
|
1978
|
+
"remove",
|
|
1979
|
+
"--scope",
|
|
1980
|
+
"local",
|
|
1981
|
+
"browser-bridge"
|
|
1982
|
+
]);
|
|
1983
|
+
await tryRun("claude", [
|
|
1984
|
+
"mcp",
|
|
1985
|
+
"remove",
|
|
1986
|
+
"--scope",
|
|
1987
|
+
"project",
|
|
1988
|
+
"browser-bridge"
|
|
1989
|
+
]);
|
|
1990
|
+
await tryRun("claude", [
|
|
1991
|
+
"mcp",
|
|
1992
|
+
"remove",
|
|
1993
|
+
"--scope",
|
|
1994
|
+
"user",
|
|
1995
|
+
"browser-bridge"
|
|
1996
|
+
]);
|
|
1997
|
+
await runQuiet("claude", [
|
|
1998
|
+
"mcp",
|
|
1999
|
+
"add",
|
|
2000
|
+
"--scope",
|
|
2001
|
+
"user",
|
|
2002
|
+
"--transport",
|
|
2003
|
+
"stdio",
|
|
2004
|
+
"browser-bridge",
|
|
2005
|
+
"--",
|
|
2006
|
+
"browser-bridge",
|
|
2007
|
+
"mcp"
|
|
2008
|
+
]);
|
|
2009
|
+
return { ok: true };
|
|
2010
|
+
}
|
|
2011
|
+
const cursor = await installCursorMcp();
|
|
2012
|
+
return { ok: true, details: { cursorSettingsPath: cursor.settingsPath } };
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
const message = error instanceof Error ? error.message : "Unknown error.";
|
|
2015
|
+
return {
|
|
2016
|
+
ok: false,
|
|
2017
|
+
error: { code: "MCP_INSTALL_FAILED", message }
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
|
|
1840
2022
|
// packages/cli/src/commands/mcp.ts
|
|
1841
2023
|
var registerMcpCommand = (program2) => {
|
|
1842
|
-
|
|
2024
|
+
const startServer = async (options, command) => {
|
|
1843
2025
|
const globals = getGlobalOptions(command);
|
|
1844
2026
|
const coreClient = createCoreClient({
|
|
1845
2027
|
host: globals.host,
|
|
@@ -1856,7 +2038,39 @@ var registerMcpCommand = (program2) => {
|
|
|
1856
2038
|
console.error(error);
|
|
1857
2039
|
process.exitCode = 1;
|
|
1858
2040
|
}
|
|
2041
|
+
};
|
|
2042
|
+
const mcp = program2.command("mcp").description("MCP server and helpers");
|
|
2043
|
+
mcp.command("install").description("Install Browser Bridge as an MCP server in supported clients").action(async (_options, command) => {
|
|
2044
|
+
await runLocal(command, async ({ json }) => {
|
|
2045
|
+
if (json) {
|
|
2046
|
+
throw new Error("mcp install is interactive; omit --json.");
|
|
2047
|
+
}
|
|
2048
|
+
requireTty();
|
|
2049
|
+
const selected = await checkboxPrompt({
|
|
2050
|
+
message: "Install MCP into clients:",
|
|
2051
|
+
choices: [
|
|
2052
|
+
{ value: "codex", label: "Codex", checked: true },
|
|
2053
|
+
{ value: "claude", label: "Claude", checked: true },
|
|
2054
|
+
{ value: "cursor", label: "Cursor", checked: true }
|
|
2055
|
+
]
|
|
2056
|
+
});
|
|
2057
|
+
const results = {};
|
|
2058
|
+
let hadError = false;
|
|
2059
|
+
for (const harness of selected) {
|
|
2060
|
+
const result = await installMcp(harness);
|
|
2061
|
+
results[harness] = result;
|
|
2062
|
+
if (result.ok === false) {
|
|
2063
|
+
hadError = true;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
if (hadError) {
|
|
2067
|
+
process.exitCode = 1;
|
|
2068
|
+
}
|
|
2069
|
+
return { ok: true, result: { installed: results } };
|
|
2070
|
+
});
|
|
1859
2071
|
});
|
|
2072
|
+
mcp.option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(startServer);
|
|
2073
|
+
mcp.command("serve").description("Run the MCP server over stdio").option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(startServer);
|
|
1860
2074
|
};
|
|
1861
2075
|
|
|
1862
2076
|
// packages/core/src/server.ts
|
|
@@ -1865,21 +2079,21 @@ var import_express2 = __toESM(require("express"));
|
|
|
1865
2079
|
// packages/core/src/routes/session.ts
|
|
1866
2080
|
var import_express = require("express");
|
|
1867
2081
|
|
|
1868
|
-
// packages/core/src/inspect.ts
|
|
2082
|
+
// packages/core/src/inspect/service.ts
|
|
1869
2083
|
var import_readability = require("@mozilla/readability");
|
|
1870
2084
|
var import_jsdom = require("jsdom");
|
|
1871
2085
|
var import_turndown = __toESM(require("turndown"));
|
|
1872
2086
|
|
|
1873
2087
|
// packages/core/src/artifacts.ts
|
|
1874
|
-
var
|
|
1875
|
-
var
|
|
1876
|
-
var
|
|
2088
|
+
var import_promises3 = require("node:fs/promises");
|
|
2089
|
+
var import_node_os2 = __toESM(require("node:os"));
|
|
2090
|
+
var import_node_path3 = __toESM(require("node:path"));
|
|
1877
2091
|
var ARTIFACTS_DIR_NAME = "browser-agent";
|
|
1878
|
-
var resolveTempRoot = () => process.env.TMPDIR || process.env.TEMP || process.env.TMP ||
|
|
1879
|
-
var getArtifactRootDir = (sessionId) =>
|
|
2092
|
+
var resolveTempRoot = () => process.env.TMPDIR || process.env.TEMP || process.env.TMP || import_node_os2.default.tmpdir();
|
|
2093
|
+
var getArtifactRootDir = (sessionId) => import_node_path3.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, sessionId);
|
|
1880
2094
|
var ensureArtifactRootDir = async (sessionId) => {
|
|
1881
2095
|
const rootDir = getArtifactRootDir(sessionId);
|
|
1882
|
-
await (0,
|
|
2096
|
+
await (0, import_promises3.mkdir)(rootDir, { recursive: true });
|
|
1883
2097
|
return rootDir;
|
|
1884
2098
|
};
|
|
1885
2099
|
|
|
@@ -2004,23 +2218,23 @@ var PAGE_STATE_SCRIPT = [
|
|
|
2004
2218
|
].join("\n");
|
|
2005
2219
|
|
|
2006
2220
|
// packages/cli/src/open-path.ts
|
|
2007
|
-
var
|
|
2221
|
+
var import_node_child_process3 = require("node:child_process");
|
|
2008
2222
|
var openPath = async (target) => {
|
|
2009
2223
|
const platform = process.platform;
|
|
2010
2224
|
if (platform === "darwin") {
|
|
2011
|
-
const child2 = (0,
|
|
2225
|
+
const child2 = (0, import_node_child_process3.spawn)("open", [target], { detached: true, stdio: "ignore" });
|
|
2012
2226
|
child2.unref();
|
|
2013
2227
|
return;
|
|
2014
2228
|
}
|
|
2015
2229
|
if (platform === "win32") {
|
|
2016
|
-
const child2 = (0,
|
|
2230
|
+
const child2 = (0, import_node_child_process3.spawn)("cmd", ["/c", "start", "", target], {
|
|
2017
2231
|
detached: true,
|
|
2018
2232
|
stdio: "ignore"
|
|
2019
2233
|
});
|
|
2020
2234
|
child2.unref();
|
|
2021
2235
|
return;
|
|
2022
2236
|
}
|
|
2023
|
-
const child = (0,
|
|
2237
|
+
const child = (0, import_node_child_process3.spawn)("xdg-open", [target], {
|
|
2024
2238
|
detached: true,
|
|
2025
2239
|
stdio: "ignore"
|
|
2026
2240
|
});
|
|
@@ -2076,6 +2290,404 @@ var registerSessionCommands = (program2) => {
|
|
|
2076
2290
|
});
|
|
2077
2291
|
};
|
|
2078
2292
|
|
|
2293
|
+
// packages/cli/src/commands/skill.ts
|
|
2294
|
+
var import_promises7 = __toESM(require("node:fs/promises"));
|
|
2295
|
+
var import_node_os4 = __toESM(require("node:os"));
|
|
2296
|
+
var import_node_path8 = __toESM(require("node:path"));
|
|
2297
|
+
|
|
2298
|
+
// packages/cli/src/installer/harness-targets.ts
|
|
2299
|
+
var import_node_os3 = __toESM(require("node:os"));
|
|
2300
|
+
var import_node_path4 = __toESM(require("node:path"));
|
|
2301
|
+
var getDefaultHarnessTargets = (homeDir) => {
|
|
2302
|
+
const home = homeDir ?? import_node_os3.default.homedir();
|
|
2303
|
+
return [
|
|
2304
|
+
{
|
|
2305
|
+
id: "codex",
|
|
2306
|
+
label: "Codex",
|
|
2307
|
+
skillsDir: import_node_path4.default.join(home, ".agents", "skills"),
|
|
2308
|
+
supportsMcpInstall: true
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
id: "claude",
|
|
2312
|
+
label: "Claude",
|
|
2313
|
+
skillsDir: import_node_path4.default.join(home, ".claude", "skills"),
|
|
2314
|
+
supportsMcpInstall: true
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
id: "cursor",
|
|
2318
|
+
label: "Cursor",
|
|
2319
|
+
skillsDir: import_node_path4.default.join(home, ".cursor", "skills"),
|
|
2320
|
+
supportsMcpInstall: true
|
|
2321
|
+
},
|
|
2322
|
+
{
|
|
2323
|
+
id: "factory",
|
|
2324
|
+
label: "Factory",
|
|
2325
|
+
skillsDir: import_node_path4.default.join(home, ".factory", "skills"),
|
|
2326
|
+
supportsMcpInstall: false
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
id: "opencode",
|
|
2330
|
+
label: "OpenCode",
|
|
2331
|
+
skillsDir: import_node_path4.default.join(home, ".opencode", "skills"),
|
|
2332
|
+
supportsMcpInstall: false
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
id: "gemini",
|
|
2336
|
+
label: "Gemini",
|
|
2337
|
+
skillsDir: import_node_path4.default.join(home, ".gemini", "skills"),
|
|
2338
|
+
supportsMcpInstall: false
|
|
2339
|
+
},
|
|
2340
|
+
{
|
|
2341
|
+
id: "github",
|
|
2342
|
+
label: "GitHub",
|
|
2343
|
+
skillsDir: import_node_path4.default.join(home, ".github", "skills"),
|
|
2344
|
+
supportsMcpInstall: false
|
|
2345
|
+
},
|
|
2346
|
+
{
|
|
2347
|
+
id: "ampcode",
|
|
2348
|
+
label: "Ampcode",
|
|
2349
|
+
skillsDir: import_node_path4.default.join(home, ".ampcode", "skills"),
|
|
2350
|
+
supportsMcpInstall: false
|
|
2351
|
+
}
|
|
2352
|
+
];
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
// packages/cli/src/installer/package-info.ts
|
|
2356
|
+
var import_promises4 = __toESM(require("node:fs/promises"));
|
|
2357
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
2358
|
+
var PACKAGE_NAME = "@btraut/browser-bridge";
|
|
2359
|
+
var tryReadJson = async (filePath) => {
|
|
2360
|
+
try {
|
|
2361
|
+
const raw = await import_promises4.default.readFile(filePath, "utf8");
|
|
2362
|
+
return JSON.parse(raw);
|
|
2363
|
+
} catch {
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
var resolveCliPackageRootDir = async () => {
|
|
2368
|
+
let dir = __dirname;
|
|
2369
|
+
for (let i = 0; i < 12; i++) {
|
|
2370
|
+
const candidate = import_node_path5.default.join(dir, "package.json");
|
|
2371
|
+
const parsed = await tryReadJson(candidate);
|
|
2372
|
+
if (parsed && parsed.name === PACKAGE_NAME) {
|
|
2373
|
+
return dir;
|
|
2374
|
+
}
|
|
2375
|
+
const parent = import_node_path5.default.dirname(dir);
|
|
2376
|
+
if (parent === dir) {
|
|
2377
|
+
break;
|
|
2378
|
+
}
|
|
2379
|
+
dir = parent;
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error(
|
|
2382
|
+
"Unable to locate Browser Bridge package root (package.json)."
|
|
2383
|
+
);
|
|
2384
|
+
};
|
|
2385
|
+
var readCliPackageVersion = async () => {
|
|
2386
|
+
const rootDir = await resolveCliPackageRootDir();
|
|
2387
|
+
const pkgPath = import_node_path5.default.join(rootDir, "package.json");
|
|
2388
|
+
const parsed = await tryReadJson(pkgPath);
|
|
2389
|
+
if (!parsed || typeof parsed.version !== "string" || !parsed.version) {
|
|
2390
|
+
throw new Error(`Unable to read version from ${pkgPath}`);
|
|
2391
|
+
}
|
|
2392
|
+
return parsed.version;
|
|
2393
|
+
};
|
|
2394
|
+
var resolveSkillSourceDir = async () => {
|
|
2395
|
+
const rootDir = await resolveCliPackageRootDir();
|
|
2396
|
+
const packaged = import_node_path5.default.join(rootDir, "skills", "browser-bridge");
|
|
2397
|
+
try {
|
|
2398
|
+
await import_promises4.default.stat(packaged);
|
|
2399
|
+
return packaged;
|
|
2400
|
+
} catch {
|
|
2401
|
+
}
|
|
2402
|
+
const repoRoot = import_node_path5.default.resolve(rootDir, "..", "..");
|
|
2403
|
+
const docsSkill = import_node_path5.default.join(repoRoot, "docs", "skills", "browser-bridge");
|
|
2404
|
+
try {
|
|
2405
|
+
await import_promises4.default.stat(docsSkill);
|
|
2406
|
+
return docsSkill;
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2409
|
+
throw new Error(
|
|
2410
|
+
`Unable to locate packaged skill. Expected ${packaged} (npm install) or ${docsSkill} (repo dev).`
|
|
2411
|
+
);
|
|
2412
|
+
};
|
|
2413
|
+
|
|
2414
|
+
// packages/cli/src/installer/skill-install.ts
|
|
2415
|
+
var import_promises6 = __toESM(require("node:fs/promises"));
|
|
2416
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
2417
|
+
|
|
2418
|
+
// packages/cli/src/installer/skill-manifest.ts
|
|
2419
|
+
var import_promises5 = __toESM(require("node:fs/promises"));
|
|
2420
|
+
var import_node_path6 = __toESM(require("node:path"));
|
|
2421
|
+
var SKILL_MANIFEST_FILENAME = "skill.json";
|
|
2422
|
+
var readSkillManifest = async (skillDir) => {
|
|
2423
|
+
try {
|
|
2424
|
+
const raw = await import_promises5.default.readFile(
|
|
2425
|
+
import_node_path6.default.join(skillDir, SKILL_MANIFEST_FILENAME),
|
|
2426
|
+
"utf8"
|
|
2427
|
+
);
|
|
2428
|
+
const parsed = JSON.parse(raw);
|
|
2429
|
+
if (typeof parsed === "object" && parsed !== null && parsed.name === "browser-bridge" && typeof parsed.version === "string") {
|
|
2430
|
+
return parsed;
|
|
2431
|
+
}
|
|
2432
|
+
return null;
|
|
2433
|
+
} catch {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
var writeSkillManifest = async (skillDir, version) => {
|
|
2438
|
+
const payload = { name: "browser-bridge", version };
|
|
2439
|
+
await import_promises5.default.writeFile(
|
|
2440
|
+
import_node_path6.default.join(skillDir, SKILL_MANIFEST_FILENAME),
|
|
2441
|
+
JSON.stringify(payload, null, 2) + "\n",
|
|
2442
|
+
"utf8"
|
|
2443
|
+
);
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
// packages/cli/src/installer/skill-install.ts
|
|
2447
|
+
var installBrowserBridgeSkill = async (options) => {
|
|
2448
|
+
const destDir = import_node_path7.default.join(options.destSkillsDir, "browser-bridge");
|
|
2449
|
+
await import_promises6.default.mkdir(options.destSkillsDir, { recursive: true });
|
|
2450
|
+
await import_promises6.default.rm(destDir, { recursive: true, force: true });
|
|
2451
|
+
await import_promises6.default.cp(options.srcSkillDir, destDir, { recursive: true });
|
|
2452
|
+
await writeSkillManifest(destDir, options.version);
|
|
2453
|
+
return { ok: true, destDir };
|
|
2454
|
+
};
|
|
2455
|
+
|
|
2456
|
+
// packages/cli/src/commands/skill.ts
|
|
2457
|
+
var getHarnessMarkerDir = (homeDir, harness) => {
|
|
2458
|
+
switch (harness) {
|
|
2459
|
+
case "codex":
|
|
2460
|
+
return import_node_path8.default.join(homeDir, ".agents");
|
|
2461
|
+
default:
|
|
2462
|
+
return import_node_path8.default.join(homeDir, `.${harness}`);
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
var registerSkillCommands = (program2) => {
|
|
2466
|
+
const skill = program2.command("skill").description("Skill commands");
|
|
2467
|
+
skill.command("install").description("Install the Browser Bridge skill into one or more clients").option(
|
|
2468
|
+
"--client <id...>",
|
|
2469
|
+
"Client ids to install into (codex, claude, cursor, factory, opencode, gemini, github, ampcode)"
|
|
2470
|
+
).option("--harness <id...>", "Alias for --client").action(
|
|
2471
|
+
async (options, command) => {
|
|
2472
|
+
await runLocal(command, async ({ json }) => {
|
|
2473
|
+
if (json) {
|
|
2474
|
+
throw new Error("skill install is interactive; omit --json.");
|
|
2475
|
+
}
|
|
2476
|
+
const version = await readCliPackageVersion();
|
|
2477
|
+
const srcSkillDir = await resolveSkillSourceDir();
|
|
2478
|
+
const targets = getDefaultHarnessTargets();
|
|
2479
|
+
const byId = new Map(targets.map((t) => [t.id, t]));
|
|
2480
|
+
let selected;
|
|
2481
|
+
const requested = options.client?.length ? options.client : options.harness;
|
|
2482
|
+
if (requested && requested.length > 0) {
|
|
2483
|
+
selected = requested.map((id) => {
|
|
2484
|
+
if (!byId.has(id)) {
|
|
2485
|
+
throw new Error(`Unknown client: ${id}`);
|
|
2486
|
+
}
|
|
2487
|
+
return id;
|
|
2488
|
+
});
|
|
2489
|
+
} else {
|
|
2490
|
+
requireTty();
|
|
2491
|
+
const homeDir = import_node_os4.default.homedir();
|
|
2492
|
+
const checked = /* @__PURE__ */ new Set();
|
|
2493
|
+
for (const t of targets) {
|
|
2494
|
+
try {
|
|
2495
|
+
const marker = getHarnessMarkerDir(homeDir, t.id);
|
|
2496
|
+
await import_promises7.default.stat(marker);
|
|
2497
|
+
checked.add(t.id);
|
|
2498
|
+
} catch {
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
if (checked.size === 0) {
|
|
2502
|
+
checked.add("codex");
|
|
2503
|
+
}
|
|
2504
|
+
selected = await checkboxPrompt({
|
|
2505
|
+
message: "Install Browser Bridge skill into clients:",
|
|
2506
|
+
choices: targets.map((t) => ({
|
|
2507
|
+
value: t.id,
|
|
2508
|
+
label: `${t.label} (${t.skillsDir})`,
|
|
2509
|
+
checked: checked.has(t.id)
|
|
2510
|
+
}))
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
const results = {};
|
|
2514
|
+
for (const client of selected) {
|
|
2515
|
+
const target = byId.get(client);
|
|
2516
|
+
if (!target) continue;
|
|
2517
|
+
const installed = await installBrowserBridgeSkill({
|
|
2518
|
+
srcSkillDir,
|
|
2519
|
+
destSkillsDir: target.skillsDir,
|
|
2520
|
+
version
|
|
2521
|
+
});
|
|
2522
|
+
results[client] = { destDir: installed.destDir };
|
|
2523
|
+
}
|
|
2524
|
+
return {
|
|
2525
|
+
ok: true,
|
|
2526
|
+
result: {
|
|
2527
|
+
version,
|
|
2528
|
+
installed: results
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
);
|
|
2534
|
+
skill.command("status").description("Show Browser Bridge skill install status across clients").action(async (_options, command) => {
|
|
2535
|
+
await runLocal(command, async () => {
|
|
2536
|
+
const version = await readCliPackageVersion();
|
|
2537
|
+
const targets = getDefaultHarnessTargets();
|
|
2538
|
+
const rows = [];
|
|
2539
|
+
for (const t of targets) {
|
|
2540
|
+
const skillDir = import_node_path8.default.join(t.skillsDir, "browser-bridge");
|
|
2541
|
+
let installed = false;
|
|
2542
|
+
try {
|
|
2543
|
+
await import_promises7.default.stat(skillDir);
|
|
2544
|
+
installed = true;
|
|
2545
|
+
} catch {
|
|
2546
|
+
installed = false;
|
|
2547
|
+
}
|
|
2548
|
+
const manifest = installed ? await readSkillManifest(skillDir) : null;
|
|
2549
|
+
const installedVersion = manifest?.version ?? null;
|
|
2550
|
+
const upToDate = installedVersion === version;
|
|
2551
|
+
rows.push({
|
|
2552
|
+
harness: t.id,
|
|
2553
|
+
skillsDir: t.skillsDir,
|
|
2554
|
+
installed,
|
|
2555
|
+
installedVersion,
|
|
2556
|
+
expectedVersion: version,
|
|
2557
|
+
upToDate
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
return { ok: true, result: { rows } };
|
|
2561
|
+
});
|
|
2562
|
+
});
|
|
2563
|
+
};
|
|
2564
|
+
|
|
2565
|
+
// packages/cli/src/commands/install.ts
|
|
2566
|
+
var import_promises8 = __toESM(require("node:fs/promises"));
|
|
2567
|
+
var import_node_os5 = __toESM(require("node:os"));
|
|
2568
|
+
var import_node_path9 = __toESM(require("node:path"));
|
|
2569
|
+
var formatInstallSummary = (options) => {
|
|
2570
|
+
const wantsSkill = options.setup.includes("skill");
|
|
2571
|
+
const wantsMcp = options.setup.includes("mcp");
|
|
2572
|
+
const lines = [];
|
|
2573
|
+
if (wantsSkill) {
|
|
2574
|
+
const skillInstalled = options.results.filter((r) => r.skill?.ok).map((r) => `- ${r.harness}: ${r.skill?.destDir ?? ""}`);
|
|
2575
|
+
if (skillInstalled.length > 0) {
|
|
2576
|
+
lines.push("Skill installed:");
|
|
2577
|
+
lines.push(...skillInstalled);
|
|
2578
|
+
lines.push("");
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
if (wantsMcp) {
|
|
2582
|
+
const mcpOk = options.results.filter((r) => r.mcp?.ok).map((r) => `- ${r.harness}`);
|
|
2583
|
+
const mcpFailed = options.results.filter((r) => r.mcp && r.mcp.ok === false).map(
|
|
2584
|
+
(r) => r.mcp && r.mcp.ok === false ? `- ${r.harness}: ${r.mcp.error.message}` : `- ${r.harness}: unknown error`
|
|
2585
|
+
);
|
|
2586
|
+
if (mcpOk.length > 0) {
|
|
2587
|
+
lines.push("MCP installed:");
|
|
2588
|
+
lines.push(...mcpOk);
|
|
2589
|
+
lines.push("");
|
|
2590
|
+
}
|
|
2591
|
+
if (mcpFailed.length > 0) {
|
|
2592
|
+
lines.push("MCP failed:");
|
|
2593
|
+
lines.push(...mcpFailed);
|
|
2594
|
+
lines.push("");
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return lines.join("\n").trimEnd();
|
|
2598
|
+
};
|
|
2599
|
+
var getHarnessMarkerDir2 = (harness) => {
|
|
2600
|
+
const homeDir = import_node_os5.default.homedir();
|
|
2601
|
+
switch (harness) {
|
|
2602
|
+
case "codex":
|
|
2603
|
+
return import_node_path9.default.join(homeDir, ".agents");
|
|
2604
|
+
default:
|
|
2605
|
+
return import_node_path9.default.join(homeDir, `.${harness}`);
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2608
|
+
var registerInstallCommand = (program2) => {
|
|
2609
|
+
program2.command("install").description("Install Browser Bridge integrations (Skill and/or MCP)").action(async (_options, command) => {
|
|
2610
|
+
await runLocal(command, async ({ json }) => {
|
|
2611
|
+
if (json) {
|
|
2612
|
+
throw new Error("install is interactive; omit --json.");
|
|
2613
|
+
}
|
|
2614
|
+
requireTty();
|
|
2615
|
+
console.log("Browser Bridge Installer");
|
|
2616
|
+
const setup = await checkboxPrompt({
|
|
2617
|
+
message: "Choose what you'd like to install:",
|
|
2618
|
+
choices: [
|
|
2619
|
+
{ value: "skill", label: "Skill (Recommended)", checked: true },
|
|
2620
|
+
{ value: "mcp", label: "MCP (Optional)", checked: true }
|
|
2621
|
+
]
|
|
2622
|
+
});
|
|
2623
|
+
if (setup.length === 0) {
|
|
2624
|
+
return { ok: true, result: "No changes (nothing selected)." };
|
|
2625
|
+
}
|
|
2626
|
+
const wantsSkill = setup.includes("skill");
|
|
2627
|
+
const wantsMcp = setup.includes("mcp");
|
|
2628
|
+
const targets = getDefaultHarnessTargets();
|
|
2629
|
+
const checked = /* @__PURE__ */ new Set();
|
|
2630
|
+
for (const t of targets) {
|
|
2631
|
+
try {
|
|
2632
|
+
await import_promises8.default.stat(getHarnessMarkerDir2(t.id));
|
|
2633
|
+
checked.add(t.id);
|
|
2634
|
+
} catch {
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
if (checked.size === 0) {
|
|
2638
|
+
checked.add("codex");
|
|
2639
|
+
}
|
|
2640
|
+
const selected = await checkboxPrompt({
|
|
2641
|
+
message: "Install into clients:",
|
|
2642
|
+
choices: targets.map((t) => {
|
|
2643
|
+
const mcpCapable = t.supportsMcpInstall;
|
|
2644
|
+
const enabled = wantsSkill || mcpCapable;
|
|
2645
|
+
const disabled = enabled ? false : wantsMcp ? "MCP install not supported yet" : false;
|
|
2646
|
+
const suffix = wantsMcp && wantsSkill && !mcpCapable ? " (skill only)" : "";
|
|
2647
|
+
return {
|
|
2648
|
+
value: t.id,
|
|
2649
|
+
label: `${t.label}${suffix}`,
|
|
2650
|
+
checked: checked.has(t.id),
|
|
2651
|
+
disabled
|
|
2652
|
+
};
|
|
2653
|
+
})
|
|
2654
|
+
});
|
|
2655
|
+
const version = await readCliPackageVersion();
|
|
2656
|
+
const srcSkillDir = await resolveSkillSourceDir();
|
|
2657
|
+
const byId = new Map(targets.map((t) => [t.id, t]));
|
|
2658
|
+
const results = [];
|
|
2659
|
+
let hadError = false;
|
|
2660
|
+
for (const harness of selected) {
|
|
2661
|
+
const t = byId.get(harness);
|
|
2662
|
+
if (!t) continue;
|
|
2663
|
+
const row = { harness };
|
|
2664
|
+
if (wantsSkill) {
|
|
2665
|
+
const installed = await installBrowserBridgeSkill({
|
|
2666
|
+
srcSkillDir,
|
|
2667
|
+
destSkillsDir: t.skillsDir,
|
|
2668
|
+
version
|
|
2669
|
+
});
|
|
2670
|
+
row.skill = { ok: true, destDir: installed.destDir };
|
|
2671
|
+
}
|
|
2672
|
+
if (wantsMcp && t.supportsMcpInstall) {
|
|
2673
|
+
if (harness === "codex" || harness === "claude" || harness === "cursor") {
|
|
2674
|
+
row.mcp = await installMcp(harness);
|
|
2675
|
+
if (row.mcp.ok === false) {
|
|
2676
|
+
hadError = true;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
results.push(row);
|
|
2681
|
+
}
|
|
2682
|
+
if (hadError) {
|
|
2683
|
+
process.exitCode = 1;
|
|
2684
|
+
}
|
|
2685
|
+
const summary = formatInstallSummary({ setup, results });
|
|
2686
|
+
return { ok: true, result: summary || "Done." };
|
|
2687
|
+
});
|
|
2688
|
+
});
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2079
2691
|
// packages/cli/src/index.ts
|
|
2080
2692
|
var program = new import_commander.Command();
|
|
2081
2693
|
program.name("browser-bridge").description("Browser Bridge CLI").option("--host <host>", "Core host (default: 127.0.0.1)").option("--port <port>", "Core port (default: 3210)").option("--json", "Output JSON").option("--no-daemon", "Disable auto-starting Core");
|
|
@@ -2087,5 +2699,7 @@ registerDiagnosticsCommands(program);
|
|
|
2087
2699
|
registerDialogCommands(program);
|
|
2088
2700
|
registerOpenArtifactsCommand(program);
|
|
2089
2701
|
registerMcpCommand(program);
|
|
2702
|
+
registerSkillCommands(program);
|
|
2703
|
+
registerInstallCommand(program);
|
|
2090
2704
|
void program.parseAsync(process.argv);
|
|
2091
2705
|
//# sourceMappingURL=index.js.map
|