@seflless/ghosttown 1.4.0 → 1.5.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/package.json +1 -1
  2. package/src/cli.js +94 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seflless/ghosttown",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",
package/src/cli.js CHANGED
@@ -21,6 +21,7 @@
21
21
  import { execSync, spawn, spawnSync } from 'child_process';
22
22
  import fs from 'fs';
23
23
  import http from 'http';
24
+ import { createRequire } from 'module';
24
25
  import { homedir, networkInterfaces } from 'os';
25
26
  import path from 'path';
26
27
  import { fileURLToPath } from 'url';
@@ -35,6 +36,11 @@ import { asciiArt } from '../bin/ascii.js';
35
36
  const __filename = fileURLToPath(import.meta.url);
36
37
  const __dirname = path.dirname(__filename);
37
38
 
39
+ // Get version from package.json
40
+ const require = createRequire(import.meta.url);
41
+ const packageJson = require('../package.json');
42
+ const VERSION = packageJson.version;
43
+
38
44
  // ============================================================================
39
45
  // Tmux Session Management
40
46
  // ============================================================================
@@ -94,16 +100,16 @@ function getCurrentTmuxSessionName() {
94
100
  }
95
101
 
96
102
  /**
97
- * Check if currently inside a ghosttown session (named ghosttown-<N>)
103
+ * Check if currently inside a ghosttown session (named gt-<N>)
98
104
  */
99
105
  function isInsideGhosttownSession() {
100
106
  const sessionName = getCurrentTmuxSessionName();
101
- return sessionName && sessionName.startsWith('ghosttown-');
107
+ return sessionName && sessionName.startsWith('gt-');
102
108
  }
103
109
 
104
110
  /**
105
111
  * Get the next available ghosttown session ID
106
- * Scans existing tmux sessions named ghosttown-<N> and returns max(N) + 1
112
+ * Scans existing tmux sessions named gt-<N> and returns max(N) + 1
107
113
  */
108
114
  function getNextSessionId() {
109
115
  try {
@@ -113,12 +119,12 @@ function getNextSessionId() {
113
119
  stdio: ['pipe', 'pipe', 'pipe'],
114
120
  });
115
121
 
116
- // Find all ghosttown-<N> sessions and extract IDs
122
+ // Find all gt-<N> sessions and extract IDs
117
123
  const sessionIds = output
118
124
  .split('\n')
119
- .filter((name) => name.startsWith('ghosttown-'))
125
+ .filter((name) => name.startsWith('gt-'))
120
126
  .map((name) => {
121
- const id = Number.parseInt(name.replace('ghosttown-', ''), 10);
127
+ const id = Number.parseInt(name.replace('gt-', ''), 10);
122
128
  return Number.isNaN(id) ? 0 : id;
123
129
  });
124
130
 
@@ -144,9 +150,9 @@ function listSessions() {
144
150
  const BEIGE = '\x1b[38;2;255;220;150m';
145
151
 
146
152
  try {
147
- // Get detailed session information
153
+ // Get detailed session information including last activity time
148
154
  const output = execSync(
149
- 'tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"',
155
+ 'tmux list-sessions -F "#{session_name}|#{session_activity}|#{session_attached}|#{session_windows}"',
150
156
  {
151
157
  encoding: 'utf8',
152
158
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -155,12 +161,15 @@ function listSessions() {
155
161
 
156
162
  const sessions = output
157
163
  .split('\n')
158
- .filter((line) => line.startsWith('ghosttown-'))
164
+ .filter((line) => line.startsWith('gt-'))
159
165
  .map((line) => {
160
- const [name, created, attached, windows] = line.split('|');
166
+ const [name, activity, attached, windows] = line.split('|');
167
+ // Extract just the number from gt-<N>
168
+ const id = name.replace('gt-', '');
161
169
  return {
170
+ id,
162
171
  name,
163
- created: new Date(Number.parseInt(created, 10) * 1000),
172
+ activity: new Date(Number.parseInt(activity, 10) * 1000),
164
173
  attached: attached === '1',
165
174
  windows: Number.parseInt(windows, 10),
166
175
  };
@@ -173,21 +182,24 @@ function listSessions() {
173
182
  process.exit(0);
174
183
  }
175
184
 
185
+ // Sort by last activity (most recent first)
186
+ sessions.sort((a, b) => b.activity.getTime() - a.activity.getTime());
187
+
176
188
  // Print header
177
189
  console.log('\n\x1b[1mGhosttown Sessions\x1b[0m\n');
178
190
  console.log(
179
- `${CYAN}${'Name'.padEnd(15)} ${'Created'.padEnd(22)} ${'Status'.padEnd(10)} Windows${RESET}`
191
+ `${CYAN}${'ID'.padEnd(6)} ${'Last Activity'.padEnd(22)} ${'Status'.padEnd(10)} Windows${RESET}`
180
192
  );
181
193
 
182
194
  // Print sessions
183
195
  for (const session of sessions) {
184
- const createdStr = session.created.toLocaleString();
196
+ const activityStr = session.activity.toLocaleString();
185
197
  // Status has ANSI codes so we need to pad the visible text differently
186
198
  const statusPadded = session.attached
187
199
  ? `\x1b[32m${'attached'.padEnd(10)}${RESET}`
188
200
  : `\x1b[33m${'detached'.padEnd(10)}${RESET}`;
189
201
  console.log(
190
- `${session.name.padEnd(15)} ${createdStr.padEnd(22)} ${statusPadded} ${session.windows}`
202
+ `${session.id.padEnd(6)} ${activityStr.padEnd(22)} ${statusPadded} ${session.windows}`
191
203
  );
192
204
  }
193
205
 
@@ -215,7 +227,7 @@ function createTmuxSession(command) {
215
227
  }
216
228
 
217
229
  const sessionId = getNextSessionId();
218
- const sessionName = `ghosttown-${sessionId}`;
230
+ const sessionName = `gt-${sessionId}`;
219
231
 
220
232
  try {
221
233
  // Create tmux session and attach directly (not detached)
@@ -262,14 +274,17 @@ function printDetachMessage(sessionName) {
262
274
  const formatHint = (label, command) =>
263
275
  ` ${CYAN}${label.padStart(labelWidth)}${RESET} ${BEIGE}${command}${RESET}`;
264
276
 
277
+ // Extract just the ID from gt-<N>
278
+ const sessionId = sessionName.replace('gt-', '');
279
+
265
280
  return [
266
281
  '',
267
282
  ` ${BOLD_YELLOW}You've detached from your ghosttown session.${RESET}`,
268
283
  ` ${DIM}It's now running in the background.${RESET}`,
269
284
  '',
270
- formatHint('To reattach:', `ghosttown attach ${sessionName}`),
285
+ formatHint('To reattach:', `gt attach ${sessionId}`),
271
286
  '',
272
- formatHint('To list all sessions:', 'ghosttown list'),
287
+ formatHint('To list all sessions:', 'gt list'),
273
288
  '',
274
289
  '',
275
290
  ].join('\n');
@@ -363,9 +378,9 @@ function attachToSession(sessionName) {
363
378
  const RED = '\x1b[31m';
364
379
  const BEIGE = '\x1b[38;2;255;220;150m';
365
380
 
366
- // Add ghosttown- prefix if not present
367
- if (!sessionName.startsWith('ghosttown-')) {
368
- sessionName = `ghosttown-${sessionName}`;
381
+ // Add gt- prefix if not present
382
+ if (!sessionName.startsWith('gt-')) {
383
+ sessionName = `gt-${sessionName}`;
369
384
  }
370
385
 
371
386
  // Check if session exists
@@ -406,29 +421,58 @@ function attachToSession(sessionName) {
406
421
  process.exit(result.status || 0);
407
422
  }
408
423
 
424
+ /**
425
+ * Get the currently installed version of ghosttown
426
+ */
427
+ function getInstalledVersion() {
428
+ try {
429
+ const output = execSync('npm list -g @seflless/ghosttown --json 2>/dev/null', {
430
+ encoding: 'utf8',
431
+ stdio: ['pipe', 'pipe', 'pipe'],
432
+ });
433
+ const data = JSON.parse(output);
434
+ return data.dependencies?.['@seflless/ghosttown']?.version || null;
435
+ } catch (err) {
436
+ return null;
437
+ }
438
+ }
439
+
409
440
  /**
410
441
  * Update ghosttown to the latest version
411
442
  */
412
443
  function updateGhosttown() {
413
444
  const RESET = '\x1b[0m';
414
- const CYAN = '\x1b[36m';
415
445
  const BEIGE = '\x1b[38;2;255;220;150m';
416
446
  const BOLD_YELLOW = '\x1b[1;93m';
417
447
  const DIM = '\x1b[2m';
418
448
 
449
+ // Get current version before updating
450
+ const oldVersion = getInstalledVersion();
451
+
419
452
  console.log('');
420
- console.log(` ${BOLD_YELLOW}Updating ghosttown...${RESET}`);
453
+ console.log(` ${BOLD_YELLOW}Checking for ghosttown updates...${RESET}`);
421
454
  console.log('');
422
455
 
423
456
  try {
457
+ // Run npm install silently (suppress output)
424
458
  execSync('npm install -g @seflless/ghosttown', {
425
- stdio: 'inherit',
459
+ stdio: ['pipe', 'pipe', 'pipe'],
426
460
  });
427
461
 
428
- console.log('');
429
- console.log(` ${BOLD_YELLOW}ghosttown has been updated!${RESET}`);
430
- console.log('');
431
- console.log(` ${CYAN}To check your version:${RESET} ${BEIGE}ghosttown --version${RESET}`);
462
+ // Get new version after updating
463
+ const newVersion = getInstalledVersion();
464
+
465
+ if (oldVersion && newVersion && oldVersion === newVersion) {
466
+ console.log(` ${DIM}Already on the latest version: ${BEIGE}${newVersion}${RESET}`);
467
+ } else {
468
+ console.log(` ${BOLD_YELLOW}ghosttown has been updated!${RESET}`);
469
+ console.log('');
470
+ if (oldVersion && newVersion) {
471
+ console.log(` ${DIM}${oldVersion}${RESET} -> ${BEIGE}${newVersion}${RESET}`);
472
+ } else if (newVersion) {
473
+ console.log(` ${DIM}Version: ${BEIGE}${newVersion}${RESET}`);
474
+ }
475
+ }
432
476
  console.log('');
433
477
  } catch (err) {
434
478
  console.log('');
@@ -486,9 +530,9 @@ function killSession(sessionName) {
486
530
  sessionName = getCurrentTmuxSessionName();
487
531
  }
488
532
 
489
- // Add ghosttown- prefix if not present
490
- if (!sessionName.startsWith('ghosttown-')) {
491
- sessionName = `ghosttown-${sessionName}`;
533
+ // Add gt- prefix if not present
534
+ if (!sessionName.startsWith('gt-')) {
535
+ sessionName = `gt-${sessionName}`;
492
536
  }
493
537
 
494
538
  // Check if session exists
@@ -522,10 +566,13 @@ function killSession(sessionName) {
522
566
  stdio: 'pipe',
523
567
  });
524
568
 
569
+ // Extract just the ID from gt-<N>
570
+ const sessionId = sessionName.replace('gt-', '');
571
+
525
572
  console.log('');
526
- console.log(` ${BOLD_YELLOW}Session '${sessionName}' has been killed.${RESET}`);
573
+ console.log(` ${BOLD_YELLOW}Session ${sessionId} has been killed.${RESET}`);
527
574
  console.log('');
528
- console.log(` ${CYAN}To list remaining:${RESET} ${BEIGE}ghosttown list${RESET}`);
575
+ console.log(` ${CYAN}To list remaining:${RESET} ${BEIGE}gt list${RESET}`);
529
576
  console.log('');
530
577
  process.exit(0);
531
578
  } catch (err) {
@@ -555,25 +602,26 @@ Usage: ghosttown [options] [command]
555
602
 
556
603
  Options:
557
604
  -p, --port <port> Port to listen on (default: 8080, or PORT env var)
558
- -k, --kill [session] Kill a session (current if inside one, or specify)
605
+ -k, --kill [session] Kill a session (current if inside one, or specify by ID)
606
+ -v, --version Show version number
559
607
  -h, --help Show this help message
560
608
 
561
609
  Commands:
562
610
  list List all ghosttown tmux sessions
563
- attach <session> Attach to a ghosttown session
611
+ attach <id> Attach to a ghosttown session by ID (e.g., 'gt attach 1')
564
612
  detach Detach from current ghosttown session
565
613
  update Update ghosttown to the latest version
566
- <command> Run command in a new tmux session (ghosttown-<id>)
614
+ <command> Run command in a new tmux session (gt-<id>)
567
615
 
568
616
  Examples:
569
617
  ghosttown Start the web terminal server
570
618
  ghosttown -p 3000 Start the server on port 3000
571
619
  ghosttown list List all ghosttown sessions
572
- ghosttown attach ghosttown-1 Attach to session ghosttown-1
620
+ ghosttown attach 1 Attach to session gt-1
573
621
  ghosttown detach Detach from current session
574
622
  ghosttown update Update to the latest version
575
623
  ghosttown -k Kill current session (when inside one)
576
- ghosttown -k ghosttown-1 Kill a specific session
624
+ ghosttown -k 1 Kill session gt-1
577
625
  ghosttown vim Run vim in a new tmux session
578
626
  ghosttown "npm run dev" Run npm in a new tmux session
579
627
 
@@ -584,6 +632,13 @@ Aliases:
584
632
  break;
585
633
  }
586
634
 
635
+ // Handle version flag
636
+ if (arg === '-v' || arg === '--version') {
637
+ console.log(VERSION);
638
+ handled = true;
639
+ break;
640
+ }
641
+
587
642
  // Handle list command
588
643
  if (arg === 'list') {
589
644
  handled = true;
@@ -609,8 +664,8 @@ Aliases:
609
664
  else if (arg === 'attach') {
610
665
  const sessionArg = args[i + 1];
611
666
  if (!sessionArg) {
612
- console.error('Error: attach requires a session name');
613
- console.error('Usage: ghosttown attach <session>');
667
+ console.error('Error: attach requires a session ID');
668
+ console.error('Usage: gt attach <id>');
614
669
  handled = true;
615
670
  break;
616
671
  }