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.
- package/README.md +724 -0
- package/dist/package.json +61 -0
- package/dist/src/browser/chrome.d.ts +19 -0
- package/dist/src/browser/chrome.js +778 -0
- package/dist/src/browser/index.d.ts +3 -0
- package/dist/src/browser/index.js +25 -0
- package/dist/src/browser/safari.d.ts +41 -0
- package/dist/src/browser/safari.js +827 -0
- package/dist/src/browser-attach-ux-helper.d.ts +39 -0
- package/dist/src/browser-attach-ux-helper.js +157 -0
- package/dist/src/capabilities.d.ts +3 -0
- package/dist/src/capabilities.js +182 -0
- package/dist/src/chrome-relay-error-helper.d.ts +19 -0
- package/dist/src/chrome-relay-error-helper.js +78 -0
- package/dist/src/chrome-relay-helper-cli.d.ts +2 -0
- package/dist/src/chrome-relay-helper-cli.js +97 -0
- package/dist/src/chrome-relay-helper.d.ts +29 -0
- package/dist/src/chrome-relay-helper.js +151 -0
- package/dist/src/chrome-relay-state.d.ts +23 -0
- package/dist/src/chrome-relay-state.js +108 -0
- package/dist/src/claude-code.d.ts +20 -0
- package/dist/src/claude-code.js +66 -0
- package/dist/src/cli-reference-adapter.d.ts +13 -0
- package/dist/src/cli-reference-adapter.js +48 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +200 -0
- package/dist/src/codex.d.ts +17 -0
- package/dist/src/codex.js +25 -0
- package/dist/src/connection-ux.d.ts +61 -0
- package/dist/src/connection-ux.js +256 -0
- package/dist/src/errors.d.ts +12 -0
- package/dist/src/errors.js +58 -0
- package/dist/src/http-reference-adapter.d.ts +34 -0
- package/dist/src/http-reference-adapter.js +61 -0
- package/dist/src/http.d.ts +3 -0
- package/dist/src/http.js +161 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +43 -0
- package/dist/src/mcp-stdio.d.ts +2 -0
- package/dist/src/mcp-stdio.js +10 -0
- package/dist/src/mcp.d.ts +25 -0
- package/dist/src/mcp.js +483 -0
- package/dist/src/reference-adapter.d.ts +32 -0
- package/dist/src/reference-adapter.js +42 -0
- package/dist/src/service/attach-service.d.ts +28 -0
- package/dist/src/service/attach-service.js +272 -0
- package/dist/src/session-metadata.d.ts +4 -0
- package/dist/src/session-metadata.js +88 -0
- package/dist/src/store/session-store.d.ts +14 -0
- package/dist/src/store/session-store.js +52 -0
- package/dist/src/target.d.ts +9 -0
- package/dist/src/target.js +61 -0
- package/dist/src/types.d.ts +397 -0
- package/dist/src/types.js +2 -0
- package/dist/tests/attach-service.test.d.ts +1 -0
- package/dist/tests/attach-service.test.js +1367 -0
- package/dist/tests/browser-attach-ux-helper.test.d.ts +1 -0
- package/dist/tests/browser-attach-ux-helper.test.js +139 -0
- package/dist/tests/chrome-relay-error-helper.test.d.ts +1 -0
- package/dist/tests/chrome-relay-error-helper.test.js +67 -0
- package/dist/tests/chrome-relay-helper.test.d.ts +1 -0
- package/dist/tests/chrome-relay-helper.test.js +142 -0
- package/dist/tests/chrome-relay-state-schema.test.d.ts +1 -0
- package/dist/tests/chrome-relay-state-schema.test.js +96 -0
- package/dist/tests/claude-code-wrapper.test.d.ts +1 -0
- package/dist/tests/claude-code-wrapper.test.js +170 -0
- package/dist/tests/codex.test.d.ts +1 -0
- package/dist/tests/codex.test.js +210 -0
- package/dist/tests/demo-client-smoke.test.d.ts +1 -0
- package/dist/tests/demo-client-smoke.test.js +405 -0
- package/dist/tests/docs-fixtures.test.d.ts +1 -0
- package/dist/tests/docs-fixtures.test.js +255 -0
- package/dist/tests/doctor-connect-wrapper.test.d.ts +1 -0
- package/dist/tests/doctor-connect-wrapper.test.js +62 -0
- package/dist/tests/fixtures/doctor-connect-cli-stub.d.ts +1 -0
- package/dist/tests/fixtures/doctor-connect-cli-stub.js +93 -0
- package/dist/tests/fixtures/public-root-cli-stub.d.ts +210 -0
- package/dist/tests/fixtures/public-root-cli-stub.js +143 -0
- package/dist/tests/fixtures/public-root-consumer.js +67 -0
- package/dist/tests/mcp.test.d.ts +1 -0
- package/dist/tests/mcp.test.js +345 -0
- package/dist/tests/public-consumer-helpers.test.d.ts +1 -0
- package/dist/tests/public-consumer-helpers.test.js +33 -0
- package/dist/tests/public-package-git-consumption.test.d.ts +1 -0
- package/dist/tests/public-package-git-consumption.test.js +56 -0
- package/dist/tests/public-root-consumer-smoke.test.d.ts +1 -0
- package/dist/tests/public-root-consumer-smoke.test.js +214 -0
- package/dist/tests/reference-adapter.test.d.ts +1 -0
- package/dist/tests/reference-adapter.test.js +220 -0
- package/dist/tests/transport-reference-adapters.test.d.ts +1 -0
- package/dist/tests/transport-reference-adapters.test.js +214 -0
- package/package.json +61 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const capabilities = {
|
|
3
|
+
schemaVersion: 1,
|
|
4
|
+
kind: "local-browser-bridge",
|
|
5
|
+
product: { name: "local-browser-bridge", version: "0.1.0" }
|
|
6
|
+
};
|
|
7
|
+
const diagnostics = {
|
|
8
|
+
browser: "chrome",
|
|
9
|
+
checkedAt: "2026-03-30T12:00:00.000Z",
|
|
10
|
+
runtime: {
|
|
11
|
+
platform: "darwin",
|
|
12
|
+
arch: "arm64",
|
|
13
|
+
nodeVersion: "v25.8.0",
|
|
14
|
+
safariRunning: true
|
|
15
|
+
},
|
|
16
|
+
host: {
|
|
17
|
+
osascriptAvailable: true,
|
|
18
|
+
screencaptureAvailable: true,
|
|
19
|
+
safariApplicationAvailable: true
|
|
20
|
+
},
|
|
21
|
+
supportedFeatures: {
|
|
22
|
+
inspectTabs: true,
|
|
23
|
+
attach: true,
|
|
24
|
+
activate: true,
|
|
25
|
+
navigate: true,
|
|
26
|
+
screenshot: true,
|
|
27
|
+
savedSessions: true,
|
|
28
|
+
cli: true,
|
|
29
|
+
httpApi: true
|
|
30
|
+
},
|
|
31
|
+
constraints: [],
|
|
32
|
+
attach: {
|
|
33
|
+
direct: {
|
|
34
|
+
mode: "direct",
|
|
35
|
+
source: "user-browser",
|
|
36
|
+
scope: "browser",
|
|
37
|
+
supported: true,
|
|
38
|
+
ready: true,
|
|
39
|
+
state: "ready",
|
|
40
|
+
blockers: []
|
|
41
|
+
},
|
|
42
|
+
relay: {
|
|
43
|
+
mode: "relay",
|
|
44
|
+
source: "extension-relay",
|
|
45
|
+
scope: "tab",
|
|
46
|
+
supported: true,
|
|
47
|
+
ready: true,
|
|
48
|
+
state: "ready",
|
|
49
|
+
blockers: []
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const session = {
|
|
54
|
+
schemaVersion: 1,
|
|
55
|
+
id: "sess-cli-relay",
|
|
56
|
+
kind: "chrome-readonly",
|
|
57
|
+
browser: "chrome",
|
|
58
|
+
target: { type: "front" },
|
|
59
|
+
tab: {
|
|
60
|
+
browser: "chrome",
|
|
61
|
+
windowIndex: 1,
|
|
62
|
+
tabIndex: 1,
|
|
63
|
+
title: "Shared tab",
|
|
64
|
+
url: "https://example.com",
|
|
65
|
+
attachedAt: "2026-03-30T12:00:00.000Z",
|
|
66
|
+
identity: {
|
|
67
|
+
signature: "sig",
|
|
68
|
+
urlKey: "https://example.com",
|
|
69
|
+
titleKey: "Shared tab",
|
|
70
|
+
origin: "https://example.com",
|
|
71
|
+
pathname: "/"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
frontTab: {
|
|
75
|
+
browser: "chrome",
|
|
76
|
+
windowIndex: 1,
|
|
77
|
+
tabIndex: 1,
|
|
78
|
+
title: "Shared tab",
|
|
79
|
+
url: "https://example.com",
|
|
80
|
+
attachedAt: "2026-03-30T12:00:00.000Z",
|
|
81
|
+
identity: {
|
|
82
|
+
signature: "sig",
|
|
83
|
+
urlKey: "https://example.com",
|
|
84
|
+
titleKey: "Shared tab",
|
|
85
|
+
origin: "https://example.com",
|
|
86
|
+
pathname: "/"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
attach: {
|
|
90
|
+
mode: "relay",
|
|
91
|
+
source: "extension-relay",
|
|
92
|
+
scope: "tab",
|
|
93
|
+
resumable: true
|
|
94
|
+
},
|
|
95
|
+
semantics: {
|
|
96
|
+
inspect: "shared-tab-only",
|
|
97
|
+
list: "saved-session",
|
|
98
|
+
resume: "current-shared-tab",
|
|
99
|
+
tabReference: {
|
|
100
|
+
windowIndex: "synthetic-shared-tab-position",
|
|
101
|
+
tabIndex: "synthetic-shared-tab-position"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
capabilities: {
|
|
105
|
+
resume: true,
|
|
106
|
+
activate: false,
|
|
107
|
+
navigate: false,
|
|
108
|
+
screenshot: false
|
|
109
|
+
},
|
|
110
|
+
status: {
|
|
111
|
+
state: "read-only",
|
|
112
|
+
canAct: false
|
|
113
|
+
},
|
|
114
|
+
createdAt: "2026-03-30T12:00:00.000Z"
|
|
115
|
+
};
|
|
116
|
+
const resumedSession = {
|
|
117
|
+
session,
|
|
118
|
+
tab: session.tab,
|
|
119
|
+
resumedAt: "2026-03-30T12:05:00.000Z",
|
|
120
|
+
resolution: {
|
|
121
|
+
strategy: "front",
|
|
122
|
+
matched: true,
|
|
123
|
+
attachMode: "relay",
|
|
124
|
+
semantics: "current-shared-tab"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
function writeJson(payload) {
|
|
128
|
+
process.stdout.write(JSON.stringify(payload));
|
|
129
|
+
}
|
|
130
|
+
const [command, ...args] = process.argv.slice(2);
|
|
131
|
+
if (command === "capabilities") {
|
|
132
|
+
writeJson({ capabilities });
|
|
133
|
+
}
|
|
134
|
+
else if (command === "diagnostics" && args[0] === "--browser" && args[1] === "chrome") {
|
|
135
|
+
writeJson({ diagnostics });
|
|
136
|
+
}
|
|
137
|
+
else if (command === "resume" && args[0] === "--id" && args[1] === "sess-cli-relay") {
|
|
138
|
+
writeJson({ resumedSession });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
process.stderr.write(`Unexpected args: ${[command, ...args].join(" ")}`);
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.connectViaPublicHttpConsumer = connectViaPublicHttpConsumer;
|
|
4
|
+
exports.connectViaPublicCliConsumer = connectViaPublicCliConsumer;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const node_util_1 = require("node:util");
|
|
7
|
+
const { connectViaBridge, createCliBridgeAdapter, createHttpBridgeAdapter } = require("local-browser-bridge");
|
|
8
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
9
|
+
function normalizeRoute(value) {
|
|
10
|
+
if (value === "safari" || value === "chrome-direct" || value === "chrome-relay") {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Unsupported route: ${value}`);
|
|
14
|
+
}
|
|
15
|
+
function toBridgeRoute(routeName, sessionId) {
|
|
16
|
+
const browser = routeName === "safari" ? "safari" : "chrome";
|
|
17
|
+
const attachMode = routeName === "chrome-relay" ? "relay" : "direct";
|
|
18
|
+
return sessionId ? { browser, attachMode, sessionId } : { browser, attachMode };
|
|
19
|
+
}
|
|
20
|
+
async function connectViaPublicHttpConsumer(baseUrl, routeName, sessionId) {
|
|
21
|
+
const adapter = createHttpBridgeAdapter({
|
|
22
|
+
async execute(request) {
|
|
23
|
+
const response = await fetch(`${baseUrl}${request.path}`, {
|
|
24
|
+
method: request.method,
|
|
25
|
+
headers: { "content-type": "application/json" },
|
|
26
|
+
body: request.body === undefined ? undefined : JSON.stringify(request.body)
|
|
27
|
+
});
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
return {
|
|
30
|
+
status: response.status,
|
|
31
|
+
body: text.length > 0 ? JSON.parse(text) : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return connectViaBridge(adapter, toBridgeRoute(routeName, sessionId));
|
|
36
|
+
}
|
|
37
|
+
async function connectViaPublicCliConsumer(cliEntrypoint, routeName, sessionId) {
|
|
38
|
+
const adapter = createCliBridgeAdapter({
|
|
39
|
+
async execute(command) {
|
|
40
|
+
const result = await execFileAsync(process.execPath, [cliEntrypoint, ...command.args], {
|
|
41
|
+
cwd: process.cwd()
|
|
42
|
+
});
|
|
43
|
+
return { stdout: result.stdout };
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return connectViaBridge(adapter, toBridgeRoute(routeName, sessionId));
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
const [transport, arg1, arg2, arg3] = process.argv.slice(2);
|
|
50
|
+
if (transport === "http") {
|
|
51
|
+
const connection = await connectViaPublicHttpConsumer(arg1, normalizeRoute(arg2), arg3);
|
|
52
|
+
process.stdout.write(JSON.stringify(connection));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (transport === "cli") {
|
|
56
|
+
const connection = await connectViaPublicCliConsumer(arg1, normalizeRoute(arg2), arg3);
|
|
57
|
+
process.stdout.write(JSON.stringify(connection));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
throw new Error("Usage: public-root-consumer.ts <http|cli> <arg1> <route> [sessionId]");
|
|
61
|
+
}
|
|
62
|
+
if (require.main === module) {
|
|
63
|
+
void main().catch((error) => {
|
|
64
|
+
process.stderr.write(error instanceof Error ? error.message : String(error));
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,345 @@
|
|
|
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_stream_1 = require("node:stream");
|
|
8
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
9
|
+
const src_1 = require("../src");
|
|
10
|
+
function createSafariSession() {
|
|
11
|
+
return {
|
|
12
|
+
id: "session-safari-demo",
|
|
13
|
+
schemaVersion: 1,
|
|
14
|
+
kind: "safari-actionable",
|
|
15
|
+
browser: "safari",
|
|
16
|
+
target: { type: "front" },
|
|
17
|
+
tab: {
|
|
18
|
+
browser: "safari",
|
|
19
|
+
windowIndex: 1,
|
|
20
|
+
tabIndex: 1,
|
|
21
|
+
title: "Example",
|
|
22
|
+
url: "https://example.com",
|
|
23
|
+
attachedAt: "2026-03-31T00:00:00.000Z",
|
|
24
|
+
identity: {
|
|
25
|
+
signature: "safari-sig",
|
|
26
|
+
urlKey: "https://example.com",
|
|
27
|
+
titleKey: "Example",
|
|
28
|
+
origin: "https://example.com",
|
|
29
|
+
pathname: "/"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
frontTab: {
|
|
33
|
+
browser: "safari",
|
|
34
|
+
windowIndex: 1,
|
|
35
|
+
tabIndex: 1,
|
|
36
|
+
title: "Example",
|
|
37
|
+
url: "https://example.com",
|
|
38
|
+
attachedAt: "2026-03-31T00:00:00.000Z",
|
|
39
|
+
identity: {
|
|
40
|
+
signature: "safari-sig",
|
|
41
|
+
urlKey: "https://example.com",
|
|
42
|
+
titleKey: "Example",
|
|
43
|
+
origin: "https://example.com",
|
|
44
|
+
pathname: "/"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
attach: {
|
|
48
|
+
mode: "direct",
|
|
49
|
+
source: "user-browser",
|
|
50
|
+
scope: "browser"
|
|
51
|
+
},
|
|
52
|
+
semantics: {
|
|
53
|
+
inspect: "browser-tabs",
|
|
54
|
+
list: "saved-session",
|
|
55
|
+
resume: "saved-browser-target",
|
|
56
|
+
tabReference: {
|
|
57
|
+
windowIndex: "browser-position",
|
|
58
|
+
tabIndex: "browser-position"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
capabilities: { resume: true, activate: true, navigate: true, screenshot: true },
|
|
62
|
+
status: { state: "actionable", canAct: true },
|
|
63
|
+
createdAt: "2026-03-31T00:00:00.000Z"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createStubService() {
|
|
67
|
+
return {
|
|
68
|
+
getCapabilities() {
|
|
69
|
+
return { schemaVersion: 1, product: { name: "local-browser-bridge" } };
|
|
70
|
+
},
|
|
71
|
+
async diagnostics(browser) {
|
|
72
|
+
if (browser === "safari") {
|
|
73
|
+
return {
|
|
74
|
+
browser,
|
|
75
|
+
checkedAt: "2026-03-31T00:00:00.000Z",
|
|
76
|
+
runtime: { platform: "darwin", arch: "arm64", nodeVersion: "v25.8.0" },
|
|
77
|
+
host: {},
|
|
78
|
+
supportedFeatures: {},
|
|
79
|
+
constraints: [],
|
|
80
|
+
preflight: {
|
|
81
|
+
inspect: { ready: true, blockers: [] },
|
|
82
|
+
automation: { ready: true, blockers: [] },
|
|
83
|
+
screenshot: { ready: true, blockers: [] }
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
browser,
|
|
89
|
+
checkedAt: "2026-03-31T00:00:00.000Z",
|
|
90
|
+
runtime: { platform: "darwin", arch: "arm64", nodeVersion: "v25.8.0" },
|
|
91
|
+
host: {},
|
|
92
|
+
supportedFeatures: {},
|
|
93
|
+
constraints: [],
|
|
94
|
+
attach: {
|
|
95
|
+
direct: { mode: "direct", ready: false, state: "unavailable", blockers: [] },
|
|
96
|
+
relay: {
|
|
97
|
+
mode: "relay",
|
|
98
|
+
ready: false,
|
|
99
|
+
state: "unavailable",
|
|
100
|
+
blockers: [
|
|
101
|
+
{
|
|
102
|
+
code: "relay_share_required",
|
|
103
|
+
message: "Share the tab first."
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
async attach(browser, request) {
|
|
111
|
+
if (browser === "safari") {
|
|
112
|
+
return createSafariSession();
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`unexpected attach: ${browser}:${request?.attach?.mode}`);
|
|
115
|
+
},
|
|
116
|
+
async listTabs(browser) {
|
|
117
|
+
if (browser === "safari") {
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
browser: "safari",
|
|
121
|
+
windowIndex: 1,
|
|
122
|
+
tabIndex: 1,
|
|
123
|
+
title: "Example",
|
|
124
|
+
url: "https://example.com",
|
|
125
|
+
attachedAt: "2026-03-31T00:00:00.000Z",
|
|
126
|
+
identity: {
|
|
127
|
+
signature: "safari-sig",
|
|
128
|
+
urlKey: "https://example.com",
|
|
129
|
+
titleKey: "Example",
|
|
130
|
+
origin: "https://example.com",
|
|
131
|
+
pathname: "/"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`unexpected listTabs: ${browser}`);
|
|
137
|
+
},
|
|
138
|
+
async resumeSession() {
|
|
139
|
+
throw new Error("resume should not run in this test");
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function createExchange() {
|
|
144
|
+
const input = new node_stream_1.PassThrough();
|
|
145
|
+
const output = new node_stream_1.PassThrough();
|
|
146
|
+
const stderr = new node_stream_1.PassThrough();
|
|
147
|
+
const lines = [];
|
|
148
|
+
let buffer = "";
|
|
149
|
+
output.setEncoding("utf8");
|
|
150
|
+
output.on("data", (chunk) => {
|
|
151
|
+
buffer += chunk;
|
|
152
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
153
|
+
while (newlineIndex !== -1) {
|
|
154
|
+
lines.push(buffer.slice(0, newlineIndex));
|
|
155
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
156
|
+
newlineIndex = buffer.indexOf("\n");
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
(0, src_1.runMcpStdioServer)({ service: createStubService(), input, output, error: stderr });
|
|
160
|
+
async function send(message) {
|
|
161
|
+
const targetCount = lines.length + 1;
|
|
162
|
+
input.write(JSON.stringify(message) + "\n");
|
|
163
|
+
await waitFor(() => lines.length >= targetCount);
|
|
164
|
+
return JSON.parse(lines[targetCount - 1]);
|
|
165
|
+
}
|
|
166
|
+
return { send, input };
|
|
167
|
+
}
|
|
168
|
+
async function waitFor(predicate) {
|
|
169
|
+
const deadline = Date.now() + 2000;
|
|
170
|
+
while (!predicate()) {
|
|
171
|
+
if (Date.now() > deadline) {
|
|
172
|
+
throw new Error("timed out waiting for MCP response");
|
|
173
|
+
}
|
|
174
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
(0, node_test_1.default)("MCP stdio server initializes and lists the minimal RC tool surface", async () => {
|
|
178
|
+
const exchange = await createExchange();
|
|
179
|
+
const initialized = await exchange.send({
|
|
180
|
+
jsonrpc: "2.0",
|
|
181
|
+
id: 1,
|
|
182
|
+
method: "initialize",
|
|
183
|
+
params: {
|
|
184
|
+
protocolVersion: "2025-03-26",
|
|
185
|
+
capabilities: {},
|
|
186
|
+
clientInfo: { name: "test-client", version: "1.0.0" }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
strict_1.default.equal(initialized.result.protocolVersion, "2025-03-26");
|
|
190
|
+
strict_1.default.equal(initialized.result.serverInfo.name, "local-browser-bridge");
|
|
191
|
+
exchange.input.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
192
|
+
const tools = await exchange.send({
|
|
193
|
+
jsonrpc: "2.0",
|
|
194
|
+
id: 2,
|
|
195
|
+
method: "tools/list"
|
|
196
|
+
});
|
|
197
|
+
strict_1.default.deepEqual(tools.result.tools.map((tool) => tool.name), ["browser_doctor", "browser_tabs", "browser_connect"]);
|
|
198
|
+
});
|
|
199
|
+
(0, node_test_1.default)("browser_doctor returns structured Chrome relay truth without pretending relay is actionable", async () => {
|
|
200
|
+
const exchange = await createExchange();
|
|
201
|
+
await exchange.send({
|
|
202
|
+
jsonrpc: "2.0",
|
|
203
|
+
id: 1,
|
|
204
|
+
method: "initialize",
|
|
205
|
+
params: {
|
|
206
|
+
protocolVersion: "2025-06-18",
|
|
207
|
+
capabilities: {},
|
|
208
|
+
clientInfo: { name: "test-client", version: "1.0.0" }
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
exchange.input.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
212
|
+
const response = await exchange.send({
|
|
213
|
+
jsonrpc: "2.0",
|
|
214
|
+
id: 2,
|
|
215
|
+
method: "tools/call",
|
|
216
|
+
params: {
|
|
217
|
+
name: "browser_doctor",
|
|
218
|
+
arguments: {
|
|
219
|
+
route: "chrome-relay"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
strict_1.default.equal(response.result.isError, undefined);
|
|
224
|
+
strict_1.default.equal(response.result.structuredContent.tool, "browser_doctor");
|
|
225
|
+
strict_1.default.equal(response.result.structuredContent.ok, false);
|
|
226
|
+
strict_1.default.equal(response.result.structuredContent.outcome, "blocked");
|
|
227
|
+
strict_1.default.equal(response.result.structuredContent.status, "blocked");
|
|
228
|
+
strict_1.default.equal(response.result.structuredContent.category, "route-blocked");
|
|
229
|
+
strict_1.default.deepEqual(response.result.structuredContent.reason, {
|
|
230
|
+
code: "relay_share_required",
|
|
231
|
+
message: "Share the tab first."
|
|
232
|
+
});
|
|
233
|
+
strict_1.default.equal(response.result.structuredContent.truth.readOnly, true);
|
|
234
|
+
strict_1.default.equal(response.result.structuredContent.truth.sharedTabScoped, true);
|
|
235
|
+
strict_1.default.deepEqual(response.result.structuredContent.truth.unsupportedRuntimeActions, ["activate", "navigate", "screenshot"]);
|
|
236
|
+
strict_1.default.match(response.result.structuredContent.envelope.prompt, /Share the tab first/i);
|
|
237
|
+
});
|
|
238
|
+
(0, node_test_1.default)("browser_connect returns structured Safari session truth for actionable routes", async () => {
|
|
239
|
+
const exchange = await createExchange();
|
|
240
|
+
await exchange.send({
|
|
241
|
+
jsonrpc: "2.0",
|
|
242
|
+
id: 1,
|
|
243
|
+
method: "initialize",
|
|
244
|
+
params: {
|
|
245
|
+
protocolVersion: "2025-11-25",
|
|
246
|
+
capabilities: {},
|
|
247
|
+
clientInfo: { name: "test-client", version: "1.0.0" }
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
exchange.input.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
251
|
+
const response = await exchange.send({
|
|
252
|
+
jsonrpc: "2.0",
|
|
253
|
+
id: 2,
|
|
254
|
+
method: "tools/call",
|
|
255
|
+
params: {
|
|
256
|
+
name: "browser_connect",
|
|
257
|
+
arguments: {
|
|
258
|
+
route: "safari"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
strict_1.default.equal(response.result.isError, undefined);
|
|
263
|
+
strict_1.default.equal(response.result.structuredContent.tool, "browser_connect");
|
|
264
|
+
strict_1.default.equal(response.result.structuredContent.connected, true);
|
|
265
|
+
strict_1.default.equal(response.result.structuredContent.outcome, "success");
|
|
266
|
+
strict_1.default.equal(response.result.structuredContent.status, "connected");
|
|
267
|
+
strict_1.default.equal(response.result.structuredContent.category, "session-connected");
|
|
268
|
+
strict_1.default.equal(response.result.structuredContent.truth.actionable, true);
|
|
269
|
+
strict_1.default.deepEqual(response.result.structuredContent.truth.unsupportedRuntimeActions, []);
|
|
270
|
+
strict_1.default.equal(response.result.structuredContent.envelope.session.id, "session-safari-demo");
|
|
271
|
+
strict_1.default.equal(response.result.structuredContent.envelope.session.kind, "safari-actionable");
|
|
272
|
+
});
|
|
273
|
+
(0, node_test_1.default)("browser_tabs lists Safari tabs with structured success payloads", async () => {
|
|
274
|
+
const exchange = await createExchange();
|
|
275
|
+
await exchange.send({
|
|
276
|
+
jsonrpc: "2.0",
|
|
277
|
+
id: 1,
|
|
278
|
+
method: "initialize",
|
|
279
|
+
params: {
|
|
280
|
+
protocolVersion: "2025-11-25",
|
|
281
|
+
capabilities: {},
|
|
282
|
+
clientInfo: { name: "test-client", version: "1.0.0" }
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
exchange.input.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
286
|
+
const response = await exchange.send({
|
|
287
|
+
jsonrpc: "2.0",
|
|
288
|
+
id: 2,
|
|
289
|
+
method: "tools/call",
|
|
290
|
+
params: {
|
|
291
|
+
name: "browser_tabs",
|
|
292
|
+
arguments: {
|
|
293
|
+
route: "safari"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
strict_1.default.equal(response.result.isError, undefined);
|
|
298
|
+
strict_1.default.equal(response.result.structuredContent.tool, "browser_tabs");
|
|
299
|
+
strict_1.default.equal(response.result.structuredContent.ok, true);
|
|
300
|
+
strict_1.default.equal(response.result.structuredContent.blocked, false);
|
|
301
|
+
strict_1.default.equal(response.result.structuredContent.outcome, "success");
|
|
302
|
+
strict_1.default.equal(response.result.structuredContent.status, "listed");
|
|
303
|
+
strict_1.default.equal(response.result.structuredContent.category, "tab-list");
|
|
304
|
+
strict_1.default.equal(response.result.structuredContent.count, 1);
|
|
305
|
+
strict_1.default.equal(response.result.structuredContent.truth.actionable, true);
|
|
306
|
+
strict_1.default.equal(response.result.structuredContent.truth.sharedTabScoped, false);
|
|
307
|
+
strict_1.default.equal(response.result.structuredContent.tabs[0].title, "Example");
|
|
308
|
+
});
|
|
309
|
+
(0, node_test_1.default)("browser_tabs returns a structured blocked result for chrome-relay without marking it as an error", async () => {
|
|
310
|
+
const exchange = await createExchange();
|
|
311
|
+
await exchange.send({
|
|
312
|
+
jsonrpc: "2.0",
|
|
313
|
+
id: 1,
|
|
314
|
+
method: "initialize",
|
|
315
|
+
params: {
|
|
316
|
+
protocolVersion: "2025-11-25",
|
|
317
|
+
capabilities: {},
|
|
318
|
+
clientInfo: { name: "test-client", version: "1.0.0" }
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
exchange.input.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
322
|
+
const response = await exchange.send({
|
|
323
|
+
jsonrpc: "2.0",
|
|
324
|
+
id: 2,
|
|
325
|
+
method: "tools/call",
|
|
326
|
+
params: {
|
|
327
|
+
name: "browser_tabs",
|
|
328
|
+
arguments: {
|
|
329
|
+
route: "chrome-relay"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
strict_1.default.equal(response.result.isError, undefined);
|
|
334
|
+
strict_1.default.equal(response.result.structuredContent.tool, "browser_tabs");
|
|
335
|
+
strict_1.default.equal(response.result.structuredContent.ok, false);
|
|
336
|
+
strict_1.default.equal(response.result.structuredContent.blocked, true);
|
|
337
|
+
strict_1.default.equal(response.result.structuredContent.outcome, "unsupported");
|
|
338
|
+
strict_1.default.equal(response.result.structuredContent.status, "unsupported");
|
|
339
|
+
strict_1.default.equal(response.result.structuredContent.category, "shared-tab-scope");
|
|
340
|
+
strict_1.default.equal(response.result.structuredContent.truth.readOnly, true);
|
|
341
|
+
strict_1.default.equal(response.result.structuredContent.truth.sharedTabScoped, true);
|
|
342
|
+
strict_1.default.deepEqual(response.result.structuredContent.supportedRoutes, ["safari", "chrome-direct"]);
|
|
343
|
+
strict_1.default.equal(response.result.structuredContent.blockedReason.code, "shared_tab_scope_only");
|
|
344
|
+
strict_1.default.deepEqual(response.result.structuredContent.reason, response.result.structuredContent.blockedReason);
|
|
345
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
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_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const src_1 = require("../src");
|
|
9
|
+
(0, node_test_1.default)("public consumer entrypoint re-exports the stable helper surface", () => {
|
|
10
|
+
strict_1.default.equal(typeof src_1.interpretChromeRelayFailure, "function");
|
|
11
|
+
strict_1.default.equal(typeof src_1.chromeRelayBranchPrompt, "function");
|
|
12
|
+
strict_1.default.equal(typeof src_1.chromeRelayRetryGuidance, "function");
|
|
13
|
+
strict_1.default.equal(typeof src_1.chromeRelayScopeNote, "function");
|
|
14
|
+
strict_1.default.equal(typeof src_1.interpretBrowserAttachUxFromDiagnostics, "function");
|
|
15
|
+
strict_1.default.equal(typeof src_1.interpretBrowserAttachUxFromSession, "function");
|
|
16
|
+
strict_1.default.equal(typeof src_1.interpretBrowserAttachUxFromError, "function");
|
|
17
|
+
strict_1.default.equal(typeof src_1.normalizeConnectionRouteName, "function");
|
|
18
|
+
strict_1.default.equal(typeof src_1.doctorConnectionRoute, "function");
|
|
19
|
+
strict_1.default.equal(typeof src_1.connectConnectionRoute, "function");
|
|
20
|
+
strict_1.default.equal(typeof src_1.createServiceBridgeAdapter, "function");
|
|
21
|
+
strict_1.default.equal(typeof src_1.createBridgeAdapter, "function");
|
|
22
|
+
strict_1.default.equal(typeof src_1.createHttpBridgeAdapter, "function");
|
|
23
|
+
strict_1.default.equal(typeof src_1.createCliBridgeAdapter, "function");
|
|
24
|
+
strict_1.default.equal(typeof src_1.normalizeCodexRoute, "function");
|
|
25
|
+
strict_1.default.equal(typeof src_1.connectCodexViaHttp, "function");
|
|
26
|
+
strict_1.default.equal(typeof src_1.connectCodexViaCli, "function");
|
|
27
|
+
strict_1.default.equal(typeof src_1.normalizeClaudeCodeRoute, "function");
|
|
28
|
+
strict_1.default.equal(typeof src_1.prepareClaudeCodeRoute, "function");
|
|
29
|
+
strict_1.default.equal(typeof src_1.createMcpServer, "function");
|
|
30
|
+
strict_1.default.equal(typeof src_1.runMcpStdioServer, "function");
|
|
31
|
+
strict_1.default.equal(typeof src_1.sessionFromBridgeResult, "function");
|
|
32
|
+
strict_1.default.equal(typeof src_1.connectViaBridge, "function");
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
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_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
10
|
+
(0, node_test_1.default)("package metadata exposes built JS and declarations for git consumption", () => {
|
|
11
|
+
const root = process.cwd();
|
|
12
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(root, "package.json"), "utf8"));
|
|
13
|
+
strict_1.default.equal(packageJson.main, "./dist/src/index.js");
|
|
14
|
+
strict_1.default.equal(packageJson.types, "./dist/src/index.d.ts");
|
|
15
|
+
strict_1.default.equal(packageJson.scripts?.prepare, "npm run build");
|
|
16
|
+
strict_1.default.deepEqual(packageJson.files, ["dist/**/*", "README.md"]);
|
|
17
|
+
const rootExport = typeof packageJson.exports?.["."] === "string" ? undefined : packageJson.exports?.["."];
|
|
18
|
+
const uxHelperExport = typeof packageJson.exports?.["./browser-attach-ux-helper"] === "string"
|
|
19
|
+
? undefined
|
|
20
|
+
: packageJson.exports?.["./browser-attach-ux-helper"];
|
|
21
|
+
const codexExport = typeof packageJson.exports?.["./codex"] === "string"
|
|
22
|
+
? undefined
|
|
23
|
+
: packageJson.exports?.["./codex"];
|
|
24
|
+
const mcpExport = typeof packageJson.exports?.["./mcp"] === "string"
|
|
25
|
+
? undefined
|
|
26
|
+
: packageJson.exports?.["./mcp"];
|
|
27
|
+
const relayHelperExport = typeof packageJson.exports?.["./chrome-relay-error-helper"] === "string"
|
|
28
|
+
? undefined
|
|
29
|
+
: packageJson.exports?.["./chrome-relay-error-helper"];
|
|
30
|
+
strict_1.default.equal(rootExport?.require, "./dist/src/index.js");
|
|
31
|
+
strict_1.default.equal(rootExport?.types, "./dist/src/index.d.ts");
|
|
32
|
+
strict_1.default.equal(codexExport?.types, "./dist/src/codex.d.ts");
|
|
33
|
+
strict_1.default.equal(mcpExport?.types, "./dist/src/mcp.d.ts");
|
|
34
|
+
strict_1.default.equal(uxHelperExport?.types, "./dist/src/browser-attach-ux-helper.d.ts");
|
|
35
|
+
strict_1.default.equal(relayHelperExport?.types, "./dist/src/chrome-relay-error-helper.d.ts");
|
|
36
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "index.js")));
|
|
37
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "index.d.ts")));
|
|
38
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "codex.d.ts")));
|
|
39
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "mcp.d.ts")));
|
|
40
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "browser-attach-ux-helper.d.ts")));
|
|
41
|
+
strict_1.default.ok((0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, "dist", "src", "chrome-relay-error-helper.d.ts")));
|
|
42
|
+
});
|
|
43
|
+
(0, node_test_1.default)("package root resolves the stable helper surface from built output", () => {
|
|
44
|
+
const publicEntry = require(process.cwd());
|
|
45
|
+
strict_1.default.equal(typeof publicEntry.connectViaBridge, "function");
|
|
46
|
+
strict_1.default.equal(typeof publicEntry.createBridgeAdapter, "function");
|
|
47
|
+
strict_1.default.equal(typeof publicEntry.createHttpBridgeAdapter, "function");
|
|
48
|
+
strict_1.default.equal(typeof publicEntry.createCliBridgeAdapter, "function");
|
|
49
|
+
strict_1.default.equal(typeof publicEntry.normalizeCodexRoute, "function");
|
|
50
|
+
strict_1.default.equal(typeof publicEntry.connectCodexViaCli, "function");
|
|
51
|
+
strict_1.default.equal(typeof publicEntry.connectCodexViaHttp, "function");
|
|
52
|
+
strict_1.default.equal(typeof publicEntry.createMcpServer, "function");
|
|
53
|
+
strict_1.default.equal(typeof publicEntry.runMcpStdioServer, "function");
|
|
54
|
+
strict_1.default.equal(typeof publicEntry.interpretBrowserAttachUxFromSession, "function");
|
|
55
|
+
strict_1.default.equal(typeof publicEntry.interpretChromeRelayFailure, "function");
|
|
56
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|