@seflless/ghosttown 1.2.3 → 1.3.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.
Files changed (2) hide show
  1. package/bin/ghosttown.js +153 -14
  2. package/package.json +3 -2
package/bin/ghosttown.js CHANGED
@@ -3,22 +3,24 @@
3
3
  /**
4
4
  * ghosttown CLI - Web-based terminal emulator
5
5
  *
6
- * Starts a local HTTP server with WebSocket PTY support.
6
+ * Starts a local HTTP server with WebSocket PTY support,
7
+ * or runs a command in a new tmux session.
7
8
  *
8
9
  * Usage:
9
- * ghosttown [options]
10
+ * ghosttown [options] [command]
10
11
  *
11
12
  * Options:
12
13
  * -p, --port <port> Port to listen on (default: 8080, or PORT env var)
13
14
  * -h, --help Show this help message
14
15
  *
15
16
  * Examples:
16
- * ghosttown
17
- * ghosttown -p 3000
18
- * ghosttown --port 3000
19
- * PORT=3000 ghosttown
17
+ * ghosttown Start the web terminal server
18
+ * ghosttown -p 3000 Start the server on port 3000
19
+ * ghosttown vim Run vim in a new tmux session
20
+ * ghosttown "npm run dev" Run npm in a new tmux session
20
21
  */
21
22
 
23
+ import { execSync, spawn } from 'child_process';
22
24
  import fs from 'fs';
23
25
  import http from 'http';
24
26
  import { homedir, networkInterfaces } from 'os';
@@ -35,6 +37,118 @@ import { asciiArt } from './ascii.js';
35
37
  const __filename = fileURLToPath(import.meta.url);
36
38
  const __dirname = path.dirname(__filename);
37
39
 
40
+ // ============================================================================
41
+ // Tmux Session Management
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Check if tmux is installed
46
+ */
47
+ function checkTmuxInstalled() {
48
+ try {
49
+ execSync('which tmux', { stdio: 'pipe' });
50
+ return true;
51
+ } catch (err) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Print tmux installation instructions and exit
58
+ */
59
+ function printTmuxInstallHelp() {
60
+ console.error('\x1b[31mError: tmux is not installed.\x1b[0m\n');
61
+ console.error('tmux is required to run commands in ghosttown sessions.\n');
62
+ console.error('To install tmux:\n');
63
+
64
+ if (process.platform === 'darwin') {
65
+ console.error(' \x1b[36mmacOS (Homebrew):\x1b[0m brew install tmux');
66
+ console.error(' \x1b[36mmacOS (MacPorts):\x1b[0m sudo port install tmux');
67
+ } else if (process.platform === 'linux') {
68
+ console.error(' \x1b[36mDebian/Ubuntu:\x1b[0m sudo apt install tmux');
69
+ console.error(' \x1b[36mFedora:\x1b[0m sudo dnf install tmux');
70
+ console.error(' \x1b[36mArch:\x1b[0m sudo pacman -S tmux');
71
+ } else {
72
+ console.error(' Please install tmux using your system package manager.');
73
+ }
74
+
75
+ console.error('');
76
+ process.exit(1);
77
+ }
78
+
79
+ /**
80
+ * Get the next available ghosttown session ID
81
+ * Scans existing tmux sessions named ghosttown-<N> and returns max(N) + 1
82
+ */
83
+ function getNextSessionId() {
84
+ try {
85
+ // List all tmux sessions
86
+ const output = execSync('tmux list-sessions -F "#{session_name}"', {
87
+ encoding: 'utf8',
88
+ stdio: ['pipe', 'pipe', 'pipe'],
89
+ });
90
+
91
+ // Find all ghosttown-<N> sessions and extract IDs
92
+ const sessionIds = output
93
+ .split('\n')
94
+ .filter((name) => name.startsWith('ghosttown-'))
95
+ .map((name) => {
96
+ const id = parseInt(name.replace('ghosttown-', ''), 10);
97
+ return isNaN(id) ? 0 : id;
98
+ });
99
+
100
+ // Return max + 1, or 1 if no sessions exist
101
+ return sessionIds.length > 0 ? Math.max(...sessionIds) + 1 : 1;
102
+ } catch (err) {
103
+ // tmux not running or no sessions - start with 1
104
+ return 1;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Create a new tmux session and run the command
110
+ */
111
+ function createTmuxSession(command) {
112
+ // Check if tmux is installed
113
+ if (!checkTmuxInstalled()) {
114
+ printTmuxInstallHelp();
115
+ }
116
+
117
+ const sessionId = getNextSessionId();
118
+ const sessionName = `ghosttown-${sessionId}`;
119
+
120
+ try {
121
+ // Create tmux session and attach directly (not detached)
122
+ // Use -x and -y to set initial size, avoiding "terminal too small" issues
123
+ // The command runs in the session, and we attach immediately
124
+ const attach = spawn(
125
+ 'tmux',
126
+ [
127
+ 'new-session',
128
+ '-s',
129
+ sessionName,
130
+ '-x',
131
+ '200',
132
+ '-y',
133
+ '50',
134
+ // Set status off (no HUD), run the command, then start a shell
135
+ // so the session stays open and interactive after command completes
136
+ 'tmux set-option status off; ' + command + '; exec $SHELL',
137
+ ],
138
+ {
139
+ stdio: 'inherit',
140
+ }
141
+ );
142
+
143
+ attach.on('exit', (code) => {
144
+ process.exit(code || 0);
145
+ });
146
+ } catch (err) {
147
+ console.error(`Error creating tmux session: ${err.message}`);
148
+ process.exit(1);
149
+ }
150
+ }
151
+
38
152
  // ============================================================================
39
153
  // Parse CLI arguments
40
154
  // ============================================================================
@@ -42,23 +156,28 @@ const __dirname = path.dirname(__filename);
42
156
  function parseArgs() {
43
157
  const args = process.argv.slice(2);
44
158
  let port = null;
159
+ let command = null;
45
160
 
46
161
  for (let i = 0; i < args.length; i++) {
47
162
  const arg = args[i];
48
163
 
49
164
  if (arg === '-h' || arg === '--help') {
50
165
  console.log(`
51
- Usage: ghosttown [options]
166
+ Usage: ghosttown [options] [command]
52
167
 
53
168
  Options:
54
169
  -p, --port <port> Port to listen on (default: 8080, or PORT env var)
55
170
  -h, --help Show this help message
56
171
 
172
+ Commands:
173
+ If a command is provided, ghosttown creates a new tmux session
174
+ named ghosttown-<id> (with status bar disabled) and runs the command.
175
+
57
176
  Examples:
58
- ghosttown
59
- ghosttown -p 3000
60
- ghosttown --port 3000
61
- PORT=3000 ghosttown
177
+ ghosttown Start the web terminal server
178
+ ghosttown -p 3000 Start the server on port 3000
179
+ ghosttown vim Run vim in a new tmux session
180
+ ghosttown "npm run dev" Run npm in a new tmux session
62
181
  `);
63
182
  process.exit(0);
64
183
  }
@@ -75,14 +194,33 @@ Examples:
75
194
  process.exit(1);
76
195
  }
77
196
  i++; // Skip the next argument since we consumed it
197
+ continue;
198
+ }
199
+
200
+ // Any non-flag argument is treated as the command
201
+ if (!arg.startsWith('-')) {
202
+ command = arg;
78
203
  }
79
204
  }
80
205
 
81
- return { port };
206
+ return { port, command };
82
207
  }
83
208
 
84
209
  const cliArgs = parseArgs();
85
- const HTTP_PORT = cliArgs.port || process.env.PORT || 8080;
210
+
211
+ // If a command is provided, create a tmux session instead of starting server
212
+ if (cliArgs.command) {
213
+ createTmuxSession(cliArgs.command);
214
+ // createTmuxSession spawns tmux attach and waits for it to exit
215
+ // The script will exit when tmux attach exits (via the exit handler)
216
+ // We must not continue to server code, so we stop here
217
+ } else {
218
+ // Server mode (no command provided)
219
+ startWebServer();
220
+ }
221
+
222
+ function startWebServer() {
223
+ const HTTP_PORT = cliArgs.port || process.env.PORT || 8080;
86
224
 
87
225
  // ============================================================================
88
226
  // Locate ghosttown assets
@@ -651,7 +789,7 @@ function printBanner(url) {
651
789
  const BEIGE = '\x1b[38;2;255;220;150m'; // Warm yellow/beige for values (RGB: 255,220,150)
652
790
  const DIM = '\x1b[2m'; // Dimmed text
653
791
 
654
- console.log('');
792
+ // console.log('');
655
793
 
656
794
  // Open: URL
657
795
  console.log(` ${CYAN}Open:${RESET} ${BEIGE}${url}${RESET}`);
@@ -708,3 +846,4 @@ httpServer.listen(HTTP_PORT, '0.0.0.0', async () => {
708
846
 
709
847
  printBanner(`http://localhost:${HTTP_PORT}`);
710
848
  });
849
+ } // end startWebServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seflless/ghosttown",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Web-based terminal emulator using Ghostty's VT100 parser via WebAssembly",
5
5
  "type": "module",
6
6
  "main": "./dist/ghostty-web.umd.cjs",
@@ -64,7 +64,8 @@
64
64
  "lint": "biome check .",
65
65
  "lint:fix": "biome check --write .",
66
66
  "prepublishOnly": "bun run build",
67
- "cli:publish": "node scripts/cli-publish.js"
67
+ "cli:publish": "node scripts/cli-publish.js",
68
+ "kill:8080": "kill -9 $(lsof -ti :8080)"
68
69
  },
69
70
  "dependencies": {
70
71
  "@lydell/node-pty": "^1.0.1",