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