ghostterm 1.2.0 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1 (2026-03-17)
4
+
5
+ ### Bug Fixes
6
+ - **Windows background mode** — fixed node-pty crash in detached mode by using `windowsHide` instead of raw detach
7
+ - **Supervisor crash loop** — fixed `--supervisor` arg being caught by "already running" check
8
+
3
9
  ## 1.2.0 (2026-03-17)
4
10
 
5
11
  ### Features
package/bin/ghostterm.js CHANGED
@@ -486,8 +486,8 @@ function handleSubcommand() {
486
486
  process.exit(0);
487
487
  }
488
488
 
489
- // No subcommand = start in background (unless already daemonized via --daemon)
490
- if (arg !== '--daemon') {
489
+ // No subcommand = start in background (unless already daemonized via --daemon or --supervisor)
490
+ if (arg !== '--daemon' && arg !== '--supervisor') {
491
491
  // Check if already running
492
492
  if (fs.existsSync(PID_FILE)) {
493
493
  const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
@@ -505,18 +505,35 @@ function handleSubcommand() {
505
505
  return 'foreground-first-login';
506
506
  }
507
507
 
508
- // Spawn self as daemon
509
- const { spawn } = require('child_process');
508
+ // Spawn self as supervisor in background
509
+ const { spawn, execSync } = require('child_process');
510
510
  const out = fs.openSync(LOG_FILE, 'a');
511
- const child = spawn(process.execPath, [__filename, '--daemon'], {
512
- detached: true,
513
- stdio: ['ignore', out, out],
514
- env: { ...process.env },
515
- });
516
- child.unref();
517
- fs.writeFileSync(PID_FILE, String(child.pid));
511
+
512
+ if (os.platform() === 'win32') {
513
+ // Windows: spawn with hidden window (windowsHide) but NOT detached
514
+ // node-pty requires a console; detached:true removes the console and crashes
515
+ // windowsHide:true creates a hidden console window
516
+ const child = spawn(process.execPath, [__filename, '--supervisor'], {
517
+ detached: true,
518
+ windowsHide: true,
519
+ stdio: ['ignore', out, out],
520
+ env: { ...process.env },
521
+ });
522
+ child.unref();
523
+ fs.writeFileSync(PID_FILE, String(child.pid));
524
+ } else {
525
+ // macOS/Linux: detached works fine
526
+ const child = spawn(process.execPath, [__filename, '--supervisor'], {
527
+ detached: true,
528
+ stdio: ['ignore', out, out],
529
+ env: { ...process.env },
530
+ });
531
+ child.unref();
532
+ fs.writeFileSync(PID_FILE, String(child.pid));
533
+ }
534
+ const pid = fs.existsSync(PID_FILE) ? fs.readFileSync(PID_FILE, 'utf8').trim() : '?';
518
535
  console.log('');
519
- console.log(` ✅ GhostTerm running in background (PID: ${child.pid})`);
536
+ console.log(` ✅ GhostTerm running in background (PID: ${pid})`);
520
537
  console.log(' 📱 Open: ghostterm.pages.dev');
521
538
  console.log(' 🛑 Stop: npx ghostterm stop');
522
539
  console.log(' 📋 Logs: npx ghostterm logs');
@@ -524,8 +541,35 @@ function handleSubcommand() {
524
541
  process.exit(0);
525
542
  }
526
543
 
544
+ // --supervisor: watchdog that restarts worker on crash
545
+ if (arg === '--supervisor') {
546
+ fs.writeFileSync(PID_FILE, String(process.pid));
547
+ const { spawn } = require('child_process');
548
+ let restartCount = 0;
549
+
550
+ function spawnWorker() {
551
+ const logFd = fs.openSync(LOG_FILE, 'a');
552
+ const worker = spawn(process.execPath, [__filename, '--daemon'], {
553
+ stdio: ['ignore', logFd, logFd],
554
+ env: { ...process.env },
555
+ });
556
+ worker.on('exit', (code) => {
557
+ if (code === 0) {
558
+ // Clean exit (e.g. from stop command), don't restart
559
+ process.exit(0);
560
+ }
561
+ restartCount++;
562
+ const delay = Math.min(restartCount * 3000, 30000); // 3s, 6s, 9s... max 30s
563
+ console.log(` [supervisor] Worker exited (code ${code}), restarting in ${delay / 1000}s... (restart #${restartCount})`);
564
+ setTimeout(spawnWorker, delay);
565
+ });
566
+ }
567
+
568
+ spawnWorker();
569
+ return;
570
+ }
571
+
527
572
  // --daemon: write PID file and continue to main()
528
- fs.writeFileSync(PID_FILE, String(process.pid));
529
573
  return 'daemon';
530
574
  }
531
575
 
@@ -547,23 +591,13 @@ async function main() {
547
591
  console.log(' Will use pairing code instead');
548
592
  }
549
593
 
550
- // First login done in foreground — now restart as daemon
594
+ // First login done in foreground — now restart in background
551
595
  if (mode === 'foreground-first-login' && googleToken) {
552
596
  console.log('');
553
- console.log(' Login successful! Restarting in background...');
554
- const { spawn } = require('child_process');
555
- const out = fs.openSync(LOG_FILE, 'a');
556
- const child = spawn(process.execPath, [__filename, '--daemon'], {
557
- detached: true,
558
- stdio: ['ignore', out, out],
559
- env: { ...process.env },
560
- });
561
- child.unref();
562
- fs.writeFileSync(PID_FILE, String(child.pid));
563
- console.log(` ✅ GhostTerm running in background (PID: ${child.pid})`);
564
- console.log(' 📱 Open: ghostterm.pages.dev');
565
- console.log(' 🛑 Stop: npx ghostterm stop');
566
- console.log('');
597
+ console.log(' Login successful! Starting in background...');
598
+ // Re-run ourselves which will take the normal background start path
599
+ const { execSync } = require('child_process');
600
+ execSync(`"${process.execPath}" "${__filename}"`, { stdio: 'inherit' });
567
601
  process.exit(0);
568
602
  }
569
603
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostterm",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Mobile terminal for Claude Code — control your PC from your phone",
5
5
  "bin": {
6
6
  "ghostterm": "bin/ghostterm.js"