@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/dist/index.js CHANGED
@@ -673,7 +673,7 @@ var resolveTimeoutMs = (timeoutMs) => {
673
673
  }
674
674
  return Math.floor(parsed);
675
675
  };
676
- var normalizePath = (path2) => path2.startsWith("/") ? path2 : `/${path2}`;
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, path2, body) => {
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(path2)}`, {
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 (path2, body) => {
777
+ const post = async (path9, body) => {
778
778
  await ensureReady();
779
- return requestJson("POST", path2, body);
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 = (path2) => path2.startsWith("/") ? path2 : `/${path2}`;
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 (path2, body) => {
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(path2)}`, {
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 (path2, body) => {
1402
- return requestJson(path2, body);
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
- program2.command("mcp").description("Run the MCP server over stdio").option("--name <name>", "MCP server name").option("--version <version>", "MCP server version").action(async (options, command) => {
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 import_promises2 = require("node:fs/promises");
1875
- var import_node_os = __toESM(require("node:os"));
1876
- var import_node_path2 = __toESM(require("node:path"));
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 || import_node_os.default.tmpdir();
1879
- var getArtifactRootDir = (sessionId) => import_node_path2.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, 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, import_promises2.mkdir)(rootDir, { recursive: true });
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 import_node_child_process2 = require("node:child_process");
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, import_node_child_process2.spawn)("open", [target], { detached: true, stdio: "ignore" });
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, import_node_child_process2.spawn)("cmd", ["/c", "start", "", target], {
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, import_node_child_process2.spawn)("xdg-open", [target], {
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