cli-tunnel 1.3.1-beta.4 → 1.3.1-beta.6
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/README.md +3 -2
- package/dist/index.js +5 -13
- package/package.json +1 -1
- package/remote-ui/app.js +71 -32
- package/remote-ui/styles.css +73 -0
package/README.md
CHANGED
|
@@ -163,12 +163,13 @@ cli-tunnel uses a layered security model:
|
|
|
163
163
|
|
|
164
164
|
## Terminal Size Behavior
|
|
165
165
|
|
|
166
|
-
cli-tunnel uses a single PTY shared between your local terminal and all remote viewers.
|
|
166
|
+
cli-tunnel uses a single PTY shared between your local terminal and all remote viewers. The PTY stays at your local terminal's dimensions — remote devices view the terminal through their own xterm.js viewport.
|
|
167
167
|
|
|
168
168
|
**Tips for the best experience:**
|
|
169
169
|
- Rotate your phone to landscape for a wider terminal
|
|
170
170
|
- Use the key bar (↑↓←→ Tab Enter Esc Ctrl+C) at the bottom for navigation
|
|
171
|
-
-
|
|
171
|
+
- To select text on the local terminal, hold **Shift** while clicking/dragging (raw mode captures all input otherwise)
|
|
172
|
+
- Use `--no-wait` flag to skip the press-any-key prompt (useful for scripting)
|
|
172
173
|
|
|
173
174
|
## FAQ
|
|
174
175
|
|
package/dist/index.js
CHANGED
|
@@ -551,14 +551,10 @@ wss.on('connection', (ws, req) => {
|
|
|
551
551
|
ptyProcess.write(msg.data);
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
|
-
//
|
|
554
|
+
// pty_resize from remote clients is ignored — PTY stays at local terminal size
|
|
555
|
+
// The phone's xterm.js handles display via its own viewport/scrolling
|
|
555
556
|
if (msg.type === 'pty_resize') {
|
|
556
|
-
|
|
557
|
-
const rows = Number(msg.rows);
|
|
558
|
-
const localRecentlyResized = Date.now() - localResizeAt < 2000;
|
|
559
|
-
if (Number.isFinite(cols) && Number.isFinite(rows) && ptyProcess && !localRecentlyResized) {
|
|
560
|
-
ptyProcess.resize(Math.max(1, Math.min(500, cols)), Math.max(1, Math.min(200, rows)));
|
|
561
|
-
}
|
|
557
|
+
// Only log, don't resize — prevents breaking local terminal layout
|
|
562
558
|
}
|
|
563
559
|
}
|
|
564
560
|
catch {
|
|
@@ -789,13 +785,9 @@ async function main() {
|
|
|
789
785
|
if (process.stdin.isTTY)
|
|
790
786
|
process.stdin.setRawMode(true);
|
|
791
787
|
process.stdin.resume();
|
|
792
|
-
process.stdin.once('data', () =>
|
|
793
|
-
if (process.stdin.isTTY)
|
|
794
|
-
process.stdin.setRawMode(false);
|
|
795
|
-
process.stdin.pause();
|
|
796
|
-
resolve();
|
|
797
|
-
});
|
|
788
|
+
process.stdin.once('data', () => resolve());
|
|
798
789
|
});
|
|
790
|
+
// Don't pause or reset raw mode — we'll set it up properly for PTY below
|
|
799
791
|
}
|
|
800
792
|
console.log(` ${DIM}Starting ${command}...${RESET}\n`);
|
|
801
793
|
// Clear screen before PTY takes over — prevents overlap with banner/QR output
|
package/package.json
CHANGED
package/remote-ui/app.js
CHANGED
|
@@ -259,55 +259,94 @@
|
|
|
259
259
|
var resp = await fetch('/api/sessions', { headers: headers });
|
|
260
260
|
if (!resp.ok) throw new Error('Status ' + resp.status);
|
|
261
261
|
var data = await resp.json();
|
|
262
|
-
|
|
262
|
+
if (!isHubMode) {
|
|
263
|
+
renderNonHubSessions(data.sessions || []);
|
|
264
|
+
} else {
|
|
265
|
+
renderDashboard(data.sessions || []);
|
|
266
|
+
}
|
|
263
267
|
} catch (err) {
|
|
264
268
|
dashboard.innerHTML = '<div style="padding:20px 12px;color:var(--text-dim);text-align:center">' +
|
|
265
269
|
escapeHtml('Sessions unavailable. Use Hub mode (cli-tunnel with no command) to see all sessions.') + '</div>';
|
|
266
270
|
}
|
|
267
271
|
}
|
|
268
272
|
|
|
273
|
+
function renderNonHubSessions(sessions) {
|
|
274
|
+
var currentName = document.title || 'this session';
|
|
275
|
+
var html = '<div class="non-hub-view">' +
|
|
276
|
+
'<div class="non-hub-current">You\'re connected to: <strong>' + escapeHtml(currentName) + '</strong></div>' +
|
|
277
|
+
'<div class="non-hub-back"><a href="#" data-action="back-to-terminal">← Back to terminal</a></div>' +
|
|
278
|
+
'<div class="non-hub-hint">Start a Hub to see all sessions: <code>cli-tunnel</code> (no command)</div>' +
|
|
279
|
+
'</div>';
|
|
280
|
+
dashboard.innerHTML = html;
|
|
281
|
+
var backLink = dashboard.querySelector('[data-action="back-to-terminal"]');
|
|
282
|
+
if (backLink) {
|
|
283
|
+
backLink.addEventListener('click', function(e) { e.preventDefault(); toggleView(); });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
269
287
|
function renderDashboard(sessions) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
288
|
+
var filtered = showOffline ? sessions : sessions.filter(function(s) { return s.online; });
|
|
289
|
+
var offlineCount = sessions.filter(function(s) { return !s.online; }).length;
|
|
290
|
+
var connectable = filtered.filter(function(s) { return s.online && s.hasToken; });
|
|
291
|
+
var remoteCount = filtered.length - connectable.length;
|
|
292
|
+
|
|
293
|
+
// Hub header
|
|
294
|
+
var html = '<div class="hub-header">' +
|
|
295
|
+
'<h2 class="hub-title">cli-tunnel Hub</h2>' +
|
|
296
|
+
'<div class="hub-stats">' + connectable.length + ' connectable · ' + remoteCount + ' remote' +
|
|
297
|
+
(offlineCount > 0 ? ' · ' + offlineCount + ' offline' : '') +
|
|
298
|
+
' <span class="hub-refresh-indicator" title="Auto-refreshes every 10s">↻</span>' +
|
|
299
|
+
'</div>' +
|
|
300
|
+
'</div>';
|
|
301
|
+
|
|
302
|
+
// Toolbar actions
|
|
303
|
+
html += '<div class="hub-toolbar">' +
|
|
304
|
+
'<button data-action="toggle-offline" class="hub-toolbar-btn">' + (showOffline ? 'Hide offline' : 'Show offline') + '</button>' +
|
|
305
|
+
(offlineCount > 0 ? '<button data-action="clean-offline" class="hub-toolbar-btn hub-toolbar-btn-danger">Clean offline</button>' : '') +
|
|
306
|
+
'<button data-action="refresh" class="hub-toolbar-btn">↻ Refresh</button>' +
|
|
307
|
+
'</div>';
|
|
308
|
+
|
|
309
|
+
// Grid banner when 2+ connectable
|
|
310
|
+
if (connectable.length >= 2) {
|
|
311
|
+
html += '<div class="grid-banner" data-action="grid-view">' +
|
|
312
|
+
'<span>Monitor all sessions live</span>' +
|
|
313
|
+
'<span class="grid-banner-btn">⊞ Open Grid View</span>' +
|
|
314
|
+
'</div>';
|
|
315
|
+
}
|
|
283
316
|
|
|
284
317
|
if (filtered.length === 0) {
|
|
285
318
|
html += '<div style="padding:20px 12px;color:var(--text-dim);text-align:center">' +
|
|
286
319
|
(sessions.length === 0 ? 'No cli-tunnel sessions found.' : 'No online sessions. Tap "Show offline" to see stale ones.') +
|
|
287
320
|
'</div>';
|
|
288
321
|
} else {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
322
|
+
filtered.forEach(function(s) {
|
|
323
|
+
var canConnect = s.online && s.hasToken;
|
|
324
|
+
html += '<div class="session-card-v2' + (canConnect ? ' connectable' : '') + '"' +
|
|
325
|
+
(canConnect ? ' data-session-port="' + s.port + '" data-session-base-url="' + escapeHtml(s.url) + '"' : '') + '>' +
|
|
326
|
+
'<div class="card-header">' +
|
|
327
|
+
'<span class="card-status ' + (s.online ? 'online' : 'offline') + '"></span>' +
|
|
328
|
+
'<span class="card-name">' + escapeHtml(s.name) + '</span>' +
|
|
329
|
+
(canConnect ? '<span class="card-connect">Connect →</span>' :
|
|
330
|
+
s.online ? '<span class="card-remote">Remote 🔒</span>' :
|
|
331
|
+
'<span class="card-offline">Offline</span>') +
|
|
332
|
+
'</div>' +
|
|
333
|
+
'<div class="card-details">' +
|
|
334
|
+
'<span>💻 ' + escapeHtml(s.machine) + '</span>' +
|
|
335
|
+
'<span>📦 ' + escapeHtml(s.repo) + '</span>' +
|
|
336
|
+
'<span>🌿 ' + escapeHtml(s.branch) + '</span>' +
|
|
337
|
+
'</div>' +
|
|
338
|
+
(!s.online ? '<button data-delete-id="' + escapeHtml(s.id) + '" class="card-delete" title="Remove">✕</button>' : '') +
|
|
339
|
+
'</div>';
|
|
340
|
+
});
|
|
305
341
|
}
|
|
342
|
+
|
|
306
343
|
dashboard.innerHTML = html;
|
|
307
344
|
cachedSessions = sessions;
|
|
345
|
+
|
|
308
346
|
// Event delegation
|
|
309
|
-
dashboard.querySelectorAll('.session-card[data-session-port]').forEach(function(card) {
|
|
310
|
-
card.addEventListener('click', function() {
|
|
347
|
+
dashboard.querySelectorAll('.session-card-v2[data-session-port]').forEach(function(card) {
|
|
348
|
+
card.addEventListener('click', function(e) {
|
|
349
|
+
if (e.target.closest('[data-delete-id]')) return;
|
|
311
350
|
var port = card.dataset.sessionPort;
|
|
312
351
|
var baseUrl = card.dataset.sessionBaseUrl;
|
|
313
352
|
var tokenParam = new URLSearchParams(window.location.search).get('token');
|
package/remote-ui/styles.css
CHANGED
|
@@ -250,6 +250,79 @@ header {
|
|
|
250
250
|
.session-card .machine { color: var(--text-dim); font-size: 11px; }
|
|
251
251
|
.session-card .arrow { color: var(--text-dim); }
|
|
252
252
|
|
|
253
|
+
/* Hub Dashboard Header */
|
|
254
|
+
.hub-header { padding: 16px 12px 8px; }
|
|
255
|
+
.hub-title { color: var(--text-bright); font-size: 18px; font-weight: bold; margin: 0 0 4px; font-family: var(--font); }
|
|
256
|
+
.hub-stats { color: var(--text-dim); font-size: 12px; }
|
|
257
|
+
.hub-refresh-indicator { display: inline-block; animation: spin 2s linear infinite; font-size: 10px; }
|
|
258
|
+
@keyframes spin { 100% { transform: rotate(360deg); } }
|
|
259
|
+
|
|
260
|
+
/* Hub Toolbar */
|
|
261
|
+
.hub-toolbar { display: flex; gap: 6px; padding: 4px 12px 8px; flex-wrap: wrap; }
|
|
262
|
+
.hub-toolbar-btn {
|
|
263
|
+
background: none; border: 1px solid var(--border); color: var(--text-dim);
|
|
264
|
+
font-family: var(--font); font-size: 11px; padding: 3px 8px;
|
|
265
|
+
border-radius: 4px; cursor: pointer;
|
|
266
|
+
}
|
|
267
|
+
.hub-toolbar-btn:hover { border-color: var(--blue); color: var(--blue); }
|
|
268
|
+
.hub-toolbar-btn-danger { border-color: var(--red); color: var(--red); }
|
|
269
|
+
.hub-toolbar-btn-danger:hover { background: var(--red); color: #fff; }
|
|
270
|
+
|
|
271
|
+
/* Grid Banner */
|
|
272
|
+
.grid-banner {
|
|
273
|
+
margin: 8px; padding: 12px 16px; background: var(--bg-tool);
|
|
274
|
+
border: 1px solid var(--blue); border-radius: 8px;
|
|
275
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
276
|
+
cursor: pointer;
|
|
277
|
+
}
|
|
278
|
+
.grid-banner:hover { background: var(--border); }
|
|
279
|
+
.grid-banner span:first-child { color: var(--text); font-size: 13px; }
|
|
280
|
+
.grid-banner-btn { color: var(--blue); font-weight: bold; font-size: 13px; }
|
|
281
|
+
|
|
282
|
+
/* Session Card v2 */
|
|
283
|
+
.session-card-v2 {
|
|
284
|
+
background: var(--bg-tool); border: 1px solid var(--border); border-radius: 8px;
|
|
285
|
+
padding: 12px; margin: 6px 8px; position: relative;
|
|
286
|
+
}
|
|
287
|
+
.session-card-v2.connectable { cursor: pointer; }
|
|
288
|
+
.session-card-v2.connectable:hover { border-color: var(--green); }
|
|
289
|
+
.card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
290
|
+
.card-status { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
|
291
|
+
.card-status.online { background: var(--green); }
|
|
292
|
+
.card-status.offline { background: var(--text-dim); }
|
|
293
|
+
.card-name { color: var(--text-bright); font-weight: bold; font-size: 15px; flex: 1; }
|
|
294
|
+
.card-connect { color: var(--green); font-size: 13px; font-weight: bold; }
|
|
295
|
+
.card-remote { color: var(--text-dim); font-size: 11px; }
|
|
296
|
+
.card-offline { color: var(--red); font-size: 11px; }
|
|
297
|
+
.card-details { display: flex; gap: 12px; flex-wrap: wrap; color: var(--text-dim); font-size: 11px; }
|
|
298
|
+
.card-delete {
|
|
299
|
+
background: none; border: none; color: var(--red); cursor: pointer;
|
|
300
|
+
font-size: 14px; position: absolute; top: 8px; right: 8px;
|
|
301
|
+
}
|
|
302
|
+
.card-delete:hover { color: var(--text-bright); }
|
|
303
|
+
|
|
304
|
+
/* Non-Hub Sessions View */
|
|
305
|
+
.non-hub-view { padding: 24px 16px; text-align: center; }
|
|
306
|
+
.non-hub-current {
|
|
307
|
+
color: var(--text-bright); font-size: 15px; margin-bottom: 16px;
|
|
308
|
+
padding: 12px; background: var(--bg-tool); border: 1px solid var(--border);
|
|
309
|
+
border-radius: 8px;
|
|
310
|
+
}
|
|
311
|
+
.non-hub-current strong { color: var(--green); }
|
|
312
|
+
.non-hub-back { margin-bottom: 16px; }
|
|
313
|
+
.non-hub-back a {
|
|
314
|
+
color: var(--blue); text-decoration: none; font-size: 13px;
|
|
315
|
+
}
|
|
316
|
+
.non-hub-back a:hover { text-decoration: underline; }
|
|
317
|
+
.non-hub-hint {
|
|
318
|
+
color: var(--text-dim); font-size: 12px;
|
|
319
|
+
padding: 10px; background: var(--bg-tool); border-radius: 6px;
|
|
320
|
+
}
|
|
321
|
+
.non-hub-hint code {
|
|
322
|
+
color: var(--cyan); background: var(--bg); padding: 2px 6px;
|
|
323
|
+
border-radius: 3px; font-family: var(--font);
|
|
324
|
+
}
|
|
325
|
+
|
|
253
326
|
/* Grid View (multi-terminal with layout modes) */
|
|
254
327
|
#grid-view {
|
|
255
328
|
flex: 1;
|