@browserbridge/bbx 1.0.0 → 1.0.1

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 (52) hide show
  1. package/README.md +3 -1
  2. package/docs/api-reference.md +33 -33
  3. package/docs/mcp-vs-cli.md +104 -104
  4. package/docs/publishing.md +1 -3
  5. package/docs/quickstart.md +6 -6
  6. package/docs/unpacked-extension.md +72 -0
  7. package/manifest.json +3 -17
  8. package/package.json +44 -42
  9. package/packages/agent-client/src/cli-helpers.js +10 -5
  10. package/packages/agent-client/src/cli.js +65 -135
  11. package/packages/agent-client/src/client.js +37 -17
  12. package/packages/agent-client/src/command-registry.js +101 -69
  13. package/packages/agent-client/src/detect.js +3 -6
  14. package/packages/agent-client/src/install.js +10 -27
  15. package/packages/agent-client/src/mcp-config.js +11 -30
  16. package/packages/agent-client/src/runtime.js +41 -20
  17. package/packages/agent-client/src/setup-status.js +13 -28
  18. package/packages/extension/src/background-helpers.js +51 -36
  19. package/packages/extension/src/background-routing.js +11 -13
  20. package/packages/extension/src/background.js +562 -299
  21. package/packages/extension/src/content-script-helpers.js +17 -16
  22. package/packages/extension/src/content-script.js +175 -109
  23. package/packages/extension/src/sidepanel-helpers.js +3 -1
  24. package/packages/extension/ui/popup.js +39 -20
  25. package/packages/extension/ui/sidepanel.js +108 -191
  26. package/packages/extension/ui/ui.css +2 -1
  27. package/packages/mcp-server/src/handlers.js +546 -250
  28. package/packages/mcp-server/src/server.js +558 -257
  29. package/packages/native-host/bin/bridge-daemon.js +6 -2
  30. package/packages/native-host/bin/install-manifest.js +2 -2
  31. package/packages/native-host/bin/postinstall.js +4 -2
  32. package/packages/native-host/src/config.js +11 -7
  33. package/packages/native-host/src/daemon.js +143 -92
  34. package/packages/native-host/src/install-manifest.js +73 -22
  35. package/packages/native-host/src/native-host.js +55 -40
  36. package/packages/protocol/src/budget.js +3 -7
  37. package/packages/protocol/src/capabilities.js +3 -3
  38. package/packages/protocol/src/errors.js +11 -11
  39. package/packages/protocol/src/protocol.js +104 -71
  40. package/packages/protocol/src/registry.js +300 -45
  41. package/packages/protocol/src/summary.js +249 -106
  42. package/packages/protocol/src/types.js +1 -1
  43. package/skills/browser-bridge/SKILL.md +1 -1
  44. package/skills/browser-bridge/agents/openai.yaml +3 -3
  45. package/skills/browser-bridge/references/interaction.md +33 -11
  46. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  47. package/skills/browser-bridge/references/protocol.md +125 -70
  48. package/skills/browser-bridge/references/tailwind.md +12 -11
  49. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  50. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  51. package/packages/extension/ui/offscreen.html +0 -6
  52. package/packages/extension/ui/offscreen.js +0 -61
package/package.json CHANGED
@@ -1,9 +1,36 @@
1
1
  {
2
2
  "name": "@browserbridge/bbx",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
- "type": "module",
5
+ "keywords": [
6
+ "agent-tools",
7
+ "ai-agent",
8
+ "browser",
9
+ "browser-automation",
10
+ "browser-bridge",
11
+ "chrome",
12
+ "chrome-extension",
13
+ "developer-tools",
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "native-messaging",
17
+ "ui-debugging"
18
+ ],
19
+ "homepage": "https://github.com/koltyakov/browser-bridge#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/koltyakov/browser-bridge/issues"
22
+ },
6
23
  "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/koltyakov/browser-bridge.git"
27
+ },
28
+ "bin": {
29
+ "bbx": "packages/agent-client/src/cli.js",
30
+ "bbx-daemon": "packages/native-host/bin/bridge-daemon.js",
31
+ "bbx-install": "packages/native-host/bin/install-manifest.js",
32
+ "bbx-mcp": "packages/mcp-server/src/bin.js"
33
+ },
7
34
  "files": [
8
35
  "assets",
9
36
  "CHANGELOG.md",
@@ -21,39 +48,15 @@
21
48
  "packages/protocol/src",
22
49
  "skills/browser-bridge"
23
50
  ],
24
- "bin": {
25
- "bbx": "packages/agent-client/src/cli.js",
26
- "bbx-mcp": "packages/mcp-server/src/bin.js",
27
- "bbx-daemon": "packages/native-host/bin/bridge-daemon.js",
28
- "bbx-install": "packages/native-host/bin/install-manifest.js"
29
- },
51
+ "type": "module",
30
52
  "publishConfig": {
31
53
  "access": "public"
32
54
  },
33
- "repository": {
34
- "type": "git",
35
- "url": "git+https://github.com/koltyakov/browser-bridge.git"
36
- },
37
- "homepage": "https://github.com/koltyakov/browser-bridge#readme",
38
- "bugs": {
39
- "url": "https://github.com/koltyakov/browser-bridge/issues"
40
- },
41
- "keywords": [
42
- "browser-bridge",
43
- "browser",
44
- "chrome",
45
- "chrome-extension",
46
- "native-messaging",
47
- "mcp",
48
- "model-context-protocol",
49
- "ai-agent",
50
- "agent-tools",
51
- "developer-tools",
52
- "ui-debugging",
53
- "browser-automation"
54
- ],
55
55
  "scripts": {
56
- "lint": "eslint .",
56
+ "lint": "oxlint . && oxfmt --check .",
57
+ "format": "oxfmt .",
58
+ "format:check": "oxfmt --check .",
59
+ "test:runtime": "node --test packages/protocol/test/*.test.js packages/native-host/test/*.test.js packages/agent-client/test/*.test.js packages/mcp-server/test/*.test.js packages/extension/test/*.test.js",
57
60
  "test": "c8 node --test packages/protocol/test/*.test.js packages/native-host/test/*.test.js packages/agent-client/test/*.test.js packages/mcp-server/test/*.test.js packages/extension/test/*.test.js",
58
61
  "coverage": "c8 report --reporter=text --reporter=text-summary",
59
62
  "coverage:check": "c8 check-coverage --lines 70",
@@ -67,20 +70,19 @@
67
70
  "daemon": "node packages/native-host/bin/bridge-daemon.js",
68
71
  "install-manifest": "node packages/native-host/bin/install-manifest.js"
69
72
  },
70
- "engines": {
71
- "node": ">=18"
73
+ "dependencies": {
74
+ "@modelcontextprotocol/sdk": "^1.29.0",
75
+ "zod": "^4.3.6"
72
76
  },
73
77
  "devDependencies": {
74
- "@eslint/js": "^10.0.1",
75
- "@types/chrome": "^0.1.38",
76
- "@types/node": "^25.4.0",
78
+ "@types/chrome": "^0.1.40",
79
+ "@types/node": "^25.5.2",
77
80
  "c8": "^11.0.0",
78
- "eslint": "^10.1.0",
79
- "globals": "^17.4.0",
80
- "typescript": "^6.0.2"
81
+ "oxfmt": "^0.47.0",
82
+ "oxlint": "^1.62.0",
83
+ "typescript": "^6.0.3"
81
84
  },
82
- "dependencies": {
83
- "@modelcontextprotocol/sdk": "^1.28.0",
84
- "zod": "^4.3.6"
85
+ "engines": {
86
+ "node": ">=18"
85
87
  }
86
88
  }
@@ -30,7 +30,10 @@ export function parseCommaList(value) {
30
30
  if (!value) {
31
31
  return [];
32
32
  }
33
- return value.split(',').map((item) => item.trim()).filter(Boolean);
33
+ return value
34
+ .split(',')
35
+ .map((item) => item.trim())
36
+ .filter(Boolean);
34
37
  }
35
38
 
36
39
  /**
@@ -50,7 +53,9 @@ export function parseJsonObject(value) {
50
53
  }
51
54
 
52
55
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
53
- throw new Error(`Expected a JSON object but got ${Array.isArray(parsed) ? 'array' : typeof parsed}. Wrap your input in {}.`);
56
+ throw new Error(
57
+ `Expected a JSON object but got ${Array.isArray(parsed) ? 'array' : typeof parsed}. Wrap your input in {}.`
58
+ );
54
59
  }
55
60
 
56
61
  return /** @type {Record<string, unknown>} */ (parsed);
@@ -131,7 +136,7 @@ export async function interactiveCheckbox(title, items) {
131
136
  }
132
137
 
133
138
  process.stdout.write('\x1b[?25l'); // hide cursor
134
- if (typeof /** @type {any} */ (process.stdin).setRawMode === 'function') {
139
+ if (typeof (/** @type {any} */ (process.stdin).setRawMode) === 'function') {
135
140
  /** @type {any} */ (process.stdin).setRawMode(true);
136
141
  }
137
142
  process.stdin.resume();
@@ -143,7 +148,7 @@ export async function interactiveCheckbox(title, items) {
143
148
  */
144
149
  function cleanup(result) {
145
150
  process.stdin.removeListener('keypress', onKeypress);
146
- if (typeof /** @type {any} */ (process.stdin).setRawMode === 'function') {
151
+ if (typeof (/** @type {any} */ (process.stdin).setRawMode) === 'function') {
147
152
  /** @type {any} */ (process.stdin).setRawMode(false);
148
153
  }
149
154
  process.stdin.pause();
@@ -204,7 +209,7 @@ export async function interactiveConfirm(prompt, options = {}) {
204
209
  const suffix = defaultValue ? ' [Y/n] ' : ' [y/N] ';
205
210
  const rl = readline.createInterface({
206
211
  input: process.stdin,
207
- output: process.stdout
212
+ output: process.stdout,
208
213
  });
209
214
 
210
215
  try {
@@ -42,16 +42,9 @@ import {
42
42
  MCP_CLIENT_NAMES,
43
43
  removeMcpConfig,
44
44
  } from './mcp-config.js';
45
- import {
46
- getDoctorReport,
47
- requestBridge,
48
- resolveRef,
49
- } from './runtime.js';
45
+ import { getDoctorReport, requestBridge, resolveRef } from './runtime.js';
50
46
  import { collectSetupStatus } from './setup-status.js';
51
- import {
52
- annotateBridgeSummary,
53
- summarizeBridgeResponse,
54
- } from './subagent.js';
47
+ import { annotateBridgeSummary, summarizeBridgeResponse } from './subagent.js';
55
48
 
56
49
  /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
57
50
  /** @typedef {{ image: string, rect: Record<string, unknown> }} ScreenshotResult */
@@ -66,7 +59,7 @@ const REQUEST_SOURCE = 'cli';
66
59
  * @returns {string}
67
60
  */
68
61
  function stripAnsi(str) {
69
- // eslint-disable-next-line no-control-regex
62
+ // oxlint-disable-next-line no-control-regex
70
63
  return str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\x1b[^[]/g, '');
71
64
  }
72
65
 
@@ -82,7 +75,10 @@ function sanitizeOutput(value) {
82
75
  if (Array.isArray(value)) return value.map(sanitizeOutput);
83
76
  if (value !== null && typeof value === 'object') {
84
77
  return Object.fromEntries(
85
- Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [k, sanitizeOutput(v)])
78
+ Object.entries(/** @type {Record<string, unknown>} */ (value)).map(([k, v]) => [
79
+ k,
80
+ sanitizeOutput(v),
81
+ ])
86
82
  );
87
83
  }
88
84
  return value;
@@ -98,9 +94,7 @@ function readStdin() {
98
94
  const chunks = /** @type {Buffer[]} */ ([]);
99
95
  process.stdin.setEncoding('utf8');
100
96
  process.stdin.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
101
- process.stdin.on('end', () =>
102
- resolve(Buffer.concat(chunks).toString('utf8').trim()),
103
- );
97
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim()));
104
98
  process.stdin.on('error', reject);
105
99
  // If stdin is a TTY and nothing is piped, read nothing
106
100
  if (process.stdin.isTTY) {
@@ -117,7 +111,10 @@ if (!command || ['help', '--help', '-h'].includes(command)) {
117
111
  }
118
112
 
119
113
  if (['--version', '-v'].includes(command)) {
120
- const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
114
+ const pkgPath = path.resolve(
115
+ path.dirname(fileURLToPath(import.meta.url)),
116
+ '../../../package.json'
117
+ );
121
118
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
122
119
  process.stdout.write(`${pkg.version}\n`);
123
120
  process.exit(0);
@@ -125,7 +122,7 @@ if (['--version', '-v'].includes(command)) {
125
122
 
126
123
  if (command === 'skill') {
127
124
  process.stdout.write(
128
- `${JSON.stringify(createRuntimeContext(), null, process.stdout.isTTY ? 2 : undefined)}\n`,
125
+ `${JSON.stringify(createRuntimeContext(), null, process.stdout.isTTY ? 2 : undefined)}\n`
129
126
  );
130
127
  process.exit(0);
131
128
  }
@@ -135,7 +132,7 @@ if (command === 'install') {
135
132
  const { fileURLToPath } = await import('node:url');
136
133
  const installScript = path.resolve(
137
134
  path.dirname(fileURLToPath(import.meta.url)),
138
- '../../native-host/bin/install-manifest.js',
135
+ '../../native-host/bin/install-manifest.js'
139
136
  );
140
137
  execFileSync(process.execPath, [installScript, ...rest], {
141
138
  stdio: 'inherit',
@@ -173,12 +170,10 @@ if (command === 'install-skill') {
173
170
  const installedManagedTargets = new Set(
174
171
  setupStatus.skillTargets
175
172
  .filter((entry) => entry.installed && entry.managed)
176
- .map((entry) => entry.key),
173
+ .map((entry) => entry.key)
177
174
  );
178
175
  const installedManagedTargetList =
179
- /** @type {import('./install.js').SupportedTarget[]} */ ([
180
- ...installedManagedTargets,
181
- ]);
176
+ /** @type {import('./install.js').SupportedTarget[]} */ ([...installedManagedTargets]);
182
177
 
183
178
  // Aliases like 'openai' and 'google' map to canonical targets and stay omitted.
184
179
  const items = SUPPORTED_TARGETS.map((t) => ({
@@ -193,30 +188,23 @@ if (command === 'install-skill') {
193
188
 
194
189
  const selected = await interactiveCheckbox(
195
190
  'Select agents to install skill for (↑↓ move · space toggle · a all · enter confirm)',
196
- items,
191
+ items
197
192
  );
198
193
 
199
194
  /** @type {import('./install.js').SupportedTarget[]} */
200
195
  let targets;
201
196
  if (selected === null) {
202
197
  // Non-TTY: prefer managed installs, then detected targets (always includes 'agents').
203
- targets =
204
- installedManagedTargets.size > 0
205
- ? installedManagedTargetList
206
- : detected;
198
+ targets = installedManagedTargets.size > 0 ? installedManagedTargetList : detected;
207
199
  } else {
208
- targets = /** @type {import('./install.js').SupportedTarget[]} */ (
209
- selected
210
- );
200
+ targets = /** @type {import('./install.js').SupportedTarget[]} */ (selected);
211
201
  }
212
202
 
213
203
  const projectPath = isGlobal ? os.homedir() : process.cwd();
214
204
  if (selected !== null) {
215
205
  const deselectedTargets =
216
206
  /** @type {import('./install.js').SupportedTarget[]} */ (
217
- installedManagedTargetList.filter(
218
- (target) => !targets.includes(target),
219
- )
207
+ installedManagedTargetList.filter((target) => !targets.includes(target))
220
208
  );
221
209
  const removableTargets = await findInstalledManagedTargets({
222
210
  targets: deselectedTargets,
@@ -225,7 +213,7 @@ if (command === 'install-skill') {
225
213
  });
226
214
  if (removableTargets.length > 0) {
227
215
  const confirmed = await interactiveConfirm(
228
- `Remove Browser Bridge skill from deselected targets: ${removableTargets.join(', ')}?`,
216
+ `Remove Browser Bridge skill from deselected targets: ${removableTargets.join(', ')}?`
229
217
  );
230
218
  if (confirmed) {
231
219
  const removedPaths = await removeAgentFiles({
@@ -289,14 +277,10 @@ if (command === 'install-mcp') {
289
277
  });
290
278
  const detected = detectMcpClients();
291
279
  const configuredClients = new Set(
292
- setupStatus.mcpClients
293
- .filter((entry) => entry.configured)
294
- .map((entry) => entry.key),
280
+ setupStatus.mcpClients.filter((entry) => entry.configured).map((entry) => entry.key)
295
281
  );
296
282
  const configuredClientList =
297
- /** @type {import('./mcp-config.js').McpClientName[]} */ ([
298
- ...configuredClients,
299
- ]);
283
+ /** @type {import('./mcp-config.js').McpClientName[]} */ ([...configuredClients]);
300
284
  const items = MCP_CLIENT_NAMES.map((c) => ({
301
285
  value: c,
302
286
  label: `${c.padEnd(10)} ${MCP_CLIENT_LABELS[c]}`,
@@ -309,7 +293,7 @@ if (command === 'install-mcp') {
309
293
 
310
294
  const selected = await interactiveCheckbox(
311
295
  'Select clients to configure (↑↓ move · space toggle · a all · enter confirm)',
312
- items,
296
+ items
313
297
  );
314
298
 
315
299
  if (selected === null) {
@@ -321,17 +305,13 @@ if (command === 'install-mcp') {
321
305
  ? detected
322
306
  : [...MCP_CLIENT_NAMES];
323
307
  } else {
324
- clients = /** @type {import('./mcp-config.js').McpClientName[]} */ (
325
- selected
326
- );
308
+ clients = /** @type {import('./mcp-config.js').McpClientName[]} */ (selected);
327
309
  }
328
310
 
329
311
  if (selected !== null) {
330
312
  const deselectedClients =
331
313
  /** @type {import('./mcp-config.js').McpClientName[]} */ (
332
- configuredClientList.filter(
333
- (clientName) => !clients.includes(clientName),
334
- )
314
+ configuredClientList.filter((clientName) => !clients.includes(clientName))
335
315
  );
336
316
  const removableClients = await findConfiguredMcpClients({
337
317
  clients: deselectedClients,
@@ -340,7 +320,7 @@ if (command === 'install-mcp') {
340
320
  });
341
321
  if (removableClients.length > 0) {
342
322
  const confirmed = await interactiveConfirm(
343
- `Remove Browser Bridge MCP config from deselected clients: ${removableClients.join(', ')}?`,
323
+ `Remove Browser Bridge MCP config from deselected clients: ${removableClients.join(', ')}?`
344
324
  );
345
325
  if (confirmed) {
346
326
  for (const clientName of removableClients) {
@@ -371,7 +351,7 @@ if (command === 'install-mcp') {
371
351
  for (const part of parts) {
372
352
  if (!isMcpClientName(part)) {
373
353
  process.stderr.write(
374
- `Unknown client "${part}". Supported: ${MCP_CLIENT_NAMES.join(', ')}, all\n`,
354
+ `Unknown client "${part}". Supported: ${MCP_CLIENT_NAMES.join(', ')}, all\n`
375
355
  );
376
356
  process.exit(1);
377
357
  }
@@ -396,9 +376,7 @@ if (command === 'mcp') {
396
376
  }
397
377
  if (subcommand === 'config') {
398
378
  if (!clientName || !isMcpClientName(clientName)) {
399
- process.stderr.write(
400
- `Usage: bbx mcp config <${MCP_CLIENT_NAMES.join('|')}>\n`,
401
- );
379
+ process.stderr.write(`Usage: bbx mcp config <${MCP_CLIENT_NAMES.join('|')}>\n`);
402
380
  process.exit(1);
403
381
  }
404
382
  process.stdout.write(formatMcpConfig(clientName));
@@ -419,7 +397,7 @@ async function main() {
419
397
  client,
420
398
  'health.ping',
421
399
  {},
422
- { source: REQUEST_SOURCE },
400
+ { source: REQUEST_SOURCE }
423
401
  );
424
402
  await printSummary(healthResponse);
425
403
  return;
@@ -427,12 +405,7 @@ async function main() {
427
405
 
428
406
  if (command === 'access-request') {
429
407
  await printSummary(
430
- await requestBridge(
431
- client,
432
- 'access.request',
433
- {},
434
- { source: REQUEST_SOURCE },
435
- ),
408
+ await requestBridge(client, 'access.request', {}, { source: REQUEST_SOURCE })
436
409
  );
437
410
  return;
438
411
  }
@@ -444,28 +417,19 @@ async function main() {
444
417
  summary:
445
418
  report.issues.length === 0
446
419
  ? 'Browser Bridge is ready.'
447
- : `Browser Bridge has ${report.issues.length} setup issue(s).`,
420
+ : `Browser Bridge has ${report.issues.length} readiness issue(s).`,
448
421
  evidence: report,
449
422
  });
450
423
  return;
451
424
  }
452
425
 
453
426
  if (command === 'logs') {
454
- await printSummary(
455
- await requestBridge(client, 'log.tail', {}, { source: REQUEST_SOURCE }),
456
- );
427
+ await printSummary(await requestBridge(client, 'log.tail', {}, { source: REQUEST_SOURCE }));
457
428
  return;
458
429
  }
459
430
 
460
431
  if (command === 'tabs') {
461
- await printSummary(
462
- await requestBridge(
463
- client,
464
- 'tabs.list',
465
- {},
466
- { source: REQUEST_SOURCE },
467
- ),
468
- );
432
+ await printSummary(await requestBridge(client, 'tabs.list', {}, { source: REQUEST_SOURCE }));
469
433
  return;
470
434
  }
471
435
 
@@ -477,7 +441,7 @@ async function main() {
477
441
  {
478
442
  url: url || undefined,
479
443
  },
480
- { source: REQUEST_SOURCE },
444
+ { source: REQUEST_SOURCE }
481
445
  );
482
446
  await printSummary(response);
483
447
  return;
@@ -494,7 +458,7 @@ async function main() {
494
458
  {
495
459
  tabId: parseIntArg(tabId, 'tabId'),
496
460
  },
497
- { source: REQUEST_SOURCE },
461
+ { source: REQUEST_SOURCE }
498
462
  );
499
463
  await printSummary(response);
500
464
  return;
@@ -514,9 +478,7 @@ async function main() {
514
478
  await ensureClientConnection();
515
479
  const input = rest[0];
516
480
  if (!input) {
517
- throw new Error(
518
- 'Usage: batch \'[{"method":"...","params":{...}}, ...]\'',
519
- );
481
+ throw new Error('Usage: batch \'[{"method":"...","params":{...}}, ...]\'');
520
482
  }
521
483
  const calls = JSON.parse(input);
522
484
  if (!Array.isArray(calls)) {
@@ -534,7 +496,10 @@ async function main() {
534
496
  durationMs: 0,
535
497
  approxTokens: 0,
536
498
  meta: { protocol_version: '1.0' },
537
- error: { code: 'INVALID_REQUEST', message: 'Each batch call needs a method.' },
499
+ error: {
500
+ code: 'INVALID_REQUEST',
501
+ message: 'Each batch call needs a method.',
502
+ },
538
503
  response: null,
539
504
  };
540
505
  }
@@ -548,14 +513,16 @@ async function main() {
548
513
  durationMs: 0,
549
514
  approxTokens: 0,
550
515
  meta: { protocol_version: '1.0' },
551
- error: { code: 'INVALID_REQUEST', message: `Unknown bridge method "${call.method}".` },
516
+ error: {
517
+ code: 'INVALID_REQUEST',
518
+ message: `Unknown bridge method "${call.method}".`,
519
+ },
552
520
  response: null,
553
521
  };
554
522
  }
555
523
  const method = /** @type {BridgeMethod} */ (call.method);
556
- const tabId = methodNeedsTab(call.method) && typeof call.tabId === 'number'
557
- ? call.tabId
558
- : null;
524
+ const tabId =
525
+ methodNeedsTab(call.method) && typeof call.tabId === 'number' ? call.tabId : null;
559
526
  const startTime = Date.now();
560
527
  try {
561
528
  const response = await client.request({
@@ -578,20 +545,14 @@ async function main() {
578
545
  durationMs: Date.now() - startTime,
579
546
  });
580
547
  }
581
- }),
548
+ })
582
549
  );
583
550
  printJson(results);
584
551
  return;
585
552
  }
586
553
 
587
- if (
588
- command.includes('.')
589
- && METHODS.includes(/** @type {BridgeMethod} */ (command))
590
- ) {
591
- const { tabId, method, params } = await parseCallCommand([
592
- command,
593
- ...rest,
594
- ]);
554
+ if (command.includes('.') && METHODS.includes(/** @type {BridgeMethod} */ (command))) {
555
+ const { tabId, method, params } = await parseCallCommand([command, ...rest]);
595
556
  const response = await requestBridge(client, method, params, {
596
557
  tabId,
597
558
  source: REQUEST_SOURCE,
@@ -605,18 +566,13 @@ async function main() {
605
566
  let elementRef;
606
567
  if (shortcutCmd.resolve) {
607
568
  if (!rest[0]) throw new Error(`Usage: ${command} <ref|selector>`);
608
- elementRef = await resolveRef(
609
- client,
610
- rest[0],
611
- null,
612
- REQUEST_SOURCE,
613
- );
569
+ elementRef = await resolveRef(client, rest[0], null, REQUEST_SOURCE);
614
570
  }
615
571
  const response = await requestBridge(
616
572
  client,
617
573
  shortcutCmd.method,
618
574
  shortcutCmd.build(rest, elementRef),
619
- { source: REQUEST_SOURCE },
575
+ { source: REQUEST_SOURCE }
620
576
  );
621
577
  await printSummary(response, shortcutCmd.printMethod);
622
578
  return;
@@ -626,12 +582,7 @@ async function main() {
626
582
  const [key, refOrSelector] = rest;
627
583
  if (!key) throw new Error('Usage: press-key <key> [ref|selector]');
628
584
  const elementRef = refOrSelector
629
- ? await resolveRef(
630
- client,
631
- refOrSelector,
632
- null,
633
- REQUEST_SOURCE,
634
- )
585
+ ? await resolveRef(client, refOrSelector, null, REQUEST_SOURCE)
635
586
  : undefined;
636
587
  const response = await requestBridge(
637
588
  client,
@@ -640,7 +591,7 @@ async function main() {
640
591
  key,
641
592
  target: elementRef ? { elementRef } : undefined,
642
593
  },
643
- { source: REQUEST_SOURCE },
594
+ { source: REQUEST_SOURCE }
644
595
  );
645
596
  await printSummary(response);
646
597
  return;
@@ -649,33 +600,22 @@ async function main() {
649
600
  if (command === 'screenshot') {
650
601
  const [refOrSelector, outputPath] = rest;
651
602
  if (!refOrSelector) throw new Error('Usage: screenshot <ref|selector> [path]');
652
- const elementRef = await resolveRef(
653
- client,
654
- refOrSelector,
655
- null,
656
- REQUEST_SOURCE,
657
- );
603
+ const elementRef = await resolveRef(client, refOrSelector, null, REQUEST_SOURCE);
658
604
  const response = await requestBridge(
659
605
  client,
660
606
  'screenshot.capture_element',
661
607
  {
662
608
  elementRef,
663
609
  },
664
- { source: REQUEST_SOURCE },
610
+ { source: REQUEST_SOURCE }
665
611
  );
666
612
  if (!response.ok) {
667
613
  await printSummary(response);
668
614
  return;
669
615
  }
670
- const screenshotResult = /** @type {ScreenshotResult} */ (
671
- response.result
672
- );
673
- const filePath =
674
- outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.png`);
675
- const data = screenshotResult.image.replace(
676
- /^data:image\/png;base64,/,
677
- '',
678
- );
616
+ const screenshotResult = /** @type {ScreenshotResult} */ (response.result);
617
+ const filePath = outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.png`);
618
+ const data = screenshotResult.image.replace(/^data:image\/png;base64,/, '');
679
619
  await fs.promises.writeFile(filePath, Buffer.from(data, 'base64'));
680
620
  printJson({
681
621
  ok: true,
@@ -689,9 +629,7 @@ async function main() {
689
629
  let expression = rest.join(' ');
690
630
  if (!expression || expression === '-') expression = await readStdin();
691
631
  if (!expression)
692
- throw new Error(
693
- 'Usage: eval <expression> (or pipe via stdin: echo "expr" | bbx eval -)',
694
- );
632
+ throw new Error('Usage: eval <expression> (or pipe via stdin: echo "expr" | bbx eval -)');
695
633
  const response = await requestBridge(
696
634
  client,
697
635
  'page.evaluate',
@@ -699,7 +637,7 @@ async function main() {
699
637
  expression,
700
638
  returnByValue: true,
701
639
  },
702
- { source: REQUEST_SOURCE },
640
+ { source: REQUEST_SOURCE }
703
641
  );
704
642
  await printSummary(response);
705
643
  return;
@@ -710,10 +648,7 @@ async function main() {
710
648
  process.exitCode = 1;
711
649
  } catch (error) {
712
650
  const message = error instanceof Error ? error.message : String(error);
713
- const raw =
714
- error instanceof Error && 'code' in error
715
- ? /** @type {any} */ (error).code
716
- : '';
651
+ const raw = error instanceof Error && 'code' in error ? /** @type {any} */ (error).code : '';
717
652
  let code = 'ERROR';
718
653
  if (raw === 'ENOENT' || raw === 'ECONNREFUSED') {
719
654
  code = 'DAEMON_OFFLINE';
@@ -766,10 +701,7 @@ async function ensureClientConnection() {
766
701
  * @returns {Promise<void>}
767
702
  */
768
703
  async function printSummary(response, method) {
769
- printJson(annotateBridgeSummary(
770
- summarizeBridgeResponse(response, method),
771
- response,
772
- ));
704
+ printJson(annotateBridgeSummary(summarizeBridgeResponse(response, method), response));
773
705
  }
774
706
 
775
707
  /**
@@ -778,7 +710,7 @@ async function printSummary(response, method) {
778
710
  */
779
711
  function printJson(value) {
780
712
  process.stdout.write(
781
- `${JSON.stringify(sanitizeOutput(value), null, process.stdout.isTTY ? 2 : undefined)}\n`,
713
+ `${JSON.stringify(sanitizeOutput(value), null, process.stdout.isTTY ? 2 : undefined)}\n`
782
714
  );
783
715
  }
784
716
 
@@ -846,9 +778,7 @@ async function parseCallCommand(args) {
846
778
  if (first.includes('.')) {
847
779
  const method = /** @type {BridgeMethod} */ (first);
848
780
  if (!METHODS.includes(method)) {
849
- throw new Error(
850
- `Unknown method "${first}". Run bbx skill to see available methods.`,
851
- );
781
+ throw new Error(`Unknown method "${first}". Run bbx skill to see available methods.`);
852
782
  }
853
783
  let rawParams = second;
854
784
  // Support piped stdin: `echo '{"key":"val"}' | bbx call method -`