termbeam 1.14.4 → 1.14.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 CHANGED
@@ -109,7 +109,8 @@ flowchart LR
109
109
  | `--port <port>` | Server port | `3456` |
110
110
  | `--host <addr>` | Bind address | `127.0.0.1` |
111
111
  | `--lan` | Bind to all interfaces (LAN access) | Off |
112
- | `--public` | Allow public tunnel access (no Microsoft login) | Off |
112
+ | `--public` | Public tunnel access (default) | On |
113
+ | `--private` | Require Microsoft login for tunnel access | Off |
113
114
  | `-i, --interactive` | Interactive setup wizard | Off |
114
115
  | `--log-level <level>` | Log verbosity (error/warn/info/debug) | `info` |
115
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termbeam",
3
- "version": "1.14.4",
3
+ "version": "1.14.5",
4
4
  "description": "Beam your terminal to any device — mobile-optimized web terminal with multi-session support",
5
5
  "main": "src/server/index.js",
6
6
  "bin": {
package/src/cli/index.js CHANGED
@@ -25,10 +25,10 @@ Options:
25
25
  --password <pw> Set access password (or TERMBEAM_PASSWORD env var)
26
26
  --generate-password Auto-generate a secure password (default: auto)
27
27
  --no-password Disable password authentication
28
- --tunnel Create a devtunnel URL (default: on, private access)
28
+ --tunnel Create a devtunnel URL (default: on, public access)
29
29
  --no-tunnel Disable tunnel (LAN-only mode)
30
30
  --persisted-tunnel Create a reusable devtunnel URL (stable across restarts)
31
- --public Allow public tunnel access (default: private, owner-only)
31
+ --private Require Microsoft login to access the tunnel (owner-only)
32
32
  --port <port> Set port (default: 3456, or PORT env var)
33
33
  --host <addr> Bind address (default: 127.0.0.1)
34
34
  --lan Bind to 0.0.0.0 (allow LAN access, default: localhost only)
@@ -40,9 +40,9 @@ Options:
40
40
 
41
41
  Defaults:
42
42
  By default, TermBeam enables tunnel + auto-generated password for secure
43
- mobile access (clipboard, HTTPS). Tunnels are private (owner-only via
44
- Microsoft login). Use --public for public access, or
45
- --no-tunnel for LAN-only mode.
43
+ mobile access (clipboard, HTTPS). Tunnels are public (anonymous) — TermBeam's
44
+ own password auth protects your terminal. Use --private for owner-only access
45
+ via Microsoft login, or --no-tunnel for LAN-only mode.
46
46
 
47
47
  Examples:
48
48
  termbeam Start with tunnel + auto password
@@ -249,7 +249,7 @@ function parseArgs() {
249
249
  let useTunnel = true;
250
250
  let noTunnel = false;
251
251
  let persistedTunnel = false;
252
- let publicTunnel = false;
252
+ let privateTunnel = false;
253
253
  let interactive = false;
254
254
  let force = false;
255
255
  let explicitPassword = !!password;
@@ -268,8 +268,10 @@ function parseArgs() {
268
268
  } else if (args[i] === '--persisted-tunnel') {
269
269
  useTunnel = true;
270
270
  persistedTunnel = true;
271
+ } else if (args[i] === '--private') {
272
+ privateTunnel = true;
271
273
  } else if (args[i] === '--public') {
272
- publicTunnel = true;
274
+ // no-op: public is now the default; kept for backwards compatibility
273
275
  } else if (args[i].startsWith('--password=')) {
274
276
  password = args[i].split('=')[1];
275
277
  if (!password) {
@@ -332,24 +334,24 @@ function parseArgs() {
332
334
  // --no-tunnel disables the default tunnel
333
335
  if (noTunnel) useTunnel = false;
334
336
 
335
- // --public requires a tunnel
336
- if (publicTunnel && !useTunnel) {
337
+ // --private requires a tunnel
338
+ if (privateTunnel && !useTunnel) {
337
339
  const rd = '\x1b[31m';
338
340
  const rs = '\x1b[0m';
339
- log.error('--public requires a tunnel');
341
+ log.error('--private requires a tunnel');
340
342
  console.error(
341
- `${rd}Error: --public requires a tunnel. Remove --no-tunnel or remove --public.${rs}`,
343
+ `${rd}Error: --private requires a tunnel. Remove --no-tunnel or remove --private.${rs}`,
342
344
  );
343
345
  process.exit(1);
344
346
  }
345
347
 
346
- // --public requires password authentication
347
- if (publicTunnel && !password) {
348
+ // Public tunnels (default) require password authentication
349
+ if (useTunnel && !privateTunnel && !password) {
348
350
  const rd = '\x1b[31m';
349
351
  const rs = '\x1b[0m';
350
352
  log.error('Public tunnels require password authentication');
351
353
  console.error(
352
- `${rd}Error: Public tunnels require password authentication. Remove --no-password or remove --public.${rs}`,
354
+ `${rd}Error: Public tunnels require password authentication. Remove --no-password or add --private.${rs}`,
353
355
  );
354
356
  process.exit(1);
355
357
  }
@@ -366,7 +368,7 @@ function parseArgs() {
366
368
  password,
367
369
  useTunnel,
368
370
  persistedTunnel,
369
- publicTunnel,
371
+ privateTunnel,
370
372
  shell,
371
373
  shellArgs,
372
374
  cwd,
@@ -378,7 +380,7 @@ function parseArgs() {
378
380
  };
379
381
 
380
382
  log.debug(
381
- `Config: port=${port}, host=${host}, shell=${shell}, tunnel=${useTunnel}, persisted=${persistedTunnel}, public=${publicTunnel}`,
383
+ `Config: port=${port}, host=${host}, shell=${shell}, tunnel=${useTunnel}, persisted=${persistedTunnel}, private=${privateTunnel}`,
382
384
  );
383
385
 
384
386
  return config;
@@ -58,7 +58,7 @@ async function runInteractiveSetup(baseConfig) {
58
58
  password: baseConfig.password,
59
59
  useTunnel: baseConfig.useTunnel,
60
60
  persistedTunnel: baseConfig.persistedTunnel,
61
- publicTunnel: baseConfig.publicTunnel,
61
+ privateTunnel: baseConfig.privateTunnel,
62
62
  shell: baseConfig.shell,
63
63
  shellArgs: baseConfig.shellArgs,
64
64
  cwd: baseConfig.cwd,
@@ -153,19 +153,18 @@ async function runInteractiveSetup(baseConfig) {
153
153
  showProgress(2);
154
154
  const publicChoice = await choose(rl, 'Tunnel access:', [
155
155
  {
156
- label: 'Private (owner-only)',
157
- hint: 'Only the Microsoft account that created the tunnel can access it',
156
+ label: 'Public',
157
+ hint: 'Anonymous access TermBeam password protects your terminal (recommended)',
158
158
  },
159
159
  {
160
- label: 'Public',
161
- hint: '🚨 No Microsoft login anyone with the URL can reach your terminal',
162
- danger: true,
160
+ label: 'Private (owner-only)',
161
+ hint: 'Requires Microsoft login to reach the tunnel adds extra auth layer',
163
162
  },
164
163
  ]);
165
- config.publicTunnel = publicChoice.index === 1;
164
+ config.privateTunnel = publicChoice.index === 1;
166
165
 
167
166
  // Auto-generate password if public tunnel with no password
168
- if (config.publicTunnel && !config.password) {
167
+ if (!config.privateTunnel && !config.password) {
169
168
  console.log(yellow(' ⚠ Public tunnels require password authentication.'));
170
169
  config.password = crypto.randomBytes(16).toString('base64url');
171
170
  process.stdout.write(dim(` Auto-generated password: ${config.password}`) + '\n');
@@ -178,22 +177,22 @@ async function runInteractiveSetup(baseConfig) {
178
177
  config.host = '0.0.0.0';
179
178
  config.useTunnel = false;
180
179
  config.persistedTunnel = false;
181
- config.publicTunnel = false;
180
+ config.privateTunnel = false;
182
181
  } else {
183
182
  // Localhost only
184
183
  config.host = '127.0.0.1';
185
184
  config.useTunnel = false;
186
185
  config.persistedTunnel = false;
187
- config.publicTunnel = false;
186
+ config.privateTunnel = false;
188
187
  }
189
188
 
190
189
  const accessLabel = !config.useTunnel
191
190
  ? config.host === '0.0.0.0'
192
191
  ? 'LAN (0.0.0.0)'
193
192
  : 'Localhost only'
194
- : config.publicTunnel
195
- ? 'DevTunnel (public)'
196
- : 'DevTunnel (private)';
193
+ : config.privateTunnel
194
+ ? 'DevTunnel (private)'
195
+ : 'DevTunnel (public)';
197
196
  log.debug(`Access mode selected: ${accessLabel}`);
198
197
  decisions.push({ label: 'Access', value: accessLabel });
199
198
 
@@ -226,7 +225,7 @@ async function runInteractiveSetup(baseConfig) {
226
225
  console.log(` Tunnel: ${config.useTunnel ? cyan('enabled') : yellow('disabled')}`);
227
226
  if (config.useTunnel) {
228
227
  console.log(` Persisted: ${config.persistedTunnel ? cyan('yes') : dim('no')}`);
229
- console.log(` Public: ${config.publicTunnel ? yellow('yes') : dim('no')}`);
228
+ console.log(` Public: ${config.privateTunnel ? dim('no (private)') : cyan('yes')}`);
230
229
  }
231
230
  console.log(` Shell: ${cyan(config.shell || 'default')}`);
232
231
  console.log(` Directory: ${cyan(config.cwd)}`);
@@ -247,7 +246,7 @@ async function runInteractiveSetup(baseConfig) {
247
246
  if (config.host === '0.0.0.0') cmdParts.push('--lan');
248
247
  } else {
249
248
  if (config.persistedTunnel) cmdParts.push('--persisted-tunnel');
250
- if (config.publicTunnel) cmdParts.push('--public');
249
+ if (config.privateTunnel) cmdParts.push('--private');
251
250
  }
252
251
  if (config.logLevel !== 'info') cmdParts.push('--log-level', config.logLevel);
253
252
  const cliCommand = cmdParts.join(' ');
@@ -80,8 +80,8 @@ function buildArgs(config) {
80
80
  if (config.persistedTunnel) {
81
81
  args.push('--persisted-tunnel');
82
82
  }
83
- if (config.publicTunnel) {
84
- args.push('--public');
83
+ if (config.privateTunnel) {
84
+ args.push('--private');
85
85
  }
86
86
  if (config.logLevel && config.logLevel !== 'info') {
87
87
  args.push('--log-level', config.logLevel);
@@ -291,17 +291,16 @@ async function actionInstall() {
291
291
  showProgress(3);
292
292
  const publicChoice = await choose(rl, 'Tunnel access:', [
293
293
  {
294
- label: 'Private (requires Microsoft login)',
295
- hint: 'Only you can access the tunnel secured via your Microsoft account',
294
+ label: 'Public (anyone with the link)',
295
+ hint: 'Anonymous access TermBeam password protects your terminal (recommended)',
296
296
  },
297
297
  {
298
- label: 'Public (anyone with the link)',
299
- hint: '🚨 Anyone with the URL can reach your terminalpassword is the only protection',
300
- danger: true,
298
+ label: 'Private (requires Microsoft login)',
299
+ hint: 'Only you can access the tunnelsecured via your Microsoft account',
301
300
  },
302
301
  ]);
303
- config.publicTunnel = publicChoice.index === 1;
304
- if (config.publicTunnel && config.password === false) {
302
+ config.privateTunnel = publicChoice.index === 1;
303
+ if (!config.privateTunnel && config.password === false) {
305
304
  console.log(yellow(' ⚠ Public tunnels require password authentication.'));
306
305
  config.password = crypto.randomBytes(16).toString('base64url');
307
306
  process.stdout.write(dim(` Auto-generated password: ${config.password}`) + '\n');
@@ -319,9 +318,9 @@ async function actionInstall() {
319
318
  ? config.lan
320
319
  ? 'LAN (0.0.0.0)'
321
320
  : 'Localhost only'
322
- : config.publicTunnel
323
- ? 'DevTunnel (public)'
324
- : 'DevTunnel (private)';
321
+ : config.privateTunnel
322
+ ? 'DevTunnel (private)'
323
+ : 'DevTunnel (public)';
325
324
  decisions.push({ label: 'Access', value: accessLabel });
326
325
 
327
326
  // Working directory
@@ -368,7 +367,7 @@ async function actionInstall() {
368
367
  console.log(` Tunnel: ${config.noTunnel ? yellow('disabled') : cyan('enabled')}`);
369
368
  if (!config.noTunnel) {
370
369
  console.log(` Persisted: ${config.persistedTunnel ? cyan('yes') : dim('no')}`);
371
- console.log(` Public: ${config.publicTunnel ? yellow('yes') : dim('no')}`);
370
+ console.log(` Public: ${config.privateTunnel ? yellow('no (private)') : dim('yes')}`);
372
371
  }
373
372
  console.log(` Directory: ${cyan(config.cwd)}`);
374
373
  console.log(` Shell: ${cyan(config.shell || 'default')}`);
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  const os = require('os');
3
3
  const path = require('path');
4
- const readline = require('readline');
5
4
  const express = require('express');
6
5
  const cookieParser = require('cookie-parser');
7
6
  const http = require('http');
@@ -18,7 +17,6 @@ const { createPreviewProxy } = require('./preview');
18
17
  const { writeConnectionConfig, removeConnectionConfig } = require('../cli/resume');
19
18
  const { checkForUpdate, detectInstallMethod } = require('../utils/update-check');
20
19
 
21
- // --- Helpers ---
22
20
  function getLocalIP() {
23
21
  const interfaces = os.networkInterfaces();
24
22
  for (const name of Object.keys(interfaces)) {
@@ -29,16 +27,6 @@ function getLocalIP() {
29
27
  return '127.0.0.1';
30
28
  }
31
29
 
32
- function confirmPublicTunnel() {
33
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
34
- return new Promise((resolve) => {
35
- rl.question(' Do you want to continue with public access? (y/N): ', (answer) => {
36
- rl.close();
37
- resolve(answer.trim().toLowerCase() === 'y');
38
- });
39
- });
40
- }
41
-
42
30
  /**
43
31
  * Create a TermBeam server instance without starting it.
44
32
  * @param {object} [overrides] - Optional overrides
@@ -140,26 +128,13 @@ function createTermBeamServer(overrides = {}) {
140
128
  }
141
129
  }
142
130
 
143
- // Warn and require consent for public tunnel access
144
- if (config.useTunnel && config.publicTunnel) {
145
- const rd = '\x1b[31m';
131
+ // Warn about private tunnel access when --private flag is used
132
+ if (config.useTunnel && config.privateTunnel) {
146
133
  const yl = '\x1b[33m';
147
134
  const rs = '\x1b[0m';
148
- const bd = '\x1b[1m';
149
- console.log('');
150
- console.log(` ${rd}${bd}⚠️ DANGER: Public tunnel access requested${rs}`);
151
135
  console.log('');
152
- console.log(` ${yl}This will make your terminal accessible to ANYONE with the URL.${rs}`);
153
- console.log(` ${yl}No Microsoft login will be required to reach the tunnel.${rs}`);
154
- console.log(` ${yl}Only the TermBeam password will protect your terminal.${rs}`);
136
+ console.log(` ${yl}ℹ️ Private tunnel: Microsoft login required to access the tunnel.${rs}`);
155
137
  console.log('');
156
- const confirmed = await confirmPublicTunnel();
157
- if (!confirmed) {
158
- console.log('');
159
- console.log(' Aborted. Restart without --public for private access.');
160
- console.log('');
161
- process.exit(1);
162
- }
163
138
  }
164
139
 
165
140
  return new Promise((resolve) => {
@@ -229,7 +204,7 @@ function createTermBeamServer(overrides = {}) {
229
204
  if (config.useTunnel) {
230
205
  const tunnel = await startTunnel(actualPort, {
231
206
  persisted: config.persistedTunnel,
232
- anonymous: config.publicTunnel,
207
+ private: config.privateTunnel,
233
208
  });
234
209
  if (tunnel) {
235
210
  publicUrl = tunnel.url;
@@ -234,8 +234,16 @@ async function startTunnel(port, options = {}) {
234
234
  { stdio: 'pipe' },
235
235
  );
236
236
  } catch {}
237
- // Set tunnel access: public (anonymous) or private (owner-only via Microsoft login)
238
- if (options.anonymous) {
237
+ // Set tunnel access: public (anonymous, default) or private (owner-only via Microsoft login)
238
+ if (options.private) {
239
+ // Remove any existing anonymous access to ensure the tunnel is private
240
+ try {
241
+ execFileSync(devtunnelCmd, ['access', 'reset', tunnelId], {
242
+ stdio: 'pipe',
243
+ });
244
+ } catch {}
245
+ log.info('Tunnel access: private (owner-only via Microsoft login)');
246
+ } else {
239
247
  try {
240
248
  execFileSync(
241
249
  devtunnelCmd,
@@ -244,14 +252,6 @@ async function startTunnel(port, options = {}) {
244
252
  );
245
253
  } catch {}
246
254
  log.info('Tunnel access: public (anonymous)');
247
- } else {
248
- // Remove any existing anonymous access to ensure the tunnel is private
249
- try {
250
- execFileSync(devtunnelCmd, ['access', 'reset', tunnelId], {
251
- stdio: 'pipe',
252
- });
253
- } catch {}
254
- log.info('Tunnel access: private (owner-only via Microsoft login)');
255
255
  }
256
256
 
257
257
  const hostProc = spawn(devtunnelCmd, ['host', tunnelId], {