@browserbridge/bbx 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +6 -4
  2. package/package.json +53 -53
  3. package/packages/agent-client/src/cli-helpers.js +43 -5
  4. package/packages/agent-client/src/cli.js +176 -171
  5. package/packages/agent-client/src/client.js +66 -21
  6. package/packages/agent-client/src/command-registry.js +104 -69
  7. package/packages/agent-client/src/detect.js +162 -54
  8. package/packages/agent-client/src/install.js +34 -28
  9. package/packages/agent-client/src/mcp-config.js +40 -40
  10. package/packages/agent-client/src/runtime.js +41 -20
  11. package/packages/agent-client/src/setup-status.js +23 -30
  12. package/packages/mcp-server/src/bin.js +57 -5
  13. package/packages/mcp-server/src/handlers.js +573 -256
  14. package/packages/mcp-server/src/server.js +568 -257
  15. package/packages/native-host/bin/bridge-daemon.js +39 -6
  16. package/packages/native-host/bin/install-manifest.js +26 -4
  17. package/packages/native-host/bin/postinstall.js +4 -2
  18. package/packages/native-host/src/config.js +142 -13
  19. package/packages/native-host/src/daemon-process.js +396 -0
  20. package/packages/native-host/src/daemon.js +350 -150
  21. package/packages/native-host/src/framing.js +131 -11
  22. package/packages/native-host/src/install-manifest.js +194 -29
  23. package/packages/native-host/src/native-host.js +154 -102
  24. package/packages/protocol/src/budget.js +3 -7
  25. package/packages/protocol/src/capabilities.js +6 -3
  26. package/packages/protocol/src/defaults.js +1 -0
  27. package/packages/protocol/src/errors.js +15 -11
  28. package/packages/protocol/src/payload-cost.js +19 -6
  29. package/packages/protocol/src/protocol.js +242 -73
  30. package/packages/protocol/src/registry.js +311 -45
  31. package/packages/protocol/src/summary.js +260 -109
  32. package/packages/protocol/src/types.js +29 -4
  33. package/skills/browser-bridge/SKILL.md +3 -2
  34. package/skills/browser-bridge/agents/openai.yaml +3 -3
  35. package/skills/browser-bridge/references/interaction.md +34 -11
  36. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  37. package/skills/browser-bridge/references/protocol.md +127 -71
  38. package/skills/browser-bridge/references/tailwind.md +12 -11
  39. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  40. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  41. package/CHANGELOG.md +0 -55
  42. package/assets/banner.jpg +0 -0
  43. package/assets/logo.png +0 -0
  44. package/assets/logo.svg +0 -65
  45. package/docs/api-reference.md +0 -157
  46. package/docs/cli-guide.md +0 -128
  47. package/docs/index.md +0 -25
  48. package/docs/manual-setup.md +0 -140
  49. package/docs/mcp-vs-cli.md +0 -258
  50. package/docs/publishing.md +0 -114
  51. package/docs/quickstart.md +0 -104
  52. package/docs/troubleshooting.md +0 -59
  53. package/docs/usage-scenarios.md +0 -136
  54. package/manifest.json +0 -52
  55. package/packages/extension/assets/icon-128.png +0 -0
  56. package/packages/extension/assets/icon-16.png +0 -0
  57. package/packages/extension/assets/icon-32.png +0 -0
  58. package/packages/extension/assets/icon-48.png +0 -0
  59. package/packages/extension/src/background-helpers.js +0 -459
  60. package/packages/extension/src/background-routing.js +0 -91
  61. package/packages/extension/src/background.js +0 -3227
  62. package/packages/extension/src/content-script-helpers.js +0 -281
  63. package/packages/extension/src/content-script.js +0 -1977
  64. package/packages/extension/src/debugger-coordinator.js +0 -188
  65. package/packages/extension/src/sidepanel-helpers.js +0 -102
  66. package/packages/extension/ui/offscreen.html +0 -6
  67. package/packages/extension/ui/offscreen.js +0 -61
  68. package/packages/extension/ui/popup.html +0 -35
  69. package/packages/extension/ui/popup.js +0 -279
  70. package/packages/extension/ui/sidepanel.html +0 -102
  71. package/packages/extension/ui/sidepanel.js +0 -1854
  72. package/packages/extension/ui/ui.css +0 -1159
@@ -1,12 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
3
  import { BridgeDaemon } from '../src/daemon.js';
4
- import { getSocketPath } from '../src/config.js';
4
+ import {
5
+ applyWindowsTcpTransportDefaults,
6
+ formatBridgeTransport,
7
+ getBridgeTransport,
8
+ } from '../src/config.js';
9
+ import { clearDaemonPidFile, writeDaemonPidFile } from '../src/daemon-process.js';
5
10
 
6
- const daemon = new BridgeDaemon({ socketPath: getSocketPath() });
7
- await daemon.start();
11
+ applyWindowsTcpTransportDefaults();
12
+ const transport = getBridgeTransport();
13
+ const daemon = new BridgeDaemon({ transport });
8
14
 
9
- process.stdout.write(`Browser Bridge daemon listening on ${getSocketPath()}\n`);
15
+ /**
16
+ * @param {unknown} error
17
+ * @returns {boolean}
18
+ */
19
+ function isExistingDaemonError(error) {
20
+ return (
21
+ error instanceof Error && error.message.startsWith('Another daemon is already running on ')
22
+ );
23
+ }
24
+
25
+ try {
26
+ await daemon.start();
27
+ await writeDaemonPidFile(process.pid);
28
+ } catch (error) {
29
+ if (isExistingDaemonError(error)) {
30
+ process.stdout.write(`${error.message}\n`);
31
+ process.exit(0);
32
+ }
33
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
34
+ process.exit(1);
35
+ }
36
+
37
+ process.stdout.write(`Browser Bridge daemon listening on ${formatBridgeTransport(transport)}\n`);
10
38
 
11
39
  let shuttingDown = false;
12
40
 
@@ -21,6 +49,7 @@ async function shutdown() {
21
49
  shuttingDown = true;
22
50
  try {
23
51
  await daemon.stop();
52
+ await clearDaemonPidFile({ pid: process.pid });
24
53
  process.exit(0);
25
54
  } catch (error) {
26
55
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
@@ -35,10 +64,14 @@ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
35
64
  }
36
65
 
37
66
  process.on('unhandledRejection', (reason) => {
38
- process.stderr.write(`Unhandled rejection: ${reason instanceof Error ? reason.stack : String(reason)}\n`);
67
+ process.stderr.write(
68
+ `Unhandled rejection: ${reason instanceof Error ? reason.stack : String(reason)}\n`
69
+ );
39
70
  });
40
71
 
41
72
  process.on('uncaughtException', (error) => {
42
- process.stderr.write(`Uncaught exception: ${error instanceof Error ? error.stack : String(error)}\n`);
73
+ process.stderr.write(
74
+ `Uncaught exception: ${error instanceof Error ? error.stack : String(error)}\n`
75
+ );
43
76
  void shutdown();
44
77
  });
@@ -3,22 +3,31 @@
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
6
- import { installNativeManifest, parseExtensionId } from '../src/install-manifest.js';
6
+ import {
7
+ installNativeManifest,
8
+ parseExtensionId,
9
+ uninstallNativeManifest,
10
+ } from '../src/install-manifest.js';
7
11
  import { SUPPORTED_BROWSERS } from '../src/config.js';
8
12
 
9
13
  /** @typedef {import('../src/config.js').SupportedBrowser} SupportedBrowser */
10
14
 
15
+ const USAGE = 'Usage: bbx-install [<extension-id>] [--browser <browser>] [--all] [--uninstall]\n';
16
+
11
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
18
  const repoRoot = path.resolve(__dirname, '../../..');
13
19
 
14
20
  const args = process.argv.slice(2);
15
21
  let extensionIdArg = /** @type {string | undefined} */ (undefined);
16
22
  let installAll = false;
23
+ let uninstall = false;
17
24
  const browsers = /** @type {SupportedBrowser[]} */ ([]);
18
25
 
19
26
  for (let i = 0; i < args.length; i++) {
20
27
  if (args[i] === '--all') {
21
28
  installAll = true;
29
+ } else if (args[i] === '--uninstall') {
30
+ uninstall = true;
22
31
  } else if (args[i] === '--browser' && args[i + 1]) {
23
32
  const candidate = args[i + 1];
24
33
  if (!SUPPORTED_BROWSERS.includes(/** @type {SupportedBrowser} */ (candidate))) {
@@ -34,10 +43,15 @@ for (let i = 0; i < args.length; i++) {
34
43
  }
35
44
  }
36
45
 
46
+ if (uninstall && extensionIdArg) {
47
+ process.stderr.write(USAGE);
48
+ process.exit(1);
49
+ }
50
+
37
51
  if (extensionIdArg && !parseExtensionId(extensionIdArg)) {
38
52
  process.stderr.write(
39
53
  `Invalid extension ID: ${extensionIdArg}\n` +
40
- 'Expected 32 lowercase letters (e.g. abcdefghijklmnopabcdefghijklmnop)\n'
54
+ 'Expected 32 lowercase letters (e.g. abcdefghijklmnopabcdefghijklmnop)\n'
41
55
  );
42
56
  process.exit(1);
43
57
  }
@@ -48,10 +62,18 @@ const targets = installAll
48
62
  ? browsers
49
63
  : [/** @type {SupportedBrowser} */ ('chrome')];
50
64
 
51
- for (const target of targets) {
65
+ for (const [index, target] of targets.entries()) {
66
+ if (uninstall) {
67
+ await uninstallNativeManifest({
68
+ browser: target,
69
+ removeBridgeDir: index === targets.length - 1,
70
+ });
71
+ continue;
72
+ }
73
+
52
74
  await installNativeManifest({
53
75
  repoRoot,
54
76
  extensionIdArg,
55
- browser: target
77
+ browser: target,
56
78
  });
57
79
  }
@@ -16,10 +16,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
16
  const repoRoot = path.resolve(__dirname, '../../..');
17
17
 
18
18
  try {
19
- await installNativeManifest({ repoRoot });
19
+ await installNativeManifest({ repoRoot, preserveCustomExtensionId: true });
20
20
  process.stdout.write('Browser Bridge: native host installed. Run `bbx doctor` to verify.\n');
21
21
  } catch (err) {
22
22
  // Non-fatal - user can run `bbx install` manually.
23
23
  const message = err instanceof Error ? err.message : String(err);
24
- process.stderr.write(`Browser Bridge: native host auto-install skipped (${message}).\nRun \`bbx install\` manually if needed.\n`);
24
+ process.stderr.write(
25
+ `Browser Bridge: native host auto-install skipped (${message}).\nRun \`bbx install\` manually if needed.\n`
26
+ );
25
27
  }
@@ -5,6 +5,10 @@ import path from 'node:path';
5
5
 
6
6
  export const APP_NAME = 'com.browserbridge.browser_bridge';
7
7
  export const BRIDGE_HOME_ENV = 'BROWSER_BRIDGE_HOME';
8
+ export const BRIDGE_TCP_PORT_ENV = 'BBX_TCP_PORT';
9
+ export const DEFAULT_WINDOWS_TCP_PORT = 9223;
10
+
11
+ /** @typedef {{ type: 'socket', socketPath: string, label: string } | { type: 'tcp', host: string, port: number, label: string }} BridgeTransport */
8
12
 
9
13
  /**
10
14
  * The official Chrome Web Store extension ID used when callers do not provide
@@ -15,10 +19,11 @@ export const BRIDGE_HOME_ENV = 'BROWSER_BRIDGE_HOME';
15
19
  export const PUBLISHED_EXTENSION_ID = 'jjjkmmcdkpcgamlopogicbnnhdgebhie';
16
20
 
17
21
  /**
22
+ * @param {NodeJS.ProcessEnv} [env=process.env]
18
23
  * @returns {string}
19
24
  */
20
- export function getBridgeDir() {
21
- const override = process.env[BRIDGE_HOME_ENV];
25
+ export function getBridgeDir(env = process.env) {
26
+ const override = env[BRIDGE_HOME_ENV];
22
27
  if (override) {
23
28
  return override;
24
29
  }
@@ -31,28 +36,146 @@ export function getBridgeDir() {
31
36
  }
32
37
 
33
38
  if (platform === 'win32') {
34
- const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
39
+ const localAppData = env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
35
40
  return path.join(localAppData, 'Browser Bridge');
36
41
  }
37
42
 
38
- const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, '.local', 'share');
43
+ const xdgDataHome = env.XDG_DATA_HOME || path.join(home, '.local', 'share');
39
44
  return path.join(xdgDataHome, 'browser-bridge');
40
45
  }
41
46
 
42
47
  /**
48
+ * Resolve the IPC endpoint the daemon listens on and the CLI / native host
49
+ * connect to. On Windows we use a Named Pipe by default because Node's AF_UNIX
50
+ * support fails with EACCES on listen() under recent Node + Windows 11
51
+ * combinations, while Named Pipes are the historical and reliable Windows IPC
52
+ * mechanism. An explicit bridge-home override keeps the legacy socket path so
53
+ * callers can opt into custom test or compatibility setups.
54
+ *
55
+ * @param {NodeJS.ProcessEnv} [env=process.env]
56
+ * @returns {string}
57
+ */
58
+ export function getSocketPath(env = process.env) {
59
+ if (os.platform() === 'win32' && !env[BRIDGE_HOME_ENV]) {
60
+ return `\\\\.\\pipe\\${APP_NAME}`;
61
+ }
62
+ return path.join(getBridgeDir(env), 'bridge.sock');
63
+ }
64
+
65
+ /**
66
+ * @param {string} socketPath
67
+ * @returns {BridgeTransport}
68
+ */
69
+ export function createSocketBridgeTransport(socketPath) {
70
+ return {
71
+ type: 'socket',
72
+ socketPath,
73
+ label: socketPath,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * @param {number} port
79
+ * @param {string} [host='127.0.0.1']
80
+ * @returns {BridgeTransport}
81
+ */
82
+ export function createTcpBridgeTransport(port, host = '127.0.0.1') {
83
+ return {
84
+ type: 'tcp',
85
+ host,
86
+ port,
87
+ label: `${host}:${port}`,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * @param {NodeJS.ProcessEnv} [env=process.env]
93
+ * @returns {number | null}
94
+ */
95
+ export function getBridgeTcpPort(env = process.env) {
96
+ const raw = env[BRIDGE_TCP_PORT_ENV];
97
+ if (raw == null || raw === '') {
98
+ return null;
99
+ }
100
+
101
+ const port = Number.parseInt(raw, 10);
102
+ if (!Number.isInteger(port) || String(port) !== String(raw).trim() || port < 1 || port > 65535) {
103
+ throw new Error(
104
+ `${BRIDGE_TCP_PORT_ENV} must be an integer between 1 and 65535 (got ${JSON.stringify(raw)}).`
105
+ );
106
+ }
107
+
108
+ return port;
109
+ }
110
+
111
+ /**
112
+ * Align Windows CLI/daemon entrypoints with the installed native-host launcher,
113
+ * which uses TCP by default. Preserve explicit overrides and custom bridge-home
114
+ * test setups that rely on the socket transport.
115
+ *
116
+ * @param {NodeJS.ProcessEnv} [env=process.env]
117
+ * @returns {boolean}
118
+ */
119
+ export function applyWindowsTcpTransportDefaults(env = process.env) {
120
+ if (os.platform() !== 'win32') {
121
+ return false;
122
+ }
123
+ if (env[BRIDGE_TCP_PORT_ENV] != null && env[BRIDGE_TCP_PORT_ENV] !== '') {
124
+ return false;
125
+ }
126
+ if (env[BRIDGE_HOME_ENV]) {
127
+ return false;
128
+ }
129
+
130
+ env[BRIDGE_TCP_PORT_ENV] = String(DEFAULT_WINDOWS_TCP_PORT);
131
+ return true;
132
+ }
133
+
134
+ /**
135
+ * @param {NodeJS.ProcessEnv} [env=process.env]
136
+ * @returns {BridgeTransport}
137
+ */
138
+ export function getBridgeTransport(env = process.env) {
139
+ const tcpPort = getBridgeTcpPort(env);
140
+ if (tcpPort !== null) {
141
+ return createTcpBridgeTransport(tcpPort);
142
+ }
143
+
144
+ return createSocketBridgeTransport(getSocketPath(env));
145
+ }
146
+
147
+ /**
148
+ * @param {BridgeTransport} [transport=getBridgeTransport()]
149
+ * @returns {import('node:net').ListenOptions | string}
150
+ */
151
+ export function getBridgeListenTarget(transport = getBridgeTransport()) {
152
+ if (transport.type === 'tcp') {
153
+ return { host: transport.host, port: transport.port };
154
+ }
155
+ return transport.socketPath;
156
+ }
157
+
158
+ /**
159
+ * @param {BridgeTransport} [transport=getBridgeTransport()]
160
+ * @returns {string}
161
+ */
162
+ export function formatBridgeTransport(transport = getBridgeTransport()) {
163
+ return transport.label;
164
+ }
165
+
166
+ /**
167
+ * @param {NodeJS.ProcessEnv} [env=process.env]
43
168
  * @returns {string}
44
169
  */
45
- export function getSocketPath() {
46
- return path.join(getBridgeDir(), 'bridge.sock');
170
+ export function getDaemonPidPath(env = process.env) {
171
+ return path.join(getBridgeDir(env), 'daemon.pid');
47
172
  }
48
173
 
49
174
  /**
50
175
  * @returns {string}
51
176
  */
52
177
  export function getLauncherFilename() {
53
- return os.platform() === 'win32'
54
- ? 'native-host-launcher.cmd'
55
- : 'native-host-launcher.sh';
178
+ return os.platform() === 'win32' ? 'native-host-launcher.cmd' : 'native-host-launcher.sh';
56
179
  }
57
180
 
58
181
  /**
@@ -84,7 +207,7 @@ export function getManifestInstallDir(browser = 'chrome') {
84
207
  edge: path.join(macBase, 'Microsoft Edge', 'NativeMessagingHosts'),
85
208
  brave: path.join(macBase, 'BraveSoftware', 'Brave-Browser', 'NativeMessagingHosts'),
86
209
  chromium: path.join(macBase, 'Chromium', 'NativeMessagingHosts'),
87
- arc: path.join(macBase, 'Arc', 'User Data', 'NativeMessagingHosts')
210
+ arc: path.join(macBase, 'Arc', 'User Data', 'NativeMessagingHosts'),
88
211
  };
89
212
  return macPaths[browser] ?? macPaths.chrome;
90
213
  }
@@ -94,9 +217,15 @@ export function getManifestInstallDir(browser = 'chrome') {
94
217
  const winPaths = {
95
218
  chrome: path.join(winBase, 'Google', 'Chrome', 'User Data', 'NativeMessagingHosts'),
96
219
  edge: path.join(winBase, 'Microsoft', 'Edge', 'User Data', 'NativeMessagingHosts'),
97
- brave: path.join(winBase, 'BraveSoftware', 'Brave-Browser', 'User Data', 'NativeMessagingHosts'),
220
+ brave: path.join(
221
+ winBase,
222
+ 'BraveSoftware',
223
+ 'Brave-Browser',
224
+ 'User Data',
225
+ 'NativeMessagingHosts'
226
+ ),
98
227
  chromium: path.join(winBase, 'Chromium', 'User Data', 'NativeMessagingHosts'),
99
- arc: path.join(winBase, 'Arc', 'User Data', 'NativeMessagingHosts')
228
+ arc: path.join(winBase, 'Arc', 'User Data', 'NativeMessagingHosts'),
100
229
  };
101
230
  return winPaths[browser] ?? winPaths.chrome;
102
231
  }
@@ -107,7 +236,7 @@ export function getManifestInstallDir(browser = 'chrome') {
107
236
  edge: path.join(home, '.config', 'microsoft-edge', 'NativeMessagingHosts'),
108
237
  brave: path.join(home, '.config', 'BraveSoftware', 'Brave-Browser', 'NativeMessagingHosts'),
109
238
  chromium: path.join(home, '.config', 'chromium', 'NativeMessagingHosts'),
110
- arc: path.join(home, '.config', 'Arc', 'User Data', 'NativeMessagingHosts')
239
+ arc: path.join(home, '.config', 'Arc', 'User Data', 'NativeMessagingHosts'),
111
240
  };
112
241
  return linuxPaths[browser] ?? linuxPaths.chrome;
113
242
  }