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
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const promises_1 = require("node:fs/promises");
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_test_1 = __importDefault(require("node:test"));
9
+ const node_path_1 = require("node:path");
10
+ async function readJson(path) {
11
+ return JSON.parse(await (0, promises_1.readFile)(path, "utf8"));
12
+ }
13
+ (0, node_test_1.default)("documentation fixtures preserve the stable contract examples", async () => {
14
+ const root = process.cwd();
15
+ const capabilities = await readJson((0, node_path_1.resolve)(root, "examples", "capabilities.example.json"));
16
+ strict_1.default.equal(capabilities.capabilities.schemaVersion, 1);
17
+ strict_1.default.equal(capabilities.capabilities.product.summary, "Reusable, agent-agnostic local browser bridge with honest capability signaling. Safari is actionable; Chrome/Chromium is read-only in v1.");
18
+ const safari = capabilities.capabilities.browsers.find((browser) => browser.browser === "safari");
19
+ const chrome = capabilities.capabilities.browsers.find((browser) => browser.browser === "chrome");
20
+ strict_1.default.ok(safari);
21
+ strict_1.default.ok(chrome);
22
+ strict_1.default.equal(safari.kind, "safari-actionable");
23
+ strict_1.default.equal(safari.attachModes?.[0]?.mode, "direct");
24
+ strict_1.default.equal(safari.attachModes?.[0]?.source, "user-browser");
25
+ strict_1.default.equal(safari.attachModes?.[0]?.scope, "browser");
26
+ strict_1.default.equal(safari.operations.resumeSession, true);
27
+ strict_1.default.equal(safari.operations.activate, true);
28
+ strict_1.default.equal(safari.operations.navigate, true);
29
+ strict_1.default.equal(safari.operations.screenshot, true);
30
+ strict_1.default.equal(chrome.kind, "chrome-readonly");
31
+ strict_1.default.match(chrome.bridge.implementation, /read-only in v1/i);
32
+ strict_1.default.equal(chrome.attachModes?.[0]?.mode, "direct");
33
+ strict_1.default.equal(chrome.attachModes?.[1]?.mode, "relay");
34
+ strict_1.default.equal(chrome.attachModes?.[1]?.scope, "tab");
35
+ strict_1.default.equal(chrome.operations.resumeSession, true);
36
+ strict_1.default.equal(chrome.operations.activate, false);
37
+ strict_1.default.equal(chrome.operations.navigate, false);
38
+ strict_1.default.equal(chrome.operations.screenshot, false);
39
+ const safariSession = await readJson((0, node_path_1.resolve)(root, "examples", "session.safari-actionable.example.json"));
40
+ strict_1.default.equal(safariSession.session.schemaVersion, 1);
41
+ strict_1.default.equal(safariSession.session.kind, "safari-actionable");
42
+ strict_1.default.equal(safariSession.session.attach.mode, "direct");
43
+ strict_1.default.equal(safariSession.session.attach.scope, "browser");
44
+ strict_1.default.equal(safariSession.session.semantics.inspect, "browser-tabs");
45
+ strict_1.default.equal(safariSession.session.semantics.resume, "saved-browser-target");
46
+ strict_1.default.equal(safariSession.session.semantics.tabReference.windowIndex, "browser-position");
47
+ strict_1.default.equal(safariSession.session.status.state, "actionable");
48
+ strict_1.default.equal(safariSession.session.status.canAct, true);
49
+ strict_1.default.equal(safariSession.session.capabilities.resume, true);
50
+ strict_1.default.equal(safariSession.session.capabilities.activate, true);
51
+ strict_1.default.equal(safariSession.session.capabilities.navigate, true);
52
+ strict_1.default.equal(safariSession.session.capabilities.screenshot, true);
53
+ const chromeSession = await readJson((0, node_path_1.resolve)(root, "examples", "session.chrome-readonly.example.json"));
54
+ strict_1.default.equal(chromeSession.session.schemaVersion, 1);
55
+ strict_1.default.equal(chromeSession.session.kind, "chrome-readonly");
56
+ strict_1.default.equal(chromeSession.session.attach.mode, "direct");
57
+ strict_1.default.equal(chromeSession.session.attach.source, "user-browser");
58
+ strict_1.default.equal(chromeSession.session.semantics.inspect, "browser-tabs");
59
+ strict_1.default.equal(chromeSession.session.semantics.resume, "saved-browser-target");
60
+ strict_1.default.equal(chromeSession.session.semantics.tabReference.tabIndex, "browser-position");
61
+ strict_1.default.equal(chromeSession.session.status.state, "read-only");
62
+ strict_1.default.equal(chromeSession.session.status.canAct, false);
63
+ strict_1.default.equal(chromeSession.session.capabilities.resume, true);
64
+ strict_1.default.equal(chromeSession.session.capabilities.activate, false);
65
+ strict_1.default.equal(chromeSession.session.capabilities.navigate, false);
66
+ strict_1.default.equal(chromeSession.session.capabilities.screenshot, false);
67
+ const chromeRelaySession = await readJson((0, node_path_1.resolve)(root, "examples", "session.chrome-relay-readonly.example.json"));
68
+ strict_1.default.equal(chromeRelaySession.session.schemaVersion, 1);
69
+ strict_1.default.equal(chromeRelaySession.session.kind, "chrome-readonly");
70
+ strict_1.default.equal(chromeRelaySession.session.attach.mode, "relay");
71
+ strict_1.default.equal(chromeRelaySession.session.attach.source, "extension-relay");
72
+ strict_1.default.equal(chromeRelaySession.session.attach.scope, "tab");
73
+ strict_1.default.equal(chromeRelaySession.session.attach.resumeRequiresUserGesture, true);
74
+ strict_1.default.equal(chromeRelaySession.session.semantics.inspect, "shared-tab-only");
75
+ strict_1.default.equal(chromeRelaySession.session.semantics.resume, "current-shared-tab");
76
+ strict_1.default.equal(chromeRelaySession.session.semantics.tabReference.windowIndex, "synthetic-shared-tab-position");
77
+ strict_1.default.match(chromeRelaySession.session.semantics.notes?.[0] ?? "", /last tab explicitly shared/i);
78
+ strict_1.default.equal(chromeRelaySession.session.status.state, "read-only");
79
+ strict_1.default.equal(chromeRelaySession.session.status.canAct, false);
80
+ strict_1.default.equal(chromeRelaySession.session.capabilities.resume, true);
81
+ strict_1.default.equal(chromeRelaySession.session.capabilities.activate, false);
82
+ strict_1.default.equal(chromeRelaySession.session.capabilities.navigate, false);
83
+ strict_1.default.equal(chromeRelaySession.session.capabilities.screenshot, false);
84
+ const chromeRelayError = await readJson((0, node_path_1.resolve)(root, "examples", "error.chrome-relay-share-required.example.json"));
85
+ strict_1.default.equal(chromeRelayError.error.code, "relay_share_required");
86
+ strict_1.default.equal(chromeRelayError.error.statusCode, 503);
87
+ strict_1.default.equal(chromeRelayError.error.details.context.browser, "chrome");
88
+ strict_1.default.equal(chromeRelayError.error.details.context.attachMode, "relay");
89
+ strict_1.default.equal(chromeRelayError.error.details.context.operation, "attach");
90
+ strict_1.default.equal(chromeRelayError.error.details.relay.branch, "share-tab");
91
+ strict_1.default.equal(chromeRelayError.error.details.relay.retryable, true);
92
+ strict_1.default.equal(chromeRelayError.error.details.relay.userActionRequired, true);
93
+ strict_1.default.equal(chromeRelayError.error.details.relay.phase, "diagnostics");
94
+ strict_1.default.equal(chromeRelayError.error.details.relay.sharedTabScope, "current-shared-tab");
95
+ });
96
+ (0, node_test_1.default)("consumer sample stays aligned with the stable contract guidance", async () => {
97
+ const root = process.cwd();
98
+ const sample = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "clients", "http-node.ts"), "utf8");
99
+ const httpConsumer = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "clients", "http-consumer.ts"), "utf8");
100
+ const cliConsumer = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "clients", "cli-consumer.ts"), "utf8");
101
+ const doctorConnectWrapper = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "clients", "doctor-connect-wrapper.ts"), "utf8");
102
+ const claudeCodeInstalledPackageMcpConfig = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "mcp", "claude-code.installed-package.mcp.json"), "utf8");
103
+ const genericRepoCheckoutMcpConfig = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "examples", "mcp", "generic-stdio.repo-checkout.json"), "utf8");
104
+ const consumerGuide = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "consuming-the-bridge.md"), "utf8");
105
+ const adapterPatterns = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "adapter-patterns.md"), "utf8");
106
+ const readme = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "README.md"), "utf8");
107
+ strict_1.default.match(sample, /schemaVersion !== 1/);
108
+ strict_1.default.match(sample, /chrome-direct/);
109
+ strict_1.default.match(sample, /chrome-relay/);
110
+ strict_1.default.match(sample, /\/v1\/diagnostics\?browser=/);
111
+ strict_1.default.match(sample, /Safari \(actionable\)/);
112
+ strict_1.default.match(sample, /Chrome \(direct, read-only\)/);
113
+ strict_1.default.match(sample, /Chrome \(shared tab, read-only\)/);
114
+ strict_1.default.match(sample, /Chrome direct attach needs a local DevTools endpoint/);
115
+ strict_1.default.match(sample, /Chrome relay only works for a tab you explicitly share/);
116
+ strict_1.default.match(sample, /does not silently fall back between Chrome direct and relay/);
117
+ strict_1.default.match(sample, /Derive relay behavior from attach\.mode=relay plus the returned semantics fields\./);
118
+ strict_1.default.match(sample, /Tab reference semantics: windowIndex=/);
119
+ strict_1.default.match(sample, /Relay failure branch:/);
120
+ strict_1.default.match(sample, /Relay user prompt:/);
121
+ strict_1.default.match(sample, /sharedTabScope\?: "current-shared-tab"/);
122
+ strict_1.default.match(sample, /LOCAL_BROWSER_BRIDGE_SESSION_ID/);
123
+ strict_1.default.match(sample, /Hide activate\/navigate\/screenshot\./);
124
+ strict_1.default.match(sample, /require\("\.\.\/\.\.\/dist\/src"\) as typeof import\("\.\.\/\.\.\/src"\)/);
125
+ strict_1.default.match(httpConsumer, /createHttpBridgeAdapter/);
126
+ strict_1.default.match(httpConsumer, /connectViaBridge/);
127
+ strict_1.default.match(httpConsumer, /interpretBrowserAttachUxFromError/);
128
+ strict_1.default.match(httpConsumer, /chrome-direct/);
129
+ strict_1.default.match(httpConsumer, /chrome-relay/);
130
+ strict_1.default.match(httpConsumer, /LOCAL_BROWSER_BRIDGE_SESSION_ID/);
131
+ strict_1.default.match(httpConsumer, /transport: http/);
132
+ strict_1.default.match(cliConsumer, /createCliBridgeAdapter/);
133
+ strict_1.default.match(cliConsumer, /connectViaBridge/);
134
+ strict_1.default.match(cliConsumer, /interpretBrowserAttachUxFromError/);
135
+ strict_1.default.match(cliConsumer, /chrome-direct/);
136
+ strict_1.default.match(cliConsumer, /chrome-relay/);
137
+ strict_1.default.match(cliConsumer, /LOCAL_BROWSER_BRIDGE_SESSION_ID/);
138
+ strict_1.default.match(cliConsumer, /transport: cli/);
139
+ strict_1.default.match(doctorConnectWrapper, /runBridgeJson<DoctorPayload>\(\["doctor"/);
140
+ strict_1.default.match(doctorConnectWrapper, /runBridgeJson<ConnectPayload>\(\["connect"/);
141
+ strict_1.default.match(doctorConnectWrapper, /LOCAL_BROWSER_BRIDGE_SESSION_ID/);
142
+ strict_1.default.match(doctorConnectWrapper, /LOCAL_BROWSER_BRIDGE_BIN/);
143
+ strict_1.default.match(doctorConnectWrapper, /wrapper: "doctor-connect"/);
144
+ strict_1.default.match(doctorConnectWrapper, /outcome: "success"/);
145
+ strict_1.default.match(doctorConnectWrapper, /category: "session-connected"/);
146
+ strict_1.default.match(doctorConnectWrapper, /payload\.reason/);
147
+ strict_1.default.match(doctorConnectWrapper, /"chrome-relay"/);
148
+ strict_1.default.match(doctorConnectWrapper, /"safari"/);
149
+ strict_1.default.match(doctorConnectWrapper, /sharedTabScoped/);
150
+ strict_1.default.match(doctorConnectWrapper, /suggestedActions/);
151
+ strict_1.default.match(claudeCodeInstalledPackageMcpConfig, /node_modules\/\.bin\/local-browser-bridge-mcp/);
152
+ strict_1.default.doesNotMatch(claudeCodeInstalledPackageMcpConfig, /dist\/src\/mcp-stdio\.js/);
153
+ strict_1.default.match(genericRepoCheckoutMcpConfig, /"command": "node"/);
154
+ strict_1.default.match(genericRepoCheckoutMcpConfig, /dist\/src\/mcp-stdio\.js/);
155
+ strict_1.default.doesNotMatch(genericRepoCheckoutMcpConfig, /node_modules\/\.bin\/local-browser-bridge-mcp/);
156
+ strict_1.default.match(consumerGuide, /src\/index\.ts/);
157
+ strict_1.default.match(consumerGuide, /import \{ interpretChromeRelayFailure \} from "\.\.\/src"/);
158
+ strict_1.default.match(consumerGuide, /interpretBrowserAttachUxFromSession[\s\S]*from "\.\.\/src"/);
159
+ strict_1.default.match(consumerGuide, /Chrome relay parity example/);
160
+ strict_1.default.match(consumerGuide, /Adapter Patterns/);
161
+ strict_1.default.match(consumerGuide, /\.\/adapter-patterns\.md/);
162
+ strict_1.default.match(consumerGuide, /schema\/chrome-relay-error\.schema\.json/);
163
+ strict_1.default.match(consumerGuide, /examples\/error\.chrome-relay-share-required\.example\.json/);
164
+ strict_1.default.match(consumerGuide, /attach --browser chrome --attach-mode relay/);
165
+ strict_1.default.match(consumerGuide, /"attach":\{"mode":"relay"\}/);
166
+ strict_1.default.match(consumerGuide, /session\.attach/);
167
+ strict_1.default.match(consumerGuide, /session\.semantics/);
168
+ strict_1.default.match(consumerGuide, /error\.details\.relay\.branch/);
169
+ strict_1.default.match(consumerGuide, /error\.details\.relay\.sharedTabScope = "current-shared-tab"/);
170
+ strict_1.default.match(consumerGuide, /interpretChromeRelayFailure/);
171
+ strict_1.default.match(consumerGuide, /retryable/);
172
+ strict_1.default.match(consumerGuide, /examples\/clients\/http-consumer\.ts/);
173
+ strict_1.default.match(consumerGuide, /examples\/clients\/cli-consumer\.ts/);
174
+ strict_1.default.match(consumerGuide, /examples\/clients\/doctor-connect-wrapper\.ts/);
175
+ strict_1.default.match(consumerGuide, /examples\/clients\/codex-consumer\.ts/);
176
+ strict_1.default.match(consumerGuide, /examples\/clients\/claude-code-tool\.ts/);
177
+ strict_1.default.match(consumerGuide, /createHttpBridgeAdapter/);
178
+ strict_1.default.match(consumerGuide, /createCliBridgeAdapter/);
179
+ strict_1.default.match(consumerGuide, /connectViaBridge/);
180
+ strict_1.default.match(consumerGuide, /normalizeCodexRoute/);
181
+ strict_1.default.match(consumerGuide, /connectCodexViaCli/);
182
+ strict_1.default.match(consumerGuide, /connectCodexViaHttp/);
183
+ strict_1.default.match(consumerGuide, /normalizeClaudeCodeRoute/);
184
+ strict_1.default.match(consumerGuide, /prepareClaudeCodeRoute/);
185
+ strict_1.default.match(consumerGuide, /node --experimental-strip-types examples\/clients\/doctor-connect-wrapper\.ts safari/);
186
+ strict_1.default.match(consumerGuide, /outcome`, `status`, `category`, and optional `reason`/);
187
+ strict_1.default.match(consumerGuide, /"outcome": "blocked"/);
188
+ strict_1.default.match(consumerGuide, /"category": "session-connected"/);
189
+ strict_1.default.match(consumerGuide, /Chrome relay only works for a tab you explicitly share/);
190
+ strict_1.default.match(consumerGuide, /suggestedActions/);
191
+ strict_1.default.doesNotMatch(consumerGuide, /OpenClaw\/browser-style consumer demo/);
192
+ strict_1.default.match(adapterPatterns, /OpenClaw/);
193
+ strict_1.default.match(adapterPatterns, /AWOS/);
194
+ strict_1.default.match(adapterPatterns, /Codex/);
195
+ strict_1.default.match(adapterPatterns, /Claude Code/);
196
+ strict_1.default.match(adapterPatterns, /transport-neutral/i);
197
+ strict_1.default.match(adapterPatterns, /agent-agnostic/i);
198
+ strict_1.default.match(adapterPatterns, /CLI for one-shot invocation/);
199
+ strict_1.default.match(adapterPatterns, /local HTTP for long-running connectors/);
200
+ strict_1.default.match(adapterPatterns, /Shared consumer surface/);
201
+ strict_1.default.match(adapterPatterns, /from "local-browser-bridge"/);
202
+ strict_1.default.match(adapterPatterns, /Minimal adapter skeleton:/);
203
+ strict_1.default.match(adapterPatterns, /connectBrowserRoute/);
204
+ strict_1.default.match(adapterPatterns, /runBrowserTool/);
205
+ strict_1.default.match(adapterPatterns, /runAgentStep/);
206
+ strict_1.default.match(adapterPatterns, /normalizeCodexRoute/);
207
+ strict_1.default.match(adapterPatterns, /connectCodexViaCli/);
208
+ strict_1.default.match(adapterPatterns, /connectCodexViaHttp/);
209
+ strict_1.default.match(adapterPatterns, /prepareToolPrompt/);
210
+ strict_1.default.match(adapterPatterns, /export async function connect\(/);
211
+ strict_1.default.match(adapterPatterns, /examples\/clients\/doctor-connect-wrapper\.ts/);
212
+ strict_1.default.match(adapterPatterns, /outcome` \/ `status` \/ `category` \/ `reason`/);
213
+ strict_1.default.match(readme, /examples\/clients\/doctor-connect-wrapper\.ts/);
214
+ strict_1.default.match(readme, /local-browser-bridge doctor --route/);
215
+ strict_1.default.match(readme, /outcome` \/ `status` \/ `category` \/ `reason`/);
216
+ strict_1.default.match(readme, /chrome-relay` read-only plus shared-tab scoped/);
217
+ strict_1.default.match(readme, /`browser_tabs` is available for Safari and `chrome-direct`/);
218
+ strict_1.default.match(readme, /structured non-error blocked result/);
219
+ strict_1.default.match(readme, /Runtime action tools such as `activate`, `navigate`, or `screenshot` are intentionally not part of this RC MCP surface/);
220
+ strict_1.default.match(readme, /examples\/mcp\/claude-code\.installed-package\.mcp\.json/);
221
+ strict_1.default.match(readme, /examples\/mcp\/generic-stdio\.repo-checkout\.json/);
222
+ strict_1.default.match(readme, /npm run cli -- --help/);
223
+ strict_1.default.doesNotMatch(readme, /npm run bridge -- --help/);
224
+ strict_1.default.match(readme, /consumer project[\s\S]*node_modules\/\.bin\/local-browser-bridge-mcp/);
225
+ strict_1.default.match(readme, /Equivalent direct repo-checkout command after `npm run build`:\s*```bash\s*node \.\/dist\/src\/mcp-stdio\.js\s*```/);
226
+ strict_1.default.match(readme, /node \.\/dist\/src\/mcp-stdio\.js/);
227
+ strict_1.default.doesNotMatch(readme, /Equivalent direct repo-checkout command after `npm run build`:\s*```bash\s*\.\/node_modules\/\.bin\/local-browser-bridge-mcp\s*```/);
228
+ });
229
+ (0, node_test_1.default)("chrome relay error schema artifact stays documented and aligned with the example", async () => {
230
+ const root = process.cwd();
231
+ const schema = await readJson((0, node_path_1.resolve)(root, "schema", "chrome-relay-error.schema.json"));
232
+ const readme = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "README.md"), "utf8");
233
+ const consumerGuide = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "consuming-the-bridge.md"), "utf8");
234
+ const adapterPatterns = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "adapter-patterns.md"), "utf8");
235
+ const integrationContract = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "docs", "agent-integration-contract.md"), "utf8");
236
+ const prd = await (0, promises_1.readFile)((0, node_path_1.resolve)(root, "PRD.md"), "utf8");
237
+ strict_1.default.match(schema.$id, /chrome-relay-error\.schema\.json$/);
238
+ strict_1.default.equal(schema.properties?.error?.properties?.details?.$ref, "#/$defs/chromeRelayErrorDetails");
239
+ strict_1.default.match(readme, /schema\/chrome-relay-error\.schema\.json/);
240
+ strict_1.default.match(readme, /examples\/error\.chrome-relay-share-required\.example\.json/);
241
+ strict_1.default.match(readme, /docs\/adapter-patterns\.md/);
242
+ strict_1.default.match(readme, /src\/index\.ts/);
243
+ strict_1.default.match(consumerGuide, /\.\/adapter-patterns\.md/);
244
+ strict_1.default.match(adapterPatterns, /Agent Integration Contract/);
245
+ strict_1.default.match(adapterPatterns, /Consuming local-browser-bridge/);
246
+ strict_1.default.match(consumerGuide, /schema\/chrome-relay-error\.schema\.json/);
247
+ strict_1.default.match(consumerGuide, /examples\/error\.chrome-relay-share-required\.example\.json/);
248
+ strict_1.default.match(integrationContract, /schema\/chrome-relay-error\.schema\.json/);
249
+ strict_1.default.match(integrationContract, /examples\/error\.chrome-relay-share-required\.example\.json/);
250
+ strict_1.default.match(integrationContract, /additive transport-neutral error envelope/i);
251
+ strict_1.default.match(integrationContract, /shared-tab read-only path/i);
252
+ strict_1.default.match(prd, /Chrome relay structured failure contract/);
253
+ strict_1.default.match(prd, /transport-neutral structured error details aligned across CLI and local HTTP/);
254
+ strict_1.default.match(prd, /error\.details\.relay\.sharedTabScope = "current-shared-tab"/);
255
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_path_1 = require("node:path");
9
+ const node_util_1 = require("node:util");
10
+ const node_test_1 = __importDefault(require("node:test"));
11
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
12
+ (0, node_test_1.default)("doctor/connect wrapper example surfaces blocked relay prompts", async () => {
13
+ const wrapperPath = (0, node_path_1.resolve)(process.cwd(), "examples/clients/doctor-connect-wrapper.ts");
14
+ const stubPath = (0, node_path_1.resolve)(process.cwd(), "dist/tests/fixtures/doctor-connect-cli-stub.js");
15
+ const result = await execFileAsync(process.execPath, ["--experimental-strip-types", wrapperPath, "chrome-relay"], {
16
+ cwd: process.cwd(),
17
+ env: {
18
+ ...process.env,
19
+ LOCAL_BROWSER_BRIDGE_BIN: process.execPath,
20
+ LOCAL_BROWSER_BRIDGE_CLI_PATH: stubPath
21
+ }
22
+ });
23
+ const payload = JSON.parse(result.stdout);
24
+ strict_1.default.equal(payload.result.ok, false);
25
+ strict_1.default.equal(payload.result.stage, "doctor");
26
+ strict_1.default.equal(payload.result.outcome, "blocked");
27
+ strict_1.default.equal(payload.result.status, "blocked");
28
+ strict_1.default.equal(payload.result.category, "route-blocked");
29
+ strict_1.default.equal(payload.result.reason?.code, "relay_share_required");
30
+ strict_1.default.equal(payload.result.route, "chrome-relay");
31
+ strict_1.default.equal(payload.result.label, "Chrome (shared tab, read-only)");
32
+ strict_1.default.match(payload.result.prompt, /Share the tab first/i);
33
+ strict_1.default.equal(payload.result.nextStep.action, "fix-blocker");
34
+ strict_1.default.equal(payload.result.readOnly, true);
35
+ strict_1.default.equal(payload.result.sharedTabScoped, true);
36
+ });
37
+ (0, node_test_1.default)("doctor/connect wrapper example returns concise session results for Safari", async () => {
38
+ const wrapperPath = (0, node_path_1.resolve)(process.cwd(), "examples/clients/doctor-connect-wrapper.ts");
39
+ const stubPath = (0, node_path_1.resolve)(process.cwd(), "dist/tests/fixtures/doctor-connect-cli-stub.js");
40
+ const result = await execFileAsync(process.execPath, ["--experimental-strip-types", wrapperPath, "safari"], {
41
+ cwd: process.cwd(),
42
+ env: {
43
+ ...process.env,
44
+ LOCAL_BROWSER_BRIDGE_BIN: process.execPath,
45
+ LOCAL_BROWSER_BRIDGE_CLI_PATH: stubPath
46
+ }
47
+ });
48
+ const payload = JSON.parse(result.stdout);
49
+ strict_1.default.equal(payload.wrapper, "doctor-connect");
50
+ strict_1.default.equal(payload.result.ok, true);
51
+ strict_1.default.equal(payload.result.stage, "connect");
52
+ strict_1.default.equal(payload.result.outcome, "success");
53
+ strict_1.default.equal(payload.result.status, "connected");
54
+ strict_1.default.equal(payload.result.category, "session-connected");
55
+ strict_1.default.equal(payload.result.route, "safari");
56
+ strict_1.default.equal(payload.result.label, "Safari (actionable)");
57
+ strict_1.default.equal(payload.result.readOnly, false);
58
+ strict_1.default.equal(payload.result.session?.id, "session-safari-demo");
59
+ strict_1.default.equal(payload.result.session?.kind, "safari-actionable");
60
+ strict_1.default.equal(payload.result.session?.canAct, true);
61
+ strict_1.default.deepEqual(payload.result.session?.suggestedActions, ["resume", "activate", "navigate", "screenshot"]);
62
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function writeJson(payload) {
4
+ process.stdout.write(JSON.stringify(payload));
5
+ }
6
+ const [command, ...args] = process.argv.slice(2);
7
+ const route = args[1];
8
+ if (command === "doctor" && args[0] === "--route" && route === "chrome-relay") {
9
+ writeJson({
10
+ ok: false,
11
+ command: "doctor",
12
+ outcome: "blocked",
13
+ status: "blocked",
14
+ category: "route-blocked",
15
+ reason: {
16
+ code: "relay_share_required",
17
+ message: "Share the tab first."
18
+ },
19
+ summary: "Chrome (shared tab, read-only) is not ready yet. It remains read-only and only covers the currently shared tab.",
20
+ prompt: "Chrome relay only works for a tab you explicitly share. Share the tab first, then retry.",
21
+ nextStep: {
22
+ action: "fix-blocker",
23
+ prompt: "Chrome relay only works for a tab you explicitly share. Share the tab first, then retry."
24
+ },
25
+ routeUx: {
26
+ label: "Chrome (shared tab, read-only)",
27
+ state: "blocked",
28
+ readOnly: true,
29
+ sharedTabScoped: true
30
+ }
31
+ });
32
+ }
33
+ else if (command === "doctor" && args[0] === "--route" && route === "safari") {
34
+ writeJson({
35
+ ok: true,
36
+ command: "doctor",
37
+ outcome: "success",
38
+ status: "ready",
39
+ category: "route-ready",
40
+ summary: "Safari (actionable) is ready for attach. It is actionable.",
41
+ nextStep: {
42
+ action: "connect",
43
+ prompt: "Run local-browser-bridge connect --route safari to continue.",
44
+ command: "local-browser-bridge connect --route safari"
45
+ },
46
+ routeUx: {
47
+ label: "Safari (actionable)",
48
+ state: "ready",
49
+ readOnly: false,
50
+ sharedTabScoped: false
51
+ }
52
+ });
53
+ }
54
+ else if (command === "connect" && args[0] === "--route" && route === "safari") {
55
+ writeJson({
56
+ ok: true,
57
+ command: "connect",
58
+ connected: true,
59
+ outcome: "success",
60
+ status: "connected",
61
+ category: "session-connected",
62
+ summary: "Connected Safari (actionable) session session-safari-demo. It is actionable.",
63
+ nextStep: {
64
+ action: "session-ready",
65
+ prompt: "Use session session-safari-demo for follow-up actions like activate, navigate, or screenshot."
66
+ },
67
+ routeUx: {
68
+ label: "Safari (actionable)",
69
+ state: "ready",
70
+ readOnly: false,
71
+ sharedTabScoped: false
72
+ },
73
+ sessionUx: {
74
+ label: "Safari (actionable)",
75
+ state: "ready",
76
+ readOnly: false,
77
+ sharedTabScoped: false
78
+ },
79
+ session: {
80
+ id: "session-safari-demo",
81
+ kind: "safari-actionable",
82
+ capabilities: {
83
+ activate: true,
84
+ navigate: true,
85
+ screenshot: true
86
+ }
87
+ }
88
+ });
89
+ }
90
+ else {
91
+ process.stderr.write(`Unexpected args: ${[command, ...args].join(" ")}`);
92
+ process.exitCode = 1;
93
+ }
@@ -0,0 +1,210 @@
1
+ declare const capabilities: {
2
+ schemaVersion: number;
3
+ kind: string;
4
+ product: {
5
+ name: string;
6
+ version: string;
7
+ };
8
+ };
9
+ declare const diagnostics: {
10
+ browser: "chrome";
11
+ checkedAt: string;
12
+ runtime: {
13
+ platform: string;
14
+ arch: string;
15
+ nodeVersion: string;
16
+ safariRunning: boolean;
17
+ };
18
+ host: {
19
+ osascriptAvailable: boolean;
20
+ screencaptureAvailable: boolean;
21
+ safariApplicationAvailable: boolean;
22
+ };
23
+ supportedFeatures: {
24
+ inspectTabs: boolean;
25
+ attach: boolean;
26
+ activate: boolean;
27
+ navigate: boolean;
28
+ screenshot: boolean;
29
+ savedSessions: boolean;
30
+ cli: boolean;
31
+ httpApi: boolean;
32
+ };
33
+ constraints: never[];
34
+ attach: {
35
+ direct: {
36
+ mode: "direct";
37
+ source: "user-browser";
38
+ scope: "browser";
39
+ supported: boolean;
40
+ ready: boolean;
41
+ state: "ready";
42
+ blockers: never[];
43
+ };
44
+ relay: {
45
+ mode: "relay";
46
+ source: "extension-relay";
47
+ scope: "tab";
48
+ supported: boolean;
49
+ ready: boolean;
50
+ state: "ready";
51
+ blockers: never[];
52
+ };
53
+ };
54
+ };
55
+ declare const session: {
56
+ schemaVersion: 1;
57
+ id: string;
58
+ kind: "chrome-readonly";
59
+ browser: "chrome";
60
+ target: {
61
+ type: "front";
62
+ };
63
+ tab: {
64
+ browser: "chrome";
65
+ windowIndex: number;
66
+ tabIndex: number;
67
+ title: string;
68
+ url: string;
69
+ attachedAt: string;
70
+ identity: {
71
+ signature: string;
72
+ urlKey: string;
73
+ titleKey: string;
74
+ origin: string;
75
+ pathname: string;
76
+ };
77
+ };
78
+ frontTab: {
79
+ browser: "chrome";
80
+ windowIndex: number;
81
+ tabIndex: number;
82
+ title: string;
83
+ url: string;
84
+ attachedAt: string;
85
+ identity: {
86
+ signature: string;
87
+ urlKey: string;
88
+ titleKey: string;
89
+ origin: string;
90
+ pathname: string;
91
+ };
92
+ };
93
+ attach: {
94
+ mode: "relay";
95
+ source: "extension-relay";
96
+ scope: "tab";
97
+ resumable: boolean;
98
+ };
99
+ semantics: {
100
+ inspect: "shared-tab-only";
101
+ list: "saved-session";
102
+ resume: "current-shared-tab";
103
+ tabReference: {
104
+ windowIndex: "synthetic-shared-tab-position";
105
+ tabIndex: "synthetic-shared-tab-position";
106
+ };
107
+ };
108
+ capabilities: {
109
+ resume: true;
110
+ activate: boolean;
111
+ navigate: boolean;
112
+ screenshot: boolean;
113
+ };
114
+ status: {
115
+ state: "read-only";
116
+ canAct: boolean;
117
+ };
118
+ createdAt: string;
119
+ };
120
+ declare const resumedSession: {
121
+ session: {
122
+ schemaVersion: 1;
123
+ id: string;
124
+ kind: "chrome-readonly";
125
+ browser: "chrome";
126
+ target: {
127
+ type: "front";
128
+ };
129
+ tab: {
130
+ browser: "chrome";
131
+ windowIndex: number;
132
+ tabIndex: number;
133
+ title: string;
134
+ url: string;
135
+ attachedAt: string;
136
+ identity: {
137
+ signature: string;
138
+ urlKey: string;
139
+ titleKey: string;
140
+ origin: string;
141
+ pathname: string;
142
+ };
143
+ };
144
+ frontTab: {
145
+ browser: "chrome";
146
+ windowIndex: number;
147
+ tabIndex: number;
148
+ title: string;
149
+ url: string;
150
+ attachedAt: string;
151
+ identity: {
152
+ signature: string;
153
+ urlKey: string;
154
+ titleKey: string;
155
+ origin: string;
156
+ pathname: string;
157
+ };
158
+ };
159
+ attach: {
160
+ mode: "relay";
161
+ source: "extension-relay";
162
+ scope: "tab";
163
+ resumable: boolean;
164
+ };
165
+ semantics: {
166
+ inspect: "shared-tab-only";
167
+ list: "saved-session";
168
+ resume: "current-shared-tab";
169
+ tabReference: {
170
+ windowIndex: "synthetic-shared-tab-position";
171
+ tabIndex: "synthetic-shared-tab-position";
172
+ };
173
+ };
174
+ capabilities: {
175
+ resume: true;
176
+ activate: boolean;
177
+ navigate: boolean;
178
+ screenshot: boolean;
179
+ };
180
+ status: {
181
+ state: "read-only";
182
+ canAct: boolean;
183
+ };
184
+ createdAt: string;
185
+ };
186
+ tab: {
187
+ browser: "chrome";
188
+ windowIndex: number;
189
+ tabIndex: number;
190
+ title: string;
191
+ url: string;
192
+ attachedAt: string;
193
+ identity: {
194
+ signature: string;
195
+ urlKey: string;
196
+ titleKey: string;
197
+ origin: string;
198
+ pathname: string;
199
+ };
200
+ };
201
+ resumedAt: string;
202
+ resolution: {
203
+ strategy: "front";
204
+ matched: boolean;
205
+ attachMode: "relay";
206
+ semantics: "current-shared-tab";
207
+ };
208
+ };
209
+ declare function writeJson(payload: unknown): void;
210
+ declare const command: string, args: string[];