claude-relay 1.0.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/LICENSE +21 -0
- package/README.md +92 -0
- package/bin/cli.js +121 -0
- package/lib/public/app.js +1217 -0
- package/lib/public/index.html +56 -0
- package/lib/public/style.css +1211 -0
- package/lib/server.js +557 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# claude-relay
|
|
2
|
+
|
|
3
|
+
Run `npx claude-relay` in any directory. Access Claude Code on that directory from any device.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
$ cd ~/my-project
|
|
7
|
+
$ npx claude-relay
|
|
8
|
+
|
|
9
|
+
Claude Relay running at http://100.64.1.5:3456
|
|
10
|
+
Project: my-project
|
|
11
|
+
Directory: /Users/you/my-project
|
|
12
|
+
|
|
13
|
+
Open the URL on your phone to start chatting.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Why?
|
|
17
|
+
|
|
18
|
+
Yes, you can use Claude Code from the Claude app — but it requires a GitHub repo, runs in a sandboxed VM, and comes with limitations. No local tools, no custom skills, no access to your actual dev environment.
|
|
19
|
+
|
|
20
|
+
**claude-relay** gives you the real thing. One command in any directory — even `~` — and you get full Claude Code on **your machine**, from any device. Your files, your tools, your skills, your environment. No GitHub required, no sandbox.
|
|
21
|
+
|
|
22
|
+
## How it works
|
|
23
|
+
|
|
24
|
+
claude-relay spawns `claude` CLI processes and bridges them to a web UI over WebSocket. Your browser talks to the relay, the relay talks to Claude Code. Sessions persist across reconnects.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Browser (any device) <--> claude-relay (your machine) <--> claude CLI
|
|
28
|
+
WebSocket HTTP + WS stdin/stdout
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **One command** — `npx claude-relay` and you're live
|
|
34
|
+
- **Mobile-first UI** — designed for phones and tablets, works everywhere
|
|
35
|
+
- **Multi-session** — run multiple Claude Code sessions, switch between them
|
|
36
|
+
- **Streaming** — real-time token streaming, tool execution, thinking blocks
|
|
37
|
+
- **Session persistence** — sessions survive server restarts and reconnects
|
|
38
|
+
- **Tailscale-aware** — prefers Tailscale IP for secure remote access
|
|
39
|
+
- **Slash commands** — full slash command support with autocomplete
|
|
40
|
+
- **Zero config** — no API keys, no setup. Uses your local `claude` installation
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
45
|
+
- Node.js 18+
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Run directly (no install needed)
|
|
51
|
+
npx claude-relay
|
|
52
|
+
|
|
53
|
+
# Or install globally
|
|
54
|
+
npm install -g claude-relay
|
|
55
|
+
claude-relay
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Start in current directory
|
|
62
|
+
npx claude-relay
|
|
63
|
+
|
|
64
|
+
# Custom port
|
|
65
|
+
npx claude-relay -p 8080
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then open the URL on any device connected to the same network.
|
|
69
|
+
|
|
70
|
+
### Remote access with Tailscale
|
|
71
|
+
|
|
72
|
+
claude-relay automatically detects [Tailscale](https://tailscale.com) and uses your Tailscale IP. Install Tailscale on your machine and phone, and you can access Claude Code from anywhere — coffee shop, commute, couch.
|
|
73
|
+
|
|
74
|
+
## Limitations
|
|
75
|
+
|
|
76
|
+
- Permission prompts (tool approval) are not yet relayed to the browser
|
|
77
|
+
- Image/file attachments from the browser are not yet supported
|
|
78
|
+
- Session persistence is unstable
|
|
79
|
+
|
|
80
|
+
These are planned for future releases. Contributions welcome.
|
|
81
|
+
|
|
82
|
+
## Security
|
|
83
|
+
|
|
84
|
+
**claude-relay has no built-in authentication or encryption.** Anyone with access to the URL gets full Claude Code access to your machine — reading, writing, and executing files with your user permissions.
|
|
85
|
+
|
|
86
|
+
We strongly recommend using a private network layer such as [Tailscale](https://tailscale.com), WireGuard, or a VPN. claude-relay automatically detects Tailscale and prefers its IP for this reason.
|
|
87
|
+
|
|
88
|
+
If you choose to expose it beyond your private network, that's your call. **Entirely at your own risk.** The authors assume no responsibility for any damage, data loss, or security incidents.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { createServer } = require("../lib/server");
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
let port = 3456;
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
if (args[i] === "-p" || args[i] === "--port") {
|
|
11
|
+
port = parseInt(args[i + 1], 10);
|
|
12
|
+
if (isNaN(port)) {
|
|
13
|
+
console.error("Invalid port number");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
i++;
|
|
17
|
+
} else if (args[i] === "-h" || args[i] === "--help") {
|
|
18
|
+
console.log("Usage: claude-relay [-p|--port <port>]");
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log("Options:");
|
|
21
|
+
console.log(" -p, --port <port> Port to listen on (default: 3456)");
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
|
|
28
|
+
function getLocalIP() {
|
|
29
|
+
const interfaces = os.networkInterfaces();
|
|
30
|
+
|
|
31
|
+
// Prefer Tailscale IP
|
|
32
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
33
|
+
if (/^(tailscale|utun)/.test(name)) {
|
|
34
|
+
for (const addr of addrs) {
|
|
35
|
+
if (addr.family === "IPv4" && !addr.internal && addr.address.startsWith("100.")) {
|
|
36
|
+
return addr.address;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check all interfaces for Tailscale CGNAT range (100.64.0.0/10)
|
|
43
|
+
for (const addrs of Object.values(interfaces)) {
|
|
44
|
+
for (const addr of addrs) {
|
|
45
|
+
if (addr.family === "IPv4" && !addr.internal && addr.address.startsWith("100.")) {
|
|
46
|
+
return addr.address;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fall back to LAN IP
|
|
52
|
+
for (const addrs of Object.values(interfaces)) {
|
|
53
|
+
for (const addr of addrs) {
|
|
54
|
+
if (addr.family === "IPv4" && !addr.internal) {
|
|
55
|
+
return addr.address;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return "localhost";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function confirm(callback) {
|
|
64
|
+
const readline = require("readline");
|
|
65
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
66
|
+
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log(" \x1b[1;33m⚠ WARNING — READ BEFORE CONTINUING\x1b[0m");
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log(" claude-relay has \x1b[1mno built-in authentication or encryption.\x1b[0m");
|
|
71
|
+
console.log(" Anyone with access to the URL gets \x1b[1mfull Claude Code access\x1b[0m to");
|
|
72
|
+
console.log(" this machine — reading, writing, and executing files with your");
|
|
73
|
+
console.log(" user permissions.");
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(" We strongly recommend using a private network layer such as");
|
|
76
|
+
console.log(" Tailscale, WireGuard, or a VPN.");
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(" If you choose to expose it beyond your private network, that's");
|
|
79
|
+
console.log(" your call. \x1b[1mEntirely at your own risk.\x1b[0m The authors assume no");
|
|
80
|
+
console.log(" responsibility for any damage, data loss, or security incidents.");
|
|
81
|
+
console.log("");
|
|
82
|
+
|
|
83
|
+
rl.question(" Do you understand and accept? (type \x1b[1my\x1b[0m to continue): ", (answer) => {
|
|
84
|
+
rl.close();
|
|
85
|
+
if (/^(y|yes)$/i.test(answer.trim())) {
|
|
86
|
+
callback();
|
|
87
|
+
} else {
|
|
88
|
+
console.log("\n Aborted.\n");
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function start() {
|
|
95
|
+
const server = createServer(cwd);
|
|
96
|
+
|
|
97
|
+
server.on("error", (err) => {
|
|
98
|
+
if (err.code === "EADDRINUSE") {
|
|
99
|
+
console.error(`\n Port ${port} is already in use.`);
|
|
100
|
+
console.error(` Run with a different port: claude-relay -p <port>`);
|
|
101
|
+
console.error(` Or kill the existing process: lsof -ti :${port} | xargs kill\n`);
|
|
102
|
+
} else {
|
|
103
|
+
console.error(`\n Server error: ${err.message}\n`);
|
|
104
|
+
}
|
|
105
|
+
process.exit(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
server.listen(port, () => {
|
|
109
|
+
const ip = getLocalIP();
|
|
110
|
+
const project = require("path").basename(cwd);
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(` Claude Relay running at http://${ip}:${port}`);
|
|
113
|
+
console.log(` Project: ${project}`);
|
|
114
|
+
console.log(` Directory: ${cwd}`);
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log(" Open the URL on your phone to start chatting.");
|
|
117
|
+
console.log("");
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
confirm(start);
|