ghostterm 1.1.3 → 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,8 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 1.1.3 (2026-03-17)
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
+
9
+ ## 1.2.0 (2026-03-17)
4
10
 
5
11
  ### Features
12
+ - **Background mode by default** — `npx ghostterm` now runs in the background automatically, no terminal window
13
+ - **CLI commands** — `npx ghostterm stop`, `npx ghostterm status`, `npx ghostterm logs`
14
+ - **First-run login** — opens browser for Google sign-in in foreground, then auto-restarts as daemon
15
+ - **Duplicate detection** — prevents starting multiple instances
6
16
  - **12345 numpad popup** — quick number selection for CLI surveys/prompts (raw keypress without Enter)
7
17
  - **Screen button** — renamed from "Shot" for clarity
8
18
  - **Paste button shows filename** — after uploading, displays shortened filename instead of generic "Paste"
package/README.md CHANGED
@@ -38,13 +38,22 @@ No VPN. No Tailscale. No port forwarding. Just one command.
38
38
  npx ghostterm
39
39
  ```
40
40
 
41
- First run opens a browser for Google sign-in. After that, it remembers you.
41
+ First run opens a browser for Google sign-in. After that, it runs in the background automatically — no terminal window.
42
42
 
43
- **Run in background (no terminal window):**
43
+ ```
44
+ $ npx ghostterm
45
+ ✅ GhostTerm running in background (PID: 12345)
46
+ 📱 Open: ghostterm.pages.dev
47
+ 🛑 Stop: npx ghostterm stop
48
+ 📋 Logs: npx ghostterm logs
49
+ ```
50
+
51
+ Other commands:
44
52
 
45
53
  ```bash
46
- pm2 start ghostterm.js --name ghostterm
47
- pm2 save
54
+ npx ghostterm status # check if running
55
+ npx ghostterm stop # stop the background process
56
+ npx ghostterm logs # show recent logs
48
57
  ```
49
58
 
50
59
  ### 2. On your phone
package/bin/ghostterm.js CHANGED
@@ -433,8 +433,150 @@ function handleRelayMessage(msg) {
433
433
  }
434
434
  }
435
435
 
436
+ // ==================== Background Mode ====================
437
+ const PID_FILE = path.join(CRED_DIR, 'ghostterm.pid');
438
+ const LOG_FILE = path.join(CRED_DIR, 'ghostterm.log');
439
+
440
+ function isRunning(pid) {
441
+ try { process.kill(pid, 0); return true; } catch { return false; }
442
+ }
443
+
444
+ function handleSubcommand() {
445
+ const arg = process.argv[2];
446
+
447
+ if (arg === 'stop') {
448
+ if (fs.existsSync(PID_FILE)) {
449
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
450
+ if (isRunning(pid)) {
451
+ process.kill(pid);
452
+ fs.unlinkSync(PID_FILE);
453
+ console.log(` GhostTerm stopped (PID: ${pid})`);
454
+ } else {
455
+ fs.unlinkSync(PID_FILE);
456
+ console.log(' GhostTerm was not running');
457
+ }
458
+ } else {
459
+ console.log(' GhostTerm is not running');
460
+ }
461
+ process.exit(0);
462
+ }
463
+
464
+ if (arg === 'status') {
465
+ if (fs.existsSync(PID_FILE)) {
466
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
467
+ if (isRunning(pid)) {
468
+ console.log(` GhostTerm is running (PID: ${pid})`);
469
+ } else {
470
+ fs.unlinkSync(PID_FILE);
471
+ console.log(' GhostTerm is not running');
472
+ }
473
+ } else {
474
+ console.log(' GhostTerm is not running');
475
+ }
476
+ process.exit(0);
477
+ }
478
+
479
+ if (arg === 'logs') {
480
+ if (fs.existsSync(LOG_FILE)) {
481
+ const lines = fs.readFileSync(LOG_FILE, 'utf8').split('\n').slice(-50);
482
+ console.log(lines.join('\n'));
483
+ } else {
484
+ console.log(' No logs found');
485
+ }
486
+ process.exit(0);
487
+ }
488
+
489
+ // No subcommand = start in background (unless already daemonized via --daemon or --supervisor)
490
+ if (arg !== '--daemon' && arg !== '--supervisor') {
491
+ // Check if already running
492
+ if (fs.existsSync(PID_FILE)) {
493
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
494
+ if (isRunning(pid)) {
495
+ console.log(` GhostTerm is already running (PID: ${pid})`);
496
+ console.log(' Stop: npx ghostterm stop');
497
+ process.exit(0);
498
+ }
499
+ }
500
+
501
+ // First-time login needs foreground (browser opens)
502
+ const needsLogin = !fs.existsSync(CRED_FILE);
503
+ if (needsLogin) {
504
+ // Run in foreground for first login, then restart as daemon
505
+ return 'foreground-first-login';
506
+ }
507
+
508
+ // Spawn self as supervisor in background
509
+ const { spawn, execSync } = require('child_process');
510
+ const out = fs.openSync(LOG_FILE, 'a');
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() : '?';
535
+ console.log('');
536
+ console.log(` ✅ GhostTerm running in background (PID: ${pid})`);
537
+ console.log(' 📱 Open: ghostterm.pages.dev');
538
+ console.log(' 🛑 Stop: npx ghostterm stop');
539
+ console.log(' 📋 Logs: npx ghostterm logs');
540
+ console.log('');
541
+ process.exit(0);
542
+ }
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
+
572
+ // --daemon: write PID file and continue to main()
573
+ return 'daemon';
574
+ }
575
+
436
576
  // ==================== Main ====================
437
577
  async function main() {
578
+ const mode = handleSubcommand();
579
+
438
580
  console.log('');
439
581
  console.log(' ╔═══════════════════════════════════╗');
440
582
  console.log(' ║ GhostTerm Companion ║');
@@ -449,6 +591,16 @@ async function main() {
449
591
  console.log(' Will use pairing code instead');
450
592
  }
451
593
 
594
+ // First login done in foreground — now restart in background
595
+ if (mode === 'foreground-first-login' && googleToken) {
596
+ 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' });
601
+ process.exit(0);
602
+ }
603
+
452
604
  connectToRelay();
453
605
  }
454
606
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostterm",
3
- "version": "1.1.3",
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"