@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.
- package/package.json +1 -1
- package/src/cli.js +94 -39
package/package.json
CHANGED
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
|
|
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('
|
|
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
|
|
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
|
|
122
|
+
// Find all gt-<N> sessions and extract IDs
|
|
117
123
|
const sessionIds = output
|
|
118
124
|
.split('\n')
|
|
119
|
-
.filter((name) => name.startsWith('
|
|
125
|
+
.filter((name) => name.startsWith('gt-'))
|
|
120
126
|
.map((name) => {
|
|
121
|
-
const id = Number.parseInt(name.replace('
|
|
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}|#{
|
|
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('
|
|
164
|
+
.filter((line) => line.startsWith('gt-'))
|
|
159
165
|
.map((line) => {
|
|
160
|
-
const [name,
|
|
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
|
-
|
|
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}${'
|
|
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
|
|
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.
|
|
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 = `
|
|
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:', `
|
|
285
|
+
formatHint('To reattach:', `gt attach ${sessionId}`),
|
|
271
286
|
'',
|
|
272
|
-
formatHint('To list all sessions:', '
|
|
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
|
|
367
|
-
if (!sessionName.startsWith('
|
|
368
|
-
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}
|
|
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: '
|
|
459
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
426
460
|
});
|
|
427
461
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
490
|
-
if (!sessionName.startsWith('
|
|
491
|
-
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
|
|
573
|
+
console.log(` ${BOLD_YELLOW}Session ${sessionId} has been killed.${RESET}`);
|
|
527
574
|
console.log('');
|
|
528
|
-
console.log(` ${CYAN}To list remaining:${RESET} ${BEIGE}
|
|
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 <
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
613
|
-
console.error('Usage:
|
|
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
|
}
|