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 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
- --no-replay Disable replay buffer (on by default)
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(); ptyProcess.resize(process.stdout.columns || 120, process.stdout.rows || 30); });
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.2",
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
- canvas = termContainer.querySelector('canvas');
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
- canvas = termContainer.querySelector('canvas');
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
- const tokenParam = new URLSearchParams(window.location.search).get('token');
243
- const headers = tokenParam ? { 'Authorization': 'Bearer ' + tokenParam } : {};
244
- const resp = await fetch('/api/sessions', { headers });
245
- const data = await resp.json();
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(--red)">' + escapeHtml('Failed to load sessions: ' + err.message) + '</div>';
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