claude-code-remote-pilot 0.4.3 → 0.4.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.5 — 2026-05-06
4
+
5
+ ### Added
6
+ - **Terminal redesign**: input is now embedded inside the terminal box (dark background, monospace font, `❯` prompt) — feels like a real terminal. After sending, focus returns to the input automatically.
7
+ - **Full-height terminal**: terminal fills the viewport height (`calc(100vh - 210px)`) with the output scrolling above the pinned input row.
8
+ - **Password auth**: `web [port] [host] [password]` — if a password is given, the browser shows a login screen. Token is stored in localStorage and sent as `Authorization: Bearer` on all requests (query param for SSE). `POST /api/login` is the only unauthenticated endpoint.
9
+
10
+ ---
11
+
12
+ ## 0.4.4 — 2026-05-06
13
+
14
+ ### Added
15
+ - Press `w` in watch mode to open (or reuse) the web dashboard in the browser. Starts the server on `127.0.0.1:3742` if not already running. Shown in the watch footer: `w: web ui`.
16
+
17
+ ---
18
+
3
19
  ## 0.4.3 — 2026-05-06
4
20
 
5
21
  ### Added
@@ -170,7 +170,7 @@ function renderWatchTable(allSessions, selectedIdx) {
170
170
  footer = ` [t] terminal [k] kill Esc: back`;
171
171
  }
172
172
  } else {
173
- footer = ` [1-${Math.min(allSessions.length, 9)}]: select session q: exit watch`;
173
+ footer = ` [1-${Math.min(allSessions.length, 9)}]: select session w: web ui q: exit watch`;
174
174
  }
175
175
 
176
176
  return ['\n', ' Claude Code Remote Pilot', bar, header, bar, ...rows, bar, footer, ''].join('\n');
@@ -222,6 +222,18 @@ function startWatch(manager, rl) {
222
222
 
223
223
  if (selectedIdx < 0) {
224
224
  if (str === 'q' || str === 'Q') { exitWatch(); return; }
225
+ if (str === 'w' || str === 'W') {
226
+ let webServer = manager._webServer;
227
+ if (!webServer) {
228
+ webServer = new WebServer(manager, 3742, '127.0.0.1');
229
+ manager._webServer = webServer;
230
+ webServer.start();
231
+ }
232
+ const url = `http://${webServer.host}:${webServer.port}`;
233
+ const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
234
+ spawn(opener, [url], { stdio: 'ignore', detached: true }).unref();
235
+ return;
236
+ }
225
237
  const n = parseInt(str);
226
238
  if (!isNaN(n) && n >= 1 && n <= Math.min(allSessions.length, 9)) {
227
239
  selectedIdx = n - 1;
@@ -319,7 +331,7 @@ const HELP = `
319
331
  spawn <path> [name] Start Claude at path (name defaults to dir name)
320
332
  list Show all sessions
321
333
  watch Live session monitor (q to exit)
322
- web [port] [host] Start web dashboard (default: 3742 127.0.0.1)
334
+ web [port] [host] [password] Start web dashboard (default: 3742 127.0.0.1)
323
335
  attach <name> Open tmux session in this terminal
324
336
  kill <name> Stop a session
325
337
  resume [message] Show or set the message sent after a limit resets
@@ -433,16 +445,18 @@ ${HELP}`);
433
445
  case 'web': {
434
446
  const port = parseInt(args[0]) || 3742;
435
447
  const host = args[1] || '127.0.0.1';
448
+ const password = args[2] || null;
436
449
  let webServer = manager._webServer;
437
450
  if (webServer) {
438
451
  console.log(` Web dashboard already running at http://${webServer.host}:${webServer.port}`);
439
452
  break;
440
453
  }
441
- webServer = new WebServer(manager, port, host);
454
+ webServer = new WebServer(manager, port, host, password);
442
455
  manager._webServer = webServer;
443
456
  webServer.start();
444
457
  const url = `http://${host}:${port}`;
445
458
  console.log(` ✓ Web dashboard started at ${url}`);
459
+ if (password) console.log(` Password protected. Enter password in the browser.`);
446
460
  const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
447
461
  spawn(opener, [url], { stdio: 'ignore', detached: true }).unref();
448
462
  break;
package/lib/WebServer.js CHANGED
@@ -2,16 +2,19 @@
2
2
  const http = require('http');
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const crypto = require('crypto');
5
6
  const { spawnSync } = require('child_process');
6
7
  const config = require('./config');
7
8
 
8
9
  const STRIP_ANSI = /\x1b\[[0-9;]*[mGKHFABCDJsuhl]|\x1b[()][AB012]/g;
9
10
 
10
11
  class WebServer {
11
- constructor(manager, port = 3742, host = '127.0.0.1') {
12
+ constructor(manager, port = 3742, host = '127.0.0.1', password = null) {
12
13
  this.manager = manager;
13
14
  this.port = port;
14
15
  this.host = host;
16
+ this.password = password || null;
17
+ this._token = password ? crypto.randomBytes(20).toString('hex') : null;
15
18
  this.startedAt = new Date();
16
19
  this.server = null;
17
20
  this._clients = new Set();
@@ -47,7 +50,30 @@ class WebServer {
47
50
  });
48
51
  }
49
52
 
50
- _handleApi(req, res, pathname) {
53
+ // Returns true if authorized (or no password set). Sends 401 and returns false otherwise.
54
+ _checkAuth(req, res, url) {
55
+ if (!this.password) return true;
56
+ const authHeader = req.headers['authorization'];
57
+ if (authHeader === `Bearer ${this._token}`) return true;
58
+ if (url && url.searchParams.get('token') === this._token) return true;
59
+ this._json(res, 401, { error: 'Unauthorized' });
60
+ return false;
61
+ }
62
+
63
+ _handleApi(req, res, pathname, url) {
64
+ // POST /api/login — no auth required
65
+ if (req.method === 'POST' && pathname === '/api/login') {
66
+ return this._readBody(req, (err, body) => {
67
+ if (err) return this._json(res, 400, { error: err.message });
68
+ if (!this.password) return this._json(res, 200, { token: null });
69
+ if (body.password !== this.password) return this._json(res, 401, { error: 'Wrong password' });
70
+ return this._json(res, 200, { token: this._token });
71
+ });
72
+ }
73
+
74
+ // All other API routes require auth
75
+ if (!this._checkAuth(req, res, url)) return;
76
+
51
77
  // GET /api/sessions
52
78
  if (req.method === 'GET' && pathname === '/api/sessions') {
53
79
  return this._json(res, 200, this._buildAllSessions());
@@ -141,6 +167,7 @@ class WebServer {
141
167
  }
142
168
 
143
169
  if (pathname === '/events') {
170
+ if (!this._checkAuth(req, res, url)) return;
144
171
  res.writeHead(200, {
145
172
  'Content-Type': 'text/event-stream',
146
173
  'Cache-Control': 'no-cache',
@@ -153,7 +180,7 @@ class WebServer {
153
180
  }
154
181
 
155
182
  if (pathname.startsWith('/api/')) {
156
- return this._handleApi(req, res, pathname);
183
+ return this._handleApi(req, res, pathname, url);
157
184
  }
158
185
 
159
186
  res.writeHead(404);