@tlbx-ai/midterm 0.1.0 → 8.2.1

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
@@ -6,7 +6,7 @@ Launch MidTerm through `npx`.
6
6
  npx @tlbx-ai/midterm
7
7
  ```
8
8
 
9
- The launcher downloads the native MidTerm release for your platform, caches it in your user profile, and runs it locally.
9
+ The launcher downloads the native MidTerm release for your platform, caches it in your user profile, runs it locally, and opens MidTerm in your default browser.
10
10
 
11
11
  Supported platforms:
12
12
 
@@ -24,10 +24,12 @@ npx @tlbx-ai/midterm -- --port 2001 --bind 127.0.0.1
24
24
  Launcher-only options:
25
25
 
26
26
  - `--channel stable|dev`
27
+ - `--no-browser`
27
28
  - `--help-launcher`
28
29
 
29
30
  Notes:
30
31
 
31
32
  - Default channel is `stable`
32
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`
33
35
  - The launcher sets `MIDTERM_LAUNCH_MODE=npx` for the child process
package/bin/midterm.js CHANGED
@@ -4,11 +4,15 @@
4
4
 
5
5
  const fs = require('node:fs');
6
6
  const fsp = require('node:fs/promises');
7
+ const https = require('node:https');
7
8
  const os = require('node:os');
8
9
  const path = require('node:path');
9
10
  const { spawn, spawnSync } = require('node:child_process');
10
11
 
11
- const PACKAGE_VERSION = '0.1.0';
12
+ const { version: PACKAGE_VERSION } = require('../package.json');
13
+ const DEFAULT_PORT = 2000;
14
+ const SERVER_READY_TIMEOUT_MS = 15000;
15
+ const SERVER_READY_INTERVAL_MS = 500;
12
16
  const REPO_OWNER = 'tlbx-ai';
13
17
  const REPO_NAME = 'MidTerm';
14
18
  const GITHUB_API = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}`;
@@ -32,10 +36,14 @@ async function main() {
32
36
  }
33
37
 
34
38
  const childArgs = passthrough.slice();
35
- if (!hasArg(childArgs, '--bind')) {
39
+ const explicitBind = getArgValue(childArgs, '--bind');
40
+ const explicitPort = parsePortArg(getArgValue(childArgs, '--port'));
41
+
42
+ if (!explicitBind) {
36
43
  childArgs.push('--bind', '127.0.0.1');
37
44
  }
38
45
 
46
+ const browserUrl = buildBrowserUrl(explicitBind ?? '127.0.0.1', explicitPort ?? DEFAULT_PORT);
39
47
  const childEnv = {
40
48
  ...process.env,
41
49
  MIDTERM_LAUNCH_MODE: 'npx',
@@ -49,6 +57,10 @@ async function main() {
49
57
  env: childEnv
50
58
  });
51
59
 
60
+ if (launcher.openBrowser) {
61
+ void openBrowserWhenReady(browserUrl);
62
+ }
63
+
52
64
  forwardSignal(child, 'SIGINT');
53
65
  forwardSignal(child, 'SIGTERM');
54
66
  forwardSignal(child, 'SIGHUP');
@@ -66,7 +78,8 @@ async function main() {
66
78
  function parseArgs(args) {
67
79
  const launcher = {
68
80
  help: false,
69
- channel: 'stable'
81
+ channel: 'stable',
82
+ openBrowser: true
70
83
  };
71
84
  const passthrough = [];
72
85
 
@@ -83,6 +96,11 @@ function parseArgs(args) {
83
96
  continue;
84
97
  }
85
98
 
99
+ if (arg === '--no-browser') {
100
+ launcher.openBrowser = false;
101
+ continue;
102
+ }
103
+
86
104
  if (arg === '--channel') {
87
105
  const value = args[i + 1];
88
106
  if (value !== 'stable' && value !== 'dev') {
@@ -106,6 +124,7 @@ function printHelp() {
106
124
  console.log('');
107
125
  console.log('Launcher options:');
108
126
  console.log(' --channel stable|dev Choose the release channel (default: stable)');
127
+ console.log(' --no-browser Do not auto-open MidTerm in the default browser');
109
128
  console.log(' --help-launcher Show launcher help');
110
129
  console.log('');
111
130
  console.log('All other arguments are passed to mt.');
@@ -295,6 +314,127 @@ function hasArg(args, name) {
295
314
  return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
296
315
  }
297
316
 
317
+ function getArgValue(args, name) {
318
+ for (let i = 0; i < args.length; i++) {
319
+ const arg = args[i];
320
+ if (arg === name) {
321
+ return args[i + 1];
322
+ }
323
+ if (arg.startsWith(`${name}=`)) {
324
+ return arg.slice(name.length + 1);
325
+ }
326
+ }
327
+
328
+ return undefined;
329
+ }
330
+
331
+ function parsePortArg(value) {
332
+ if (!value) {
333
+ return undefined;
334
+ }
335
+
336
+ const parsed = Number.parseInt(value, 10);
337
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
338
+ return undefined;
339
+ }
340
+
341
+ return parsed;
342
+ }
343
+
344
+ function buildBrowserUrl(bindAddress, port) {
345
+ const normalized = normalizeHostForBrowser(bindAddress);
346
+ return `https://${normalized}:${port}`;
347
+ }
348
+
349
+ function normalizeHostForBrowser(bindAddress) {
350
+ const raw = String(bindAddress || '').trim();
351
+ if (!raw || raw === '0.0.0.0' || raw === '::' || raw === '[::]') {
352
+ return '127.0.0.1';
353
+ }
354
+
355
+ const host = raw.replace(/^\[(.*)\]$/, '$1');
356
+ if (host.includes(':')) {
357
+ return `[${host}]`;
358
+ }
359
+
360
+ return host;
361
+ }
362
+
363
+ async function openBrowserWhenReady(url) {
364
+ const ready = await waitForServer(url, SERVER_READY_TIMEOUT_MS);
365
+ if (!ready) {
366
+ console.error(`@tlbx-ai/midterm: server did not become ready within ${SERVER_READY_TIMEOUT_MS}ms, opening browser anyway`);
367
+ }
368
+
369
+ openUrl(url);
370
+ }
371
+
372
+ async function waitForServer(url, timeoutMs) {
373
+ const deadline = Date.now() + timeoutMs;
374
+
375
+ while (Date.now() < deadline) {
376
+ if (await probeUrl(url)) {
377
+ return true;
378
+ }
379
+
380
+ await sleep(SERVER_READY_INTERVAL_MS);
381
+ }
382
+
383
+ return false;
384
+ }
385
+
386
+ function probeUrl(url) {
387
+ return new Promise((resolve) => {
388
+ const request = https.request(url, {
389
+ method: 'GET',
390
+ rejectUnauthorized: false,
391
+ timeout: SERVER_READY_INTERVAL_MS
392
+ }, (response) => {
393
+ response.resume();
394
+ resolve(true);
395
+ });
396
+
397
+ request.on('error', () => resolve(false));
398
+ request.on('timeout', () => {
399
+ request.destroy();
400
+ resolve(false);
401
+ });
402
+ request.end();
403
+ });
404
+ }
405
+
406
+ function sleep(ms) {
407
+ return new Promise((resolve) => {
408
+ setTimeout(resolve, ms);
409
+ });
410
+ }
411
+
412
+ function openUrl(url) {
413
+ let command;
414
+ let args;
415
+
416
+ if (process.platform === 'win32') {
417
+ command = 'cmd';
418
+ args = ['/c', 'start', '', url];
419
+ } else if (process.platform === 'darwin') {
420
+ command = 'open';
421
+ args = [url];
422
+ } else {
423
+ command = 'xdg-open';
424
+ args = [url];
425
+ }
426
+
427
+ const result = spawn(command, args, {
428
+ detached: true,
429
+ stdio: 'ignore',
430
+ windowsHide: true
431
+ });
432
+ result.on('error', (error) => {
433
+ console.error(`@tlbx-ai/midterm: failed to open browser automatically: ${error.message}`);
434
+ });
435
+ result.unref();
436
+ }
437
+
298
438
  function forwardSignal(child, signal) {
299
439
  process.on(signal, () => {
300
440
  if (!child.killed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tlbx-ai/midterm",
3
- "version": "0.1.0",
3
+ "version": "8.2.1",
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": {