termbeam 0.0.4 → 0.0.5
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 +6 -0
- package/package.json +2 -2
- package/public/index.html +44 -3
- package/public/terminal.html +35 -2
- package/src/cli.js +52 -1
- package/src/routes.js +9 -1
- package/src/sessions.js +6 -1
- package/src/shells.js +77 -0
- package/src/tunnel.js +61 -8
package/README.md
CHANGED
|
@@ -65,6 +65,12 @@ termbeam --password mysecret
|
|
|
65
65
|
termbeam --tunnel --generate-password
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
> Requires the [Azure Dev Tunnels CLI](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started):
|
|
69
|
+
>
|
|
70
|
+
> - **Windows:** `winget install Microsoft.devtunnel`
|
|
71
|
+
> - **macOS:** `brew install --cask devtunnel`
|
|
72
|
+
> - **Linux:** `curl -sL https://aka.ms/DevTunnelCliInstall | bash`
|
|
73
|
+
|
|
68
74
|
## 📖 Usage
|
|
69
75
|
|
|
70
76
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termbeam",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"cookie-parser": "^1.4.7",
|
|
59
59
|
"express": "^5.2.1",
|
|
60
|
-
"node-pty": "1.
|
|
60
|
+
"node-pty": "^1.1.0",
|
|
61
61
|
"qrcode": "^1.5.4",
|
|
62
62
|
"ws": "^8.19.0"
|
|
63
63
|
},
|
package/public/index.html
CHANGED
|
@@ -231,8 +231,18 @@
|
|
|
231
231
|
color: #e0e0e0;
|
|
232
232
|
font-size: 15px;
|
|
233
233
|
outline: none;
|
|
234
|
+
-webkit-appearance: none;
|
|
235
|
+
appearance: none;
|
|
234
236
|
}
|
|
235
|
-
.modal
|
|
237
|
+
.modal select {
|
|
238
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
239
|
+
background-repeat: no-repeat;
|
|
240
|
+
background-position: right 12px center;
|
|
241
|
+
padding-right: 32px;
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
}
|
|
244
|
+
.modal input:focus,
|
|
245
|
+
.modal select:focus {
|
|
236
246
|
border-color: #533483;
|
|
237
247
|
}
|
|
238
248
|
.modal-actions {
|
|
@@ -459,8 +469,12 @@
|
|
|
459
469
|
<h2>New Session</h2>
|
|
460
470
|
<label for="sess-name">Name</label>
|
|
461
471
|
<input type="text" id="sess-name" placeholder="My Session" />
|
|
462
|
-
<label for="sess-shell">Shell
|
|
463
|
-
<
|
|
472
|
+
<label for="sess-shell">Shell</label>
|
|
473
|
+
<select id="sess-shell">
|
|
474
|
+
<option value="">Loading shells…</option>
|
|
475
|
+
</select>
|
|
476
|
+
<label for="sess-cmd">Initial Command <span style="color:#666;font-weight:normal">(optional)</span></label>
|
|
477
|
+
<input type="text" id="sess-cmd" placeholder="e.g. copilot, htop, vim" />
|
|
464
478
|
<label for="sess-cwd">Working Directory</label>
|
|
465
479
|
<div class="cwd-picker">
|
|
466
480
|
<input type="text" id="sess-cwd" placeholder="/Users/dorlugasigal" />
|
|
@@ -568,6 +582,7 @@
|
|
|
568
582
|
}
|
|
569
583
|
|
|
570
584
|
document.getElementById('new-session-btn').addEventListener('click', () => {
|
|
585
|
+
loadShells();
|
|
571
586
|
modal.classList.add('visible');
|
|
572
587
|
});
|
|
573
588
|
document.getElementById('modal-cancel').addEventListener('click', () => {
|
|
@@ -581,11 +596,13 @@
|
|
|
581
596
|
const name = document.getElementById('sess-name').value.trim();
|
|
582
597
|
const shell = document.getElementById('sess-shell').value.trim();
|
|
583
598
|
const cwd = document.getElementById('sess-cwd').value.trim();
|
|
599
|
+
const initialCommand = document.getElementById('sess-cmd').value.trim();
|
|
584
600
|
|
|
585
601
|
const body = {};
|
|
586
602
|
if (name) body.name = name;
|
|
587
603
|
if (shell) body.shell = shell;
|
|
588
604
|
if (cwd) body.cwd = cwd;
|
|
605
|
+
if (initialCommand) body.initialCommand = initialCommand;
|
|
589
606
|
|
|
590
607
|
const res = await fetch('/api/sessions', {
|
|
591
608
|
method: 'POST',
|
|
@@ -596,6 +613,30 @@
|
|
|
596
613
|
location.href = data.url;
|
|
597
614
|
});
|
|
598
615
|
|
|
616
|
+
// --- Shell detection ---
|
|
617
|
+
let shellsLoaded = false;
|
|
618
|
+
async function loadShells() {
|
|
619
|
+
if (shellsLoaded) return;
|
|
620
|
+
const shellSelect = document.getElementById('sess-shell');
|
|
621
|
+
try {
|
|
622
|
+
const res = await fetch('/api/shells');
|
|
623
|
+
const data = await res.json();
|
|
624
|
+
shellSelect.innerHTML = '';
|
|
625
|
+
for (const s of data.shells) {
|
|
626
|
+
const opt = document.createElement('option');
|
|
627
|
+
opt.value = s.cmd;
|
|
628
|
+
opt.textContent = `${s.name} (${s.cmd})`;
|
|
629
|
+
if (s.cmd === data.default || s.path === data.default) {
|
|
630
|
+
opt.selected = true;
|
|
631
|
+
}
|
|
632
|
+
shellSelect.appendChild(opt);
|
|
633
|
+
}
|
|
634
|
+
shellsLoaded = true;
|
|
635
|
+
} catch {
|
|
636
|
+
shellSelect.innerHTML = '<option value="">Could not detect shells</option>';
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
599
640
|
// --- Swipe to delete ---
|
|
600
641
|
async function deleteSession(id, e) {
|
|
601
642
|
e.stopPropagation();
|
package/public/terminal.html
CHANGED
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
align-items: center;
|
|
66
66
|
gap: 8px;
|
|
67
67
|
}
|
|
68
|
+
#status-bar .right {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
}
|
|
68
73
|
#back-btn {
|
|
69
74
|
background: none;
|
|
70
75
|
border: none;
|
|
@@ -76,6 +81,22 @@
|
|
|
76
81
|
#back-btn:active {
|
|
77
82
|
color: #e0e0e0;
|
|
78
83
|
}
|
|
84
|
+
#stop-btn {
|
|
85
|
+
background: #e74c3c;
|
|
86
|
+
border: none;
|
|
87
|
+
color: white;
|
|
88
|
+
font-size: 11px;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
padding: 4px 10px;
|
|
92
|
+
border-radius: 6px;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 4px;
|
|
96
|
+
}
|
|
97
|
+
#stop-btn:active {
|
|
98
|
+
background: #c0392b;
|
|
99
|
+
}
|
|
79
100
|
#status-dot {
|
|
80
101
|
width: 8px;
|
|
81
102
|
height: 8px;
|
|
@@ -207,8 +228,11 @@
|
|
|
207
228
|
<span id="status-dot"></span>
|
|
208
229
|
<span id="session-name">…</span>
|
|
209
230
|
</div>
|
|
210
|
-
<
|
|
211
|
-
|
|
231
|
+
<div class="right">
|
|
232
|
+
<span id="status-text">Connecting…</span>
|
|
233
|
+
<span id="version-text" style="font-size: 11px; color: #555"></span>
|
|
234
|
+
<button id="stop-btn" title="Stop session">■ Stop</button>
|
|
235
|
+
</div>
|
|
212
236
|
</div>
|
|
213
237
|
|
|
214
238
|
<div id="terminal-container"></div>
|
|
@@ -437,6 +461,15 @@
|
|
|
437
461
|
connect();
|
|
438
462
|
});
|
|
439
463
|
|
|
464
|
+
// Stop session
|
|
465
|
+
document.getElementById('stop-btn').addEventListener('click', async () => {
|
|
466
|
+
if (!confirm('Stop this session? The process will be killed.')) return;
|
|
467
|
+
try {
|
|
468
|
+
await fetch(`/api/sessions/${sessionId}`, { method: 'DELETE' });
|
|
469
|
+
} catch {}
|
|
470
|
+
location.href = '/';
|
|
471
|
+
});
|
|
472
|
+
|
|
440
473
|
// Tap terminal area to toggle keyboard (intentional user action)
|
|
441
474
|
container.addEventListener('click', () => term.focus());
|
|
442
475
|
|
package/src/cli.js
CHANGED
|
@@ -32,10 +32,61 @@ Environment:
|
|
|
32
32
|
`);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function getDefaultShell() {
|
|
36
|
+
const { execFileSync } = require('child_process');
|
|
37
|
+
const ppid = process.ppid;
|
|
38
|
+
console.log(`[termbeam] Detecting shell (parent PID: ${ppid}, platform: ${os.platform()})`);
|
|
39
|
+
|
|
40
|
+
if (os.platform() === 'win32') {
|
|
41
|
+
// Detect parent process on Windows via WMIC
|
|
42
|
+
try {
|
|
43
|
+
const result = execFileSync(
|
|
44
|
+
'wmic',
|
|
45
|
+
['process', 'where', `ProcessId=${ppid}`, 'get', 'Name', '/value'],
|
|
46
|
+
{ stdio: ['pipe', 'pipe', 'ignore'], encoding: 'utf8', timeout: 3000 },
|
|
47
|
+
);
|
|
48
|
+
const match = result.match(/Name=(.+)/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const name = match[1].trim().toLowerCase();
|
|
51
|
+
console.log(`[termbeam] Detected parent process: ${name}`);
|
|
52
|
+
if (name === 'pwsh.exe') return 'pwsh.exe';
|
|
53
|
+
if (name === 'powershell.exe') return 'powershell.exe';
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.log(`[termbeam] Could not detect parent process: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
const fallback = process.env.COMSPEC || 'cmd.exe';
|
|
59
|
+
console.log(`[termbeam] Falling back to: ${fallback}`);
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Unix: detect parent shell via ps
|
|
64
|
+
try {
|
|
65
|
+
const result = execFileSync('ps', ['-o', 'comm=', '-p', String(ppid)], {
|
|
66
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
timeout: 3000,
|
|
69
|
+
});
|
|
70
|
+
const comm = result.trim();
|
|
71
|
+
if (comm) {
|
|
72
|
+
const shell = comm.startsWith('-') ? comm.slice(1) : comm;
|
|
73
|
+
console.log(`[termbeam] Detected parent shell: ${shell}`);
|
|
74
|
+
return shell;
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.log(`[termbeam] Could not detect parent shell: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fallback to SHELL env or /bin/sh
|
|
81
|
+
const fallback = process.env.SHELL || '/bin/sh';
|
|
82
|
+
console.log(`[termbeam] Falling back to: ${fallback}`);
|
|
83
|
+
return fallback;
|
|
84
|
+
}
|
|
85
|
+
|
|
35
86
|
function parseArgs() {
|
|
36
87
|
let port = parseInt(process.env.PORT || '3456', 10);
|
|
37
88
|
let host = '0.0.0.0';
|
|
38
|
-
const defaultShell =
|
|
89
|
+
const defaultShell = getDefaultShell();
|
|
39
90
|
const cwd = process.env.TERMBEAM_CWD || process.env.PTY_CWD || process.cwd();
|
|
40
91
|
let password = process.env.TERMBEAM_PASSWORD || process.env.PTY_PASSWORD || null;
|
|
41
92
|
let useTunnel = false;
|
package/src/routes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const { detectShells } = require('./shells');
|
|
4
5
|
|
|
5
6
|
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
|
|
6
7
|
|
|
@@ -46,16 +47,23 @@ function setupRoutes(app, { auth, sessions, config }) {
|
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
app.post('/api/sessions', auth.middleware, (req, res) => {
|
|
49
|
-
const { name, shell, args: shellArgs, cwd } = req.body || {};
|
|
50
|
+
const { name, shell, args: shellArgs, cwd, initialCommand } = req.body || {};
|
|
50
51
|
const id = sessions.create({
|
|
51
52
|
name: name || `Session ${sessions.sessions.size + 1}`,
|
|
52
53
|
shell: shell || config.defaultShell,
|
|
53
54
|
args: shellArgs || [],
|
|
54
55
|
cwd: cwd || config.cwd,
|
|
56
|
+
initialCommand: initialCommand || null,
|
|
55
57
|
});
|
|
56
58
|
res.json({ id, url: `/terminal?id=${id}` });
|
|
57
59
|
});
|
|
58
60
|
|
|
61
|
+
// Available shells
|
|
62
|
+
app.get('/api/shells', auth.middleware, (_req, res) => {
|
|
63
|
+
const shells = detectShells();
|
|
64
|
+
res.json({ shells, default: config.defaultShell });
|
|
65
|
+
});
|
|
66
|
+
|
|
59
67
|
app.delete('/api/sessions/:id', auth.middleware, (req, res) => {
|
|
60
68
|
if (sessions.delete(req.params.id)) {
|
|
61
69
|
res.json({ ok: true });
|
package/src/sessions.js
CHANGED
|
@@ -6,7 +6,7 @@ class SessionManager {
|
|
|
6
6
|
this.sessions = new Map();
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
create({ name, shell, args = [], cwd }) {
|
|
9
|
+
create({ name, shell, args = [], cwd, initialCommand = null }) {
|
|
10
10
|
const id = crypto.randomBytes(4).toString('hex');
|
|
11
11
|
const ptyProcess = pty.spawn(shell, args, {
|
|
12
12
|
name: 'xterm-256color',
|
|
@@ -16,6 +16,11 @@ class SessionManager {
|
|
|
16
16
|
env: { ...process.env, TERM: 'xterm-256color' },
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
// Send initial command once the shell is ready
|
|
20
|
+
if (initialCommand) {
|
|
21
|
+
setTimeout(() => ptyProcess.write(initialCommand + '\r'), 300);
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
const session = {
|
|
20
25
|
pty: ptyProcess,
|
|
21
26
|
name,
|
package/src/shells.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const KNOWN_WINDOWS_SHELLS = [
|
|
6
|
+
{ name: 'PowerShell (Core)', cmd: 'pwsh.exe' },
|
|
7
|
+
{ name: 'Windows PowerShell', cmd: 'powershell.exe' },
|
|
8
|
+
{ name: 'Command Prompt', cmd: 'cmd.exe' },
|
|
9
|
+
{ name: 'Git Bash', cmd: 'bash.exe' },
|
|
10
|
+
{ name: 'WSL', cmd: 'wsl.exe' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function detectShells() {
|
|
14
|
+
if (os.platform() === 'win32') {
|
|
15
|
+
return detectWindowsShells();
|
|
16
|
+
}
|
|
17
|
+
return detectUnixShells();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function detectWindowsShells() {
|
|
21
|
+
const shells = [];
|
|
22
|
+
for (const { name, cmd } of KNOWN_WINDOWS_SHELLS) {
|
|
23
|
+
try {
|
|
24
|
+
const result = execFileSync('where', [cmd], {
|
|
25
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
timeout: 3000,
|
|
28
|
+
});
|
|
29
|
+
const fullPath = result.trim().split('\n')[0].trim();
|
|
30
|
+
if (fullPath) {
|
|
31
|
+
shells.push({ name, path: fullPath, cmd });
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// not installed
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return shells;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectUnixShells() {
|
|
41
|
+
const shells = [];
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
|
|
44
|
+
// Read /etc/shells
|
|
45
|
+
try {
|
|
46
|
+
const content = fs.readFileSync('/etc/shells', 'utf8');
|
|
47
|
+
for (const line of content.split('\n')) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
50
|
+
const name = trimmed.split('/').pop();
|
|
51
|
+
if (!seen.has(name)) {
|
|
52
|
+
seen.add(name);
|
|
53
|
+
shells.push({ name, path: trimmed, cmd: trimmed });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// /etc/shells not available, try common paths
|
|
59
|
+
const common = ['/bin/bash', '/bin/zsh', '/bin/sh', '/usr/bin/fish', '/bin/fish'];
|
|
60
|
+
for (const p of common) {
|
|
61
|
+
try {
|
|
62
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
63
|
+
const name = p.split('/').pop();
|
|
64
|
+
if (!seen.has(name)) {
|
|
65
|
+
seen.add(name);
|
|
66
|
+
shells.push({ name, path: p, cmd: p });
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// not installed
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return shells;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { detectShells };
|
package/src/tunnel.js
CHANGED
|
@@ -1,26 +1,79 @@
|
|
|
1
1
|
const { execSync, spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
2
4
|
|
|
3
5
|
let tunnelId = null;
|
|
4
6
|
let tunnelProc = null;
|
|
7
|
+
let devtunnelCmd = 'devtunnel';
|
|
8
|
+
|
|
9
|
+
function findDevtunnel() {
|
|
10
|
+
// Try devtunnel directly
|
|
11
|
+
try {
|
|
12
|
+
execSync('devtunnel --version', { stdio: 'pipe' });
|
|
13
|
+
return 'devtunnel';
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
16
|
+
// On Windows, check common install locations
|
|
17
|
+
if (process.platform === 'win32') {
|
|
18
|
+
const candidates = [
|
|
19
|
+
path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WindowsApps', 'devtunnel.exe'),
|
|
20
|
+
path.join(process.env.PROGRAMFILES || '', 'Microsoft', 'devtunnel', 'devtunnel.exe'),
|
|
21
|
+
];
|
|
22
|
+
for (const p of candidates) {
|
|
23
|
+
if (fs.existsSync(p)) {
|
|
24
|
+
return p;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
async function startTunnel(port) {
|
|
33
|
+
// Check if devtunnel CLI is installed
|
|
34
|
+
const found = findDevtunnel();
|
|
35
|
+
if (!found) {
|
|
36
|
+
console.error('[termbeam] ❌ devtunnel CLI is not installed.');
|
|
37
|
+
console.error('');
|
|
38
|
+
console.error(' The --tunnel flag requires the Azure Dev Tunnels CLI.');
|
|
39
|
+
console.error('');
|
|
40
|
+
console.error(' Install it:');
|
|
41
|
+
console.error(' Windows: winget install Microsoft.devtunnel');
|
|
42
|
+
console.error(' or: Invoke-WebRequest -Uri https://aka.ms/TunnelsCliDownload/win-x64 -OutFile devtunnel.exe');
|
|
43
|
+
console.error(' macOS: brew install --cask devtunnel');
|
|
44
|
+
console.error(' Linux: curl -sL https://aka.ms/DevTunnelCliInstall | bash');
|
|
45
|
+
console.error('');
|
|
46
|
+
console.error(' Then restart your terminal and try again.');
|
|
47
|
+
console.error(' Docs: https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started');
|
|
48
|
+
console.error('');
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
devtunnelCmd = found;
|
|
52
|
+
|
|
7
53
|
console.log('[termbeam] Starting devtunnel...');
|
|
8
54
|
try {
|
|
55
|
+
// Ensure user is logged in
|
|
56
|
+
let loggedIn = false;
|
|
9
57
|
try {
|
|
10
|
-
execSync(
|
|
11
|
-
|
|
58
|
+
const userOut = execSync(`"${devtunnelCmd}" user show`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
59
|
+
// user show can succeed but show "not logged in" status
|
|
60
|
+
loggedIn = userOut && !userOut.toLowerCase().includes('not logged in');
|
|
61
|
+
} catch {}
|
|
62
|
+
|
|
63
|
+
if (!loggedIn) {
|
|
12
64
|
console.log('[termbeam] devtunnel not logged in, launching login...');
|
|
13
|
-
|
|
65
|
+
console.log('[termbeam] A browser window will open for authentication.');
|
|
66
|
+
execSync(`"${devtunnelCmd}" user login`, { stdio: 'inherit' });
|
|
14
67
|
}
|
|
15
68
|
|
|
16
|
-
const createOut = execSync(
|
|
69
|
+
const createOut = execSync(`"${devtunnelCmd}" create --expiration 1d --json`, { encoding: 'utf-8' });
|
|
17
70
|
const tunnelData = JSON.parse(createOut);
|
|
18
71
|
tunnelId = tunnelData.tunnel.tunnelId;
|
|
19
72
|
|
|
20
|
-
execSync(`
|
|
21
|
-
execSync(`
|
|
73
|
+
execSync(`"${devtunnelCmd}" port create ${tunnelId} -p ${port} --protocol http`, { stdio: 'pipe' });
|
|
74
|
+
execSync(`"${devtunnelCmd}" access create ${tunnelId} -p ${port} --anonymous`, { stdio: 'pipe' });
|
|
22
75
|
|
|
23
|
-
const hostProc = spawn(
|
|
76
|
+
const hostProc = spawn(devtunnelCmd, ['host', tunnelId], {
|
|
24
77
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
25
78
|
detached: true,
|
|
26
79
|
});
|
|
@@ -56,7 +109,7 @@ function cleanupTunnel() {
|
|
|
56
109
|
if (tunnelId) {
|
|
57
110
|
try {
|
|
58
111
|
if (tunnelProc) tunnelProc.kill();
|
|
59
|
-
execSync(`
|
|
112
|
+
execSync(`"${devtunnelCmd}" delete ${tunnelId} -f`, { stdio: 'pipe' });
|
|
60
113
|
console.log('[termbeam] Tunnel cleaned up');
|
|
61
114
|
} catch {
|
|
62
115
|
/* best effort */
|