@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/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 = (path2) => path2.startsWith("/") ? path2 : `/${path2}`;
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, path2, body) => {
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(path2)}`, {
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 (path2, body) => {
782
+ const post = async (path9, body) => {
778
783
  await ensureReady();
779
- return requestJson("POST", path2, body);
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 = (path2) => path2.startsWith("/") ? path2 : `/${path2}`;
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 (path2, body) => {
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(path2)}`, {
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 (path2, body) => {
1402
- return requestJson(path2, body);
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
- 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) => {
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 import_promises2 = require("node:fs/promises");
1875
- var import_node_os = __toESM(require("node:os"));
1876
- var import_node_path2 = __toESM(require("node:path"));
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 || import_node_os.default.tmpdir();
1879
- var getArtifactRootDir = (sessionId) => import_node_path2.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, 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, import_promises2.mkdir)(rootDir, { recursive: true });
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 import_node_child_process2 = require("node:child_process");
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, import_node_child_process2.spawn)("open", [target], { detached: true, stdio: "ignore" });
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, import_node_child_process2.spawn)("cmd", ["/c", "start", "", target], {
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, import_node_child_process2.spawn)("xdg-open", [target], {
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