ghostterm 1.1.2 → 1.2.0

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 ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## 1.2.0 (2026-03-17)
4
+
5
+ ### Features
6
+ - **Background mode by default** — `npx ghostterm` now runs in the background automatically, no terminal window
7
+ - **CLI commands** — `npx ghostterm stop`, `npx ghostterm status`, `npx ghostterm logs`
8
+ - **First-run login** — opens browser for Google sign-in in foreground, then auto-restarts as daemon
9
+ - **Duplicate detection** — prevents starting multiple instances
10
+ - **12345 numpad popup** — quick number selection for CLI surveys/prompts (raw keypress without Enter)
11
+ - **Screen button** — renamed from "Shot" for clarity
12
+ - **Paste button shows filename** — after uploading, displays shortened filename instead of generic "Paste"
13
+ - **Session persistence** — refreshing the page restores your last active terminal session
14
+ - **PM2 support** — can run in background without a terminal window
15
+
16
+ ### Security & Stability
17
+ - **Heartbeat ping/pong** — relay pings every 20s; mobile detects dead connections within 30s and auto-reconnects
18
+ - **Disconnect overlay** — clear full-screen prompt when companion goes offline
19
+ - **Session loading spinner** — visual feedback when switching sessions
20
+ - **Improved toast notifications** — slide-in animation
21
+
22
+ ### Docs
23
+ - Updated README with new features, PM2 instructions, heartbeat details
24
+ - Added CHANGELOG
25
+
26
+ ## 1.1.1 (2026-03-16)
27
+
28
+ ### Security Hardening
29
+ - **Rate limiting** — 50 messages/second per WebSocket connection
30
+ - **IP connection limit** — max 5 concurrent connections per IP
31
+ - **Brute force protection** — pair code entry locked after 3 failed attempts (60s cooldown)
32
+ - **Origin verification** — only ghostterm.pages.dev allowed
33
+ - **Max payload** — 1MB limit per WebSocket message
34
+ - **Timing-safe HMAC** — prevents timing attacks on token verification
35
+ - **Upload validation** — 5MB size limit + extension whitelist
36
+ - **Pair code format validation** — must be exactly 6 digits
37
+ - **WSS enforcement** — prevents MITM downgrade attacks
38
+ - **Exponential backoff** — reconnection delay 1s → 30s max
39
+
40
+ ### Bug Fixes
41
+ - Fixed ghost duplication (stale session list cleared on reconnect)
42
+ - Fixed auto-scroll during thinking output (touch/button scroll disables auto-scroll)
43
+ - Fixed smartScroll jumping to top of terminal
44
+
45
+ ## 1.1.0 (2026-03-15)
46
+
47
+ ### Features
48
+ - Google OAuth auto-pairing (sign in once, auto-reconnects)
49
+ - Long-lived token (30 days, no repeated Google sign-in)
50
+ - 4 simultaneous terminal sessions with ghost cell previews
51
+ - Pixel office mode
52
+ - File upload and screenshot support
53
+ - D-pad controls, quick keys (y/n/Tab/Shift+Tab)
54
+ - Copy mode for terminal text selection
55
+
56
+ ## 1.0.0 (2026-03-14)
57
+
58
+ - Initial release
59
+ - Basic terminal relay via WebSocket
60
+ - Pair code pairing
package/README.md CHANGED
@@ -38,7 +38,23 @@ 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
+
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:
52
+
53
+ ```bash
54
+ npx ghostterm status # check if running
55
+ npx ghostterm stop # stop the background process
56
+ npx ghostterm logs # show recent logs
57
+ ```
42
58
 
43
59
  ### 2. On your phone
44
60
 
@@ -73,16 +89,18 @@ Open **[ghostterm.pages.dev](https://ghostterm.pages.dev)** in any mobile browse
73
89
  ### Controls
74
90
 
75
91
  - **Quick keys** — one-tap `y`/`n` for Claude Code approvals
76
- - **D-pad** — arrow keys, Enter, Tab, Shift+Tab
92
+ - **12345 numpad** — tap to pop up number selection for CLI surveys/prompts (raw keypress, no Enter)
93
+ - **D-pad** — arrow keys, Enter, Tab, Shift+Tab, Space
77
94
  - **`claude` button** — quick-launch menu: new session, resume, continue, dangerous mode
78
- - **Ctrl+C** — interrupt running processes
95
+ - **Ctrl+C (Stop)** — interrupt running processes
79
96
  - **Text input** — full keyboard input with Send button
80
97
  - **Copy mode** — select and copy terminal text
81
98
 
82
99
  ### File Transfer
83
100
 
84
- - **Screenshot** — capture your phone screen and send it to your PC terminal
101
+ - **Screen** — capture your terminal screen and send it to your PC as a file
85
102
  - **File upload** — upload files directly from your phone to your desktop
103
+ - **Paste button** — after uploading, shows shortened filename; tap to paste the file path into the terminal
86
104
 
87
105
  ### Visual
88
106
 
@@ -95,7 +113,9 @@ Open **[ghostterm.pages.dev](https://ghostterm.pages.dev)** in any mobile browse
95
113
  - **Google auto-pairing** — sign in once, auto-reconnects forever
96
114
  - **Encrypted relay** — all traffic over WSS (WebSocket Secure)
97
115
  - **Zero data stored** — the relay only forwards messages
98
- - **Auto-reconnect** — handles network drops gracefully
116
+ - **Heartbeat** — relay pings every 20s; dead connections detected and auto-reconnected within 30s
117
+ - **Auto-reconnect** — exponential backoff (1s → 30s max), handles network drops gracefully
118
+ - **Session persistence** — refreshing the page restores your last active terminal session
99
119
 
100
120
  ---
101
121
 
@@ -175,8 +195,7 @@ GHOSTTERM_RELAY=wss://your-relay.example.com npx ghostterm
175
195
 
176
196
  ## Pricing
177
197
 
178
- - **Free**: 1 hour/day, all features included
179
- - **Pro**: $5/month or $12/quarter — unlimited access
198
+ GhostTerm is currently **free** during early access. Pro plans coming soon.
180
199
 
181
200
  ---
182
201
 
package/bin/ghostterm.js CHANGED
@@ -433,8 +433,106 @@ 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)
490
+ if (arg !== '--daemon') {
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 daemon
509
+ const { spawn } = require('child_process');
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));
518
+ console.log('');
519
+ console.log(` ✅ GhostTerm running in background (PID: ${child.pid})`);
520
+ console.log(' 📱 Open: ghostterm.pages.dev');
521
+ console.log(' 🛑 Stop: npx ghostterm stop');
522
+ console.log(' 📋 Logs: npx ghostterm logs');
523
+ console.log('');
524
+ process.exit(0);
525
+ }
526
+
527
+ // --daemon: write PID file and continue to main()
528
+ fs.writeFileSync(PID_FILE, String(process.pid));
529
+ return 'daemon';
530
+ }
531
+
436
532
  // ==================== Main ====================
437
533
  async function main() {
534
+ const mode = handleSubcommand();
535
+
438
536
  console.log('');
439
537
  console.log(' ╔═══════════════════════════════════╗');
440
538
  console.log(' ║ GhostTerm Companion ║');
@@ -449,6 +547,26 @@ async function main() {
449
547
  console.log(' Will use pairing code instead');
450
548
  }
451
549
 
550
+ // First login done in foreground — now restart as daemon
551
+ if (mode === 'foreground-first-login' && googleToken) {
552
+ 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('');
567
+ process.exit(0);
568
+ }
569
+
452
570
  connectToRelay();
453
571
  }
454
572
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostterm",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Mobile terminal for Claude Code — control your PC from your phone",
5
5
  "bin": {
6
6
  "ghostterm": "bin/ghostterm.js"