@woopsy/mcpanel 1.0.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/dist/index.js ADDED
@@ -0,0 +1,776 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const readline = __importStar(require("readline"));
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ const figlet_1 = __importDefault(require("figlet"));
45
+ const configManager_1 = require("./config/configManager");
46
+ const processManager_1 = require("./services/processManager");
47
+ const serverManager_1 = require("./managers/serverManager");
48
+ const backupManager_1 = require("./managers/backupManager");
49
+ const playitManager_1 = require("./managers/playitManager");
50
+ const commandRouter_1 = require("./commands/commandRouter");
51
+ const colors = __importStar(require("./utils/colors"));
52
+ const helpers_1 = require("./utils/helpers");
53
+ const logger_1 = require("./utils/logger");
54
+ // Initialize managers
55
+ const configManager = new configManager_1.ConfigManager();
56
+ configManager.initialize();
57
+ const processManager = new processManager_1.ProcessManager();
58
+ const serverManager = new serverManager_1.ServerManager(configManager);
59
+ const backupManager = new backupManager_1.BackupManager(configManager);
60
+ const playitManager = new playitManager_1.PlayitManager(configManager);
61
+ const router = new commandRouter_1.CommandRouter(configManager, processManager, serverManager, backupManager, playitManager);
62
+ const HISTORY_PATH = path.join(configManager_1.APP_ROOT, 'logs', '.history');
63
+ let currentState = 'COMMAND';
64
+ const propertiesContext = {
65
+ properties: {},
66
+ keys: [],
67
+ selectedKey: '',
68
+ };
69
+ let consoleActiveServer = '';
70
+ let logViewServer = '';
71
+ // Readline interface
72
+ let rl;
73
+ /**
74
+ * Renders the figlet "MCPANEL" ASCII banner with a chalk gradient.
75
+ */
76
+ function renderBanner() {
77
+ const banner = figlet_1.default.textSync('MCPANEL', { font: 'Standard' });
78
+ const lines = banner.split('\n');
79
+ const tints = [chalk_1.default.cyanBright, chalk_1.default.cyan, chalk_1.default.greenBright, chalk_1.default.green, chalk_1.default.green];
80
+ console.log();
81
+ lines.forEach((line, i) => console.log((tints[i] || chalk_1.default.green)(line)));
82
+ console.log(chalk_1.default.greenBright.bold(' Minecraft Server Manager') + chalk_1.default.gray(' v1.0.0'));
83
+ }
84
+ /**
85
+ * Renders the neofetch / Arch-Linux-style info block for the synced server.
86
+ */
87
+ function renderInfo() {
88
+ const server = configManager.getServer();
89
+ const osType = (0, helpers_1.detectOS)();
90
+ const java = (0, helpers_1.checkJava)(configManager.getConfig().defaultJavaPath);
91
+ const tunnel = playitManager.getStatus().status;
92
+ const running = server ? !!processManager.getActiveServer(server.name) : false;
93
+ const label = (k) => chalk_1.default.cyanBright.bold((k + ':').padEnd(10));
94
+ const sep = chalk_1.default.gray('─'.repeat(50));
95
+ const rows = [];
96
+ if (server) {
97
+ rows.push(['path', server.path]);
98
+ rows.push(['type', `${server.software} ${server.version}`]);
99
+ rows.push(['ram', server.ram]);
100
+ rows.push(['status', running ? chalk_1.default.greenBright('Running') : chalk_1.default.gray('Offline')]);
101
+ }
102
+ rows.push(['java', java.installed ? java.version : chalk_1.default.red('not found')]);
103
+ rows.push(['os', osType]);
104
+ rows.push(['node', process.version]);
105
+ rows.push([
106
+ 'tunnel',
107
+ tunnel === 'Online' ? chalk_1.default.greenBright('Online') : tunnel === 'Connecting' ? chalk_1.default.yellow('Connecting') : chalk_1.default.gray('Offline'),
108
+ ]);
109
+ console.log();
110
+ console.log(sep);
111
+ for (const [k, v] of rows) {
112
+ console.log(` ${label(k)}${v}`);
113
+ }
114
+ console.log(sep);
115
+ // neofetch-style color palette strip for that terminal-fetch feel.
116
+ const palette = chalk_1.default.bgRed(' ') + chalk_1.default.bgYellow(' ') + chalk_1.default.bgGreen(' ') +
117
+ chalk_1.default.bgCyan(' ') + chalk_1.default.bgBlue(' ') + chalk_1.default.bgMagenta(' ') +
118
+ chalk_1.default.bgWhite(' ');
119
+ console.log(' ' + palette);
120
+ console.log();
121
+ }
122
+ /**
123
+ * Full header (banner + info). Used by /clear to redraw the top of the screen.
124
+ */
125
+ function renderHeader() {
126
+ renderBanner();
127
+ renderInfo();
128
+ }
129
+ /**
130
+ * Checks for playit agent binary and auto-downloads if missing.
131
+ */
132
+ async function ensurePlayitSetup() {
133
+ if (playitManager.isBinaryPresent()) {
134
+ console.log(colors.success('Playit detected'));
135
+ return;
136
+ }
137
+ console.log(colors.warning('Playit not found. Downloading playit agent binary...'));
138
+ return new Promise((resolve, reject) => {
139
+ playitManager.downloadBinary((pct) => {
140
+ const width = 30;
141
+ const filled = '='.repeat(Math.round(width * (pct / 100)));
142
+ const empty = '-'.repeat(width - filled.length);
143
+ process.stdout.write(`\rDownloading Playit Agent: [${filled}${empty}] ${pct}%`);
144
+ }).then(() => {
145
+ process.stdout.write('\n');
146
+ console.log(colors.success('Playit downloaded successfully.'));
147
+ resolve();
148
+ }).catch((err) => {
149
+ process.stdout.write('\n');
150
+ console.log(colors.failure(`Playit download failed: ${err.message}`));
151
+ reject(err);
152
+ });
153
+ });
154
+ }
155
+ // Master list of command templates — single source of truth for both
156
+ // tab-completion and "did you mean" suggestions.
157
+ const COMMAND_LIST = [
158
+ '/help', '/start', '/stop', '/restart', '/console', '/log', '/info', '/sync',
159
+ '/stats', '/folder', '/properties', '/java',
160
+ '/backup create', '/backup list', '/backup restore',
161
+ '/plugins list', '/plugins install', '/plugins remove',
162
+ '/tunnel java', '/tunnel bedrock', '/tunnel status', '/tunnel stop', '/tunnel reset',
163
+ '/config', '/clear', '/exit'
164
+ ];
165
+ // Subcommands offered once "<command> " has been typed.
166
+ const SUBCOMMANDS = {
167
+ '/tunnel': ['java', 'bedrock', 'status', 'stop', 'reset'],
168
+ '/backup': ['create', 'list', 'restore'],
169
+ '/plugins': ['list', 'install', 'remove'],
170
+ };
171
+ /** Returns top-level commands that share a prefix with the typed token. */
172
+ function suggestCommands(token) {
173
+ if (!token || !token.startsWith('/'))
174
+ return [];
175
+ const tops = Array.from(new Set(COMMAND_LIST.map(c => c.split(' ')[0])));
176
+ return tops.filter(c => c.startsWith(token) && c !== token);
177
+ }
178
+ /**
179
+ * Auto-completion logic
180
+ */
181
+ function completer(line) {
182
+ const lineTrimmed = line.trim();
183
+ if (currentState !== 'COMMAND') {
184
+ return [[], line];
185
+ }
186
+ const parts = lineTrimmed.split(/\s+/);
187
+ const cmd = parts[0];
188
+ const arg = parts.slice(1).join(' ');
189
+ // Completing the command word itself (e.g. "/cl" -> "/clear")
190
+ if (line.startsWith('/') && !line.includes(' ')) {
191
+ const hits = COMMAND_LIST.filter(c => c.startsWith(lineTrimmed));
192
+ return [hits.length ? hits : COMMAND_LIST, line];
193
+ }
194
+ // Subcommand completion (e.g. "/tunnel " -> java/bedrock/status/...)
195
+ if (SUBCOMMANDS[cmd] && parts.length <= 2) {
196
+ const subs = SUBCOMMANDS[cmd]
197
+ .filter(s => s.startsWith(arg.toLowerCase()))
198
+ .map(s => `${cmd} ${s}`);
199
+ if (subs.length)
200
+ return [subs, line];
201
+ }
202
+ return [[], line];
203
+ }
204
+ /**
205
+ * Persistent History Management
206
+ */
207
+ function loadHistory() {
208
+ if (fs.existsSync(HISTORY_PATH)) {
209
+ try {
210
+ const data = fs.readFileSync(HISTORY_PATH, 'utf-8');
211
+ const lines = data.split('\n')
212
+ .map(line => line.trim())
213
+ .filter(line => line.length > 0)
214
+ .reverse(); // readline history is newest-first
215
+ rl.history = lines;
216
+ }
217
+ catch {
218
+ // Fail silently
219
+ }
220
+ }
221
+ }
222
+ function saveHistoryLine(line) {
223
+ if (!line || line.trim().length === 0 || line.startsWith('/exit'))
224
+ return;
225
+ try {
226
+ fs.appendFileSync(HISTORY_PATH, `${line.trim()}\n`, 'utf-8');
227
+ }
228
+ catch {
229
+ // Fail silently
230
+ }
231
+ }
232
+ /**
233
+ * Builds and renders the smart status bar at the bottom
234
+ */
235
+ function getStatusBar() {
236
+ const server = configManager.getServer();
237
+ const running = server ? !!processManager.getActiveServer(server.name) : false;
238
+ const backupsCount = backupManager.listBackups().length;
239
+ const tunnelStatus = playitManager.getStatus().status;
240
+ const serverStr = !server
241
+ ? colors.gray('none')
242
+ : running ? colors.green('Running') : colors.red('Offline');
243
+ const tStatusStr = tunnelStatus === 'Online'
244
+ ? colors.green('Online')
245
+ : tunnelStatus === 'Connecting'
246
+ ? colors.yellow('Connecting')
247
+ : colors.red('Offline');
248
+ const name = server ? server.name : '—';
249
+ return colors.gray(`[Server: ${colors.bold(name)} ${serverStr} | Backups: ${colors.bold(backupsCount.toString())} | Tunnel: ${tStatusStr}]`);
250
+ }
251
+ /**
252
+ * Exits console log streaming mode
253
+ */
254
+ function exitConsoleMode() {
255
+ if (currentState !== 'CONSOLE')
256
+ return;
257
+ processManager.unregisterConsoleStream(consoleActiveServer);
258
+ consoleActiveServer = '';
259
+ currentState = 'COMMAND';
260
+ console.log(colors.info('\nReturned to MCPANEL shell.'));
261
+ promptUser();
262
+ }
263
+ /**
264
+ * Exits the in-place live-log view (fallback used when no terminal could open).
265
+ */
266
+ function exitLogView() {
267
+ if (currentState !== 'LOG_VIEW')
268
+ return;
269
+ processManager.unregisterConsoleStream(logViewServer);
270
+ logViewServer = '';
271
+ currentState = 'COMMAND';
272
+ console.log(colors.info('\nReturned to MCPANEL shell.'));
273
+ promptUser();
274
+ }
275
+ /**
276
+ * Prompt loop builder
277
+ */
278
+ function promptUser() {
279
+ if (currentState === 'COMMAND') {
280
+ const status = getStatusBar();
281
+ console.log(status);
282
+ rl.setPrompt(chalk_1.default.bold.cyan('mcpanel> '));
283
+ rl.prompt();
284
+ }
285
+ else if (currentState === 'WIZARD_SYNC_PATH') {
286
+ rl.setPrompt(chalk_1.default.bold.cyan('server path> '));
287
+ rl.prompt();
288
+ }
289
+ else if (currentState === 'WIZARD_TUNNEL_TYPE') {
290
+ rl.setPrompt(colors.bold('Tunnel Type (Java or Bedrock): '));
291
+ rl.prompt();
292
+ }
293
+ else if (currentState === 'PROPERTIES_SELECT') {
294
+ rl.setPrompt(colors.bold('Select property to edit (1-8): '));
295
+ rl.prompt();
296
+ }
297
+ else if (currentState === 'PROPERTIES_INPUT') {
298
+ rl.setPrompt(colors.bold(`Enter new value for ${propertiesContext.selectedKey}: `));
299
+ rl.prompt();
300
+ }
301
+ else if (currentState === 'CONSOLE' || currentState === 'LOG_VIEW') {
302
+ // Log/console streaming has no custom prompt.
303
+ rl.setPrompt('');
304
+ }
305
+ }
306
+ /**
307
+ * Renders the properties edit menu
308
+ */
309
+ function showPropertiesMenu() {
310
+ const server = configManager.getServer();
311
+ console.log(`\n${colors.bold(colors.cyan(`Editing Server Properties: ${server?.name ?? ''}`))}`);
312
+ console.log(colors.gray('──────────────────────────────────────────────'));
313
+ propertiesContext.keys.forEach((key, idx) => {
314
+ console.log(` ${idx + 1}) ${colors.bold(key.padEnd(20))}: ${propertiesContext.properties[key]}`);
315
+ });
316
+ console.log(` 7) ${colors.green('Save and Exit')}`);
317
+ console.log(` 8) ${colors.red('Cancel')}`);
318
+ console.log(colors.gray('──────────────────────────────────────────────'));
319
+ }
320
+ /**
321
+ * Starts the properties editor flow
322
+ */
323
+ function startPropertiesEditor() {
324
+ const server = configManager.getServer();
325
+ if (!server) {
326
+ console.log(colors.failure('No server connected. Use /sync <path>.'));
327
+ currentState = 'COMMAND';
328
+ promptUser();
329
+ return;
330
+ }
331
+ const propsPath = path.join(server.path, 'server.properties');
332
+ if (!fs.existsSync(propsPath)) {
333
+ console.log(colors.failure(`server.properties was not found in: ${server.path}`));
334
+ currentState = 'COMMAND';
335
+ promptUser();
336
+ return;
337
+ }
338
+ const loaded = serverManager.readPropertiesFile(propsPath);
339
+ const targetKeys = ['motd', 'difficulty', 'max-players', 'pvp', 'spawn-protection', 'online-mode'];
340
+ propertiesContext.properties = {};
341
+ propertiesContext.keys = targetKeys;
342
+ targetKeys.forEach(k => {
343
+ propertiesContext.properties[k] = loaded[k] !== undefined ? loaded[k] : 'default';
344
+ });
345
+ currentState = 'PROPERTIES_SELECT';
346
+ showPropertiesMenu();
347
+ promptUser();
348
+ }
349
+ /**
350
+ * Console log streamer mode (interactive — sends typed lines to the server).
351
+ */
352
+ function enterConsoleMode() {
353
+ const server = configManager.getServer();
354
+ if (!server) {
355
+ console.log(colors.failure('No server connected.'));
356
+ currentState = 'COMMAND';
357
+ promptUser();
358
+ return;
359
+ }
360
+ if (!processManager.getActiveServer(server.name)) {
361
+ console.log(colors.failure(`Server "${server.name}" is not running. Start it first using /start.`));
362
+ currentState = 'COMMAND';
363
+ promptUser();
364
+ return;
365
+ }
366
+ consoleActiveServer = server.name;
367
+ currentState = 'CONSOLE';
368
+ console.log(colors.bold(colors.magenta(`\n--- Entering Live Console: ${server.name} ---`)));
369
+ console.log(colors.gray('Type /exit or /back to return to MCPANEL shell.'));
370
+ console.log(colors.gray('Commands without "/" will be sent directly to the server.\n'));
371
+ const logPath = logger_1.logger.getServerLogPath(server.name);
372
+ if (fs.existsSync(logPath)) {
373
+ const logs = fs.readFileSync(logPath, 'utf-8').split('\n');
374
+ process.stdout.write(logs.slice(-20).join('\n'));
375
+ }
376
+ processManager.registerConsoleStream(server.name, (data) => {
377
+ process.stdout.write(data);
378
+ });
379
+ }
380
+ /**
381
+ * /log — opens live server logs in a NEW terminal window (tail -f). Falls back
382
+ * to a read-only in-place stream if no terminal emulator could be launched.
383
+ */
384
+ function handleLogCommand() {
385
+ const server = configManager.getServer();
386
+ if (!server) {
387
+ console.log(colors.failure('No server connected.'));
388
+ return;
389
+ }
390
+ const logPath = logger_1.logger.getServerLogPath(server.name);
391
+ // Ensure the file exists so `tail -f` has something to follow.
392
+ if (!fs.existsSync(logPath)) {
393
+ try {
394
+ fs.writeFileSync(logPath, '', 'utf-8');
395
+ }
396
+ catch { /* ignore */ }
397
+ }
398
+ const running = !!processManager.getActiveServer(server.name);
399
+ const opened = (0, helpers_1.openTerminalTail)(logPath, `MCPANEL Logs - ${server.name}`);
400
+ if (opened) {
401
+ console.log(colors.success('Live server logs opened in a new terminal window.'));
402
+ if (!running) {
403
+ console.log(colors.warning('Server is not running yet — log lines will appear once you /start it.'));
404
+ }
405
+ return;
406
+ }
407
+ // Fallback: stream the logs read-only inside this shell.
408
+ console.log(colors.warning('Could not open a separate terminal window — showing logs here instead.'));
409
+ logViewServer = server.name;
410
+ currentState = 'LOG_VIEW';
411
+ console.log(colors.bold(colors.magenta(`\n--- Live Logs: ${server.name} (type /back to return) ---`)));
412
+ if (fs.existsSync(logPath)) {
413
+ const logs = fs.readFileSync(logPath, 'utf-8').split('\n');
414
+ process.stdout.write(logs.slice(-30).join('\n') + '\n');
415
+ }
416
+ processManager.registerConsoleStream(server.name, (data) => {
417
+ process.stdout.write(data);
418
+ });
419
+ }
420
+ /**
421
+ * Command line loop orchestrator
422
+ */
423
+ async function handleLine(line) {
424
+ const trimmed = line.trim();
425
+ saveHistoryLine(line);
426
+ switch (currentState) {
427
+ case 'COMMAND':
428
+ await handleCommandState(trimmed);
429
+ break;
430
+ case 'WIZARD_SYNC_PATH': {
431
+ if (!trimmed) {
432
+ console.log(colors.failure('Please enter a folder path (or type /exit to quit).'));
433
+ promptUser();
434
+ break;
435
+ }
436
+ if (trimmed === '/exit') {
437
+ process.exit(0);
438
+ }
439
+ try {
440
+ const meta = serverManager.syncServer(trimmed);
441
+ console.log(colors.success(`Connected to "${meta.name}" (${meta.software} ${meta.version})`));
442
+ currentState = 'COMMAND';
443
+ await finishStartup();
444
+ }
445
+ catch (err) {
446
+ console.log(colors.failure(err.message));
447
+ console.log(colors.gray('Enter the full path to a valid Minecraft server folder.'));
448
+ promptUser();
449
+ }
450
+ break;
451
+ }
452
+ case 'WIZARD_TUNNEL_TYPE': {
453
+ const tunnelType = trimmed.toLowerCase();
454
+ if (tunnelType !== 'java' && tunnelType !== 'bedrock') {
455
+ console.log(colors.failure('Unsupported tunnel type. Please enter "java" or "bedrock".'));
456
+ promptUser();
457
+ break;
458
+ }
459
+ currentState = 'COMMAND';
460
+ const output = await router.executeTunnelCreate(tunnelType);
461
+ console.log(output);
462
+ promptUser();
463
+ break;
464
+ }
465
+ case 'PROPERTIES_SELECT':
466
+ if (trimmed === '8') {
467
+ currentState = 'COMMAND';
468
+ console.log(colors.info('Properties edits discarded.'));
469
+ promptUser();
470
+ }
471
+ else if (trimmed === '7') {
472
+ try {
473
+ serverManager.updateServerProperties(propertiesContext.properties);
474
+ console.log(colors.success('Properties saved successfully.'));
475
+ }
476
+ catch (err) {
477
+ console.log(colors.failure(`Failed to save properties: ${err.message}`));
478
+ }
479
+ currentState = 'COMMAND';
480
+ promptUser();
481
+ }
482
+ else {
483
+ const idx = parseInt(trimmed, 10) - 1;
484
+ if (isNaN(idx) || idx < 0 || idx >= propertiesContext.keys.length) {
485
+ console.log(colors.failure('Invalid selection. Select 1-8.'));
486
+ promptUser();
487
+ }
488
+ else {
489
+ propertiesContext.selectedKey = propertiesContext.keys[idx];
490
+ currentState = 'PROPERTIES_INPUT';
491
+ promptUser();
492
+ }
493
+ }
494
+ break;
495
+ case 'PROPERTIES_INPUT':
496
+ propertiesContext.properties[propertiesContext.selectedKey] = trimmed;
497
+ currentState = 'PROPERTIES_SELECT';
498
+ showPropertiesMenu();
499
+ promptUser();
500
+ break;
501
+ case 'CONSOLE':
502
+ if (trimmed === '/exit' || trimmed === '/back') {
503
+ exitConsoleMode();
504
+ }
505
+ else if (trimmed.startsWith('/send ')) {
506
+ const cmd = trimmed.substring(6).trim();
507
+ processManager.sendCommand(consoleActiveServer, cmd);
508
+ }
509
+ else {
510
+ processManager.sendCommand(consoleActiveServer, trimmed);
511
+ }
512
+ break;
513
+ case 'LOG_VIEW':
514
+ // Read-only: only /back or /exit leaves; everything else is ignored.
515
+ if (trimmed === '/exit' || trimmed === '/back') {
516
+ exitLogView();
517
+ }
518
+ break;
519
+ }
520
+ }
521
+ /**
522
+ * Handle execution of commands in STATE_COMMAND
523
+ */
524
+ async function handleCommandState(line) {
525
+ if (!line) {
526
+ promptUser();
527
+ return;
528
+ }
529
+ const parts = line.split(/\s+/);
530
+ const cmd = parts[0].toLowerCase();
531
+ const args = parts.slice(1);
532
+ if (!line.startsWith('/')) {
533
+ console.log(colors.failure(`Unknown command: "${line}". All commands must start with "/". Type /help for assistance.`));
534
+ promptUser();
535
+ return;
536
+ }
537
+ switch (cmd) {
538
+ case '/help':
539
+ console.log(router.getHelpText());
540
+ break;
541
+ case '/clear':
542
+ try {
543
+ fs.writeFileSync(HISTORY_PATH, '', 'utf-8');
544
+ if (rl) {
545
+ rl.history = [];
546
+ }
547
+ // \x1b[2J = clear screen, \x1b[3J = clear scrollback, \x1b[H = cursor home
548
+ process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
549
+ renderHeader();
550
+ console.log(colors.success('Command output and history cleared.'));
551
+ }
552
+ catch (err) {
553
+ console.log(colors.failure(`Failed to clear: ${err.message}`));
554
+ }
555
+ break;
556
+ case '/exit':
557
+ logger_1.logger.info('Exiting MCPANEL manager.');
558
+ playitManager.stopTunnel();
559
+ console.log(colors.cyan('\nStopping the server if running...'));
560
+ {
561
+ const active = Array.from(processManager.getActiveServers().keys());
562
+ for (const serverName of active) {
563
+ await processManager.stopServer(serverName);
564
+ }
565
+ }
566
+ console.log(colors.success('Goodbye!'));
567
+ process.exit(0);
568
+ break;
569
+ case '/sync':
570
+ if (args.length === 0) {
571
+ console.log(colors.failure('Syntax: /sync <path-to-server-folder>'));
572
+ }
573
+ else {
574
+ console.log(router.executeSync(args.join(' ')));
575
+ }
576
+ break;
577
+ case '/info':
578
+ case '/path':
579
+ console.log(router.executeInfo());
580
+ break;
581
+ case '/start':
582
+ console.log(colors.cyan('Starting server...'));
583
+ console.log(await router.executeStart());
584
+ break;
585
+ case '/stop':
586
+ console.log(colors.cyan('Stopping server...'));
587
+ console.log(await router.executeStop());
588
+ break;
589
+ case '/restart':
590
+ console.log(colors.cyan('Restarting server...'));
591
+ console.log(await router.executeRestart());
592
+ break;
593
+ case '/console':
594
+ enterConsoleMode();
595
+ break;
596
+ case '/log':
597
+ handleLogCommand();
598
+ break;
599
+ case '/stats':
600
+ console.log(await router.executeStats());
601
+ break;
602
+ case '/folder':
603
+ console.log(router.executeFolder());
604
+ break;
605
+ case '/properties':
606
+ startPropertiesEditor();
607
+ break;
608
+ case '/java':
609
+ console.log(router.executeJava(args.length ? args.join(' ') : undefined));
610
+ break;
611
+ case '/config':
612
+ console.log(router.executeConfig());
613
+ break;
614
+ case '/backup':
615
+ if (args.length === 0) {
616
+ console.log(colors.failure('Syntax: /backup [create|list|restore]'));
617
+ }
618
+ else if (args[0].toLowerCase() === 'create') {
619
+ console.log(router.executeBackupCreate());
620
+ }
621
+ else if (args[0].toLowerCase() === 'list') {
622
+ console.log(router.executeBackupList());
623
+ }
624
+ else if (args[0].toLowerCase() === 'restore') {
625
+ if (!args[1])
626
+ console.log(colors.failure('Syntax: /backup restore <backup-id>'));
627
+ else
628
+ console.log(router.executeBackupRestore(args[1]));
629
+ }
630
+ else {
631
+ console.log(colors.failure('Syntax: /backup [create|list|restore]'));
632
+ }
633
+ break;
634
+ case '/plugins':
635
+ if (args.length === 0) {
636
+ console.log(colors.failure('Syntax: /plugins [list|install|remove]'));
637
+ }
638
+ else if (args[0].toLowerCase() === 'list') {
639
+ console.log(router.executePluginsList());
640
+ }
641
+ else if (args[0].toLowerCase() === 'install') {
642
+ if (!args[1])
643
+ console.log(colors.failure('Syntax: /plugins install <plugin-url>'));
644
+ else
645
+ console.log(await router.executePluginsInstall(args[1]));
646
+ }
647
+ else if (args[0].toLowerCase() === 'remove') {
648
+ if (!args[1])
649
+ console.log(colors.failure('Syntax: /plugins remove <plugin-name>'));
650
+ else
651
+ console.log(router.executePluginsRemove(args[1]));
652
+ }
653
+ else {
654
+ console.log(colors.failure('Syntax: /plugins [list|install|remove]'));
655
+ }
656
+ break;
657
+ case '/tunnel': {
658
+ const sub = (args[0] || '').toLowerCase();
659
+ if (!sub) {
660
+ console.log(colors.failure('Syntax: /tunnel [java|bedrock|status|stop|reset]'));
661
+ }
662
+ else if (sub === 'java' || sub === 'bedrock') {
663
+ console.log(await router.executeTunnelCreate(sub));
664
+ }
665
+ else if (sub === 'create') {
666
+ const type = (args[1] || '').toLowerCase();
667
+ if (type === 'java' || type === 'bedrock') {
668
+ console.log(await router.executeTunnelCreate(type));
669
+ }
670
+ else {
671
+ currentState = 'WIZARD_TUNNEL_TYPE';
672
+ }
673
+ }
674
+ else if (sub === 'stop') {
675
+ console.log(router.executeTunnelStop());
676
+ }
677
+ else if (sub === 'status') {
678
+ console.log(router.executeTunnelStatus());
679
+ }
680
+ else if (sub === 'reset') {
681
+ console.log(await router.executeTunnelReset());
682
+ }
683
+ else {
684
+ console.log(colors.failure('Syntax: /tunnel [java|bedrock|status|stop|reset]'));
685
+ }
686
+ break;
687
+ }
688
+ default: {
689
+ const suggestions = suggestCommands(cmd);
690
+ if (suggestions.length) {
691
+ console.log(colors.failure(`Unknown command: "${cmd}".`) + ' ' + colors.gray(`Did you mean: ${suggestions.join(', ')} ?`));
692
+ }
693
+ else {
694
+ console.log(colors.failure(`Unknown command: "${cmd}". Type /help for available commands.`));
695
+ }
696
+ break;
697
+ }
698
+ }
699
+ if (currentState === 'COMMAND') {
700
+ promptUser();
701
+ }
702
+ }
703
+ /**
704
+ * Renders the server info screen, ensures playit is ready, and drops into the
705
+ * command prompt. Shared by the "already synced" and "just synced" paths.
706
+ */
707
+ async function finishStartup() {
708
+ renderInfo();
709
+ try {
710
+ await ensurePlayitSetup();
711
+ }
712
+ catch {
713
+ // Continue despite download failure (tunnel will fail until resolved).
714
+ }
715
+ console.log('\nType ' + chalk_1.default.cyan('/help') + ' for available commands\n');
716
+ currentState = 'COMMAND';
717
+ promptUser();
718
+ }
719
+ /**
720
+ * Main application setup
721
+ */
722
+ async function main() {
723
+ renderBanner();
724
+ rl = readline.createInterface({
725
+ input: process.stdin,
726
+ output: process.stdout,
727
+ completer: completer,
728
+ });
729
+ loadHistory();
730
+ rl.on('line', (line) => {
731
+ handleLine(line).catch((err) => {
732
+ console.error(colors.failure(`Uncaught error in command loop: ${err.message}`));
733
+ promptUser();
734
+ });
735
+ });
736
+ rl.on('SIGINT', () => {
737
+ if (currentState === 'CONSOLE') {
738
+ exitConsoleMode();
739
+ }
740
+ else if (currentState === 'LOG_VIEW') {
741
+ exitLogView();
742
+ }
743
+ else if (currentState === 'WIZARD_SYNC_PATH') {
744
+ console.log(colors.info('\nType /exit to quit, or enter a server folder path.'));
745
+ promptUser();
746
+ }
747
+ else if (currentState !== 'COMMAND') {
748
+ currentState = 'COMMAND';
749
+ console.log(colors.info('\nCancelled.'));
750
+ promptUser();
751
+ }
752
+ else {
753
+ console.log(colors.info('\nType /exit to exit MCPANEL.'));
754
+ promptUser();
755
+ }
756
+ });
757
+ // Single-server model: require a connected server folder before commands.
758
+ const server = configManager.getServer();
759
+ const valid = server && fs.existsSync(server.path);
760
+ if (!valid) {
761
+ if (server && !fs.existsSync(server.path)) {
762
+ console.log(colors.warning(`\nSaved server folder no longer exists: ${server.path}`));
763
+ }
764
+ console.log(colors.info('\nNo Minecraft server is connected yet.'));
765
+ console.log(colors.gray('Enter the full path to your server folder to connect it.\n'));
766
+ currentState = 'WIZARD_SYNC_PATH';
767
+ promptUser();
768
+ }
769
+ else {
770
+ await finishStartup();
771
+ }
772
+ }
773
+ main().catch((err) => {
774
+ console.error(colors.failure(`Fatal initialization error: ${err.stack || err}`));
775
+ process.exit(1);
776
+ });