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 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
- ## Quick Start
5
+ ## Getting Started
6
6
 
7
- ### Install from npm
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
- ### Or run from source
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
- git clone https://github.com/donovan-yohan/claude-remote-cli.git
18
- cd claude-remote-cli
19
- npm install
20
- npm start
42
+ claude-remote-cli --bg
21
43
  ```
22
44
 
23
- On first launch you'll be prompted to set a PIN. Then open `http://localhost:3456` in your browser.
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
- ## Prerequisites
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
- - **Node.js 24+**
28
- - **Claude Code CLI** installed and available in your PATH (or configure `claudeCommand` in config)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-cli",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "Remote web interface for Claude Code CLI sessions",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",
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.svg" />
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>
@@ -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
 
package/public/sw.js ADDED
@@ -0,0 +1,5 @@
1
+ // Minimal service worker for PWA install prompt support.
2
+ // Does not cache — all requests pass through to the network.
3
+ self.addEventListener('fetch', function (event) {
4
+ event.respondWith(fetch(event.request));
5
+ });