cli-tunnel 1.3.1-beta.2 → 1.3.1-beta.4
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/dist/index.js +6 -21
- package/package.json +3 -1
- package/remote-ui/app.js +25 -8
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ ${BOLD}Options:${RESET}
|
|
|
58
58
|
--local Disable devtunnel (localhost only)
|
|
59
59
|
--port <n> Bridge port (default: random)
|
|
60
60
|
--name <name> Session name (shown in dashboard)
|
|
61
|
-
--
|
|
61
|
+
--replay (deprecated, screen buffer is always on)
|
|
62
62
|
--help, -h Show this help
|
|
63
63
|
|
|
64
64
|
${BOLD}Examples:${RESET}
|
|
@@ -79,12 +79,13 @@ pass through to the underlying app. cli-tunnel's own flags
|
|
|
79
79
|
const hasLocal = args.includes('--local');
|
|
80
80
|
const hasTunnel = !hasLocal;
|
|
81
81
|
const hasReplay = !args.includes('--no-replay');
|
|
82
|
+
const noWait = args.includes('--no-wait');
|
|
82
83
|
const portIdx = args.indexOf('--port');
|
|
83
84
|
const port = (portIdx !== -1 && args[portIdx + 1]) ? parseInt(args[portIdx + 1], 10) : 0;
|
|
84
85
|
const nameIdx = args.indexOf('--name');
|
|
85
86
|
const sessionName = (nameIdx !== -1 && args[nameIdx + 1]) ? args[nameIdx + 1] : '';
|
|
86
87
|
// Everything that's not our flags is the command
|
|
87
|
-
const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay']);
|
|
88
|
+
const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay', '--no-wait']);
|
|
88
89
|
const cmdArgs = [];
|
|
89
90
|
let skip = false;
|
|
90
91
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -96,7 +97,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
96
97
|
skip = true;
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
99
|
-
if (args[i] === '--local' || args[i] === '--tunnel' || args[i] === '--no-replay')
|
|
100
|
+
if (args[i] === '--local' || args[i] === '--tunnel' || args[i] === '--no-replay' || args[i] === '--no-wait')
|
|
100
101
|
continue;
|
|
101
102
|
cmdArgs.push(args[i]);
|
|
102
103
|
}
|
|
@@ -525,11 +526,6 @@ wss.on('connection', (ws, req) => {
|
|
|
525
526
|
// F-10: WS ping/pong heartbeat
|
|
526
527
|
ws._isAlive = true;
|
|
527
528
|
ws.on('pong', () => { ws._isAlive = true; });
|
|
528
|
-
// Replay: send accumulated PTY output as one bulk write
|
|
529
|
-
// xterm.js processes the full ANSI stream so cursor codes work correctly
|
|
530
|
-
if (hasReplay && replayBuffer.length > 0) {
|
|
531
|
-
ws.send(JSON.stringify({ type: 'pty', data: replayBuffer }));
|
|
532
|
-
}
|
|
533
529
|
ws.on('message', (data) => {
|
|
534
530
|
// F-13: Enforce WS message rate limit (100 msg/sec)
|
|
535
531
|
const now = Date.now();
|
|
@@ -584,20 +580,9 @@ setInterval(() => {
|
|
|
584
580
|
ws.ping();
|
|
585
581
|
}
|
|
586
582
|
}, 30000);
|
|
587
|
-
// Replay buffer: store raw PTY bytes in a single rolling buffer
|
|
588
|
-
// xterm.js processes the full stream on connect, so ANSI cursor codes work correctly
|
|
589
|
-
const MAX_REPLAY_BYTES = 256 * 1024; // 256KB
|
|
590
|
-
let replayBuffer = '';
|
|
591
583
|
function broadcast(data) {
|
|
592
584
|
const redacted = redactSecrets(data);
|
|
593
585
|
const msg = JSON.stringify({ type: 'pty', data: redacted });
|
|
594
|
-
if (hasReplay) {
|
|
595
|
-
replayBuffer += redacted;
|
|
596
|
-
// Trim from the front if too large
|
|
597
|
-
if (replayBuffer.length > MAX_REPLAY_BYTES) {
|
|
598
|
-
replayBuffer = replayBuffer.slice(replayBuffer.length - MAX_REPLAY_BYTES);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
586
|
for (const [, ws] of connections) {
|
|
602
587
|
if (ws.readyState === WebSocket.OPEN)
|
|
603
588
|
ws.send(msg);
|
|
@@ -798,7 +783,7 @@ async function main() {
|
|
|
798
783
|
await new Promise(() => { });
|
|
799
784
|
}
|
|
800
785
|
// Wait for user to scan QR / copy URL before starting the CLI tool
|
|
801
|
-
if (hasTunnel) {
|
|
786
|
+
if (hasTunnel && !noWait) {
|
|
802
787
|
console.log(` ${BOLD}Press any key to start ${command}...${RESET}`);
|
|
803
788
|
await new Promise((resolve) => {
|
|
804
789
|
if (process.stdin.isTTY)
|
|
@@ -903,6 +888,6 @@ async function main() {
|
|
|
903
888
|
process.stdin.setRawMode(true);
|
|
904
889
|
process.stdin.resume();
|
|
905
890
|
process.stdin.on('data', (data) => ptyProcess.write(data.toString()));
|
|
906
|
-
process.stdout.on('resize', () => { localResizeAt = Date.now();
|
|
891
|
+
process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); });
|
|
907
892
|
}
|
|
908
893
|
main().catch((err) => { console.error(err); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-tunnel",
|
|
3
|
-
"version": "1.3.1-beta.
|
|
3
|
+
"version": "1.3.1-beta.4",
|
|
4
4
|
"description": "Tunnel any CLI app to your phone — PTY + devtunnel + xterm.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"node": ">=22.0.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
40
|
+
"@xterm/headless": "^6.0.0",
|
|
39
41
|
"node-pty": "1.1.0",
|
|
40
42
|
"qrcode-terminal": "0.12.0",
|
|
41
43
|
"ws": "8.19.0"
|
package/remote-ui/app.js
CHANGED
|
@@ -63,9 +63,18 @@
|
|
|
63
63
|
if (currentView === 'grid' && gridMode === 'fullscreen' && gridTerminals[focusedIndex]) {
|
|
64
64
|
canvas = gridTerminals[focusedIndex].panel.querySelector('canvas');
|
|
65
65
|
} else {
|
|
66
|
-
|
|
66
|
+
var tc = document.getElementById('terminal-container');
|
|
67
|
+
if (tc) canvas = tc.querySelector('canvas');
|
|
68
|
+
}
|
|
69
|
+
if (!canvas) {
|
|
70
|
+
// Fallback: try to find any canvas in the xterm container
|
|
71
|
+
var xtermEl = document.querySelector('.xterm');
|
|
72
|
+
if (xtermEl) canvas = xtermEl.querySelector('canvas');
|
|
73
|
+
}
|
|
74
|
+
if (!canvas || !canvas.captureStream) {
|
|
75
|
+
if (statusText) { var prev = statusText.textContent; statusText.textContent = 'Recording not supported'; setTimeout(function() { statusText.textContent = prev; }, 3000); }
|
|
76
|
+
return false;
|
|
67
77
|
}
|
|
68
|
-
if (!canvas) { console.warn('No terminal canvas found'); return false; }
|
|
69
78
|
try {
|
|
70
79
|
var stream = canvas.captureStream(30); // 30 fps
|
|
71
80
|
var mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9'
|
|
@@ -140,7 +149,13 @@
|
|
|
140
149
|
if (currentView === 'grid' && gridMode === 'fullscreen' && gridTerminals[focusedIndex]) {
|
|
141
150
|
canvas = gridTerminals[focusedIndex].panel.querySelector('canvas');
|
|
142
151
|
} else {
|
|
143
|
-
|
|
152
|
+
var tc = document.getElementById('terminal-container');
|
|
153
|
+
if (tc) canvas = tc.querySelector('canvas');
|
|
154
|
+
}
|
|
155
|
+
if (!canvas) {
|
|
156
|
+
// Fallback: try to find any canvas in the xterm container
|
|
157
|
+
var xtermEl = document.querySelector('.xterm');
|
|
158
|
+
if (xtermEl) canvas = xtermEl.querySelector('canvas');
|
|
144
159
|
}
|
|
145
160
|
if (!canvas) {
|
|
146
161
|
if (statusText) { var prev = statusText.textContent; statusText.textContent = 'No terminal to capture'; setTimeout(function() { statusText.textContent = prev; }, 2000); }
|
|
@@ -239,13 +254,15 @@
|
|
|
239
254
|
|
|
240
255
|
async function loadSessions() {
|
|
241
256
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
var tokenParam = new URLSearchParams(window.location.search).get('token');
|
|
258
|
+
var headers = tokenParam ? { 'Authorization': 'Bearer ' + tokenParam } : {};
|
|
259
|
+
var resp = await fetch('/api/sessions', { headers: headers });
|
|
260
|
+
if (!resp.ok) throw new Error('Status ' + resp.status);
|
|
261
|
+
var data = await resp.json();
|
|
246
262
|
renderDashboard(data.sessions || []);
|
|
247
263
|
} catch (err) {
|
|
248
|
-
dashboard.innerHTML = '<div style="padding:12px;color:var(--
|
|
264
|
+
dashboard.innerHTML = '<div style="padding:20px 12px;color:var(--text-dim);text-align:center">' +
|
|
265
|
+
escapeHtml('Sessions unavailable. Use Hub mode (cli-tunnel with no command) to see all sessions.') + '</div>';
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
|