@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
@@ -1,9 +1,6 @@
1
1
  // @ts-check
2
2
 
3
- import {
4
- estimateJsonPayloadCost,
5
- getCostClass,
6
- } from './index.js';
3
+ import { estimateJsonPayloadCost, getCostClass } from './index.js';
7
4
 
8
5
  /** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
9
6
  /** @typedef {import('./types.js').BridgeMethod} SummaryMethod */
@@ -53,16 +50,44 @@ import {
53
50
 
54
51
  /** @type {Record<string, ActionSummary>} */
55
52
  const ACTION_SUMMARIES = {
56
- hovered: { text: r => `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.`, evidence: r => r },
57
- dragged: { text: r => `Drag ${r.dragged ? 'completed' : 'failed'}: ${r.sourceRef} ${r.destinationRef}.`, evidence: r => r },
58
- closed: { text: r => `Tab ${r.tabId} closed.`, evidence: r => r },
59
- clicked: { text: r => `Clicked ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
60
- focused: { text: r => `Focused ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
61
- typed: { text: r => `Typed into ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
62
- pressed: { text: r => `Key pressed${r.key ? ` (${r.key})` : ''}.`, evidence: r => r },
63
- navigated: { text: r => `Navigated to ${r.url ?? 'page'}.`, evidence: r => ({ url: r.url }) },
64
- scrolled: { text: r => `Scrolled to (${r.x ?? 0}, ${r.y ?? 0}).`, evidence: r => r },
65
- resized: { text: r => `Viewport resized to ${r.width ?? '?'}×${r.height ?? '?'}.`, evidence: r => r },
53
+ hovered: {
54
+ text: (r) => `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.`,
55
+ evidence: (r) => r,
56
+ },
57
+ dragged: {
58
+ text: (r) =>
59
+ `Drag ${r.dragged ? 'completed' : 'failed'}: ${r.sourceRef} ${r.destinationRef}.`,
60
+ evidence: (r) => r,
61
+ },
62
+ closed: { text: (r) => `Tab ${r.tabId} closed.`, evidence: (r) => r },
63
+ clicked: {
64
+ text: (r) => `Clicked ${r.elementRef ?? 'element'}.`,
65
+ evidence: (r) => ({ elementRef: r.elementRef }),
66
+ },
67
+ focused: {
68
+ text: (r) => `Focused ${r.elementRef ?? 'element'}.`,
69
+ evidence: (r) => ({ elementRef: r.elementRef }),
70
+ },
71
+ typed: {
72
+ text: (r) => `Typed into ${r.elementRef ?? 'element'}.`,
73
+ evidence: (r) => ({ elementRef: r.elementRef }),
74
+ },
75
+ pressed: {
76
+ text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.`,
77
+ evidence: (r) => r,
78
+ },
79
+ navigated: {
80
+ text: (r) => `Navigated to ${r.url ?? 'page'}.`,
81
+ evidence: (r) => ({ url: r.url }),
82
+ },
83
+ scrolled: {
84
+ text: (r) => `Scrolled to (${r.x ?? 0}, ${r.y ?? 0}).`,
85
+ evidence: (r) => r,
86
+ },
87
+ resized: {
88
+ text: (r) => `Viewport resized to ${r.width ?? '?'}×${r.height ?? '?'}.`,
89
+ evidence: (r) => r,
90
+ },
66
91
  };
67
92
 
68
93
  /**
@@ -74,21 +99,24 @@ const ACTION_SUMMARIES = {
74
99
  * @returns {AnnotatedBridgeSummary}
75
100
  */
76
101
  export function annotateBridgeSummary(summary, response) {
77
- const transportBytes = getNumericMetaField(response.meta, 'transport_bytes')
78
- ?? getNumericMetaField(response.meta, 'response_bytes')
79
- ?? estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).bytes;
80
- const transportTokens = getNumericMetaField(response.meta, 'transport_approx_tokens')
81
- ?? getNumericMetaField(response.meta, 'approx_tokens')
82
- ?? estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).approxTokens;
102
+ const transportBytes =
103
+ getNumericMetaField(response.meta, 'transport_bytes') ??
104
+ getNumericMetaField(response.meta, 'response_bytes') ??
105
+ estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).bytes;
106
+ const transportTokens =
107
+ getNumericMetaField(response.meta, 'transport_approx_tokens') ??
108
+ getNumericMetaField(response.meta, 'approx_tokens') ??
109
+ estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).approxTokens;
83
110
  const summaryCost = estimateJsonPayloadCost(summary);
84
111
 
85
112
  return {
86
113
  ...summary,
87
114
  transportBytes,
88
115
  transportTokens,
89
- transportCostClass: getMetaCostClass(response.meta, 'transport_cost_class')
90
- ?? getMetaCostClass(response.meta, 'cost_class')
91
- ?? getCostClass(transportTokens),
116
+ transportCostClass:
117
+ getMetaCostClass(response.meta, 'transport_cost_class') ??
118
+ getMetaCostClass(response.meta, 'cost_class') ??
119
+ getCostClass(transportTokens),
92
120
  summaryBytes: summaryCost.bytes,
93
121
  summaryTokens: summaryCost.approxTokens,
94
122
  summaryCostClass: summaryCost.costClass,
@@ -132,28 +160,32 @@ export function summarizeBridgeResponse(response, method) {
132
160
  `${response.error.code}: ${response.error.message}${hint ? ` ${hint}` : ''}`,
133
161
  protocolWarning
134
162
  ),
135
- evidence: response.error.details ?? null
163
+ evidence: response.error.details ?? null,
136
164
  };
137
165
  }
138
166
 
139
167
  const result = toRecord(response.result);
140
168
  if (typeof result.daemon === 'string') {
141
- const access = result.access && typeof result.access === 'object'
142
- ? /** @type {Record<string, unknown>} */ (result.access)
143
- : null;
144
- const accessSummary = access == null
145
- ? ''
146
- : access.enabled
147
- ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
148
- : ' Access: disabled.';
169
+ const access =
170
+ result.access && typeof result.access === 'object'
171
+ ? /** @type {Record<string, unknown>} */ (result.access)
172
+ : null;
173
+ const accessSummary =
174
+ access == null
175
+ ? ''
176
+ : access.enabled
177
+ ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
178
+ : ' Access: disabled.';
149
179
  const connectedExtensions = Array.isArray(result.connectedExtensions)
150
180
  ? /** @type {Array<Record<string, unknown>>} */ (result.connectedExtensions)
151
181
  : [];
152
182
  const extensionSummary = result.extensionConnected
153
- ? `connected (${connectedExtensions.length}: ${connectedExtensions.map((ext) => {
154
- const label = `${ext.browserName ?? 'unknown'}${ext.profileLabel ? '/' + ext.profileLabel : ''}`;
155
- return ext.accessEnabled ? `${label}*` : label;
156
- }).join(', ')})`
183
+ ? `connected (${connectedExtensions.length}: ${connectedExtensions
184
+ .map((ext) => {
185
+ const label = `${ext.browserName ?? 'unknown'}${ext.profileLabel ? '/' + ext.profileLabel : ''}`;
186
+ return ext.accessEnabled ? `${label}*` : label;
187
+ })
188
+ .join(', ')})`
157
189
  : 'disconnected';
158
190
  return {
159
191
  ok: true,
@@ -161,22 +193,36 @@ export function summarizeBridgeResponse(response, method) {
161
193
  `Daemon: ${result.daemon}. Extension: ${extensionSummary}.${accessSummary}`,
162
194
  protocolWarning
163
195
  ),
164
- evidence: result
196
+ evidence: result,
165
197
  };
166
198
  }
167
199
  if (Array.isArray(result.mcpClients) && Array.isArray(result.skillTargets)) {
168
- const configuredMcp = result.mcpClients.filter((entry) => entry && typeof entry === 'object' && /** @type {Record<string, unknown>} */ (entry).configured).length;
169
- const installedSkills = result.skillTargets.filter((entry) => entry && typeof entry === 'object' && /** @type {Record<string, unknown>} */ (entry).installed).length;
200
+ const configuredMcp = result.mcpClients.filter(
201
+ (entry) =>
202
+ entry &&
203
+ typeof entry === 'object' &&
204
+ /** @type {Record<string, unknown>} */ (entry).configured
205
+ ).length;
206
+ const installedSkills = result.skillTargets.filter(
207
+ (entry) =>
208
+ entry &&
209
+ typeof entry === 'object' &&
210
+ /** @type {Record<string, unknown>} */ (entry).installed
211
+ ).length;
170
212
  return {
171
213
  ok: true,
172
214
  summary: appendProtocolWarning(
173
215
  `Setup: MCP configured for ${configuredMcp}/${result.mcpClients.length} clients; skill installed for ${installedSkills}/${result.skillTargets.length} targets.`,
174
216
  protocolWarning
175
217
  ),
176
- evidence: result
218
+ evidence: result,
177
219
  };
178
220
  }
179
- if (typeof result.url === 'string' && typeof result.title === 'string' && typeof result.origin === 'string') {
221
+ if (
222
+ typeof result.url === 'string' &&
223
+ typeof result.title === 'string' &&
224
+ typeof result.origin === 'string'
225
+ ) {
180
226
  /** @type {string[]} */
181
227
  const hints = [];
182
228
  if (result.hints && typeof result.hints === 'object') {
@@ -190,10 +236,18 @@ export function summarizeBridgeResponse(response, method) {
190
236
  `Page: ${result.title} (${result.origin})${hints.length ? ` [${hints.join(', ')}]` : ''}.`,
191
237
  protocolWarning
192
238
  ),
193
- evidence: { url: result.url, origin: result.origin, title: result.title, hints: result.hints }
239
+ evidence: {
240
+ url: result.url,
241
+ origin: result.origin,
242
+ title: result.title,
243
+ hints: result.hints,
244
+ },
194
245
  };
195
246
  }
196
- if ((typeof result.text === 'string' || typeof result.value === 'string') && typeof result.truncated === 'boolean') {
247
+ if (
248
+ (typeof result.text === 'string' || typeof result.value === 'string') &&
249
+ typeof result.truncated === 'boolean'
250
+ ) {
197
251
  const text = /** @type {string} */ (result.text ?? result.value);
198
252
  const len = typeof result.length === 'number' ? result.length : text.length;
199
253
  const label = method === 'dom.get_text' ? 'Element text' : 'Page text';
@@ -203,7 +257,11 @@ export function summarizeBridgeResponse(response, method) {
203
257
  `${label}: ${len} chars${result.truncated ? ' (truncated)' : ''}.`,
204
258
  protocolWarning
205
259
  ),
206
- evidence: { text: text.slice(0, 500), length: len, truncated: result.truncated }
260
+ evidence: {
261
+ text: text.slice(0, 500),
262
+ length: len,
263
+ truncated: result.truncated,
264
+ },
207
265
  };
208
266
  }
209
267
  if (Array.isArray(result.tabs)) {
@@ -215,14 +273,24 @@ export function summarizeBridgeResponse(response, method) {
215
273
  tabId: tab.tabId,
216
274
  active: tab.active,
217
275
  origin: tab.origin,
218
- title: tab.title
219
- }))
276
+ title: tab.title,
277
+ })),
220
278
  };
221
279
  }
222
- if (Array.isArray(result.nodes) && typeof result.total === 'number' && result.nodes[0]?.role !== undefined) {
280
+ if (
281
+ Array.isArray(result.nodes) &&
282
+ typeof result.total === 'number' &&
283
+ result.nodes[0]?.role !== undefined
284
+ ) {
223
285
  const nodes = /** @type {Array<Record<string, unknown>>} */ (result.nodes);
224
286
  const interactive = nodes.filter((/** @type {Record<string, unknown>} */ n) => n.interactive);
225
- const shown = interactive.length > 0 ? interactive : nodes.filter((/** @type {Record<string, unknown>} */ n) => n.role && n.role !== 'none' && n.role !== 'generic');
287
+ const shown =
288
+ interactive.length > 0
289
+ ? interactive
290
+ : nodes.filter(
291
+ (/** @type {Record<string, unknown>} */ n) =>
292
+ n.role && n.role !== 'none' && n.role !== 'generic'
293
+ );
226
294
  return {
227
295
  ok: true,
228
296
  summary: appendProtocolWarning(
@@ -235,7 +303,7 @@ export function summarizeBridgeResponse(response, method) {
235
303
  if (n.interactive) entry.interactive = true;
236
304
  if (n.value) entry.value = n.value;
237
305
  return entry;
238
- })
306
+ }),
239
307
  };
240
308
  }
241
309
  if (Array.isArray(result.nodes)) {
@@ -247,14 +315,20 @@ export function summarizeBridgeResponse(response, method) {
247
315
  if (typeof n.attrs === 'object' && n.attrs !== null) {
248
316
  const attrs = /** @type {Record<string, unknown>} */ (n.attrs);
249
317
  if (attrs.id) entry.id = attrs.id;
250
- if (typeof attrs.class === 'string') entry.cls = attrs.class.split(' ').slice(0, 3).join(' ');
318
+ if (typeof attrs.class === 'string')
319
+ entry.cls = attrs.class.split(' ').slice(0, 3).join(' ');
251
320
  if (attrs.role) entry.role = attrs.role;
252
321
  if (attrs['aria-label']) entry.label = attrs['aria-label'];
253
322
  if (attrs['data-testid']) entry.testId = attrs['data-testid'];
254
323
  }
255
324
  if (!entry.role && n.role) entry.role = n.role;
256
325
  if (!entry.label && n.name) entry.label = n.name;
257
- const text = typeof n.textExcerpt === 'string' ? n.textExcerpt : typeof n.text === 'string' ? n.text : '';
326
+ const text =
327
+ typeof n.textExcerpt === 'string'
328
+ ? n.textExcerpt
329
+ : typeof n.text === 'string'
330
+ ? n.text
331
+ : '';
258
332
  if (text) {
259
333
  entry.text = text.length > 80 ? `${text.slice(0, 79)}\u2026` : text;
260
334
  }
@@ -275,21 +349,21 @@ export function summarizeBridgeResponse(response, method) {
275
349
  `${label} ${nodes.length} element(s)${nodes.length > 15 ? '; showing first 15' : ''}.`,
276
350
  protocolWarning
277
351
  ),
278
- evidence: compact
352
+ evidence: compact,
279
353
  };
280
354
  }
281
355
  if (typeof result.rolledBack === 'boolean' || typeof result.rolled_back === 'boolean') {
282
356
  return {
283
357
  ok: true,
284
358
  summary: appendProtocolWarning('Patch rolled back.', protocolWarning),
285
- evidence: result
359
+ evidence: result,
286
360
  };
287
361
  }
288
362
  if (typeof result.patchId === 'string') {
289
363
  return {
290
364
  ok: true,
291
365
  summary: appendProtocolWarning(`Patch ${result.patchId} applied.`, protocolWarning),
292
- evidence: result
366
+ evidence: result,
293
367
  };
294
368
  }
295
369
  if (typeof result.found === 'boolean') {
@@ -301,7 +375,10 @@ export function summarizeBridgeResponse(response, method) {
301
375
  : `Element not found (timed out after ${result.duration ?? 0}ms).`,
302
376
  protocolWarning
303
377
  ),
304
- evidence: { elementRef: result.elementRef ?? null, duration: result.duration }
378
+ evidence: {
379
+ elementRef: result.elementRef ?? null,
380
+ duration: result.duration,
381
+ },
305
382
  };
306
383
  }
307
384
  if (typeof result.value !== 'undefined' && typeof result.type === 'string') {
@@ -313,7 +390,11 @@ export function summarizeBridgeResponse(response, method) {
313
390
  repr = '';
314
391
  } else if (typeof result.value === 'string') {
315
392
  repr = result.value.length > 200 ? `${result.value.slice(0, 199)}\u2026` : result.value;
316
- } else if (typeof result.value === 'object' && result.value !== null && Object.keys(result.value).length === 0) {
393
+ } else if (
394
+ typeof result.value === 'object' &&
395
+ result.value !== null &&
396
+ Object.keys(result.value).length === 0
397
+ ) {
317
398
  repr = '(empty - may be a Promise, Map, or non-serializable value)';
318
399
  } else {
319
400
  repr = JSON.stringify(result.value);
@@ -325,20 +406,33 @@ export function summarizeBridgeResponse(response, method) {
325
406
  repr ? `Evaluated to ${typeLabel}: ${repr}` : `Evaluated to ${typeLabel}.`,
326
407
  protocolWarning
327
408
  ),
328
- evidence: result
409
+ evidence: result,
329
410
  };
330
411
  }
331
- if (Array.isArray(result.entries) && result.entries.length > 0 && typeof result.entries[0]?.at === 'string' && typeof result.entries[0]?.method === 'string') {
412
+ if (
413
+ Array.isArray(result.entries) &&
414
+ result.entries.length > 0 &&
415
+ typeof result.entries[0]?.at === 'string' &&
416
+ typeof result.entries[0]?.method === 'string'
417
+ ) {
332
418
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
333
419
  return {
334
420
  ok: true,
335
421
  summary: appendProtocolWarning(`Log: ${entries.length} entries.`, protocolWarning),
336
422
  evidence: entries.slice(-10).map((/** @type {Record<string, unknown>} */ e) => ({
337
- at: e.at, method: e.method, ok: e.ok, ...(typeof e.source === 'string' && e.source ? { source: e.source } : {})
338
- }))
423
+ at: e.at,
424
+ method: e.method,
425
+ ok: e.ok,
426
+ ...(typeof e.source === 'string' && e.source ? { source: e.source } : {}),
427
+ })),
339
428
  };
340
429
  }
341
- if (Array.isArray(result.entries) && (result.entries.length > 0 ? (result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr') : method === 'page.get_network')) {
430
+ if (
431
+ Array.isArray(result.entries) &&
432
+ (result.entries.length > 0
433
+ ? result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr'
434
+ : method === 'page.get_network')
435
+ ) {
342
436
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
343
437
  return {
344
438
  ok: true,
@@ -347,8 +441,11 @@ export function summarizeBridgeResponse(response, method) {
347
441
  protocolWarning
348
442
  ),
349
443
  evidence: entries.slice(0, 20).map((/** @type {Record<string, unknown>} */ e) => ({
350
- method: e.method, url: truncateUrl(/** @type {string} */ (e.url)), status: e.status, duration: e.duration
351
- }))
444
+ method: e.method,
445
+ url: truncateUrl(/** @type {string} */ (e.url)),
446
+ status: e.status,
447
+ duration: e.duration,
448
+ })),
352
449
  };
353
450
  }
354
451
  if (Array.isArray(result.entries)) {
@@ -366,7 +463,7 @@ export function summarizeBridgeResponse(response, method) {
366
463
  entry.ago = formatRelativeTime(e.ts);
367
464
  }
368
465
  return entry;
369
- })
466
+ }),
370
467
  };
371
468
  }
372
469
  if (typeof result.html === 'string') {
@@ -376,7 +473,10 @@ export function summarizeBridgeResponse(response, method) {
376
473
  `HTML fragment: ${result.html.length} chars${result.truncated ? ' (truncated)' : ''}.`,
377
474
  protocolWarning
378
475
  ),
379
- evidence: { html: result.html.slice(0, 500), truncated: result.truncated }
476
+ evidence: {
477
+ html: result.html.slice(0, 500),
478
+ truncated: result.truncated,
479
+ },
380
480
  };
381
481
  }
382
482
  for (const [field, handler] of Object.entries(ACTION_SUMMARIES)) {
@@ -384,13 +484,12 @@ export function summarizeBridgeResponse(response, method) {
384
484
  return {
385
485
  ok: true,
386
486
  summary: appendProtocolWarning(handler.text(result), protocolWarning),
387
- evidence: handler.evidence(result)
487
+ evidence: handler.evidence(result),
388
488
  };
389
489
  }
390
490
  }
391
491
  if (typeof result.tabId === 'number' && typeof result.url === 'string') {
392
- const actionMethod =
393
- typeof result.method === 'string' ? result.method : method;
492
+ const actionMethod = typeof result.method === 'string' ? result.method : method;
394
493
  if (actionMethod === 'navigation.navigate') {
395
494
  return {
396
495
  ok: true,
@@ -405,16 +504,22 @@ export function summarizeBridgeResponse(response, method) {
405
504
  }
406
505
  return {
407
506
  ok: true,
408
- summary: appendProtocolWarning(`Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`, protocolWarning),
409
- evidence: result
507
+ summary: appendProtocolWarning(
508
+ `Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`,
509
+ protocolWarning
510
+ ),
511
+ evidence: result,
410
512
  };
411
513
  }
412
514
  if (typeof result.metrics === 'object' && result.metrics !== null) {
413
515
  const keys = Object.keys(result.metrics);
414
516
  return {
415
517
  ok: true,
416
- summary: appendProtocolWarning(`Performance: ${keys.length} metrics collected.`, protocolWarning),
417
- evidence: result.metrics
518
+ summary: appendProtocolWarning(
519
+ `Performance: ${keys.length} metrics collected.`,
520
+ protocolWarning
521
+ ),
522
+ evidence: result.metrics,
418
523
  };
419
524
  }
420
525
  if (typeof result.count === 'number' && typeof result.type === 'string' && result.entries) {
@@ -427,16 +532,26 @@ export function summarizeBridgeResponse(response, method) {
427
532
  }
428
533
  return {
429
534
  ok: true,
430
- summary: appendProtocolWarning(`Storage (${result.type}): ${result.count} entries.`, protocolWarning),
431
- evidence: safe
535
+ summary: appendProtocolWarning(
536
+ `Storage (${result.type}): ${result.count} entries.`,
537
+ protocolWarning
538
+ ),
539
+ evidence: safe,
432
540
  };
433
541
  }
434
- if (typeof result.tag === 'string' && typeof result.elementRef === 'string' && typeof result.bbox === 'object') {
542
+ if (
543
+ typeof result.tag === 'string' &&
544
+ typeof result.elementRef === 'string' &&
545
+ typeof result.bbox === 'object'
546
+ ) {
435
547
  const desc = [result.tag];
436
548
  if (result.id) desc[0] += `#${result.id}`;
437
- const textValue = typeof result.text === 'object' && result.text !== null && 'value' in result.text
438
- ? /** @type {{ value: string }} */ (result.text).value
439
- : typeof result.text === 'string' ? result.text : '';
549
+ const textValue =
550
+ typeof result.text === 'object' && result.text !== null && 'value' in result.text
551
+ ? /** @type {{ value: string }} */ (result.text).value
552
+ : typeof result.text === 'string'
553
+ ? result.text
554
+ : '';
440
555
  if (textValue) desc.push(String(textValue).slice(0, 60));
441
556
  const bbox = /** @type {Record<string, number>} */ (result.bbox);
442
557
  if (bbox.width && bbox.height) {
@@ -445,15 +560,29 @@ export function summarizeBridgeResponse(response, method) {
445
560
  return {
446
561
  ok: true,
447
562
  summary: appendProtocolWarning(`Element ${desc.join(', ')}.`, protocolWarning),
448
- evidence: { elementRef: result.elementRef, tag: result.tag, id: result.id, role: result.role, text: textValue, bbox: result.bbox }
563
+ evidence: {
564
+ elementRef: result.elementRef,
565
+ tag: result.tag,
566
+ id: result.id,
567
+ role: result.role,
568
+ text: textValue,
569
+ bbox: result.bbox,
570
+ },
449
571
  };
450
572
  }
451
- if (typeof result.properties === 'object' && result.properties !== null && typeof result.elementRef === 'string') {
573
+ if (
574
+ typeof result.properties === 'object' &&
575
+ result.properties !== null &&
576
+ typeof result.elementRef === 'string'
577
+ ) {
452
578
  const props = Object.keys(/** @type {object} */ (result.properties));
453
579
  return {
454
580
  ok: true,
455
- summary: appendProtocolWarning(`Computed ${props.length} style(s) for ${result.elementRef}.`, protocolWarning),
456
- evidence: result.properties
581
+ summary: appendProtocolWarning(
582
+ `Computed ${props.length} style(s) for ${result.elementRef}.`,
583
+ protocolWarning
584
+ ),
585
+ evidence: result.properties,
457
586
  };
458
587
  }
459
588
  if (method === 'styles.get_computed') {
@@ -461,23 +590,41 @@ export function summarizeBridgeResponse(response, method) {
461
590
  return {
462
591
  ok: true,
463
592
  summary: appendProtocolWarning(`Computed ${props.length} style(s).`, protocolWarning),
464
- evidence: result
593
+ evidence: result,
465
594
  };
466
595
  }
467
596
  if (typeof result.content === 'object' && typeof result.border === 'object') {
468
597
  const c = /** @type {Record<string, number>} */ (result.content);
469
598
  return {
470
599
  ok: true,
471
- summary: appendProtocolWarning(`Box model: ${c.width ?? '?'}×${c.height ?? '?'} at (${c.x ?? 0}, ${c.y ?? 0}).`, protocolWarning),
472
- evidence: result
600
+ summary: appendProtocolWarning(
601
+ `Box model: ${c.width ?? '?'}×${c.height ?? '?'} at (${c.x ?? 0}, ${c.y ?? 0}).`,
602
+ protocolWarning
603
+ ),
604
+ evidence: result,
473
605
  };
474
606
  }
475
- if (typeof result.x === 'number' && typeof result.y === 'number' && typeof result.width === 'number' && typeof result.height === 'number' &&
476
- !('clicked' in result || 'hovered' in result || 'focused' in result || 'resized' in result || 'tag' in result || 'elementRef' in result)) {
607
+ if (
608
+ typeof result.x === 'number' &&
609
+ typeof result.y === 'number' &&
610
+ typeof result.width === 'number' &&
611
+ typeof result.height === 'number' &&
612
+ !(
613
+ 'clicked' in result ||
614
+ 'hovered' in result ||
615
+ 'focused' in result ||
616
+ 'resized' in result ||
617
+ 'tag' in result ||
618
+ 'elementRef' in result
619
+ )
620
+ ) {
477
621
  return {
478
622
  ok: true,
479
- summary: appendProtocolWarning(`Box model: ${result.width}×${result.height} at (${result.x}, ${result.y}).`, protocolWarning),
480
- evidence: result
623
+ summary: appendProtocolWarning(
624
+ `Box model: ${result.width}×${result.height} at (${result.x}, ${result.y}).`,
625
+ protocolWarning
626
+ ),
627
+ evidence: result,
481
628
  };
482
629
  }
483
630
  if (Array.isArray(response.result) && looksLikePatchArray(response.result, method)) {
@@ -485,21 +632,24 @@ export function summarizeBridgeResponse(response, method) {
485
632
  return {
486
633
  ok: true,
487
634
  summary: appendProtocolWarning(`${patches.length} active patch(es).`, protocolWarning),
488
- evidence: patches.slice(0, 10)
635
+ evidence: patches.slice(0, 10),
489
636
  };
490
637
  }
491
638
  if (Array.isArray(result.patches)) {
492
639
  return {
493
640
  ok: true,
494
641
  summary: appendProtocolWarning(`${result.patches.length} active patch(es).`, protocolWarning),
495
- evidence: result.patches.slice(0, 10)
642
+ evidence: result.patches.slice(0, 10),
496
643
  };
497
644
  }
498
645
  const keys = Object.keys(result);
499
646
  return {
500
647
  ok: true,
501
- summary: appendProtocolWarning(`Bridge method succeeded with ${keys.length} top-level fields.`, protocolWarning),
502
- evidence: keys.slice(0, 10)
648
+ summary: appendProtocolWarning(
649
+ `Bridge method succeeded with ${keys.length} top-level fields.`,
650
+ protocolWarning
651
+ ),
652
+ evidence: keys.slice(0, 10),
503
653
  };
504
654
  }
505
655
 
@@ -518,7 +668,7 @@ export function summarizeBatchResponseItem({ method, tabId, response, durationMs
518
668
  approxTokens: cost.approxTokens,
519
669
  meta: response.meta,
520
670
  error: response.ok ? null : response.error,
521
- response: response.ok ? response.result : null
671
+ response: response.ok ? response.result : null,
522
672
  };
523
673
  }
524
674
 
@@ -535,9 +685,9 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
535
685
  error: {
536
686
  code: 'INTERNAL_ERROR',
537
687
  message,
538
- details: null
688
+ details: null,
539
689
  },
540
- meta: { protocol_version: '1.0' }
690
+ meta: { protocol_version: '1.0' },
541
691
  });
542
692
  const summary = annotateBridgeSummary(summarizeBridgeResponse(response, method), response);
543
693
  return {
@@ -548,7 +698,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
548
698
  approxTokens: 0,
549
699
  meta: response.meta,
550
700
  error: response.error,
551
- response: null
701
+ response: null,
552
702
  };
553
703
  }
554
704
 
@@ -557,9 +707,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
557
707
  * @returns {Record<string, unknown>}
558
708
  */
559
709
  function toRecord(value) {
560
- return value && typeof value === 'object'
561
- ? /** @type {Record<string, unknown>} */ (value)
562
- : {};
710
+ return value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
563
711
  }
564
712
 
565
713
  /**
@@ -623,12 +771,7 @@ function looksLikePatchArray(arr, method) {
623
771
  if (!Array.isArray(arr)) return false;
624
772
  if (arr.length === 0) return method === 'patch.list';
625
773
  const first = arr[0];
626
- return (
627
- first !== null &&
628
- typeof first === 'object' &&
629
- 'patchId' in first &&
630
- 'kind' in first
631
- );
774
+ return first !== null && typeof first === 'object' && 'patchId' in first && 'kind' in first;
632
775
  }
633
776
 
634
777
  /**
@@ -38,7 +38,7 @@ export {};
38
38
  * budget_applied?: boolean,
39
39
  * budget_truncated?: boolean,
40
40
  * continuation_hint?: string | null,
41
- * [key: string]: unknown
41
+ * [key: string]: unknown
42
42
  * }} BridgeMeta
43
43
  */
44
44
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: browser-bridge
3
- description: "Token-efficient Chrome tab inspection, interaction, and patching via local bridge extension (CLI: bbx). Reads live DOM, styles, console, network, and storage from a real Chrome tab with lower token cost than screenshots."
3
+ description: 'Token-efficient Chrome tab inspection, interaction, and patching via local bridge extension (CLI: bbx). Reads live DOM, styles, console, network, and storage from a real Chrome tab with lower token cost than screenshots.'
4
4
  ---
5
5
 
6
6
  # Browser Bridge
@@ -1,4 +1,4 @@
1
1
  interface:
2
- display_name: "Browser Bridge"
3
- short_description: "Token-efficient Chrome tab inspection, interaction, and patching via local bridge"
4
- default_prompt: "Use the browser-bridge skill for Chrome tab inspection, interaction, and patching. Start with `bbx status` to confirm connectivity. If ACCESS_DENIED, ask the user to click Enable in the extension popup/side panel, then retry. Default routing follows the active tab; use `--tab` only for a different tab. Prefer structured reads (`dom.query`, `styles.get_computed`) over screenshots. Batch independent reads with `bbx batch`. Use `bbx skill` for runtime presets."
2
+ display_name: 'Browser Bridge'
3
+ short_description: 'Token-efficient Chrome tab inspection, interaction, and patching via local bridge'
4
+ default_prompt: 'Use the browser-bridge skill for Chrome tab inspection, interaction, and patching. Start with `bbx status` to confirm connectivity. If ACCESS_DENIED, ask the user to click Enable in the extension popup/side panel, then retry. Default routing follows the active tab; use `--tab` only for a different tab. Prefer structured reads (`dom.query`, `styles.get_computed`) over screenshots. Batch independent reads with `bbx batch`. Use `bbx skill` for runtime presets.'