@browserbridge/bbx 1.7.7 → 1.8.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 (34) hide show
  1. package/PRIVACY.md +111 -0
  2. package/README.md +13 -5
  3. package/RESPONSIBLE_USE.md +16 -0
  4. package/SECURITY.md +52 -0
  5. package/package.json +12 -6
  6. package/packages/agent-client/src/cli-args.js +24 -0
  7. package/packages/agent-client/src/cli.js +46 -52
  8. package/packages/agent-client/src/client.js +140 -5
  9. package/packages/agent-client/src/command-registry.js +56 -53
  10. package/packages/agent-client/src/content-extract.js +198 -0
  11. package/packages/agent-client/src/remotes.js +1 -1
  12. package/packages/agent-client/src/runtime.js +768 -35
  13. package/packages/agent-client/src/types.ts +131 -6
  14. package/packages/mcp-server/src/guidance.js +1 -1
  15. package/packages/mcp-server/src/handlers-capture.js +41 -5
  16. package/packages/mcp-server/src/handlers-dom.js +8 -2
  17. package/packages/mcp-server/src/handlers-navigation.js +2 -1
  18. package/packages/mcp-server/src/handlers-page.js +41 -4
  19. package/packages/mcp-server/src/handlers-utils.js +67 -7
  20. package/packages/mcp-server/src/handlers.js +4 -4
  21. package/packages/mcp-server/src/server.js +102 -6
  22. package/packages/native-host/src/daemon.js +158 -6
  23. package/packages/native-host/src/native-host.js +3 -1
  24. package/packages/protocol/src/defaults.js +3 -0
  25. package/packages/protocol/src/errors.js +54 -1
  26. package/packages/protocol/src/protocol.js +297 -1
  27. package/packages/protocol/src/registry.js +66 -35
  28. package/packages/protocol/src/summary.js +87 -5
  29. package/packages/protocol/src/types.ts +234 -0
  30. package/skills/browser-bridge/SKILL.md +34 -13
  31. package/skills/browser-bridge/references/interaction.md +55 -11
  32. package/skills/browser-bridge/references/patch-workflow.md +2 -0
  33. package/skills/browser-bridge/references/protocol.md +157 -82
  34. package/skills/browser-bridge/references/token-efficiency.md +19 -13
package/PRIVACY.md ADDED
@@ -0,0 +1,111 @@
1
+ # Browser Bridge (BBX) Privacy Policy
2
+
3
+ Last updated: 2026-07-22
4
+
5
+ ## Overview
6
+
7
+ Browser Bridge is a developer tool that lets a user-approved local agent inspect and interact with web pages in an explicitly enabled Chrome window. This policy describes how Browser Bridge handles data.
8
+
9
+ ## What Browser Bridge can access
10
+
11
+ When you enable Browser Bridge for a Chrome window, Browser Bridge can access page data from tabs in that window as needed to fulfill your requests. Depending on the action you or your connected agent triggers, this can include:
12
+
13
+ - Page metadata such as URL, title, viewport, and scroll position
14
+ - DOM structure, element attributes, visible text, and layout information
15
+ - Computed styles, class/inline-style context, and semantic accessibility-tree data
16
+ - Console messages and uncaught page errors
17
+ - Network request metadata captured from page `fetch` and `XMLHttpRequest` activity, such as URL, method, status, timing, type, timestamp, and size
18
+ - Optional Chrome DevTools Protocol metadata for broader resource activity, including request ID, redacted URL, method, resource type, status, MIME type, protocol, cache indicators, redirects, failure reason, duration, and timestamp
19
+ - Current JavaScript dialog information when observable and explicitly requested, including dialog type, bounded message text, and bounded default prompt text
20
+ - `localStorage` and `sessionStorage` values when explicitly requested
21
+ - Screenshots or cropped image captures when explicitly requested
22
+ - User-triggered interaction data and results such as clicks, native pointer movement/drag, DOM or native text dispatch, key presses, selection changes, dialog decisions, and navigation actions
23
+ - Bounded semantic descriptors for optional stale-element recovery, represented in memory with normalized hashes and structural context rather than page text values or attributes written into the application DOM
24
+
25
+ All-resource network inspection excludes request and response bodies, cookies,
26
+ authorization values, and complete headers. It removes URL credentials and
27
+ fragments, replaces query parameter values with a redaction marker, and
28
+ summarizes data/blob URLs before returning them. Browser Bridge does not expose
29
+ these excluded values through that feature.
30
+
31
+ Browser Bridge is designed for local debugging and automation of the browser state you intentionally expose to it.
32
+
33
+ ## How Browser Bridge uses data
34
+
35
+ Browser Bridge uses this data only to provide its user-facing purpose: letting you inspect, test, and patch the currently enabled browser window from a connected developer tool or agent.
36
+
37
+ The normal local path sends request and response data through:
38
+
39
+ 1. The Browser Bridge Chrome extension
40
+ 2. The Browser Bridge native messaging host running on your machine
41
+ 3. The agent client or IDE that you intentionally connect
42
+
43
+ The extension/native-host segment remains on the browser machine. If you explicitly configure an authenticated remote destination, the daemon can send bridge results over your own SSH tunnel or network route to that selected client. Browser Bridge does not operate a Browser Bridge cloud service and does not send extension data to developer-operated remote servers. Raw remote TCP is not presented as encrypted.
44
+
45
+ ## Sharing and downstream processing
46
+
47
+ Browser Bridge shares page data only with the companion components and client you choose to connect. The normal path is local. If you explicitly configure Browser Bridge's authenticated remote proxy and your own tunnel or network route, the selected remote client can receive bridge results through that user-configured path; Browser Bridge does not provide or operate that network service.
48
+
49
+ Important:
50
+
51
+ - Browser Bridge itself does not sell your data.
52
+ - Browser Bridge itself does not use your data for advertising.
53
+ - Browser Bridge itself does not transfer page data to Browser Bridge-operated remote infrastructure.
54
+ - Your connected agent or IDE may, depending on its own configuration and provider, forward prompts, tool calls, or tool results to remote services. That downstream handling is controlled by the connected product, not by Browser Bridge.
55
+
56
+ You should review the privacy and retention practices of any connected agent, IDE, or model provider you use with Browser Bridge.
57
+
58
+ ## Data retention
59
+
60
+ Browser Bridge keeps data only as long as needed for the current local session and feature behavior.
61
+
62
+ - Window enablement state is stored in session storage and cleared when disabled or when the browser session ends.
63
+ - Action log entries are stored for the current browser session only.
64
+ - Console and fetch/XHR network buffers are bounded in memory and cleared when disabled or when the page state is reset.
65
+ - Optional CDP network capture is bounded in memory, explicitly started/stopped, expires after a safety interval, and is cleared on relevant debugger, tab, or access teardown.
66
+ - Dialog message/default prompt text is returned to the requesting caller when inspected but is excluded from persisted action-log summaries and size diagnostics.
67
+ - Patch rollback records and remembered element references live in the current document. Browser Bridge attempts patch rollback when access is disabled or switched, while page replacement/navigation removes document-local state.
68
+ - Hashed semantic descriptors used for optional stale recovery are bounded in memory with the element registry and do not survive the document/registry lifecycle.
69
+
70
+ Browser Bridge does not provide Browser Bridge-hosted accounts, sync, or long-term server-side storage.
71
+
72
+ ## User control
73
+
74
+ You control when Browser Bridge can access browser data.
75
+
76
+ - Access is off by default.
77
+ - You explicitly enable Browser Bridge for a Chrome window.
78
+ - You can disable it at any time.
79
+ - Restricted Chrome pages are not scriptable through Browser Bridge.
80
+
81
+ ## Remote code and runtime evaluation
82
+
83
+ Browser Bridge does not load remotely hosted extension code.
84
+
85
+ Browser Bridge can, when you explicitly invoke features such as page evaluation, execute user-requested expressions in the inspected page context through Chrome's documented Debugger API. That execution affects only the currently enabled browser window and is part of Browser Bridge's developer-tool functionality.
86
+
87
+ The same existing Debugger API permission is used only when requested for
88
+ native pointer/text dispatch, JavaScript dialog observation and explicit
89
+ handling, accessibility reads, screenshots, performance/CDP reads, interception,
90
+ and optional all-resource network capture. Browser Bridge does not silently
91
+ accept/dismiss dialogs and does not claim that accepted browser input proves an
92
+ application-level outcome.
93
+
94
+ ## Security
95
+
96
+ - Extension code is packaged with the extension; Browser Bridge does not fetch and execute remote extension scripts.
97
+ - Communication with the companion host uses Chrome Native Messaging.
98
+ - Access is limited to the explicitly enabled browser window.
99
+ - The extension includes bounded buffers and document-local rollback records for temporary page patches, with rollback attempted when enabled-window access ends or switches.
100
+
101
+ ## Chrome Web Store Limited Use statement
102
+
103
+ The use of information received from Google APIs will adhere to the Chrome Web Store User Data Policy, including the Limited Use requirements.
104
+
105
+ ## Changes
106
+
107
+ If Browser Bridge's data practices change, this policy will be updated before those changes are released.
108
+
109
+ ## Contact
110
+
111
+ - GitHub Issues: https://github.com/koltyakov/browser-bridge/issues
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > **Chrome Web Store:** Install `Browser Bridge (BBX)` from the [Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie). For local or custom builds, use the unpacked install flow in [docs/unpacked-extension.md](https://github.com/koltyakov/browser-bridge/blob/main/docs/unpacked-extension.md).
6
6
 
7
- A local bridge between your coding agent and a real Chrome tab. Browser Bridge gives the agent structured access to DOM, styles, layout, console, network, and reversible patches - starting from the actual tab you already have open, with all its real state intact.
7
+ A local bridge between your coding agent and a real Chrome tab. Browser Bridge gives the agent structured access to DOM, styles, layout, console, network, accessibility data, reliable browser input, explicit JavaScript dialog handling, and reversible patches - starting from the actual tab you already have open, with all its real state intact.
8
8
 
9
9
  See [Quickstart](https://github.com/koltyakov/browser-bridge/blob/main/docs/quickstart.md) to get started in another repo, or browse the rest of the guides in [docs/index.md](https://github.com/koltyakov/browser-bridge/blob/main/docs/index.md).
10
10
 
@@ -101,7 +101,7 @@ Managed installs support OpenAI Codex, Claude Code, Cursor, GitHub Copilot, Open
101
101
 
102
102
  Most adjacent tools optimize for different goals. [Playwright](https://playwright.dev/) and headless automation stacks are excellent for deterministic tests and CI - but they start from a clean browser context by design. [Claude in Chrome](https://support.claude.com/en/articles/12012173-get-started-with-claude-in-chrome) is great for integrated Claude workflows, and the [Codex extension](https://chromewebstore.google.com/detail/codex/hehggadaopoacecdllhhajmbjkdcmajg) is a great option if you use Codex, but both are vendor-specific. Generic MCP browser servers offer broad control without the developer-focused depth.
103
103
 
104
- Browser Bridge is optimized for the opposite starting point: **inspect the state that already exists** in a real tab - logged-in sessions, feature flags, seeded storage, SPA state - use structured reads to understand it, test a patch in place, then fix the source. It's open-source, agent-agnostic, and scoped to explicit tab sessions rather than ambient browser control.
104
+ Browser Bridge is optimized for the opposite starting point: **inspect the state that already exists** in a real tab - logged-in sessions, feature flags, seeded storage, SPA state - use structured reads to understand it, test a patch in place, then fix the source. It's open-source, agent-agnostic, and scoped to one explicitly enabled browser window rather than ambient browser control.
105
105
 
106
106
  ## Setup
107
107
 
@@ -120,9 +120,13 @@ MCP mode is self-contained: the server exposes tools and startup instructions, s
120
120
 
121
121
  - The extension is scoped to one explicitly enabled Chrome window at a time - no ambient browser access
122
122
  - Requests default to the active tab in that window unless a tab is targeted explicitly
123
- - Sessions are tab and origin scoped, auto-refreshed when possible
124
- - All patch operations are reversible and session-scoped
123
+ - Targeted input actions reject hidden, disabled, ambiguous, stale, or obscured targets instead of silently guessing; their responses report the resolution and DOM/CDP execution path (`cdp_press_key` and `scroll_into_view` use separate contracts)
124
+ - Native pointer/text input, dialog handling, all-resource network capture, accessibility trees, screenshots, and raw CDP reads use the existing debugger permission only when requested
125
+ - Element references are document-local and strict by default; stale input recovery is opt-in, same-document, requires one strong unique semantic match, and fails safely when its bounded scan cannot prove uniqueness
126
+ - Patches keep per-document rollback history and Browser Bridge attempts best-effort rollback when window access is disabled or switched; committing the patch baseline keeps current changes but discards their rollback history
127
+ - URL waits observe full navigation and same-document SPA changes, and report the final URL and observed navigation kind
125
128
  - Structured DOM/style/layout reads are the primary transport; screenshots are a fallback
129
+ - Browser input dispatch does not prove the application accepted the intended change, so agents should verify with a wait or structured read
126
130
  - Open-ended investigation should start with structured reads on a smaller, lower-cost subagent when the client supports delegation
127
131
  - The native host daemon auto-starts on demand
128
132
 
@@ -139,10 +143,14 @@ MCP mode is self-contained: the server exposes tools and startup instructions, s
139
143
 
140
144
  ## Privacy
141
145
 
142
- Browser Bridge itself routes extension data locally through the Chrome extension, native host, and the local client you choose to connect. Browser Bridge does not operate a Browser Bridge cloud service.
146
+ The extension and native host communicate on the browser machine, and local clients connect to that daemon by default. If you explicitly configure an authenticated remote destination, bridge results can travel over your user-provided SSH tunnel or network route to the selected client. Browser Bridge does not operate a Browser Bridge cloud service, and raw remote TCP is not presented as encrypted.
143
147
 
144
148
  Your connected agent or IDE may still forward tool calls or tool results to remote services under that product's own settings and privacy policy. See [PRIVACY.md](https://github.com/koltyakov/browser-bridge/blob/main/PRIVACY.md) for the Browser Bridge policy.
145
149
 
150
+ ## Responsible Use
151
+
152
+ Browser Bridge is intended for local development, debugging, and testing of web resources you own, control, or are explicitly authorized to test. Do not use it for web scraping, unauthorized data collection, or impersonating or acting as another user. You are responsible for the agents you connect, the actions they perform, and compliance with applicable laws, permissions, website terms, and third-party rights. See the [Responsible Use Agreement](https://github.com/koltyakov/browser-bridge/blob/main/RESPONSIBLE_USE.md).
153
+
146
154
  ## License
147
155
 
148
156
  MIT. See [LICENSE](https://github.com/koltyakov/browser-bridge/blob/main/LICENSE).
@@ -0,0 +1,16 @@
1
+ # Browser Bridge (BBX) Responsible Use Agreement
2
+
3
+ Last updated: 2026-07-22
4
+
5
+ Browser Bridge is intended for local development, debugging, and testing of websites, applications, and other web resources that you own, control, or have explicit permission to test.
6
+
7
+ By using Browser Bridge, you agree that you will not use it to:
8
+
9
+ - Scrape websites or perform bulk, automated, or unauthorized data collection.
10
+ - Impersonate another user or act through another person's identity, credentials, account, or authenticated session without their explicit authorization.
11
+ - Access systems or data without authorization, bypass access controls, violate privacy, or perform deceptive, fraudulent, abusive, or harmful actions.
12
+ - Violate applicable laws, regulations, contractual obligations, website terms, or third-party rights.
13
+
14
+ You are responsible for the browser windows you enable, the agents and services you connect, the instructions and actions they perform, the data they access or transmit, and any resulting impact. You must obtain all necessary permissions and consent and review the privacy and security practices of connected agents, IDEs, and model providers.
15
+
16
+ Use Browser Bridge fairly, lawfully, and only within an authorized scope. This agreement does not modify the project's [MIT License](./LICENSE).
package/SECURITY.md ADDED
@@ -0,0 +1,52 @@
1
+ # Security Policy
2
+
3
+ Browser Bridge gives coding agents structured access to a real browser profile. The daemon,
4
+ native messaging host, Chrome extension, CLI, and MCP server are all part of the trusted
5
+ path between an agent and the user's browser, so security reports are taken seriously.
6
+
7
+ ## Supported Versions
8
+
9
+ Only the latest version published to npm (`@browserbridge/bbx`) and the Chrome Web Store
10
+ receives security fixes. Older versions are not patched retroactively; upgrade to the
11
+ latest release before reporting.
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ Please do **not** open a public GitHub issue for security vulnerabilities.
16
+
17
+ Report privately via GitHub's security advisory form:
18
+ <https://github.com/koltyakov/browser-bridge/security/advisories/new>
19
+
20
+ Include the affected component (daemon, native host, extension, CLI, MCP server), a
21
+ reproduction, and the impact you believe it has. Reports are acknowledged as quickly as
22
+ possible; fixes for confirmed vulnerabilities are prioritized over regular development.
23
+
24
+ ## Scope
25
+
26
+ In scope:
27
+
28
+ - The bridge daemon: transport auth (socket permissions, TCP auth token), request routing,
29
+ and the opt-in proxy/remote mode.
30
+ - The native messaging host and its manifest installation.
31
+ - The Chrome extension (MV3 service worker, content scripts, CDP usage).
32
+ - The `bbx` CLI and MCP server, including config/skill installation paths.
33
+
34
+ Out of scope:
35
+
36
+ - Vulnerabilities in Chrome, Node.js, or other dependencies (report upstream).
37
+ - Issues requiring an already-compromised machine or browser profile.
38
+ - The inherent capability of the tool itself: an agent authorized to use Browser Bridge
39
+ can, by design, read and manipulate pages in the connected browser profile. See
40
+ [PRIVACY.md](https://github.com/koltyakov/browser-bridge/blob/main/PRIVACY.md) and
41
+ [docs/agent-permissions.md](https://github.com/koltyakov/browser-bridge/blob/main/docs/agent-permissions.md)
42
+ for the intended trust model.
43
+
44
+ ## Design Notes
45
+
46
+ - By default the daemon listens on a user-owned Unix socket (mode `0700` directory) or,
47
+ on Windows, a named pipe / localhost TCP.
48
+ - Any TCP listener requires a random 256-bit auth token stored with mode `0600`;
49
+ registration without it is rejected and the comparison is constant-time.
50
+ - Proxy mode (LAN exposure) is opt-in via `bbx proxy enable` and always provisions a
51
+ token; an invalid `bindHost` in the config rejects the config rather than widening
52
+ the bind address.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbridge/bbx",
3
- "version": "1.7.7",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "agent-tools",
@@ -33,7 +33,10 @@
33
33
  },
34
34
  "files": [
35
35
  "LICENSE",
36
+ "PRIVACY.md",
37
+ "RESPONSIBLE_USE.md",
36
38
  "README.md",
39
+ "SECURITY.md",
37
40
  "packages/agent-client/src",
38
41
  "packages/mcp-server/src",
39
42
  "packages/native-host/bin",
@@ -52,8 +55,9 @@
52
55
  "lint": "oxlint . && oxfmt --check .",
53
56
  "format": "oxfmt .",
54
57
  "format:check": "oxfmt --check .",
55
- "test:runtime": "node --test",
56
- "test": "c8 node --test",
58
+ "fixture:browser": "node tests/fixtures/browser-reliability/server.mjs",
59
+ "test:runtime": "node --import tsx --test packages/*/test/*.test.ts packages/integration-tests/*.test.ts tests/fixtures/*/*.test.ts",
60
+ "test": "c8 node --import tsx --test packages/*/test/*.test.ts packages/integration-tests/*.test.ts tests/fixtures/*/*.test.ts",
57
61
  "coverage": "c8 report --reporter=text --reporter=text-summary",
58
62
  "coverage:review": "c8 report --reporter=json-summary && node scripts/review-coverage.mjs",
59
63
  "coverage:check": "c8 check-coverage --lines 92 --branches 83",
@@ -70,15 +74,17 @@
70
74
  },
71
75
  "dependencies": {
72
76
  "@modelcontextprotocol/sdk": "^1.29.0",
77
+ "@mozilla/readability": "^0.6.0",
78
+ "linkedom": "^0.18.13",
73
79
  "zod": "^4.4.3"
74
80
  },
75
81
  "devDependencies": {
76
82
  "@types/chrome": "^0.2.2",
77
83
  "@types/node": "^26.1.1",
78
84
  "c8": "^12.0.0",
79
- "linkedom": "^0.18.13",
80
- "oxfmt": "^0.59.0",
81
- "oxlint": "^1.74.0",
85
+ "oxfmt": "^0.60.0",
86
+ "oxlint": "^1.75.0",
87
+ "tsx": "^4.23.1",
82
88
  "typescript": "^7.0.2"
83
89
  },
84
90
  "engines": {
@@ -60,6 +60,30 @@ export function extractTabFlag(args) {
60
60
  return { tabId, rest };
61
61
  }
62
62
 
63
+ /**
64
+ * @param {string[]} args
65
+ * @returns {{ format: 'png' | 'jpeg' | 'webp', quality: number | undefined, rest: string[] }}
66
+ */
67
+ export function extractScreenshotFlags(args) {
68
+ const rest = [...args];
69
+ const formatIndex = rest.indexOf('--format');
70
+ const rawFormat = formatIndex === -1 ? 'png' : rest[formatIndex + 1];
71
+ if (rawFormat !== 'png' && rawFormat !== 'jpeg' && rawFormat !== 'webp') {
72
+ throw new Error('--format must be png, jpeg, or webp.');
73
+ }
74
+ if (formatIndex !== -1) rest.splice(formatIndex, 2);
75
+ const qualityIndex = rest.indexOf('--quality');
76
+ let quality;
77
+ if (qualityIndex !== -1) {
78
+ quality = Number(rest[qualityIndex + 1]);
79
+ if (!Number.isInteger(quality) || quality < 0 || quality > 100) {
80
+ throw new Error('--quality must be an integer from 0 to 100.');
81
+ }
82
+ rest.splice(qualityIndex, 2);
83
+ }
84
+ return { format: rawFormat, quality, rest };
85
+ }
86
+
63
87
  /**
64
88
  * @param {string[]} args
65
89
  * @returns {Promise<{ tabId: number | null, method: BridgeMethod, params: Record<string, unknown> }>}
@@ -16,6 +16,7 @@ import {
16
16
  import { startBridgeMcpServer } from '../../mcp-server/src/server.js';
17
17
  import {
18
18
  extractRemoteFlag,
19
+ extractScreenshotFlags,
19
20
  extractTabFlag,
20
21
  parseCallCommand,
21
22
  parseInterceptAddArgs,
@@ -197,10 +198,10 @@ async function runLocalCommand(run) {
197
198
  */
198
199
  async function createCliClient() {
199
200
  try {
200
- return await createBridgeClientForDestination(
201
- remoteDestinationId,
202
- clientTimeoutMs ? { defaultTimeoutMs: clientTimeoutMs } : {}
203
- );
201
+ return await createBridgeClientForDestination(remoteDestinationId, {
202
+ checkProtocolOnConnect: false,
203
+ ...(clientTimeoutMs ? { defaultTimeoutMs: clientTimeoutMs } : {}),
204
+ });
204
205
  } catch (error) {
205
206
  printJson({
206
207
  ok: false,
@@ -362,62 +363,31 @@ async function main() {
362
363
 
363
364
  const shortcutCmd = SHORTCUT_COMMANDS[command];
364
365
  if (shortcutCmd) {
365
- // Allow `bbx <shortcut> --tab <id> ...` to target a specific tab.
366
- // Without this, --tab gets eaten as a positional argument and the
367
- // request hits whatever tab the bridge route happens to be pointing
368
- // at (typically the active tab). For element-resolving shortcuts
369
- // the ref must be resolved against the SAME tab the action targets,
370
- // so we pass tabId to both resolveRef and the subsequent request.
371
366
  const { tabId, rest: shortcutArgs } = extractTabFlag(rest);
372
- const selectorInput = shortcutCmd.resolve ? shortcutArgs[0] : null;
373
- if (shortcutCmd.resolve && !selectorInput) {
374
- throw new Error(`Usage: ${command} <ref|selector>`);
375
- }
376
-
377
- // Retry-on-stale: if the action fails with ELEMENT_STALE and the
378
- // original input was a selector (not an el_xxx ref), re-resolve the
379
- // selector and retry once. This handles the common case where the
380
- // agent resolved an element, then the page re-rendered (React
381
- // reconciliation, SPA navigation) before the action was dispatched.
382
- const canRetry = typeof selectorInput === 'string' && !selectorInput.startsWith('el_');
383
- const maxAttempts = canRetry ? 2 : 1;
384
- /** @type {import('./runtime.js').BridgeResponse | undefined} */
385
- let response;
386
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
387
- let elementRef;
388
- if (shortcutCmd.resolve && selectorInput) {
389
- elementRef = await resolveRef(client, selectorInput, tabId, REQUEST_SOURCE);
390
- }
391
- response = await requestBridge(
392
- client,
393
- shortcutCmd.method,
394
- shortcutCmd.build(shortcutArgs, elementRef),
395
- { source: REQUEST_SOURCE, tabId }
396
- );
397
- const isStale = !response.ok && response.error?.code === 'ELEMENT_STALE';
398
- if (isStale && attempt < maxAttempts) {
399
- process.stderr.write(
400
- `bbx: ELEMENT_STALE on "${selectorInput}", re-resolving and retrying...\n`
401
- );
402
- }
403
- if (!isStale || attempt >= maxAttempts) break;
404
- }
405
- if (response) await printSummary(response, shortcutCmd.printMethod);
367
+ const response = await requestBridge(
368
+ client,
369
+ shortcutCmd.method,
370
+ shortcutCmd.build(shortcutArgs),
371
+ { source: REQUEST_SOURCE, tabId }
372
+ );
373
+ await printSummary(response, shortcutCmd.printMethod);
406
374
  return;
407
375
  }
408
376
 
409
377
  if (command === 'press-key') {
410
378
  const [key, refOrSelector] = rest;
411
379
  if (!key) throw new Error('Usage: press-key <key> [ref|selector]');
412
- const elementRef = refOrSelector
413
- ? await resolveRef(client, refOrSelector, null, REQUEST_SOURCE)
380
+ const target = refOrSelector
381
+ ? refOrSelector.startsWith('el_')
382
+ ? { elementRef: refOrSelector }
383
+ : { selector: refOrSelector }
414
384
  : undefined;
415
385
  const response = await requestBridge(
416
386
  client,
417
387
  'input.press_key',
418
388
  {
419
389
  key,
420
- target: elementRef ? { elementRef } : undefined,
390
+ target,
421
391
  },
422
392
  { source: REQUEST_SOURCE }
423
393
  );
@@ -447,15 +417,20 @@ async function main() {
447
417
 
448
418
  if (command === 'screenshot') {
449
419
  const parsed = extractTabFlag(rest);
450
- const [refOrSelector, outputPath] = parsed.rest;
420
+ const screenshotOptions = extractScreenshotFlags(parsed.rest);
421
+ const [refOrSelector, outputPath] = screenshotOptions.rest;
451
422
  if (!refOrSelector)
452
- throw new Error('Usage: screenshot [--tab <tabId>] <ref|selector> [path]');
423
+ throw new Error(
424
+ 'Usage: screenshot [--tab <tabId>] [--format png|jpeg|webp] [--quality 0-100] <ref|selector> [path]'
425
+ );
453
426
  const elementRef = await resolveRef(client, refOrSelector, parsed.tabId, REQUEST_SOURCE);
454
427
  const response = await requestBridge(
455
428
  client,
456
429
  'screenshot.capture_element',
457
430
  {
458
431
  elementRef,
432
+ format: screenshotOptions.format,
433
+ quality: screenshotOptions.quality,
459
434
  },
460
435
  { tabId: parsed.tabId, source: REQUEST_SOURCE }
461
436
  );
@@ -464,13 +439,32 @@ async function main() {
464
439
  return;
465
440
  }
466
441
  const screenshotResult = /** @type {ScreenshotResult} */ (response.result);
467
- const filePath = outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.png`);
468
- const data = screenshotResult.image.replace(/^data:image\/png;base64,/, '');
442
+ const extension = screenshotOptions.format === 'jpeg' ? 'jpg' : screenshotOptions.format;
443
+ const acceptedExtensions =
444
+ screenshotOptions.format === 'jpeg'
445
+ ? new Set(['.jpg', '.jpeg'])
446
+ : new Set([`.${extension}`]);
447
+ let filePath = outputPath || path.join(os.tmpdir(), `bbx-${Date.now()}.${extension}`);
448
+ if (outputPath && !path.extname(outputPath)) filePath = `${outputPath}.${extension}`;
449
+ if (
450
+ outputPath &&
451
+ path.extname(outputPath) &&
452
+ !acceptedExtensions.has(path.extname(outputPath))
453
+ ) {
454
+ throw new Error(`Screenshot path extension must match ${screenshotOptions.format} format.`);
455
+ }
456
+ const data = screenshotResult.image.replace(/^data:image\/(?:png|jpeg|webp);base64,/, '');
469
457
  await fs.promises.writeFile(filePath, Buffer.from(data, 'base64'));
470
458
  printJson({
471
459
  ok: true,
472
460
  summary: `Screenshot saved to ${filePath}.`,
473
- evidence: { savedTo: filePath, rect: screenshotResult.rect },
461
+ evidence: {
462
+ savedTo: filePath,
463
+ rect: screenshotResult.rect,
464
+ format: screenshotResult.format,
465
+ complete: screenshotResult.complete,
466
+ clipped: screenshotResult.clipped,
467
+ },
474
468
  });
475
469
  return;
476
470
  }