claude-code-remote-pilot 0.5.8 → 0.5.10
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/bin/claude-pilot.js +16 -4
- package/lib/WebServer.js +9 -0
- package/lib/ui.html +15 -0
- package/package.json +1 -1
package/bin/claude-pilot.js
CHANGED
|
@@ -154,7 +154,7 @@ function buildAllSessions(manager) {
|
|
|
154
154
|
return [...active, ...offline];
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function renderWatchTable(allSessions, selectedIdx) {
|
|
157
|
+
function renderWatchTable(allSessions, selectedIdx, webServer = null) {
|
|
158
158
|
const NW = 18, SW = 14, UW = 7, TW = 16;
|
|
159
159
|
const bar = ' ' + '─'.repeat(NW + SW + UW + TW + 10);
|
|
160
160
|
const header = ` ${'#'.padEnd(3)}${'SESSION'.padEnd(NW)} ${'STATUS'.padEnd(SW)} ${'UP'.padEnd(UW)} ${'USAGE / RESET'.padEnd(TW)}`;
|
|
@@ -181,7 +181,16 @@ function renderWatchTable(allSessions, selectedIdx) {
|
|
|
181
181
|
footer = ` [1-${Math.min(allSessions.length, 9)}]: select session w: web ui q: exit watch`;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
const lines = ['\n', ' Claude Code Remote Pilot'];
|
|
185
|
+
if (webServer) {
|
|
186
|
+
if (webServer._tunnelUrl) {
|
|
187
|
+
lines.push(` ${C.blue}Tunnel${C.reset}: ${webServer._tunnelUrl} ${C.dim}local: http://127.0.0.1:${webServer.port}${C.reset}`);
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(` ${C.dim}Web UI: http://${webServer.host}:${webServer.port}${C.reset}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
lines.push(bar, header, bar, ...rows, bar, footer, '');
|
|
193
|
+
return lines.join('\n');
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
function startWatch(manager, rl) {
|
|
@@ -192,7 +201,7 @@ function startWatch(manager, rl) {
|
|
|
192
201
|
function draw() {
|
|
193
202
|
allSessions = buildAllSessions(manager);
|
|
194
203
|
process.stdout.write('\x1B[2J\x1B[0f');
|
|
195
|
-
process.stdout.write(renderWatchTable(allSessions, selectedIdx));
|
|
204
|
+
process.stdout.write(renderWatchTable(allSessions, selectedIdx, manager._webServer));
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
function startTimer() {
|
|
@@ -214,7 +223,7 @@ function startWatch(manager, rl) {
|
|
|
214
223
|
|
|
215
224
|
function redraw() {
|
|
216
225
|
process.stdout.write('\x1B[2J\x1B[0f');
|
|
217
|
-
process.stdout.write(renderWatchTable(allSessions, selectedIdx));
|
|
226
|
+
process.stdout.write(renderWatchTable(allSessions, selectedIdx, manager._webServer));
|
|
218
227
|
}
|
|
219
228
|
|
|
220
229
|
function onKeypress(str, key) {
|
|
@@ -459,6 +468,7 @@ ${HELP}`);
|
|
|
459
468
|
console.log(' Starting cloudflared tunnel...');
|
|
460
469
|
webServer.startTunnel().then(publicUrl => {
|
|
461
470
|
console.log(` ✓ Tunnel ready: ${publicUrl}`);
|
|
471
|
+
console.log(' Note: first visit may show a Cloudflare warning — click "Proceed" to open the dashboard.');
|
|
462
472
|
if (!webPassword) console.log(' ⚠ Reminder: no password set. Restart with a password for security.');
|
|
463
473
|
if (telegram.token && telegram.chatId) {
|
|
464
474
|
notifier.send(telegram.token, telegram.chatId,
|
|
@@ -538,6 +548,7 @@ ${HELP}`);
|
|
|
538
548
|
console.log(' Starting cloudflared tunnel...');
|
|
539
549
|
webServer.startTunnel().then(publicUrl => {
|
|
540
550
|
console.log(` ✓ Tunnel ready: ${publicUrl}`);
|
|
551
|
+
console.log(' Note: first visit may show a Cloudflare warning — click "Proceed" to open the dashboard.');
|
|
541
552
|
if (telegram.token && telegram.chatId) {
|
|
542
553
|
notifier.send(telegram.token, telegram.chatId,
|
|
543
554
|
`Claude Remote Pilot tunnel ready: ${publicUrl}${webServer.password ? ' (password protected)' : ' ⚠ no password set'}`);
|
|
@@ -564,6 +575,7 @@ ${HELP}`);
|
|
|
564
575
|
console.log(' Starting cloudflared tunnel...');
|
|
565
576
|
webServer.startTunnel().then(publicUrl => {
|
|
566
577
|
console.log(` ✓ Tunnel ready: ${publicUrl}`);
|
|
578
|
+
console.log(' Note: first visit may show a Cloudflare warning — click "Proceed" to open the dashboard.');
|
|
567
579
|
if (!password) console.log(' ⚠ Reminder: add a password with: web <port> <host> <password> --tunnel');
|
|
568
580
|
if (telegram.token && telegram.chatId) {
|
|
569
581
|
notifier.send(telegram.token, telegram.chatId,
|
package/lib/WebServer.js
CHANGED
|
@@ -18,6 +18,7 @@ class WebServer {
|
|
|
18
18
|
this.server = null;
|
|
19
19
|
this._clients = new Set();
|
|
20
20
|
this._broadcastInterval = null;
|
|
21
|
+
this._heartbeatInterval = null;
|
|
21
22
|
this._tunnelProcess = null;
|
|
22
23
|
this._tunnelUrl = null;
|
|
23
24
|
}
|
|
@@ -201,6 +202,7 @@ class WebServer {
|
|
|
201
202
|
'Content-Type': 'text/event-stream',
|
|
202
203
|
'Cache-Control': 'no-cache',
|
|
203
204
|
'Connection': 'keep-alive',
|
|
205
|
+
'X-Accel-Buffering': 'no', // prevent Cloudflare/nginx from buffering the stream
|
|
204
206
|
});
|
|
205
207
|
this._clients.add(res);
|
|
206
208
|
res.write(`data: ${JSON.stringify(this._buildAllSessions())}\n\n`);
|
|
@@ -217,6 +219,12 @@ class WebServer {
|
|
|
217
219
|
});
|
|
218
220
|
|
|
219
221
|
this._broadcastInterval = setInterval(() => this._broadcast(), 3000);
|
|
222
|
+
// Keep SSE connections alive through proxies (Cloudflare timeout is ~100s)
|
|
223
|
+
this._heartbeatInterval = setInterval(() => {
|
|
224
|
+
for (const res of this._clients) {
|
|
225
|
+
try { res.write(': heartbeat\n\n'); } catch { this._clients.delete(res); }
|
|
226
|
+
}
|
|
227
|
+
}, 20000);
|
|
220
228
|
this.server.listen(this.port, this.host);
|
|
221
229
|
return this.port;
|
|
222
230
|
}
|
|
@@ -259,6 +267,7 @@ class WebServer {
|
|
|
259
267
|
stop() {
|
|
260
268
|
this.stopTunnel();
|
|
261
269
|
clearInterval(this._broadcastInterval);
|
|
270
|
+
clearInterval(this._heartbeatInterval);
|
|
262
271
|
for (const res of this._clients) { try { res.end(); } catch {} }
|
|
263
272
|
this._clients.clear();
|
|
264
273
|
if (this.server) this.server.close();
|
package/lib/ui.html
CHANGED
|
@@ -1094,6 +1094,21 @@ function App() {
|
|
|
1094
1094
|
return () => { if (esRef.current) esRef.current.close(); };
|
|
1095
1095
|
}, []);
|
|
1096
1096
|
|
|
1097
|
+
// Fallback polling — loads sessions immediately and keeps them fresh when
|
|
1098
|
+
// SSE is blocked (e.g. through a Cloudflare tunnel that buffers streams).
|
|
1099
|
+
useEffect(() => {
|
|
1100
|
+
let mounted = true;
|
|
1101
|
+
const poll = () => {
|
|
1102
|
+
apiFetch('/api/sessions', { cache: 'no-store' })
|
|
1103
|
+
.then(r => r.json())
|
|
1104
|
+
.then(data => { if (mounted) setSessions(data); })
|
|
1105
|
+
.catch(() => {});
|
|
1106
|
+
};
|
|
1107
|
+
poll();
|
|
1108
|
+
const t = setInterval(poll, 5000);
|
|
1109
|
+
return () => { mounted = false; clearInterval(t); };
|
|
1110
|
+
}, []);
|
|
1111
|
+
|
|
1097
1112
|
const handleLogin = useCallback(() => {
|
|
1098
1113
|
setNeedsLogin(false);
|
|
1099
1114
|
apiFetch('/api/status').then(r => r.json()).then(setServerStatus).catch(() => {});
|
package/package.json
CHANGED