cli-tunnel 1.3.1-beta.3 → 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
@@ -23,10 +23,6 @@ import http from 'node:http';
23
23
  import readline from 'node:readline';
24
24
  import { WebSocketServer, WebSocket } from 'ws';
25
25
  import os from 'node:os';
26
- import headlessPkg from '@xterm/headless';
27
- const HeadlessTerminal = headlessPkg.Terminal;
28
- import serializePkg from '@xterm/addon-serialize';
29
- const SerializeAddon = serializePkg.SerializeAddon;
30
26
  import { redactSecrets } from './redact.js';
31
27
  // F-15: Global error handlers to prevent unclean crashes
32
28
  process.on('uncaughtException', (err) => {
@@ -83,12 +79,13 @@ pass through to the underlying app. cli-tunnel's own flags
83
79
  const hasLocal = args.includes('--local');
84
80
  const hasTunnel = !hasLocal;
85
81
  const hasReplay = !args.includes('--no-replay');
82
+ const noWait = args.includes('--no-wait');
86
83
  const portIdx = args.indexOf('--port');
87
84
  const port = (portIdx !== -1 && args[portIdx + 1]) ? parseInt(args[portIdx + 1], 10) : 0;
88
85
  const nameIdx = args.indexOf('--name');
89
86
  const sessionName = (nameIdx !== -1 && args[nameIdx + 1]) ? args[nameIdx + 1] : '';
90
87
  // Everything that's not our flags is the command
91
- const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay']);
88
+ const ourFlags = new Set(['--local', '--tunnel', '--port', '--name', '--no-replay', '--no-wait']);
92
89
  const cmdArgs = [];
93
90
  let skip = false;
94
91
  for (let i = 0; i < args.length; i++) {
@@ -100,7 +97,7 @@ for (let i = 0; i < args.length; i++) {
100
97
  skip = true;
101
98
  continue;
102
99
  }
103
- 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')
104
101
  continue;
105
102
  cmdArgs.push(args[i]);
106
103
  }
@@ -529,11 +526,6 @@ wss.on('connection', (ws, req) => {
529
526
  // F-10: WS ping/pong heartbeat
530
527
  ws._isAlive = true;
531
528
  ws.on('pong', () => { ws._isAlive = true; });
532
- // Send current screen state so new clients see what's on the terminal now
533
- const screenState = getScreenState();
534
- if (screenState) {
535
- ws.send(JSON.stringify({ type: 'pty', data: screenState }));
536
- }
537
529
  ws.on('message', (data) => {
538
530
  // F-13: Enforce WS message rate limit (100 msg/sec)
539
531
  const now = Date.now();
@@ -566,8 +558,6 @@ wss.on('connection', (ws, req) => {
566
558
  const localRecentlyResized = Date.now() - localResizeAt < 2000;
567
559
  if (Number.isFinite(cols) && Number.isFinite(rows) && ptyProcess && !localRecentlyResized) {
568
560
  ptyProcess.resize(Math.max(1, Math.min(500, cols)), Math.max(1, Math.min(200, rows)));
569
- if (screenTerminal)
570
- screenTerminal.resize(Math.max(1, Math.min(500, cols)), Math.max(1, Math.min(200, rows)));
571
561
  }
572
562
  }
573
563
  }
@@ -590,25 +580,8 @@ setInterval(() => {
590
580
  ws.ping();
591
581
  }
592
582
  }, 30000);
593
- // Screen buffer: headless xterm.js terminal that tracks the current screen state
594
- // On client connect, serialize the screen and send it — renders correctly regardless of dimensions
595
- let screenTerminal = null;
596
- let serializeAddon = null;
597
- function initScreenBuffer(cols, rows) {
598
- screenTerminal = new HeadlessTerminal({ cols, rows, scrollback: 1000, allowProposedApi: true });
599
- serializeAddon = new SerializeAddon();
600
- screenTerminal.loadAddon(serializeAddon);
601
- }
602
- function getScreenState() {
603
- if (!serializeAddon)
604
- return '';
605
- return serializeAddon.serialize();
606
- }
607
583
  function broadcast(data) {
608
584
  const redacted = redactSecrets(data);
609
- // Feed into headless terminal to track screen state
610
- if (screenTerminal)
611
- screenTerminal.write(redacted);
612
585
  const msg = JSON.stringify({ type: 'pty', data: redacted });
613
586
  for (const [, ws] of connections) {
614
587
  if (ws.readyState === WebSocket.OPEN)
@@ -810,7 +783,7 @@ async function main() {
810
783
  await new Promise(() => { });
811
784
  }
812
785
  // Wait for user to scan QR / copy URL before starting the CLI tool
813
- if (hasTunnel) {
786
+ if (hasTunnel && !noWait) {
814
787
  console.log(` ${BOLD}Press any key to start ${command}...${RESET}`);
815
788
  await new Promise((resolve) => {
816
789
  if (process.stdin.isTTY)
@@ -878,8 +851,6 @@ async function main() {
878
851
  cols, rows, cwd,
879
852
  env: safeEnv,
880
853
  });
881
- // Initialize headless terminal for screen state tracking
882
- initScreenBuffer(cols, rows);
883
854
  // Detect CSPRNG crash (rare Node.js + PTY issue) and show helpful message
884
855
  let earlyExitCode = null;
885
856
  const earlyExitCheck = new Promise((resolve) => {
@@ -917,7 +888,6 @@ async function main() {
917
888
  process.stdin.setRawMode(true);
918
889
  process.stdin.resume();
919
890
  process.stdin.on('data', (data) => ptyProcess.write(data.toString()));
920
- process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); if (screenTerminal)
921
- screenTerminal.resize(c, r); });
891
+ process.stdout.on('resize', () => { localResizeAt = Date.now(); const c = process.stdout.columns || 120; const r = process.stdout.rows || 30; ptyProcess.resize(c, r); });
922
892
  }
923
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",
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",
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