@tlbx-ai/midterm 8.2.3 → 8.2.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/README.md CHANGED
@@ -31,6 +31,6 @@ Notes:
31
31
 
32
32
  - Default channel is `stable`
33
33
  - If you do not pass `--bind`, the launcher forces `127.0.0.1`
34
- - If you do not pass `--port`, the launcher opens `https://127.0.0.1:2000`
34
+ - If you do not pass `--port`, the launcher starts at `https://127.0.0.1:2000` and automatically moves to the next free port if `2000` is unavailable
35
35
  - The launcher sets `MIDTERM_LAUNCH_MODE=npx` for the child process
36
36
  - If you invoke `npx` from WSL but it resolves to Windows `node/npm`, the launcher detects the WSL working directory and runs the Linux MidTerm build inside that distro
package/bin/midterm.js CHANGED
@@ -5,12 +5,14 @@
5
5
  const fs = require('node:fs');
6
6
  const fsp = require('node:fs/promises');
7
7
  const https = require('node:https');
8
+ const net = require('node:net');
8
9
  const os = require('node:os');
9
10
  const path = require('node:path');
10
11
  const { spawn, spawnSync } = require('node:child_process');
11
12
 
12
13
  const { version: PACKAGE_VERSION } = require('../package.json');
13
14
  const DEFAULT_PORT = 2000;
15
+ const MAX_PORT_SCAN_ATTEMPTS = 100;
14
16
  const SERVER_READY_TIMEOUT_MS = 15000;
15
17
  const SERVER_READY_INTERVAL_MS = 500;
16
18
  const REPO_OWNER = 'tlbx-ai';
@@ -33,14 +35,30 @@ async function main() {
33
35
  : await ensureInstalledRelease(release, target);
34
36
 
35
37
  const childArgs = passthrough.slice();
38
+ const hasExplicitPort = hasArg(childArgs, '--port');
36
39
  const explicitBind = getArgValue(childArgs, '--bind');
37
40
  const explicitPort = parsePortArg(getArgValue(childArgs, '--port'));
41
+ const effectiveBind = explicitBind ?? '127.0.0.1';
42
+ const startsServer = shouldStartServer(childArgs);
43
+
44
+ let effectivePort = explicitPort ?? DEFAULT_PORT;
38
45
 
39
46
  if (!explicitBind) {
40
47
  childArgs.push('--bind', '127.0.0.1');
41
48
  }
42
49
 
43
- const browserUrl = buildBrowserUrl(explicitBind ?? '127.0.0.1', explicitPort ?? DEFAULT_PORT);
50
+ if (startsServer && !hasExplicitPort) {
51
+ effectivePort = await findAvailablePort(effectiveBind, DEFAULT_PORT);
52
+ childArgs.push('--port', String(effectivePort));
53
+
54
+ if (effectivePort !== DEFAULT_PORT) {
55
+ console.error(`@tlbx-ai/midterm: port ${DEFAULT_PORT} is unavailable, using ${effectivePort}`);
56
+ }
57
+ }
58
+
59
+ const browserUrl = startsServer
60
+ ? buildBrowserUrl(effectiveBind, effectivePort)
61
+ : null;
44
62
  const childEnv = {
45
63
  ...process.env,
46
64
  MIDTERM_LAUNCH_MODE: 'npx',
@@ -57,7 +75,7 @@ async function main() {
57
75
  env: childEnv
58
76
  });
59
77
 
60
- if (launcher.openBrowser) {
78
+ if (launcher.openBrowser && browserUrl) {
61
79
  void openBrowserWhenReady(browserUrl);
62
80
  }
63
81
 
@@ -148,7 +166,7 @@ async function detectWslInteropContext() {
148
166
  return null;
149
167
  }
150
168
 
151
- const parsed = parseWslUncPath(process.cwd());
169
+ const parsed = findWslInteropPath();
152
170
  if (!parsed) {
153
171
  return null;
154
172
  }
@@ -170,6 +188,32 @@ async function detectWslInteropContext() {
170
188
  };
171
189
  }
172
190
 
191
+ function findWslInteropPath() {
192
+ const candidates = [
193
+ process.cwd(),
194
+ process.env.INIT_CWD,
195
+ process.env.npm_config_local_prefix,
196
+ getPackageDirectory(process.env.npm_package_json)
197
+ ];
198
+
199
+ for (const candidate of candidates) {
200
+ const parsed = parseWslUncPath(candidate);
201
+ if (parsed) {
202
+ return parsed;
203
+ }
204
+ }
205
+
206
+ return null;
207
+ }
208
+
209
+ function getPackageDirectory(packageJsonPath) {
210
+ if (!packageJsonPath) {
211
+ return '';
212
+ }
213
+
214
+ return path.win32.dirname(packageJsonPath);
215
+ }
216
+
173
217
  function parseWslUncPath(value) {
174
218
  const normalized = String(value || '');
175
219
  const match = normalized.match(/^\\\\wsl(?:\.localhost|\$)\\([^\\]+)(\\.*)?$/i);
@@ -476,6 +520,68 @@ function parsePortArg(value) {
476
520
  return parsed;
477
521
  }
478
522
 
523
+ function shouldStartServer(args) {
524
+ const nonServerFlags = [
525
+ '--check-update',
526
+ '--update',
527
+ '--apply-update',
528
+ '--version',
529
+ '-v',
530
+ '--help',
531
+ '-h',
532
+ '--hash-password',
533
+ '--write-secret',
534
+ '--generate-cert'
535
+ ];
536
+
537
+ return !nonServerFlags.some((flag) => hasArg(args, flag));
538
+ }
539
+
540
+ async function findAvailablePort(bindAddress, preferredPort) {
541
+ for (let offset = 0; offset < MAX_PORT_SCAN_ATTEMPTS; offset++) {
542
+ const port = preferredPort + offset;
543
+ if (port > 65535) {
544
+ break;
545
+ }
546
+
547
+ if (await isPortAvailable(bindAddress, port)) {
548
+ return port;
549
+ }
550
+ }
551
+
552
+ throw new Error(`Could not find a free port starting at ${preferredPort}`);
553
+ }
554
+
555
+ function isPortAvailable(bindAddress, port) {
556
+ const host = normalizeBindForNetProbe(bindAddress);
557
+
558
+ return new Promise((resolve) => {
559
+ const server = net.createServer();
560
+
561
+ server.once('error', (error) => {
562
+ if (error && (error.code === 'EADDRINUSE' || error.code === 'EACCES')) {
563
+ resolve(false);
564
+ return;
565
+ }
566
+
567
+ resolve(false);
568
+ });
569
+
570
+ server.listen(port, host, () => {
571
+ server.close(() => resolve(true));
572
+ });
573
+ });
574
+ }
575
+
576
+ function normalizeBindForNetProbe(bindAddress) {
577
+ const raw = String(bindAddress || '').trim();
578
+ if (!raw || raw === 'localhost') {
579
+ return '127.0.0.1';
580
+ }
581
+
582
+ return raw.replace(/^\[(.*)\]$/, '$1');
583
+ }
584
+
479
585
  function buildBrowserUrl(bindAddress, port) {
480
586
  const normalized = normalizeHostForBrowser(bindAddress);
481
587
  return `https://${normalized}:${port}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tlbx-ai/midterm",
3
- "version": "8.2.3",
3
+ "version": "8.2.4",
4
4
  "description": "Launch MidTerm via npx by downloading the native binary for your platform",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {