@seflless/ghosttown 1.3.1 → 1.3.3
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/bin/assets/ghosts.png +0 -0
- package/bin/ghosttown.js +0 -0
- package/bin/ght.js +0 -0
- package/bin/gt.js +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +26 -1
- package/src/cli.js +360 -39
package/bin/assets/ghosts.png
CHANGED
|
Binary file
|
package/bin/ghosttown.js
CHANGED
|
File without changes
|
package/bin/ght.js
CHANGED
|
File without changes
|
package/bin/gt.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { execSync } from 'child_process';
|
|
14
|
+
import { readlinkSync } from 'fs';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Check if we're on a supported platform (macOS or Linux)
|
|
@@ -44,13 +45,37 @@ function isOurCommand(resolvedPath) {
|
|
|
44
45
|
|
|
45
46
|
// Check if the path contains indicators that it's from our package
|
|
46
47
|
const indicators = [
|
|
48
|
+
// Direct package installs
|
|
47
49
|
'node_modules/ghosttown',
|
|
48
50
|
'node_modules/@seflless/ghosttown',
|
|
51
|
+
// Local development (running from repo root)
|
|
49
52
|
'/ghosttown/bin/gt',
|
|
53
|
+
// Scoped package reference
|
|
54
|
+
'@seflless/ghosttown',
|
|
50
55
|
];
|
|
51
56
|
|
|
52
57
|
const normalizedPath = resolvedPath.toLowerCase();
|
|
53
|
-
|
|
58
|
+
|
|
59
|
+
// Direct indicator match
|
|
60
|
+
if (indicators.some((indicator) => normalizedPath.includes(indicator.toLowerCase()))) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// If the path is in node_modules/.bin, it's likely a symlink created by npm/bun
|
|
65
|
+
// for this package. Check if it's a symlink and read its target.
|
|
66
|
+
if (normalizedPath.includes('node_modules/.bin/gt')) {
|
|
67
|
+
try {
|
|
68
|
+
const target = readlinkSync(resolvedPath);
|
|
69
|
+
// The symlink target will be something like ../@seflless/ghosttown/bin/gt.js
|
|
70
|
+
if (target.includes('@seflless/ghosttown') || target.includes('ghosttown/bin/gt')) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Not a symlink or can't read - continue with other checks
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
/**
|
package/src/cli.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* ghosttown "npm run dev" Run npm in a new tmux session
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { execSync, spawn } from 'child_process';
|
|
21
|
+
import { execSync, spawn, spawnSync } from 'child_process';
|
|
22
22
|
import fs from 'fs';
|
|
23
23
|
import http from 'http';
|
|
24
24
|
import { homedir, networkInterfaces } from 'os';
|
|
@@ -55,23 +55,50 @@ function checkTmuxInstalled() {
|
|
|
55
55
|
* Print tmux installation instructions and exit
|
|
56
56
|
*/
|
|
57
57
|
function printTmuxInstallHelp() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
const RESET = '\x1b[0m';
|
|
59
|
+
const RED = '\x1b[31m';
|
|
60
|
+
const CYAN = '\x1b[36m';
|
|
61
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
62
|
+
const DIM = '\x1b[2m';
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(` ${RED}ghosttown needs tmux to be installed.${RESET}`);
|
|
66
|
+
console.log(` ${DIM}tmux is used to manage terminal sessions.${RESET}`);
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(` ${CYAN}To install:${RESET} ${BEIGE}brew install tmux${RESET}`);
|
|
69
|
+
console.log('');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if currently inside a tmux session
|
|
75
|
+
*/
|
|
76
|
+
function isInsideTmux() {
|
|
77
|
+
return !!process.env.TMUX;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the name of the current tmux session
|
|
82
|
+
* Returns null if not inside tmux
|
|
83
|
+
*/
|
|
84
|
+
function getCurrentTmuxSessionName() {
|
|
85
|
+
if (!isInsideTmux()) return null;
|
|
86
|
+
try {
|
|
87
|
+
return execSync('tmux display-message -p "#{session_name}"', {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
|
+
}).trim();
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return null;
|
|
71
93
|
}
|
|
94
|
+
}
|
|
72
95
|
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Check if currently inside a ghosttown session (named ghosttown-<N>)
|
|
98
|
+
*/
|
|
99
|
+
function isInsideGhosttownSession() {
|
|
100
|
+
const sessionName = getCurrentTmuxSessionName();
|
|
101
|
+
return sessionName && sessionName.startsWith('ghosttown-');
|
|
75
102
|
}
|
|
76
103
|
|
|
77
104
|
/**
|
|
@@ -112,6 +139,10 @@ function listSessions() {
|
|
|
112
139
|
printTmuxInstallHelp();
|
|
113
140
|
}
|
|
114
141
|
|
|
142
|
+
const RESET = '\x1b[0m';
|
|
143
|
+
const CYAN = '\x1b[36m';
|
|
144
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
145
|
+
|
|
115
146
|
try {
|
|
116
147
|
// Get detailed session information
|
|
117
148
|
const output = execSync(
|
|
@@ -136,43 +167,39 @@ function listSessions() {
|
|
|
136
167
|
});
|
|
137
168
|
|
|
138
169
|
if (sessions.length === 0) {
|
|
139
|
-
console.log('
|
|
140
|
-
console.log(
|
|
141
|
-
console.log('
|
|
142
|
-
console.log('\nExample:');
|
|
143
|
-
console.log(' ghosttown vim');
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
172
|
+
console.log('');
|
|
144
173
|
process.exit(0);
|
|
145
174
|
}
|
|
146
175
|
|
|
147
176
|
// Print header
|
|
148
177
|
console.log('\n\x1b[1mGhosttown Sessions\x1b[0m\n');
|
|
149
178
|
console.log(
|
|
150
|
-
|
|
179
|
+
`${CYAN}${'Name'.padEnd(15)} ${'Created'.padEnd(22)} ${'Status'.padEnd(10)} Windows${RESET}`
|
|
151
180
|
);
|
|
152
|
-
console.log('-'.repeat(65));
|
|
153
181
|
|
|
154
182
|
// Print sessions
|
|
155
183
|
for (const session of sessions) {
|
|
156
184
|
const createdStr = session.created.toLocaleString();
|
|
157
185
|
// Status has ANSI codes so we need to pad the visible text differently
|
|
158
186
|
const statusPadded = session.attached
|
|
159
|
-
? `\x1b[32m${'attached'.padEnd(10)}
|
|
160
|
-
: `\x1b[33m${'detached'.padEnd(10)}
|
|
187
|
+
? `\x1b[32m${'attached'.padEnd(10)}${RESET}`
|
|
188
|
+
: `\x1b[33m${'detached'.padEnd(10)}${RESET}`;
|
|
161
189
|
console.log(
|
|
162
190
|
`${session.name.padEnd(15)} ${createdStr.padEnd(22)} ${statusPadded} ${session.windows}`
|
|
163
191
|
);
|
|
164
192
|
}
|
|
165
193
|
|
|
166
194
|
console.log('');
|
|
167
|
-
|
|
195
|
+
const sessionWord = sessions.length === 1 ? 'session' : 'sessions';
|
|
196
|
+
console.log(`Total: ${sessions.length} ${sessionWord}`);
|
|
168
197
|
console.log('');
|
|
169
198
|
} catch (err) {
|
|
170
199
|
// tmux not running or no sessions
|
|
171
|
-
console.log('
|
|
172
|
-
console.log(
|
|
173
|
-
console.log('
|
|
174
|
-
console.log('\nExample:');
|
|
175
|
-
console.log(' ghosttown vim');
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
202
|
+
console.log('');
|
|
176
203
|
}
|
|
177
204
|
|
|
178
205
|
process.exit(0);
|
|
@@ -222,6 +249,258 @@ function createTmuxSession(command) {
|
|
|
222
249
|
}
|
|
223
250
|
}
|
|
224
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Print styled detach success message
|
|
254
|
+
*/
|
|
255
|
+
function printDetachMessage(sessionName) {
|
|
256
|
+
const RESET = '\x1b[0m';
|
|
257
|
+
const CYAN = '\x1b[36m';
|
|
258
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
259
|
+
const DIM = '\x1b[2m';
|
|
260
|
+
const BOLD_YELLOW = '\x1b[1;93m';
|
|
261
|
+
const labelWidth = Math.max('To reattach:'.length, 'To list all sessions:'.length);
|
|
262
|
+
const formatHint = (label, command) =>
|
|
263
|
+
` ${CYAN}${label.padStart(labelWidth)}${RESET} ${BEIGE}${command}${RESET}`;
|
|
264
|
+
|
|
265
|
+
return [
|
|
266
|
+
'',
|
|
267
|
+
` ${BOLD_YELLOW}You've detached from your ghosttown session.${RESET}`,
|
|
268
|
+
` ${DIM}It's now running in the background.${RESET}`,
|
|
269
|
+
'',
|
|
270
|
+
formatHint('To reattach:', `ghosttown attach ${sessionName}`),
|
|
271
|
+
'',
|
|
272
|
+
formatHint('To list all sessions:', 'ghosttown list'),
|
|
273
|
+
'',
|
|
274
|
+
'',
|
|
275
|
+
].join('\n');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Detach from the current ghosttown tmux session
|
|
280
|
+
*/
|
|
281
|
+
function detachFromTmux() {
|
|
282
|
+
const RESET = '\x1b[0m';
|
|
283
|
+
const RED = '\x1b[31m';
|
|
284
|
+
const DIM = '\x1b[2m';
|
|
285
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
286
|
+
|
|
287
|
+
// Check if we're inside tmux at all
|
|
288
|
+
if (!isInsideTmux()) {
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(` ${RED}Error:${RESET} Not inside a tmux session.`);
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
293
|
+
console.log('');
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check if we're inside a ghosttown session specifically
|
|
298
|
+
if (!isInsideGhosttownSession()) {
|
|
299
|
+
console.log('');
|
|
300
|
+
console.log(` ${RED}Error:${RESET} Not inside a ghosttown session.`);
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log(` ${DIM}You're in a tmux session, but not one created by ghosttown.${RESET}`);
|
|
303
|
+
console.log(
|
|
304
|
+
` ${DIM}To detach from this tmux session, use: tmux detach (or ctrl+b d)${RESET}`
|
|
305
|
+
);
|
|
306
|
+
console.log('');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Get the session name for the message
|
|
311
|
+
const sessionName = getCurrentTmuxSessionName();
|
|
312
|
+
const message = printDetachMessage(sessionName);
|
|
313
|
+
const messageForShell = message.replace(/'/g, "'\\''");
|
|
314
|
+
|
|
315
|
+
// Detach only the current client (not all attached clients).
|
|
316
|
+
// Use client_tty so multiple attached windows aren't all detached.
|
|
317
|
+
let result = null;
|
|
318
|
+
let clientTty = null;
|
|
319
|
+
try {
|
|
320
|
+
clientTty = execSync('tmux display-message -p "#{client_tty}"', {
|
|
321
|
+
encoding: 'utf8',
|
|
322
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
323
|
+
}).trim();
|
|
324
|
+
|
|
325
|
+
if (clientTty) {
|
|
326
|
+
result = spawnSync(
|
|
327
|
+
'tmux',
|
|
328
|
+
['detach-client', '-t', clientTty, '-E', `printf %s '${messageForShell}'`],
|
|
329
|
+
{
|
|
330
|
+
stdio: 'inherit',
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
} catch (err) {
|
|
335
|
+
// Fall back to generic detach below.
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!result) {
|
|
339
|
+
// Detach using shell execution to avoid nesting warnings
|
|
340
|
+
result = spawnSync(process.env.SHELL || '/bin/sh', ['-c', 'exec tmux detach'], {
|
|
341
|
+
stdio: 'inherit',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// If we couldn't target a client tty, fall back to stdout.
|
|
346
|
+
if (!clientTty) {
|
|
347
|
+
process.stdout.write(message);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
process.exit(result.status || 0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Attach to a ghosttown session
|
|
355
|
+
*/
|
|
356
|
+
function attachToSession(sessionName) {
|
|
357
|
+
// Check if tmux is installed
|
|
358
|
+
if (!checkTmuxInstalled()) {
|
|
359
|
+
printTmuxInstallHelp();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const RESET = '\x1b[0m';
|
|
363
|
+
const RED = '\x1b[31m';
|
|
364
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
365
|
+
|
|
366
|
+
// Add ghosttown- prefix if not present
|
|
367
|
+
if (!sessionName.startsWith('ghosttown-')) {
|
|
368
|
+
sessionName = `ghosttown-${sessionName}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Check if session exists
|
|
372
|
+
try {
|
|
373
|
+
const output = execSync('tmux list-sessions -F "#{session_name}"', {
|
|
374
|
+
encoding: 'utf8',
|
|
375
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const sessions = output.split('\n').filter((s) => s.trim());
|
|
379
|
+
if (!sessions.includes(sessionName)) {
|
|
380
|
+
console.log('');
|
|
381
|
+
console.log(` ${RED}Error:${RESET} Session '${sessionName}' not found.`);
|
|
382
|
+
console.log('');
|
|
383
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
384
|
+
console.log('');
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log(` ${RED}Error:${RESET} No tmux sessions found.`);
|
|
390
|
+
console.log('');
|
|
391
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
392
|
+
console.log('');
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Attach to the session using spawn with shell
|
|
397
|
+
// Use the same pattern as detachFromTmux which works
|
|
398
|
+
const result = spawnSync(
|
|
399
|
+
process.env.SHELL || '/bin/sh',
|
|
400
|
+
['-c', `tmux attach-session -t ${sessionName}`],
|
|
401
|
+
{
|
|
402
|
+
stdio: 'inherit',
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
process.exit(result.status || 0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Kill a ghosttown session
|
|
411
|
+
* If sessionName is null, kills the current session (if inside one)
|
|
412
|
+
*/
|
|
413
|
+
function killSession(sessionName) {
|
|
414
|
+
// Check if tmux is installed
|
|
415
|
+
if (!checkTmuxInstalled()) {
|
|
416
|
+
printTmuxInstallHelp();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const RESET = '\x1b[0m';
|
|
420
|
+
const RED = '\x1b[31m';
|
|
421
|
+
const CYAN = '\x1b[36m';
|
|
422
|
+
const BEIGE = '\x1b[38;2;255;220;150m';
|
|
423
|
+
const DIM = '\x1b[2m';
|
|
424
|
+
const BOLD_YELLOW = '\x1b[1;93m';
|
|
425
|
+
|
|
426
|
+
// If no session specified, try to kill current session
|
|
427
|
+
if (!sessionName) {
|
|
428
|
+
if (!isInsideTmux()) {
|
|
429
|
+
console.log('');
|
|
430
|
+
console.log(` ${RED}Error:${RESET} No session specified and not inside a tmux session.`);
|
|
431
|
+
console.log('');
|
|
432
|
+
console.log(` ${DIM}Usage:${RESET}`);
|
|
433
|
+
console.log(` ${DIM} ghosttown -k <session> Kill a specific session${RESET}`);
|
|
434
|
+
console.log(
|
|
435
|
+
` ${DIM} ghosttown -k Kill current session (when inside one)${RESET}`
|
|
436
|
+
);
|
|
437
|
+
console.log('');
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!isInsideGhosttownSession()) {
|
|
442
|
+
console.log('');
|
|
443
|
+
console.log(` ${RED}Error:${RESET} Not inside a ghosttown session.`);
|
|
444
|
+
console.log('');
|
|
445
|
+
console.log(` ${DIM}You're in a tmux session, but not one created by ghosttown.${RESET}`);
|
|
446
|
+
console.log(` ${DIM}To kill this tmux session, use: tmux kill-session${RESET}`);
|
|
447
|
+
console.log('');
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
sessionName = getCurrentTmuxSessionName();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Add ghosttown- prefix if not present
|
|
455
|
+
if (!sessionName.startsWith('ghosttown-')) {
|
|
456
|
+
sessionName = `ghosttown-${sessionName}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check if session exists
|
|
460
|
+
try {
|
|
461
|
+
const output = execSync('tmux list-sessions -F "#{session_name}"', {
|
|
462
|
+
encoding: 'utf8',
|
|
463
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const sessions = output.split('\n').filter((s) => s.trim());
|
|
467
|
+
if (!sessions.includes(sessionName)) {
|
|
468
|
+
console.log('');
|
|
469
|
+
console.log(` ${RED}Error:${RESET} Session '${sessionName}' not found.`);
|
|
470
|
+
console.log('');
|
|
471
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
472
|
+
console.log('');
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.log('');
|
|
477
|
+
console.log(` ${RED}Error:${RESET} No tmux sessions found.`);
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log(` ${BEIGE}No ghosttown sessions are currently running.${RESET}`);
|
|
480
|
+
console.log('');
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Kill the session
|
|
485
|
+
try {
|
|
486
|
+
execSync(`tmux kill-session -t ${sessionName}`, {
|
|
487
|
+
stdio: 'pipe',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
console.log('');
|
|
491
|
+
console.log(` ${BOLD_YELLOW}Session '${sessionName}' has been killed.${RESET}`);
|
|
492
|
+
console.log('');
|
|
493
|
+
console.log(` ${CYAN}To list remaining:${RESET} ${BEIGE}ghosttown list${RESET}`);
|
|
494
|
+
console.log('');
|
|
495
|
+
process.exit(0);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
console.log('');
|
|
498
|
+
console.log(` ${RED}Error:${RESET} Failed to kill session '${sessionName}'.`);
|
|
499
|
+
console.log('');
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
225
504
|
// ============================================================================
|
|
226
505
|
// Parse CLI arguments
|
|
227
506
|
// ============================================================================
|
|
@@ -230,6 +509,7 @@ function parseArgs(argv) {
|
|
|
230
509
|
const args = argv.slice(2);
|
|
231
510
|
let port = null;
|
|
232
511
|
let command = null;
|
|
512
|
+
let handled = false;
|
|
233
513
|
|
|
234
514
|
for (let i = 0; i < args.length; i++) {
|
|
235
515
|
const arg = args[i];
|
|
@@ -239,33 +519,71 @@ function parseArgs(argv) {
|
|
|
239
519
|
Usage: ghosttown [options] [command]
|
|
240
520
|
|
|
241
521
|
Options:
|
|
242
|
-
-p, --port <port>
|
|
243
|
-
-
|
|
522
|
+
-p, --port <port> Port to listen on (default: 8080, or PORT env var)
|
|
523
|
+
-k, --kill [session] Kill a session (current if inside one, or specify)
|
|
524
|
+
-h, --help Show this help message
|
|
244
525
|
|
|
245
526
|
Commands:
|
|
246
527
|
list List all ghosttown tmux sessions
|
|
528
|
+
attach <session> Attach to a ghosttown session
|
|
529
|
+
detach Detach from current ghosttown session
|
|
247
530
|
<command> Run command in a new tmux session (ghosttown-<id>)
|
|
248
531
|
|
|
249
532
|
Examples:
|
|
250
533
|
ghosttown Start the web terminal server
|
|
251
534
|
ghosttown -p 3000 Start the server on port 3000
|
|
252
535
|
ghosttown list List all ghosttown sessions
|
|
536
|
+
ghosttown attach ghosttown-1 Attach to session ghosttown-1
|
|
537
|
+
ghosttown detach Detach from current session
|
|
538
|
+
ghosttown -k Kill current session (when inside one)
|
|
539
|
+
ghosttown -k ghosttown-1 Kill a specific session
|
|
253
540
|
ghosttown vim Run vim in a new tmux session
|
|
254
541
|
ghosttown "npm run dev" Run npm in a new tmux session
|
|
255
542
|
|
|
256
543
|
Aliases:
|
|
257
544
|
This CLI can also be invoked as 'gt' or 'ght'.
|
|
258
545
|
`);
|
|
259
|
-
|
|
546
|
+
handled = true;
|
|
547
|
+
break;
|
|
260
548
|
}
|
|
261
549
|
|
|
262
550
|
// Handle list command
|
|
263
551
|
if (arg === 'list') {
|
|
552
|
+
handled = true;
|
|
264
553
|
listSessions();
|
|
265
554
|
// listSessions exits, so this won't be reached
|
|
266
555
|
}
|
|
267
556
|
|
|
268
|
-
|
|
557
|
+
// Handle detach command
|
|
558
|
+
else if (arg === 'detach') {
|
|
559
|
+
handled = true;
|
|
560
|
+
detachFromTmux();
|
|
561
|
+
// detachFromTmux exits, so this won't be reached
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Handle attach command
|
|
565
|
+
else if (arg === 'attach') {
|
|
566
|
+
const sessionArg = args[i + 1];
|
|
567
|
+
if (!sessionArg) {
|
|
568
|
+
console.error('Error: attach requires a session name');
|
|
569
|
+
console.error('Usage: ghosttown attach <session>');
|
|
570
|
+
handled = true;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
handled = true;
|
|
574
|
+
attachToSession(sessionArg);
|
|
575
|
+
// attachToSession exits, so this won't be reached
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Handle kill command
|
|
579
|
+
else if (arg === '-k' || arg === '--kill') {
|
|
580
|
+
const nextArg = args[i + 1];
|
|
581
|
+
// Session name is optional - if not provided or is another flag, pass null
|
|
582
|
+
const sessionArg = nextArg && !nextArg.startsWith('-') ? nextArg : null;
|
|
583
|
+
handled = true;
|
|
584
|
+
killSession(sessionArg);
|
|
585
|
+
// killSession exits, so this won't be reached
|
|
586
|
+
} else if (arg === '-p' || arg === '--port') {
|
|
269
587
|
const nextArg = args[i + 1];
|
|
270
588
|
if (!nextArg || nextArg.startsWith('-')) {
|
|
271
589
|
console.error(`Error: ${arg} requires a port number`);
|
|
@@ -277,18 +595,17 @@ Aliases:
|
|
|
277
595
|
process.exit(1);
|
|
278
596
|
}
|
|
279
597
|
i++; // Skip the next argument since we consumed it
|
|
280
|
-
continue;
|
|
281
598
|
}
|
|
282
599
|
|
|
283
600
|
// First non-flag argument starts the command
|
|
284
601
|
// Capture it and all remaining arguments as the command
|
|
285
|
-
if (!arg.startsWith('-')) {
|
|
602
|
+
else if (!arg.startsWith('-')) {
|
|
286
603
|
command = args.slice(i).join(' ');
|
|
287
604
|
break;
|
|
288
605
|
}
|
|
289
606
|
}
|
|
290
607
|
|
|
291
|
-
return { port, command };
|
|
608
|
+
return { port, command, handled };
|
|
292
609
|
}
|
|
293
610
|
|
|
294
611
|
// ============================================================================
|
|
@@ -891,7 +1208,7 @@ function startWebServer(cliArgs) {
|
|
|
891
1208
|
console.log(` ${CYAN}Home:${RESET} ${BEIGE}${homedir()}${RESET}`);
|
|
892
1209
|
|
|
893
1210
|
console.log('');
|
|
894
|
-
console.log(` ${DIM}Press
|
|
1211
|
+
console.log(` ${DIM}Press ctrl+c to stop.${RESET}\n`);
|
|
895
1212
|
}
|
|
896
1213
|
|
|
897
1214
|
process.on('SIGINT', () => {
|
|
@@ -931,6 +1248,10 @@ function startWebServer(cliArgs) {
|
|
|
931
1248
|
export function run(argv) {
|
|
932
1249
|
const cliArgs = parseArgs(argv);
|
|
933
1250
|
|
|
1251
|
+
if (cliArgs.handled) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
934
1255
|
// If a command is provided, create a tmux session instead of starting server
|
|
935
1256
|
if (cliArgs.command) {
|
|
936
1257
|
createTmuxSession(cliArgs.command);
|