claude-bridge-cli 2.0.12 → 2.0.17

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 (5) hide show
  1. package/README.md +74 -74
  2. package/bin/cli.js +391 -391
  3. package/lib/bridge.js +1127 -1037
  4. package/lib/relay-client.js +152 -152
  5. package/package.json +34 -26
@@ -1,152 +1,152 @@
1
- "use strict";
2
-
3
- const WebSocket = require("ws");
4
- const http = require("node:http");
5
-
6
- function startRelay(config) {
7
- const { relay, bearerToken, port, host } = config;
8
- let backoff = 1000;
9
- let ws = null;
10
-
11
- function connect() {
12
- const headers = {};
13
- if (relay.cfId) headers["CF-Access-Client-Id"] = relay.cfId;
14
- if (relay.cfSecret) headers["CF-Access-Client-Secret"] = relay.cfSecret;
15
-
16
- ws = new WebSocket(relay.url, { headers });
17
-
18
- ws.on("open", () => {
19
- console.log("[relay] Connected to relay, sending hello...");
20
- ws.send(JSON.stringify({ type: "hello", machine: relay.machine, token: relay.token }));
21
- backoff = 1000;
22
- });
23
-
24
- ws.on("message", (data) => {
25
- let frame;
26
- try { frame = JSON.parse(data.toString()); } catch { return; }
27
-
28
- if (frame.type === "hello_ack") {
29
- console.log(`[relay] Authenticated as "${relay.machine}" (socket: ${frame.socket_id})`);
30
- return;
31
- }
32
-
33
- if (frame.type === "shutdown") {
34
- console.log("[relay] Received shutdown — exiting.");
35
- removeService();
36
- process.exit(0);
37
- }
38
-
39
- if (frame.type === "cancel") {
40
- return; // bridge handles its own stop
41
- }
42
-
43
- if (frame.type === "request") {
44
- handleRequest(frame);
45
- }
46
- });
47
-
48
- ws.on("close", (code, reason) => {
49
- // 4003 = bearer rejected by the relay (verify_machine_token failed).
50
- // The bearer has been rotated or admin-removed; the cached one in
51
- // auth.json is useless. Stop the reconnect loop and tell the user
52
- // exactly how to recover.
53
- if (code === 4003) {
54
- const reasonStr = reason && reason.toString ? reason.toString() : "unauthorized";
55
- console.error("");
56
- console.error(`[relay] Authorization rejected (${code} ${reasonStr}).`);
57
- console.error("[relay] The saved bearer no longer matches what the relay expects — it was probably rotated or revoked.");
58
- console.error("[relay] Recover by deleting the cached auth and re-pairing:");
59
- if (process.platform === "win32") {
60
- console.error(" del %USERPROFILE%\\.claude-bridge\\auth.json");
61
- } else {
62
- console.error(" rm ~/.claude-bridge/auth.json");
63
- }
64
- console.error(` claude-bridge-cli start --relay-url ${config.relay.url} --machine ${config.relay.machine}`);
65
- console.error("");
66
- process.exit(2);
67
- }
68
- console.log(`[relay] Disconnected (${code}). Reconnecting in ${backoff / 1000}s...`);
69
- setTimeout(connect, backoff);
70
- backoff = Math.min(60000, backoff * 2);
71
- });
72
-
73
- ws.on("error", (err) => {
74
- console.error(`[relay] Error: ${err.message}`);
75
- });
76
- }
77
-
78
- function handleRequest(frame) {
79
- const { requestId, method, path: reqPath, query, body } = frame;
80
-
81
- // Build local HTTP request to the bridge
82
- const url = new URL(`http://${host || "127.0.0.1"}:${port}${reqPath || "/"}`);
83
- if (query) {
84
- for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
85
- }
86
-
87
- const reqHeaders = {
88
- "Content-Type": "application/json",
89
- "Authorization": `Bearer ${bearerToken}`,
90
- };
91
-
92
- const reqBody = body ? JSON.stringify(body) : null;
93
- const options = {
94
- hostname: url.hostname,
95
- port: url.port,
96
- path: url.pathname + url.search,
97
- method: method || "GET",
98
- headers: reqHeaders,
99
- timeout: 7200000, // 2 hours
100
- };
101
-
102
- const req = http.request(options, (res) => {
103
- let data = "";
104
- res.on("data", (c) => { data += c; });
105
- res.on("end", () => {
106
- let respBody;
107
- try { respBody = JSON.parse(data); } catch { respBody = data ? { _raw: data } : null; }
108
- sendResponse(requestId, res.statusCode, respBody);
109
- });
110
- });
111
-
112
- req.on("error", (err) => {
113
- sendResponse(requestId, 502, { error: `local_bridge_unreachable: ${err.message}` });
114
- });
115
-
116
- req.on("timeout", () => {
117
- req.destroy();
118
- sendResponse(requestId, 504, { error: "local_bridge_timeout" });
119
- });
120
-
121
- if (reqBody) req.write(reqBody);
122
- req.end();
123
- }
124
-
125
- function sendResponse(requestId, status, body) {
126
- if (ws && ws.readyState === WebSocket.OPEN) {
127
- ws.send(JSON.stringify({ type: "response", requestId, status, body }));
128
- }
129
- }
130
-
131
- function removeService() {
132
- try {
133
- if (process.platform === "win32") {
134
- require("node:child_process").execSync('schtasks /Delete /TN "Claude Code Bridge" /F', { stdio: "ignore" });
135
- } else if (process.platform === "darwin") {
136
- const p = require("node:path").join(require("node:os").homedir(), "Library/LaunchAgents/com.claude-code-bridge.plist");
137
- try { require("node:child_process").execSync(`launchctl unload -w "${p}"`, { stdio: "ignore" }); } catch {}
138
- try { require("node:fs").unlinkSync(p); } catch {}
139
- } else {
140
- const { execSync } = require("node:child_process");
141
- try { execSync("systemctl --user disable --now claude-code-bridge.service", { stdio: "ignore" }); } catch {}
142
- const unitPath = require("node:path").join(require("node:os").homedir(), ".config/systemd/user/claude-code-bridge.service");
143
- try { require("node:fs").unlinkSync(unitPath); } catch {}
144
- try { execSync("systemctl --user daemon-reload", { stdio: "ignore" }); } catch {}
145
- }
146
- } catch {}
147
- }
148
-
149
- connect();
150
- }
151
-
152
- module.exports = { startRelay };
1
+ "use strict";
2
+
3
+ const WebSocket = require("ws");
4
+ const http = require("node:http");
5
+
6
+ function startRelay(config) {
7
+ const { relay, bearerToken, port, host } = config;
8
+ let backoff = 1000;
9
+ let ws = null;
10
+
11
+ function connect() {
12
+ const headers = {};
13
+ if (relay.cfId) headers["CF-Access-Client-Id"] = relay.cfId;
14
+ if (relay.cfSecret) headers["CF-Access-Client-Secret"] = relay.cfSecret;
15
+
16
+ ws = new WebSocket(relay.url, { headers });
17
+
18
+ ws.on("open", () => {
19
+ console.log("[relay] Connected to relay, sending hello...");
20
+ ws.send(JSON.stringify({ type: "hello", machine: relay.machine, token: relay.token }));
21
+ backoff = 1000;
22
+ });
23
+
24
+ ws.on("message", (data) => {
25
+ let frame;
26
+ try { frame = JSON.parse(data.toString()); } catch { return; }
27
+
28
+ if (frame.type === "hello_ack") {
29
+ console.log(`[relay] Authenticated as "${relay.machine}" (socket: ${frame.socket_id})`);
30
+ return;
31
+ }
32
+
33
+ if (frame.type === "shutdown") {
34
+ console.log("[relay] Received shutdown — exiting.");
35
+ removeService();
36
+ process.exit(0);
37
+ }
38
+
39
+ if (frame.type === "cancel") {
40
+ return; // bridge handles its own stop
41
+ }
42
+
43
+ if (frame.type === "request") {
44
+ handleRequest(frame);
45
+ }
46
+ });
47
+
48
+ ws.on("close", (code, reason) => {
49
+ // 4003 = bearer rejected by the relay (verify_machine_token failed).
50
+ // The bearer has been rotated or admin-removed; the cached one in
51
+ // auth.json is useless. Stop the reconnect loop and tell the user
52
+ // exactly how to recover.
53
+ if (code === 4003) {
54
+ const reasonStr = reason && reason.toString ? reason.toString() : "unauthorized";
55
+ console.error("");
56
+ console.error(`[relay] Authorization rejected (${code} ${reasonStr}).`);
57
+ console.error("[relay] The saved bearer no longer matches what the relay expects — it was probably rotated or revoked.");
58
+ console.error("[relay] Recover by deleting the cached auth and re-pairing:");
59
+ if (process.platform === "win32") {
60
+ console.error(" del %USERPROFILE%\\.claude-bridge\\auth.json");
61
+ } else {
62
+ console.error(" rm ~/.claude-bridge/auth.json");
63
+ }
64
+ console.error(` claude-bridge-cli start --relay-url ${config.relay.url} --machine ${config.relay.machine}`);
65
+ console.error("");
66
+ process.exit(2);
67
+ }
68
+ console.log(`[relay] Disconnected (${code}). Reconnecting in ${backoff / 1000}s...`);
69
+ setTimeout(connect, backoff);
70
+ backoff = Math.min(60000, backoff * 2);
71
+ });
72
+
73
+ ws.on("error", (err) => {
74
+ console.error(`[relay] Error: ${err.message}`);
75
+ });
76
+ }
77
+
78
+ function handleRequest(frame) {
79
+ const { requestId, method, path: reqPath, query, body } = frame;
80
+
81
+ // Build local HTTP request to the bridge
82
+ const url = new URL(`http://${host || "127.0.0.1"}:${port}${reqPath || "/"}`);
83
+ if (query) {
84
+ for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
85
+ }
86
+
87
+ const reqHeaders = {
88
+ "Content-Type": "application/json",
89
+ "Authorization": `Bearer ${bearerToken}`,
90
+ };
91
+
92
+ const reqBody = body ? JSON.stringify(body) : null;
93
+ const options = {
94
+ hostname: url.hostname,
95
+ port: url.port,
96
+ path: url.pathname + url.search,
97
+ method: method || "GET",
98
+ headers: reqHeaders,
99
+ timeout: 7200000, // 2 hours
100
+ };
101
+
102
+ const req = http.request(options, (res) => {
103
+ let data = "";
104
+ res.on("data", (c) => { data += c; });
105
+ res.on("end", () => {
106
+ let respBody;
107
+ try { respBody = JSON.parse(data); } catch { respBody = data ? { _raw: data } : null; }
108
+ sendResponse(requestId, res.statusCode, respBody);
109
+ });
110
+ });
111
+
112
+ req.on("error", (err) => {
113
+ sendResponse(requestId, 502, { error: `local_bridge_unreachable: ${err.message}` });
114
+ });
115
+
116
+ req.on("timeout", () => {
117
+ req.destroy();
118
+ sendResponse(requestId, 504, { error: "local_bridge_timeout" });
119
+ });
120
+
121
+ if (reqBody) req.write(reqBody);
122
+ req.end();
123
+ }
124
+
125
+ function sendResponse(requestId, status, body) {
126
+ if (ws && ws.readyState === WebSocket.OPEN) {
127
+ ws.send(JSON.stringify({ type: "response", requestId, status, body }));
128
+ }
129
+ }
130
+
131
+ function removeService() {
132
+ try {
133
+ if (process.platform === "win32") {
134
+ require("node:child_process").execSync('schtasks /Delete /TN "Claude Code Bridge" /F', { stdio: "ignore" });
135
+ } else if (process.platform === "darwin") {
136
+ const p = require("node:path").join(require("node:os").homedir(), "Library/LaunchAgents/com.claude-code-bridge.plist");
137
+ try { require("node:child_process").execSync(`launchctl unload -w "${p}"`, { stdio: "ignore" }); } catch {}
138
+ try { require("node:fs").unlinkSync(p); } catch {}
139
+ } else {
140
+ const { execSync } = require("node:child_process");
141
+ try { execSync("systemctl --user disable --now claude-code-bridge.service", { stdio: "ignore" }); } catch {}
142
+ const unitPath = require("node:path").join(require("node:os").homedir(), ".config/systemd/user/claude-code-bridge.service");
143
+ try { require("node:fs").unlinkSync(unitPath); } catch {}
144
+ try { execSync("systemctl --user daemon-reload", { stdio: "ignore" }); } catch {}
145
+ }
146
+ } catch {}
147
+ }
148
+
149
+ connect();
150
+ }
151
+
152
+ module.exports = { startRelay };
package/package.json CHANGED
@@ -1,26 +1,34 @@
1
- {
2
- "name": "claude-bridge-cli",
3
- "version": "2.0.12",
4
- "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
- "main": "lib/bridge.js",
6
- "bin": {
7
- "claude-bridge-cli": "bin/cli.js"
8
- },
9
- "scripts": {
10
- "start": "node bin/cli.js start"
11
- },
12
- "keywords": [
13
- "claude",
14
- "cli",
15
- "bridge",
16
- "relay",
17
- "websocket"
18
- ],
19
- "license": "MIT",
20
- "dependencies": {
21
- "ws": "^8.18.0"
22
- },
23
- "engines": {
24
- "node": ">=18.0.0"
25
- }
26
- }
1
+ {
2
+ "name": "claude-bridge-cli",
3
+ "version": "2.0.17",
4
+ "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
+ "main": "lib/bridge.js",
6
+ "bin": {
7
+ "claude-bridge-cli": "bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/cli.js start"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Hananc86/claude-code-bridge.git"
15
+ },
16
+ "files": [
17
+ "bin/",
18
+ "lib/"
19
+ ],
20
+ "keywords": [
21
+ "claude",
22
+ "cli",
23
+ "bridge",
24
+ "relay",
25
+ "websocket"
26
+ ],
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "ws": "^8.18.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }