claude-code-remote-pilot 0.4.4 → 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,14 @@
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
+
3
12
  ## 0.4.4 — 2026-05-06
4
13
 
5
14
  ### Added
@@ -331,7 +331,7 @@ const HELP = `
331
331
  spawn <path> [name] Start Claude at path (name defaults to dir name)
332
332
  list Show all sessions
333
333
  watch Live session monitor (q to exit)
334
- 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)
335
335
  attach <name> Open tmux session in this terminal
336
336
  kill <name> Stop a session
337
337
  resume [message] Show or set the message sent after a limit resets
@@ -445,16 +445,18 @@ ${HELP}`);
445
445
  case 'web': {
446
446
  const port = parseInt(args[0]) || 3742;
447
447
  const host = args[1] || '127.0.0.1';
448
+ const password = args[2] || null;
448
449
  let webServer = manager._webServer;
449
450
  if (webServer) {
450
451
  console.log(` Web dashboard already running at http://${webServer.host}:${webServer.port}`);
451
452
  break;
452
453
  }
453
- webServer = new WebServer(manager, port, host);
454
+ webServer = new WebServer(manager, port, host, password);
454
455
  manager._webServer = webServer;
455
456
  webServer.start();
456
457
  const url = `http://${host}:${port}`;
457
458
  console.log(` ✓ Web dashboard started at ${url}`);
459
+ if (password) console.log(` Password protected. Enter password in the browser.`);
458
460
  const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
459
461
  spawn(opener, [url], { stdio: 'ignore', detached: true }).unref();
460
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);