claude-remote-cli 1.9.1 → 1.9.2
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 +47 -19
- package/package.json +1 -1
- package/public/app.js +34 -4
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/index.html +2 -1
- package/public/manifest.json +11 -2
- package/public/style.css +10 -0
- package/public/sw.js +5 -0
package/README.md
CHANGED
|
@@ -2,30 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
Remote web interface for interacting with Claude Code CLI sessions from any device.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Getting Started
|
|
6
6
|
|
|
7
|
-
### Install
|
|
7
|
+
### 1. Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g claude-remote-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires **Node.js 24+** and **[Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)** installed and available in your PATH.
|
|
14
|
+
|
|
15
|
+
### 2. Start the server
|
|
16
|
+
|
|
17
|
+
```bash
|
|
11
18
|
claude-remote-cli
|
|
12
19
|
```
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
On first launch you'll be prompted to set a PIN. This PIN protects access to your Claude sessions.
|
|
22
|
+
|
|
23
|
+
Open `http://localhost:3456` in your browser and enter your PIN.
|
|
24
|
+
|
|
25
|
+
### 3. Add your project directories
|
|
26
|
+
|
|
27
|
+
Click **Settings** in the app to add root directories — these are parent folders that contain your git repos (scanned one level deep).
|
|
28
|
+
|
|
29
|
+
You can also edit `~/.config/claude-remote-cli/config.json` directly:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"rootDirs": ["/home/you/projects", "/home/you/work"]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 4. Run as a background service (recommended)
|
|
38
|
+
|
|
39
|
+
To keep the server running after you close your terminal and auto-start on login:
|
|
15
40
|
|
|
16
41
|
```bash
|
|
17
|
-
|
|
18
|
-
cd claude-remote-cli
|
|
19
|
-
npm install
|
|
20
|
-
npm start
|
|
42
|
+
claude-remote-cli --bg
|
|
21
43
|
```
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
This installs a persistent service (launchd on macOS, systemd on Linux) that restarts on crash. See [Background Service](#background-service) for more options.
|
|
46
|
+
|
|
47
|
+
### 5. Access from your phone with Tailscale
|
|
48
|
+
|
|
49
|
+
claude-remote-cli binds to `0.0.0.0` by default, but you should **not** expose it to the public internet. Use [Tailscale](https://tailscale.com/) to create a private encrypted network between your devices.
|
|
50
|
+
|
|
51
|
+
1. **Install Tailscale** on your computer (the one running claude-remote-cli) and on your phone/tablet
|
|
52
|
+
- macOS: `brew install tailscale` or download from [tailscale.com/download](https://tailscale.com/download)
|
|
53
|
+
- Linux: follow the [install guide](https://tailscale.com/download/linux)
|
|
54
|
+
- iOS/Android: install the Tailscale app from your app store
|
|
55
|
+
|
|
56
|
+
2. **Sign in** to the same Tailscale account on both devices
|
|
24
57
|
|
|
25
|
-
|
|
58
|
+
3. **Find your computer's Tailscale IP** — run `tailscale ip` on your computer, or check the Tailscale admin console. It will look like `100.x.y.z`.
|
|
26
59
|
|
|
27
|
-
|
|
28
|
-
|
|
60
|
+
4. **Open the app** on your phone at `http://100.x.y.z:3456`
|
|
61
|
+
|
|
62
|
+
That's it. Your traffic is encrypted end-to-end via WireGuard, no ports are exposed to the internet, and only devices on your Tailscale network can reach the server.
|
|
63
|
+
|
|
64
|
+
> **Alternatives:** You can also use [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) (`cloudflared tunnel --url http://localhost:3456`) or [ngrok](https://ngrok.com/) (`ngrok http 3456`), but these expose your server to the public internet and rely on the PIN as your only layer of defense. Tailscale keeps everything private.
|
|
29
65
|
|
|
30
66
|
## Platform Support
|
|
31
67
|
|
|
@@ -150,14 +186,6 @@ claude-remote-cli/
|
|
|
150
186
|
└── package.json
|
|
151
187
|
```
|
|
152
188
|
|
|
153
|
-
## Remote Access
|
|
154
|
-
|
|
155
|
-
To access from your phone or another device, expose the server via a tunnel or VPN:
|
|
156
|
-
|
|
157
|
-
- **Tailscale** (recommended): Install on both devices, access via Tailscale IP
|
|
158
|
-
- **Cloudflare Tunnel**: `cloudflared tunnel --url http://localhost:3456`
|
|
159
|
-
- **ngrok**: `ngrok http 3456`
|
|
160
|
-
|
|
161
189
|
## License
|
|
162
190
|
|
|
163
191
|
MIT
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
terminalScrollbarThumb.style.display = 'block';
|
|
252
|
-
var thumbHeight = Math.max(20, (term.rows / totalLines) * trackHeight);
|
|
252
|
+
var thumbHeight = Math.max(isMobileDevice ? 44 : 20, (term.rows / totalLines) * trackHeight);
|
|
253
253
|
var thumbTop = (viewportTop / (totalLines - term.rows)) * (trackHeight - thumbHeight);
|
|
254
254
|
|
|
255
255
|
terminalScrollbarThumb.style.height = thumbHeight + 'px';
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
var totalLines = buf.baseY + term.rows;
|
|
263
263
|
if (totalLines <= term.rows) return;
|
|
264
264
|
|
|
265
|
-
var thumbHeight = Math.max(20, (term.rows / totalLines) * terminalScrollbar.clientHeight);
|
|
265
|
+
var thumbHeight = Math.max(isMobileDevice ? 44 : 20, (term.rows / totalLines) * terminalScrollbar.clientHeight);
|
|
266
266
|
var trackUsable = terminalScrollbar.clientHeight - thumbHeight;
|
|
267
267
|
var relativeY = clientY - rect.top - thumbHeight / 2;
|
|
268
268
|
var ratio = Math.max(0, Math.min(1, relativeY / trackUsable));
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
var totalLines = buf.baseY + term.rows;
|
|
288
288
|
if (totalLines <= term.rows) return;
|
|
289
289
|
|
|
290
|
-
var thumbHeight = Math.max(20, (term.rows / totalLines) * terminalScrollbar.clientHeight);
|
|
290
|
+
var thumbHeight = Math.max(isMobileDevice ? 44 : 20, (term.rows / totalLines) * terminalScrollbar.clientHeight);
|
|
291
291
|
var trackUsable = terminalScrollbar.clientHeight - thumbHeight;
|
|
292
292
|
var newTop = Math.max(0, Math.min(trackUsable, scrollbarDragStartTop + deltaY));
|
|
293
293
|
var ratio = newTop / trackUsable;
|
|
@@ -1108,12 +1108,22 @@
|
|
|
1108
1108
|
var text = btn.dataset.text;
|
|
1109
1109
|
var key = btn.dataset.key;
|
|
1110
1110
|
|
|
1111
|
+
// Flush composed text before sending Enter so pending input isn't lost
|
|
1112
|
+
if (key === '\r' && mobileInput.flushComposedText) {
|
|
1113
|
+
mobileInput.flushComposedText();
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1111
1116
|
if (text !== undefined) {
|
|
1112
1117
|
ws.send(text);
|
|
1113
1118
|
} else if (key !== undefined) {
|
|
1114
1119
|
ws.send(key);
|
|
1115
1120
|
}
|
|
1116
1121
|
|
|
1122
|
+
// Clear input after Enter to reset state
|
|
1123
|
+
if (key === '\r' && mobileInput.clearInput) {
|
|
1124
|
+
mobileInput.clearInput();
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1117
1127
|
// Re-focus the mobile input to keep keyboard open
|
|
1118
1128
|
if (isMobileDevice) {
|
|
1119
1129
|
mobileInput.focus();
|
|
@@ -1342,12 +1352,12 @@
|
|
|
1342
1352
|
});
|
|
1343
1353
|
|
|
1344
1354
|
mobileInput.addEventListener('compositionend', function () {
|
|
1355
|
+
isComposing = false;
|
|
1345
1356
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1346
1357
|
var currentValue = mobileInput.value;
|
|
1347
1358
|
sendInputDiff(currentValue);
|
|
1348
1359
|
lastInputValue = currentValue;
|
|
1349
1360
|
}
|
|
1350
|
-
setTimeout(function () { isComposing = false; }, 0);
|
|
1351
1361
|
});
|
|
1352
1362
|
|
|
1353
1363
|
mobileInput.addEventListener('blur', function () {
|
|
@@ -1362,6 +1372,25 @@
|
|
|
1362
1372
|
lastInputValue = '';
|
|
1363
1373
|
});
|
|
1364
1374
|
|
|
1375
|
+
// Flush any pending composed text to the terminal.
|
|
1376
|
+
// Safe to call even if compositionend already ran: sendInputDiff
|
|
1377
|
+
// is a no-op when lastInputValue === mobileInput.value.
|
|
1378
|
+
function flushComposedText() {
|
|
1379
|
+
isComposing = false;
|
|
1380
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1381
|
+
var currentValue = mobileInput.value;
|
|
1382
|
+
sendInputDiff(currentValue);
|
|
1383
|
+
lastInputValue = currentValue;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
function clearInput() {
|
|
1387
|
+
mobileInput.value = '';
|
|
1388
|
+
lastInputValue = '';
|
|
1389
|
+
}
|
|
1390
|
+
// Expose for toolbar handler
|
|
1391
|
+
mobileInput.flushComposedText = flushComposedText;
|
|
1392
|
+
mobileInput.clearInput = clearInput;
|
|
1393
|
+
|
|
1365
1394
|
// Handle text input with autocorrect
|
|
1366
1395
|
var clearTimer = null;
|
|
1367
1396
|
mobileInput.addEventListener('input', function () {
|
|
@@ -1389,6 +1418,7 @@
|
|
|
1389
1418
|
|
|
1390
1419
|
switch (e.key) {
|
|
1391
1420
|
case 'Enter':
|
|
1421
|
+
flushComposedText();
|
|
1392
1422
|
ws.send('\r');
|
|
1393
1423
|
mobileInput.value = '';
|
|
1394
1424
|
lastInputValue = '';
|
|
Binary file
|
|
Binary file
|
package/public/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Claude Remote CLI</title>
|
|
7
7
|
<link rel="manifest" href="/manifest.json" />
|
|
8
8
|
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
|
9
|
-
<link rel="apple-touch-icon" href="/icon.
|
|
9
|
+
<link rel="apple-touch-icon" href="/icon-192.png" />
|
|
10
10
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
@@ -184,5 +184,6 @@
|
|
|
184
184
|
<script src="/vendor/xterm.js"></script>
|
|
185
185
|
<script src="/vendor/addon-fit.js"></script>
|
|
186
186
|
<script src="/app.js"></script>
|
|
187
|
+
<script>if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');</script>
|
|
187
188
|
</body>
|
|
188
189
|
</html>
|
package/public/manifest.json
CHANGED
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
"background_color": "#1a1a1a",
|
|
7
7
|
"theme_color": "#1a1a1a",
|
|
8
8
|
"icons": [
|
|
9
|
+
{
|
|
10
|
+
"src": "/icon-192.png",
|
|
11
|
+
"sizes": "192x192",
|
|
12
|
+
"type": "image/png"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"src": "/icon-512.png",
|
|
16
|
+
"sizes": "512x512",
|
|
17
|
+
"type": "image/png"
|
|
18
|
+
},
|
|
9
19
|
{
|
|
10
20
|
"src": "/icon.svg",
|
|
11
21
|
"sizes": "any",
|
|
12
|
-
"type": "image/svg+xml"
|
|
13
|
-
"purpose": "any"
|
|
22
|
+
"type": "image/svg+xml"
|
|
14
23
|
}
|
|
15
24
|
]
|
|
16
25
|
}
|
package/public/style.css
CHANGED
|
@@ -1089,6 +1089,16 @@ dialog#delete-worktree-dialog h2 {
|
|
|
1089
1089
|
|
|
1090
1090
|
#terminal-scrollbar {
|
|
1091
1091
|
display: block;
|
|
1092
|
+
width: 24px;
|
|
1093
|
+
right: 0;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#terminal-scrollbar-thumb {
|
|
1097
|
+
width: 24px;
|
|
1098
|
+
min-height: 44px;
|
|
1099
|
+
border-radius: 12px;
|
|
1100
|
+
background: var(--text-muted);
|
|
1101
|
+
opacity: 0.5;
|
|
1092
1102
|
}
|
|
1093
1103
|
}
|
|
1094
1104
|
|