local-browser-bridge 0.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 (92) hide show
  1. package/README.md +724 -0
  2. package/dist/package.json +61 -0
  3. package/dist/src/browser/chrome.d.ts +19 -0
  4. package/dist/src/browser/chrome.js +778 -0
  5. package/dist/src/browser/index.d.ts +3 -0
  6. package/dist/src/browser/index.js +25 -0
  7. package/dist/src/browser/safari.d.ts +41 -0
  8. package/dist/src/browser/safari.js +827 -0
  9. package/dist/src/browser-attach-ux-helper.d.ts +39 -0
  10. package/dist/src/browser-attach-ux-helper.js +157 -0
  11. package/dist/src/capabilities.d.ts +3 -0
  12. package/dist/src/capabilities.js +182 -0
  13. package/dist/src/chrome-relay-error-helper.d.ts +19 -0
  14. package/dist/src/chrome-relay-error-helper.js +78 -0
  15. package/dist/src/chrome-relay-helper-cli.d.ts +2 -0
  16. package/dist/src/chrome-relay-helper-cli.js +97 -0
  17. package/dist/src/chrome-relay-helper.d.ts +29 -0
  18. package/dist/src/chrome-relay-helper.js +151 -0
  19. package/dist/src/chrome-relay-state.d.ts +23 -0
  20. package/dist/src/chrome-relay-state.js +108 -0
  21. package/dist/src/claude-code.d.ts +20 -0
  22. package/dist/src/claude-code.js +66 -0
  23. package/dist/src/cli-reference-adapter.d.ts +13 -0
  24. package/dist/src/cli-reference-adapter.js +48 -0
  25. package/dist/src/cli.d.ts +3 -0
  26. package/dist/src/cli.js +200 -0
  27. package/dist/src/codex.d.ts +17 -0
  28. package/dist/src/codex.js +25 -0
  29. package/dist/src/connection-ux.d.ts +61 -0
  30. package/dist/src/connection-ux.js +256 -0
  31. package/dist/src/errors.d.ts +12 -0
  32. package/dist/src/errors.js +58 -0
  33. package/dist/src/http-reference-adapter.d.ts +34 -0
  34. package/dist/src/http-reference-adapter.js +61 -0
  35. package/dist/src/http.d.ts +3 -0
  36. package/dist/src/http.js +161 -0
  37. package/dist/src/index.d.ts +17 -0
  38. package/dist/src/index.js +43 -0
  39. package/dist/src/mcp-stdio.d.ts +2 -0
  40. package/dist/src/mcp-stdio.js +10 -0
  41. package/dist/src/mcp.d.ts +25 -0
  42. package/dist/src/mcp.js +483 -0
  43. package/dist/src/reference-adapter.d.ts +32 -0
  44. package/dist/src/reference-adapter.js +42 -0
  45. package/dist/src/service/attach-service.d.ts +28 -0
  46. package/dist/src/service/attach-service.js +272 -0
  47. package/dist/src/session-metadata.d.ts +4 -0
  48. package/dist/src/session-metadata.js +88 -0
  49. package/dist/src/store/session-store.d.ts +14 -0
  50. package/dist/src/store/session-store.js +52 -0
  51. package/dist/src/target.d.ts +9 -0
  52. package/dist/src/target.js +61 -0
  53. package/dist/src/types.d.ts +397 -0
  54. package/dist/src/types.js +2 -0
  55. package/dist/tests/attach-service.test.d.ts +1 -0
  56. package/dist/tests/attach-service.test.js +1367 -0
  57. package/dist/tests/browser-attach-ux-helper.test.d.ts +1 -0
  58. package/dist/tests/browser-attach-ux-helper.test.js +139 -0
  59. package/dist/tests/chrome-relay-error-helper.test.d.ts +1 -0
  60. package/dist/tests/chrome-relay-error-helper.test.js +67 -0
  61. package/dist/tests/chrome-relay-helper.test.d.ts +1 -0
  62. package/dist/tests/chrome-relay-helper.test.js +142 -0
  63. package/dist/tests/chrome-relay-state-schema.test.d.ts +1 -0
  64. package/dist/tests/chrome-relay-state-schema.test.js +96 -0
  65. package/dist/tests/claude-code-wrapper.test.d.ts +1 -0
  66. package/dist/tests/claude-code-wrapper.test.js +170 -0
  67. package/dist/tests/codex.test.d.ts +1 -0
  68. package/dist/tests/codex.test.js +210 -0
  69. package/dist/tests/demo-client-smoke.test.d.ts +1 -0
  70. package/dist/tests/demo-client-smoke.test.js +405 -0
  71. package/dist/tests/docs-fixtures.test.d.ts +1 -0
  72. package/dist/tests/docs-fixtures.test.js +255 -0
  73. package/dist/tests/doctor-connect-wrapper.test.d.ts +1 -0
  74. package/dist/tests/doctor-connect-wrapper.test.js +62 -0
  75. package/dist/tests/fixtures/doctor-connect-cli-stub.d.ts +1 -0
  76. package/dist/tests/fixtures/doctor-connect-cli-stub.js +93 -0
  77. package/dist/tests/fixtures/public-root-cli-stub.d.ts +210 -0
  78. package/dist/tests/fixtures/public-root-cli-stub.js +143 -0
  79. package/dist/tests/fixtures/public-root-consumer.js +67 -0
  80. package/dist/tests/mcp.test.d.ts +1 -0
  81. package/dist/tests/mcp.test.js +345 -0
  82. package/dist/tests/public-consumer-helpers.test.d.ts +1 -0
  83. package/dist/tests/public-consumer-helpers.test.js +33 -0
  84. package/dist/tests/public-package-git-consumption.test.d.ts +1 -0
  85. package/dist/tests/public-package-git-consumption.test.js +56 -0
  86. package/dist/tests/public-root-consumer-smoke.test.d.ts +1 -0
  87. package/dist/tests/public-root-consumer-smoke.test.js +214 -0
  88. package/dist/tests/reference-adapter.test.d.ts +1 -0
  89. package/dist/tests/reference-adapter.test.js +220 -0
  90. package/dist/tests/transport-reference-adapters.test.d.ts +1 -0
  91. package/dist/tests/transport-reference-adapters.test.js +214 -0
  92. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,724 @@
1
+ # local-browser-bridge
2
+
3
+ Reusable, agent-agnostic local browser bridge for AI clients, developer tools, and scripts.
4
+
5
+ `local-browser-bridge` is a small TypeScript/Node product that exposes a user's real local browser state through two consumer-neutral surfaces:
6
+
7
+ - a JSON CLI for local scripts, shells, and coding agents
8
+ - a local HTTP JSON API for editor plugins, desktop apps, daemon processes, or other clients
9
+
10
+ The project started as `safari-attach-tool`, but the product surface is now intentionally broader than any one consumer or agent runtime.
11
+
12
+ ## Implementation status
13
+
14
+ The toolkit contract in this repo is feature-complete for the v1 bridge surface.
15
+ Safari remains actionable in v1, and Chrome/Chromium remains read-only in v1.
16
+ Downstream consumer integration, including adapter wiring for consumers such as OpenClaw, remains integration work outside this repo.
17
+
18
+ ## Quick start
19
+
20
+ If you are new to the repo, start here:
21
+
22
+ - **Understand the toolkit surface first:** [src/index.ts](src/index.ts) is the shared consumer entrypoint. It re-exports the transport-neutral helper surface plus the reference CLI/HTTP adapters used by downstream runtimes and clients.
23
+ - **Use the contract artifacts as the source of truth:** start with [docs/agent-integration-contract.md](docs/agent-integration-contract.md), then the machine-readable schemas in [schema/capabilities.schema.json](schema/capabilities.schema.json) and [schema/chrome-relay-error.schema.json](schema/chrome-relay-error.schema.json), plus the example payloads in [examples/](examples/).
24
+ - **Pick a transport only at the edge:** the toolkit exposes the same bridge contract through the JSON CLI and the local HTTP API. Choose whichever fits your runtime; neither is the privileged path.
25
+ - **Copy from runnable consumers when wiring a client:** use [examples/clients/http-consumer.ts](examples/clients/http-consumer.ts), [examples/clients/cli-consumer.ts](examples/clients/cli-consumer.ts), the CLI-shell wrapper at [examples/clients/doctor-connect-wrapper.ts](examples/clients/doctor-connect-wrapper.ts), the Codex-style wrapper at [examples/clients/codex-consumer.ts](examples/clients/codex-consumer.ts), the Claude Code-style wrapper at [examples/clients/claude-code-tool.ts](examples/clients/claude-code-tool.ts), and the end-to-end demo at [examples/clients/http-node.ts](examples/clients/http-node.ts).
26
+ - **Treat consumer wrappers as convenience only:** Codex-facing helpers such as `normalizeCodexRoute(...)`, `connectCodexViaCli(...)`, and `connectCodexViaHttp(...)` stay on top of the same agent-agnostic adapter/reference layer.
27
+
28
+ ## Canonical artifacts and docs
29
+
30
+ Use these artifacts as the canonical sources for product intent, integration behavior, consumer guidance, and the Chrome relay error contract:
31
+
32
+ - [PRD.md](PRD.md) - product requirements and direction
33
+ - [docs/universal-toolkit-summary.md](docs/universal-toolkit-summary.md) - short progress summary of the shared-toolkit direction and where to start
34
+ - [docs/agent-integration-contract.md](docs/agent-integration-contract.md) - transport-neutral integration contract
35
+ - [docs/adapter-patterns.md](docs/adapter-patterns.md) - canonical runtime-neutral adapter patterns for shared consumers
36
+ - [docs/consuming-the-bridge.md](docs/consuming-the-bridge.md) - consumer implementation guidance
37
+ - [src/index.ts](src/index.ts) - shared helper/reference-adapter entrypoint for consumers importing the toolkit as code
38
+ - [schema/capabilities.schema.json](schema/capabilities.schema.json) - stable capabilities contract schema
39
+ - [schema/chrome-relay-error.schema.json](schema/chrome-relay-error.schema.json) - Chrome relay error schema
40
+ - [examples/error.chrome-relay-share-required.example.json](examples/error.chrome-relay-share-required.example.json) - Chrome relay share-required example
41
+
42
+ ## What this product is
43
+
44
+ `local-browser-bridge` is:
45
+
46
+ - **agent-agnostic**: Claude Code, Codex, AWOS, custom scripts, or any other client can consume the same contract
47
+ - **local-first**: it runs on the user's machine and exposes only local CLI and local HTTP transports
48
+ - **bridge-first**: the stable product artifact is the browser/session contract, not a single browser-specific script
49
+ - **Safari actionable in v1**: Safari on macOS is the first actionable adapter and sets the quality bar for the contract
50
+ - **Chrome/Chromium read-only in v1**: Chrome participates through the same contract, but action flows are intentionally unavailable in this phase
51
+
52
+ The canonical product requirements document lives in [PRD.md](PRD.md).
53
+ The short direction summary lives in [docs/product-direction.md](docs/product-direction.md).
54
+ The primary agent/client integration contract lives in [docs/agent-integration-contract.md](docs/agent-integration-contract.md).
55
+ The canonical runtime-neutral adapter patterns live in [docs/adapter-patterns.md](docs/adapter-patterns.md).
56
+ The client-consumption guide lives in [docs/consuming-the-bridge.md](docs/consuming-the-bridge.md).
57
+ For OpenClaw/browser-style consumer wiring specifically, see [docs/openclaw-style-consumer-integration.md](docs/openclaw-style-consumer-integration.md).
58
+ For the next-step OpenClaw adapter shape, see [docs/openclaw-adapter-draft.md](docs/openclaw-adapter-draft.md).
59
+
60
+ ## Stable contract
61
+
62
+ The bridge has a stable additive contract that clients should key off directly:
63
+
64
+ - `schemaVersion = 1`
65
+ - `kind = "safari-actionable" | "chrome-readonly"`
66
+
67
+ Those fields appear in:
68
+
69
+ - the bridge capabilities payload (`capabilities.schemaVersion`, `capabilities.browsers[*].kind`)
70
+ - every saved/returned session payload (`session.schemaVersion`, `session.kind`)
71
+
72
+ ### Contract guidance for clients
73
+
74
+ Treat the contract as follows:
75
+
76
+ - **Use `schemaVersion` for compatibility gates.** Current stable value is `1`.
77
+ - **Use `kind` as the top-level behavior switch.**
78
+ - `safari-actionable` means the saved/current session is expected to support action flows.
79
+ - `chrome-readonly` means inspection and saved-session resume may work, but runtime tab actions are intentionally unavailable in this phase.
80
+ - **Use `session.capabilities` for exact per-session checks** before showing or calling `activate`, `navigate`, or `screenshot`.
81
+ - **Use `session.attach` plus `session.semantics` for trust and UX labeling.**
82
+ - Direct Chrome sessions represent a saved browser tab reference.
83
+ - Relay Chrome sessions represent the last explicitly shared tab only.
84
+ - **Use `session.status.state` for quick UX labeling**:
85
+ - `actionable`
86
+ - `read-only`
87
+ - **Do not infer actionability from browser name alone.** Consume `kind`, `status`, and `capabilities` instead.
88
+
89
+ The machine-readable schema is in [schema/capabilities.schema.json](schema/capabilities.schema.json).
90
+
91
+ ## Current browser behavior
92
+
93
+ ### Safari: actionable
94
+
95
+ Safari on macOS is the primary adapter. It uses `osascript`/JXA plus `screencapture`; it is not a browser-native automation/debugging stack.
96
+
97
+ Current Safari behavior:
98
+
99
+ - inspect front tab / selected tab / tab list
100
+ - attach and persist sessions
101
+ - resume saved sessions after tab movement via fallback matching strategies
102
+ - activate a target or saved session
103
+ - navigate a target or saved session
104
+ - capture screenshots
105
+
106
+ Safari sessions are emitted as:
107
+
108
+ - `kind: "safari-actionable"`
109
+ - `status.state: "actionable"`
110
+ - session capabilities with `activate`, `navigate`, and `screenshot` set to `true`
111
+
112
+ ### Chrome/Chromium: read-only
113
+
114
+ Chrome/Chromium is exposed through the same bridge contract, but in this phase it is deliberately honest and read-only.
115
+
116
+ Current Chrome/Chromium behavior:
117
+
118
+ - inspect front tab / selected tab / tab list when a local DevTools HTTP endpoint is already discoverable
119
+ - attach and persist sessions
120
+ - resume saved sessions in read-only mode when the same inspectable target can still be matched
121
+ - **no** runtime `activate`, `navigate`, or `screenshot`
122
+
123
+ Chrome sessions are emitted as:
124
+
125
+ - `kind: "chrome-readonly"`
126
+ - `status.state: "read-only"`
127
+ - session capabilities with `activate`, `navigate`, and `screenshot` set to `false`
128
+
129
+ ## Features
130
+
131
+ - Inspect the current front tab
132
+ - Inspect an explicit tab by indexes or by signature
133
+ - List visible tabs with stable-ish identity metadata
134
+ - Attach and persist sessions locally
135
+ - Persist self-describing sessions with stable `schemaVersion` and `kind` metadata
136
+ - Resume saved sessions even after tabs move
137
+ - Return a machine-readable capabilities contract for clients and tools
138
+ - Report diagnostics about local browser/runtime constraints independently from capabilities
139
+ - Expose all supported functionality through both CLI and local HTTP
140
+
141
+ ## Requirements
142
+
143
+ - macOS with Safari installed for Safari support
144
+ - Node.js 20+
145
+ - `osascript`
146
+ - `screencapture`
147
+
148
+ ## Install
149
+
150
+ Build a local checkout:
151
+
152
+ ```bash
153
+ npm install
154
+ npm run build
155
+ ```
156
+
157
+ Run the CLI from a built repo checkout:
158
+
159
+ ```bash
160
+ npm run cli -- --help
161
+ ```
162
+
163
+ Install from git as a dependency or from another local project:
164
+
165
+ ```bash
166
+ npm install <git-url>#<commit-ish>
167
+ ```
168
+
169
+ The package uses `prepare`, so a git install builds `dist/` during installation and exposes the same stable root helper surface plus declarations through `local-browser-bridge`.
170
+ After install, the package exposes these executable names:
171
+
172
+ - `local-browser-bridge`
173
+ - `local-browser-bridge-mcp`
174
+ - `local-browser-bridge-chrome-relay`
175
+ - `safari-attach-tool` (compatibility alias for `local-browser-bridge`)
176
+
177
+ For the route-first connection flow, use the published `local-browser-bridge` bin. `npm run cli -- ...` is the repo-checkout convenience entrypoint for the same CLI during local development.
178
+
179
+ ## MCP stdio RC
180
+
181
+ This repo now includes a minimal MCP stdio release-candidate surface for Claude Code and generic MCP clients.
182
+
183
+ Current MCP tools:
184
+
185
+ - `browser_doctor(route, sessionId?)`
186
+ - `browser_tabs(route)`
187
+ - `browser_connect(route, sessionId?)`
188
+
189
+ `browser_tabs` is available for Safari and `chrome-direct`.
190
+ For `chrome-relay`, `browser_tabs` returns a structured non-error blocked result because relay is shared-tab scoped to the currently shared tab rather than browser-wide.
191
+ Runtime action tools such as `activate`, `navigate`, or `screenshot` are intentionally not part of this RC MCP surface.
192
+
193
+ Each MCP tool result now also includes the same additive top-level branching fields without removing the older tool-specific fields:
194
+
195
+ - `outcome`: `success | blocked | unsupported | error`
196
+ - `status`: stable high-level state such as `ready`, `connected`, `listed`, `blocked`, `unsupported`, or `failed`
197
+ - `category`: compact tool/result category such as `route-ready`, `session-connected`, `tab-list`, or `shared-tab-scope`
198
+ - `reason`: optional `{ code, message, retryable?, userActionRequired? }`
199
+
200
+ Tiny copy-paste branching example for agents or client wrappers:
201
+
202
+ ```ts
203
+ const payload = result.structuredContent;
204
+
205
+ if (payload.outcome === "success") {
206
+ // Safe to continue: use payload.envelope/session/tabs as appropriate.
207
+ } else if (payload.outcome === "blocked") {
208
+ // Ask the user to do the nextStep or surface payload.reason/prompt.
209
+ } else if (payload.outcome === "unsupported") {
210
+ // Pick a different route/tool. Example: browser_tabs + chrome-relay.
211
+ } else {
212
+ // Real error: inspect payload.reason or payload.error.
213
+ }
214
+ ```
215
+
216
+ Build and run the stdio server from this repo checkout:
217
+
218
+ ```bash
219
+ npm install
220
+ npm run build
221
+ npm run mcp
222
+ ```
223
+
224
+ Equivalent direct repo-checkout command after `npm run build`:
225
+
226
+ ```bash
227
+ node ./dist/src/mcp-stdio.js
228
+ ```
229
+
230
+ Installed dependency usage from a consumer project:
231
+
232
+ ```bash
233
+ ./node_modules/.bin/local-browser-bridge-mcp
234
+ # or, if the bin is already on PATH
235
+ local-browser-bridge-mcp
236
+ ```
237
+
238
+ ### Claude Code setup
239
+
240
+ Copy-paste example file:
241
+
242
+ - [examples/mcp/claude-code.installed-package.mcp.json](examples/mcp/claude-code.installed-package.mcp.json)
243
+
244
+ Project-local `.mcp.json` in a consumer project that installed `local-browser-bridge`:
245
+
246
+ ```json
247
+ {
248
+ "mcpServers": {
249
+ "local-browser-bridge": {
250
+ "command": "./node_modules/.bin/local-browser-bridge-mcp"
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ If the binary is already on `PATH`, you can shorten that to:
257
+
258
+ ```json
259
+ {
260
+ "mcpServers": {
261
+ "local-browser-bridge": {
262
+ "command": "local-browser-bridge-mcp"
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Generic MCP client setup
269
+
270
+ Copy-paste example file:
271
+
272
+ - [examples/mcp/generic-stdio.repo-checkout.json](examples/mcp/generic-stdio.repo-checkout.json)
273
+
274
+ Most stdio MCP clients accept the same `command` plus `args` shape. This form is useful when you want the config to point at a built checkout directly:
275
+
276
+ ```json
277
+ {
278
+ "mcpServers": {
279
+ "local-browser-bridge": {
280
+ "command": "node",
281
+ "args": [
282
+ "/ABSOLUTE/PATH/TO/local-browser-bridge/dist/src/mcp-stdio.js"
283
+ ]
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ Quick inspector smoke test:
290
+
291
+ ```bash
292
+ npx @modelcontextprotocol/inspector node /ABSOLUTE/PATH/TO/local-browser-bridge/dist/src/mcp-stdio.js
293
+ ```
294
+
295
+ Quick installed-binary smoke test:
296
+
297
+ ```bash
298
+ test -x ./node_modules/.bin/local-browser-bridge-mcp
299
+ ```
300
+
301
+ Quick repo-checkout smoke test:
302
+
303
+ ```bash
304
+ test -f ./dist/src/mcp-stdio.js
305
+ ```
306
+
307
+ Tool result notes:
308
+
309
+ - results include `structuredContent` JSON plus a text mirror for client compatibility
310
+ - `browser_doctor` keeps blockers explicit without pretending the route is connected
311
+ - `browser_connect` keeps read-only and shared-tab scope explicit in `truth` and `envelope`
312
+ - unsupported runtime actions stay explicit in `truth.unsupportedRuntimeActions`
313
+
314
+ ## Consumer surfaces and examples
315
+
316
+ ### CLI
317
+
318
+ Primary binary:
319
+
320
+ ```bash
321
+ local-browser-bridge --help
322
+ ```
323
+
324
+ Compatibility alias:
325
+
326
+ ```bash
327
+ safari-attach-tool --help
328
+ ```
329
+
330
+ Common commands:
331
+
332
+ ```bash
333
+ npm run cli -- capabilities
334
+ npm run cli -- capabilities --browser safari
335
+ npm run cli -- capabilities --browser chrome
336
+ npm run cli -- diagnostics --browser safari
337
+ npm run cli -- front-tab --browser safari
338
+ npm run cli -- tabs --browser safari
339
+ npm run cli -- attach --browser safari --window-index 1 --tab-index 2
340
+ npm run cli -- sessions
341
+ npm run cli -- session --id <session-id>
342
+ npm run cli -- resume --id <session-id>
343
+ npm run cli -- session-activate --id <session-id>
344
+ npm run cli -- session-navigate --id <session-id> --url https://example.com/next
345
+ npm run cli -- session-screenshot --id <session-id> --output .tmp/session.png
346
+ npm run cli -- serve --host 127.0.0.1 --port 3000
347
+ npm run mcp
348
+ ```
349
+
350
+ Commands:
351
+
352
+ - `front-tab [--browser safari|chrome]`
353
+ - `tab [--browser safari|chrome] (--window-index <n> --tab-index <n> | --signature <sig>)`
354
+ - `tabs [--browser safari|chrome]`
355
+ - `attach [--browser safari|chrome] [target flags]`
356
+ - `activate [--browser safari|chrome] [target flags]`
357
+ - `navigate [--browser safari|chrome] [target flags] --url <url>`
358
+ - `screenshot [--browser safari|chrome] [target flags] [--output <path>]`
359
+ - `capabilities [--browser safari|chrome]`
360
+ - `diagnostics [--browser safari|chrome]` (includes Safari `preflight.inspect|automation|screenshot` readiness when available)
361
+ - `sessions`
362
+ - `session --id <session-id>`
363
+ - `resume --id <session-id>`
364
+ - `session-activate --id <session-id>`
365
+ - `session-navigate --id <session-id> --url <url>`
366
+ - `session-screenshot --id <session-id> [--output <path>]`
367
+ - `serve [--host 127.0.0.1] [--port 3000]`
368
+
369
+ ### Local HTTP API
370
+
371
+ Start the server:
372
+
373
+ ```bash
374
+ npm run serve -- --host 127.0.0.1 --port 3000
375
+ ```
376
+
377
+ Health:
378
+
379
+ ```bash
380
+ curl http://127.0.0.1:3000/health
381
+ ```
382
+
383
+ Capabilities:
384
+
385
+ ```bash
386
+ curl "http://127.0.0.1:3000/v1/capabilities"
387
+ curl "http://127.0.0.1:3000/v1/capabilities?browser=safari"
388
+ curl "http://127.0.0.1:3000/v1/capabilities?browser=chrome"
389
+ ```
390
+
391
+ Inspection and actions:
392
+
393
+ ```bash
394
+ curl "http://127.0.0.1:3000/v1/front-tab?browser=safari"
395
+ curl "http://127.0.0.1:3000/v1/tabs?browser=safari"
396
+ curl "http://127.0.0.1:3000/v1/tab?browser=safari&windowIndex=1&tabIndex=2"
397
+
398
+ curl -X POST http://127.0.0.1:3000/v1/attach \
399
+ -H "content-type: application/json" \
400
+ -d '{"browser":"safari","target":{"windowIndex":1,"tabIndex":2}}'
401
+
402
+ curl -X POST http://127.0.0.1:3000/v1/sessions/<session-id>/resume
403
+ ```
404
+
405
+ See [docs/consuming-the-bridge.md](docs/consuming-the-bridge.md) for fuller CLI and HTTP examples, including the practical HTTP consumer demo at [examples/clients/http-node.ts](examples/clients/http-node.ts).
406
+ For runtime-neutral wrapper patterns across OpenClaw, AWOS, Codex, Claude Code, and custom consumers, including copyable minimal adapter skeletons built on the shared helper surface, see [docs/adapter-patterns.md](docs/adapter-patterns.md).
407
+ If you are importing the toolkit directly instead of shelling out or calling HTTP, start from [src/index.ts](src/index.ts), which re-exports the shared helpers plus the CLI/HTTP reference adapters used in [examples/clients/cli-consumer.ts](examples/clients/cli-consumer.ts), [examples/clients/http-consumer.ts](examples/clients/http-consumer.ts), and [examples/clients/codex-consumer.ts](examples/clients/codex-consumer.ts).
408
+
409
+ If you want the smallest route-first shell wrapper instead of importing helpers, start from [examples/clients/doctor-connect-wrapper.ts](examples/clients/doctor-connect-wrapper.ts). It shells out to `local-browser-bridge doctor --route ...` and `local-browser-bridge connect --route ...`, then returns one concise wrapper JSON result while preserving additive top-level `outcome` / `status` / `category` / `reason` branching fields and keeping Safari actionable, `chrome-direct` read-only, and `chrome-relay` read-only plus shared-tab scoped.
410
+
411
+ For a thin Codex-facing wrapper that still uses the same shared/public toolkit surface, use `normalizeCodexRoute(...)`, `connectCodexViaCli(...)`, or `connectCodexViaHttp(...)` from the package root, then copy from [examples/clients/codex-consumer.ts](examples/clients/codex-consumer.ts).
412
+ For a thin Claude Code-facing wrapper that still uses the same shared/public toolkit surface, use `normalizeClaudeCodeRoute(...)` and `prepareClaudeCodeRoute(...)` from the package root, then copy from [examples/clients/claude-code-tool.ts](examples/clients/claude-code-tool.ts).
413
+
414
+ Quick copy-paste run for the shell wrapper:
415
+
416
+ ```bash
417
+ npm install
418
+ npm run build
419
+ node --experimental-strip-types examples/clients/doctor-connect-wrapper.ts safari
420
+ node --experimental-strip-types examples/clients/doctor-connect-wrapper.ts chrome-relay
421
+ ```
422
+
423
+ Installed-package equivalent once the dependency is present on `PATH` or in `node_modules/.bin`:
424
+
425
+ ```bash
426
+ local-browser-bridge doctor --route safari
427
+ local-browser-bridge connect --route safari
428
+ local-browser-bridge doctor --route chrome-relay
429
+ local-browser-bridge connect --route chrome-relay
430
+ ```
431
+
432
+ Quick copy-paste run for the demo:
433
+
434
+ ```bash
435
+ npm install
436
+ npm run build
437
+
438
+ # terminal 1
439
+ npm run serve
440
+
441
+ # terminal 2
442
+ npx tsx examples/clients/http-node.ts safari
443
+ # or: npx tsx examples/clients/http-node.ts chrome-direct
444
+ # or: npx tsx examples/clients/http-node.ts chrome-relay
445
+
446
+ # optional: resume a saved session for the selected path
447
+ LOCAL_BROWSER_BRIDGE_SESSION_ID=<session-id> npx tsx examples/clients/http-node.ts chrome-relay
448
+ ```
449
+
450
+ The demo fetches `/v1/capabilities` plus `/v1/diagnostics`, then renders different consumer-facing labels and prompts for Safari actionable, Chrome direct read-only, and Chrome relay read-only. It keeps Chrome direct and relay explicit and does not silently fall back between them.
451
+
452
+ ## Capability payload example
453
+
454
+ Reference fixture: [examples/capabilities.example.json](examples/capabilities.example.json)
455
+
456
+ ```json
457
+ {
458
+ "capabilities": {
459
+ "schemaVersion": 1,
460
+ "schema": {
461
+ "path": "schema/capabilities.schema.json",
462
+ "version": "1.0.0"
463
+ },
464
+ "product": {
465
+ "name": "local-browser-bridge",
466
+ "displayName": "local-browser-bridge",
467
+ "summary": "Reusable, agent-agnostic local browser bridge with honest capability signaling. Safari is actionable; Chrome/Chromium is read-only in v1."
468
+ },
469
+ "targeting": {
470
+ "modes": ["front", "indexed", "signature"]
471
+ },
472
+ "browsers": [
473
+ {
474
+ "kind": "safari-actionable",
475
+ "browser": "safari",
476
+ "maturity": "primary",
477
+ "attachModes": [
478
+ {
479
+ "mode": "direct",
480
+ "source": "user-browser",
481
+ "scope": "browser",
482
+ "supported": true,
483
+ "readiness": "ready"
484
+ }
485
+ ],
486
+ "operations": {
487
+ "attach": true,
488
+ "resumeSession": true,
489
+ "activate": true,
490
+ "navigate": true,
491
+ "screenshot": true
492
+ }
493
+ },
494
+ {
495
+ "kind": "chrome-readonly",
496
+ "browser": "chrome",
497
+ "maturity": "experimental-readonly",
498
+ "attachModes": [
499
+ {
500
+ "mode": "direct",
501
+ "source": "user-browser",
502
+ "scope": "browser",
503
+ "supported": true,
504
+ "readiness": "degraded"
505
+ },
506
+ {
507
+ "mode": "relay",
508
+ "source": "extension-relay",
509
+ "scope": "tab",
510
+ "supported": true,
511
+ "readiness": "unavailable"
512
+ }
513
+ ],
514
+ "operations": {
515
+ "attach": true,
516
+ "resumeSession": true,
517
+ "activate": false,
518
+ "navigate": false,
519
+ "screenshot": false
520
+ }
521
+ }
522
+ ]
523
+ }
524
+ }
525
+ ```
526
+
527
+ ## Session payload examples
528
+
529
+ Reference fixtures:
530
+
531
+ - [examples/session.safari-actionable.example.json](examples/session.safari-actionable.example.json)
532
+ - [examples/session.chrome-readonly.example.json](examples/session.chrome-readonly.example.json)
533
+ - [examples/session.chrome-relay-readonly.example.json](examples/session.chrome-relay-readonly.example.json)
534
+ - [examples/error.chrome-relay-share-required.example.json](examples/error.chrome-relay-share-required.example.json)
535
+
536
+ Chrome relay failure contract artifacts:
537
+
538
+ - [schema/chrome-relay-error.schema.json](schema/chrome-relay-error.schema.json)
539
+ - [docs/consuming-the-bridge.md](docs/consuming-the-bridge.md)
540
+
541
+ ### Safari session example
542
+
543
+ ```json
544
+ {
545
+ "session": {
546
+ "schemaVersion": 1,
547
+ "kind": "safari-actionable",
548
+ "browser": "safari",
549
+ "attach": {
550
+ "mode": "direct",
551
+ "source": "user-browser",
552
+ "scope": "browser"
553
+ },
554
+ "semantics": {
555
+ "inspect": "browser-tabs",
556
+ "list": "saved-session",
557
+ "resume": "saved-browser-target",
558
+ "tabReference": {
559
+ "windowIndex": "browser-position",
560
+ "tabIndex": "browser-position"
561
+ }
562
+ },
563
+ "status": {
564
+ "state": "actionable",
565
+ "canAct": true
566
+ },
567
+ "capabilities": {
568
+ "resume": true,
569
+ "activate": true,
570
+ "navigate": true,
571
+ "screenshot": true
572
+ }
573
+ }
574
+ }
575
+ ```
576
+
577
+ ### Chrome session example
578
+
579
+ ```json
580
+ {
581
+ "session": {
582
+ "schemaVersion": 1,
583
+ "kind": "chrome-readonly",
584
+ "browser": "chrome",
585
+ "attach": {
586
+ "mode": "direct",
587
+ "source": "user-browser",
588
+ "scope": "browser"
589
+ },
590
+ "semantics": {
591
+ "inspect": "browser-tabs",
592
+ "list": "saved-session",
593
+ "resume": "saved-browser-target",
594
+ "tabReference": {
595
+ "windowIndex": "browser-position",
596
+ "tabIndex": "browser-position"
597
+ }
598
+ },
599
+ "status": {
600
+ "state": "read-only",
601
+ "canAct": false
602
+ },
603
+ "capabilities": {
604
+ "resume": true,
605
+ "activate": false,
606
+ "navigate": false,
607
+ "screenshot": false
608
+ }
609
+ }
610
+ }
611
+ ```
612
+
613
+ ## Error shape
614
+
615
+ CLI stderr and HTTP errors share the same machine-readable structure:
616
+
617
+ ```json
618
+ {
619
+ "error": {
620
+ "code": "missing_url",
621
+ "message": "--url is required.",
622
+ "statusCode": 400
623
+ }
624
+ }
625
+ ```
626
+
627
+ ## Constraints
628
+
629
+ ### Safari/macOS constraints
630
+
631
+ - Safari diagnostics now include a machine-readable `preflight` section so clients can distinguish attach/action readiness before calling attach, activate, navigate, or screenshot.
632
+ - Browser capability descriptors may now include additive `attachModes` metadata, and sessions may now include additive `attach` metadata so clients can distinguish direct user-browser attach from future extension relay attach without changing `kind`.
633
+ - Sessions now also include additive `semantics` metadata so clients can render truthful inspect/list/resume UX, especially for relay-scoped Chrome sessions.
634
+ - Chrome diagnostics now include a machine-readable `attach.direct` / `attach.relay` section with per-mode readiness, state, and blockers. Relay uses a local probe when available, and a minimal read-only relay attach can now create a session for the currently shared tab.
635
+ - When Safari has open windows but zero inspectable tabs, `front-tab`/tab resolution now returns `browser_no_tabs` instead of a generic availability error, matching the `preflight` blocker and empty `tabs` list.
636
+ - Safari access depends on Apple Events permission.
637
+ - Screenshots depend on Screen Recording permission.
638
+ - Activation, navigation, and screenshots visibly focus Safari.
639
+ - Session matching is heuristic, based on signature/url/title plus fallback indexes.
640
+ - If multiple tabs share the same URL/title, the first visible match may win.
641
+
642
+ ### Chrome/Chromium constraints
643
+
644
+ - Inspection currently requires an already-running local DevTools HTTP endpoint.
645
+ - The adapter is intentionally read-only in this phase.
646
+ - Diagnostics enumerate attempted endpoint discovery sources when inspection is unavailable.
647
+ - Relay diagnostics can optionally consume a local JSON probe from `LOCAL_BROWSER_BRIDGE_CHROME_RELAY_STATE_PATH`, `./.local-browser-bridge/chrome-relay-state.json`, or `~/.local-browser-bridge/chrome-relay-state.json`.
648
+ - Relay probe states distinguish extension missing, disconnected, user-click/share required, no-shared-tab, and expired-scope cases. When a shared tab is present, `attach.mode = relay` can create a read-only Chrome session for that tab.
649
+ - Chrome relay sessions are saved shared-tab references. They do not make `/v1/tab` or `/v1/tabs` relay-aware, and their `tab.windowIndex` / `tab.tabIndex` should be treated as synthetic shared-tab placeholders.
650
+ - Relay attach/resume failures reuse the same shared-tab-scoped `error.details` contract over CLI and HTTP. Consumers should branch on the returned relay metadata rather than guessing or silently falling back between direct and relay paths.
651
+
652
+ ### Chrome relay probe
653
+
654
+ If you have a local extension/helper that can write relay state, point the bridge at a JSON file:
655
+
656
+ ```bash
657
+ export LOCAL_BROWSER_BRIDGE_CHROME_RELAY_STATE_PATH="$PWD/.local-browser-bridge/chrome-relay-state.json"
658
+ ```
659
+
660
+ Producer contract and field guidance:
661
+
662
+ - [docs/chrome-relay-producer-contract.md](docs/chrome-relay-producer-contract.md)
663
+
664
+ Reference helper for local simulation:
665
+
666
+ ```bash
667
+ npm run build
668
+
669
+ # defaults to ./.local-browser-bridge/chrome-relay-state.json unless --output or
670
+ # LOCAL_BROWSER_BRIDGE_CHROME_RELAY_STATE_PATH is set
671
+ npx local-browser-bridge-chrome-relay extension-missing
672
+ npx local-browser-bridge-chrome-relay disconnected
673
+ npx local-browser-bridge-chrome-relay click-required
674
+ npx local-browser-bridge-chrome-relay share-required
675
+ npx local-browser-bridge-chrome-relay shared-tab --tab-id relay-42 --title "Shared Docs" --url https://example.com/docs
676
+ npx local-browser-bridge-chrome-relay expired-share --tab-id relay-42 --url https://example.com/docs
677
+ npx local-browser-bridge-chrome-relay clear-shared-tab
678
+ ```
679
+
680
+ The helper reuses the bridge's relay validator before writing and replaces the full JSON snapshot through a temp-file rename, matching the producer contract's overwrite model.
681
+
682
+ Example probe payload:
683
+
684
+ ```json
685
+ {
686
+ "version": "1.1.0",
687
+ "updatedAt": "2026-03-28T11:00:00.000Z",
688
+ "extensionInstalled": true,
689
+ "connected": true,
690
+ "shareRequired": false,
691
+ "userGestureRequired": false,
692
+ "sharedTab": {
693
+ "id": "tab-123",
694
+ "title": "Relay Example",
695
+ "url": "https://example.com/shared"
696
+ }
697
+ }
698
+ ```
699
+
700
+ If the probe reports a currently shared tab, `attach.relay.ready` becomes `true` and you can explicitly request relay attach with `--attach-mode relay` or `{ "attach": { "mode": "relay" } }`. Relay sessions stay `chrome-readonly`, are scoped to that one shared tab, and may carry truthful `resumable`, `resumeRequiresUserGesture`, `expiresAt`, and `trustedAt` metadata when the probe provides it. On successful saved-session resume, use the returned session as the fresh source of relay metadata for that currently shared tab. Saved relay sessions should be presented as "resume against the currently shared tab" rather than as general Chrome browser sessions.
701
+
702
+ If relay attach or relay resume fails, CLI stderr JSON and HTTP error responses now stay aligned through additive `error.details` metadata. Consumers can branch on `error.details.context.operation` plus `error.details.relay.branch` rather than inferring UX only from status codes or free-form text. The package root now re-exports two stable consumer utilities from `src/index.ts`: the narrower relay helper from `src/chrome-relay-error-helper.ts` and the broader attach/resume UX helper from `src/browser-attach-ux-helper.ts`. The sample HTTP client uses that public helper entrypoint directly.
703
+
704
+ ## Contract files
705
+
706
+ - Canonical PRD: `PRD.md`
707
+ - Direction summary: `docs/product-direction.md`
708
+ - Consumer guide: `docs/consuming-the-bridge.md`
709
+ - Relay producer guide: `docs/chrome-relay-producer-contract.md`
710
+ - Relay state schema: `schema/chrome-relay-state.schema.json`
711
+ - Capabilities schema: `schema/capabilities.schema.json`
712
+ - Example fixtures: `examples/*.json`
713
+ - Runtime capability payloads: CLI `capabilities` and HTTP `GET /v1/capabilities`
714
+
715
+ ## Storage
716
+
717
+ - Sessions: `.data/sessions.json`
718
+ - Screenshots: `.data/screenshots/*.png`
719
+
720
+ ## Validation
721
+
722
+ ```bash
723
+ npm test
724
+ ```