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