@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.
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.CommandRouter = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const colors = __importStar(require("../utils/colors"));
43
+ const helpers_1 = require("../utils/helpers");
44
+ const downloadService_1 = require("../services/downloadService");
45
+ const pidusage_1 = __importDefault(require("pidusage"));
46
+ class CommandRouter {
47
+ configManager;
48
+ processManager;
49
+ serverManager;
50
+ backupManager;
51
+ playitManager;
52
+ constructor(configManager, processManager, serverManager, backupManager, playitManager) {
53
+ this.configManager = configManager;
54
+ this.processManager = processManager;
55
+ this.serverManager = serverManager;
56
+ this.backupManager = backupManager;
57
+ this.playitManager = playitManager;
58
+ }
59
+ /**
60
+ * Returns grouped help command menu.
61
+ */
62
+ getHelpText() {
63
+ return [
64
+ colors.bold(colors.cyan('\nMCPANEL Help Menu')),
65
+ colors.gray('──────────────────────────────────────────────'),
66
+ colors.bold(colors.green('Server Commands')),
67
+ ' /start - Start the Minecraft server',
68
+ ' /stop - Stop the server gracefully',
69
+ ' /restart - Restart the server',
70
+ ' /console - Enter the interactive server console',
71
+ ' /log - Open live server logs in a new terminal window',
72
+ ' /info - Show server path, type, version and status',
73
+ ' /sync <path> - Connect a different server folder',
74
+ ' /properties - Edit server.properties interactively',
75
+ '',
76
+ colors.bold(colors.green('Tunnel Commands (Playit.gg)')),
77
+ ' /tunnel java - Auto-create & start a Java tunnel, returns address',
78
+ ' /tunnel bedrock - Auto-create & start a Bedrock tunnel, returns address',
79
+ ' /tunnel status - Check tunnel status, address and latency',
80
+ ' /tunnel stop - Stop the playit tunnel agent',
81
+ ' /tunnel reset - Clear saved agent secret (re-claim on next tunnel)',
82
+ '',
83
+ colors.bold(colors.green('Backup Commands')),
84
+ ' /backup create - Create a backup ZIP of the server',
85
+ ' /backup list - List all available backups',
86
+ ' /backup restore <id> - Restore the server from a backup ID',
87
+ '',
88
+ colors.bold(colors.green('Plugin Commands')),
89
+ ' /plugins list - List installed plugins',
90
+ ' /plugins install <url> - Download and install a plugin JAR',
91
+ ' /plugins remove <name> - Remove an installed plugin',
92
+ '',
93
+ colors.bold(colors.green('System Commands')),
94
+ ' /stats - System stats + CPU/RAM/disk of the server',
95
+ ' /java [path] - Show/list Java runtimes, or set the one used to launch',
96
+ ' /folder - Open the server folder in the file explorer',
97
+ ' /clear - Clear the screen, scrollback and command history',
98
+ ' /config - View active application config.json',
99
+ ' /exit - Close MCPANEL server manager',
100
+ colors.gray('──────────────────────────────────────────────\n')
101
+ ].join('\n');
102
+ }
103
+ /**
104
+ * Executes /sync <path> — connects/validates a server folder.
105
+ */
106
+ executeSync(dir) {
107
+ try {
108
+ const meta = this.serverManager.syncServer(dir);
109
+ return [
110
+ colors.success('Minecraft server connected'),
111
+ `Name: ${colors.bold(meta.name)}`,
112
+ `Path: ${meta.path}`,
113
+ `Type: ${meta.software} ${meta.version}`,
114
+ colors.success('Server is ready.')
115
+ ].join('\n');
116
+ }
117
+ catch (err) {
118
+ return colors.failure(err.message);
119
+ }
120
+ }
121
+ /**
122
+ * Executes /info (and /path) — details of the managed server.
123
+ */
124
+ executeInfo() {
125
+ const server = this.configManager.getServer();
126
+ if (!server) {
127
+ return colors.failure('No server connected. Use /sync <path> to connect one.');
128
+ }
129
+ const activeInfo = this.processManager.getActiveServer(server.name);
130
+ const statusStr = activeInfo
131
+ ? (activeInfo.status === 'Running' ? colors.green('Running') : colors.yellow('Starting'))
132
+ : colors.red('Offline');
133
+ return [
134
+ '\n' + colors.bold(colors.cyan('Server Details')),
135
+ `Name: ${colors.bold(server.name)}`,
136
+ `Path: ${server.path}`,
137
+ `Type: ${server.software} ${server.version}`,
138
+ `RAM: ${server.ram}`,
139
+ `Status: ${statusStr}\n`
140
+ ].join('\n');
141
+ }
142
+ /**
143
+ * Executes /start command.
144
+ */
145
+ async executeStart() {
146
+ const server = this.configManager.getServer();
147
+ if (!server) {
148
+ return colors.failure('No server connected. Use /sync <path> to connect one.');
149
+ }
150
+ if (this.processManager.getActiveServer(server.name)) {
151
+ return colors.warning(`Server "${server.name}" is already running.`);
152
+ }
153
+ if (!fs.existsSync(server.path)) {
154
+ return colors.failure(`Server folder no longer exists: ${server.path}`);
155
+ }
156
+ const jarPath = path.join(server.path, 'server.jar');
157
+ let resolvedJar = jarPath;
158
+ if (!fs.existsSync(jarPath)) {
159
+ const jarFiles = fs.readdirSync(server.path).filter(f => f.endsWith('.jar'));
160
+ if (jarFiles.length === 0) {
161
+ return colors.failure(`Missing server jar in folder: ${server.path}`);
162
+ }
163
+ resolvedJar = path.join(server.path, jarFiles[0]);
164
+ }
165
+ try {
166
+ await this.processManager.startServer(server.name, server.path, resolvedJar, server.ram, this.configManager.getConfig().defaultJavaPath);
167
+ return colors.success(`Server "${server.name}" started. Use /log to watch live logs or /console to enter the console.`);
168
+ }
169
+ catch (err) {
170
+ return colors.failure(`Failed to start server: ${err.message}`);
171
+ }
172
+ }
173
+ /**
174
+ * Executes /stop command.
175
+ */
176
+ async executeStop() {
177
+ const server = this.configManager.getServer();
178
+ if (!server) {
179
+ return colors.failure('No server connected.');
180
+ }
181
+ const success = await this.processManager.stopServer(server.name);
182
+ return success
183
+ ? colors.success(`Server "${server.name}" stopped.`)
184
+ : colors.warning(`Server "${server.name}" is not currently running.`);
185
+ }
186
+ /**
187
+ * Executes /restart command.
188
+ */
189
+ async executeRestart() {
190
+ const server = this.configManager.getServer();
191
+ if (!server) {
192
+ return colors.failure('No server connected.');
193
+ }
194
+ if (this.processManager.getActiveServer(server.name)) {
195
+ await this.executeStop();
196
+ }
197
+ return this.executeStart();
198
+ }
199
+ /**
200
+ * Executes /stats — system stats plus the managed server's resource usage.
201
+ */
202
+ async executeStats() {
203
+ const stats = (0, helpers_1.getSystemStats)();
204
+ const server = this.configManager.getServer();
205
+ const info = server ? this.processManager.getActiveServer(server.name) : undefined;
206
+ const tunnelStatus = this.playitManager.getStatus();
207
+ const out = [
208
+ '\n' + colors.bold(colors.cyan('System Performance Statistics')),
209
+ `CPU Usage: ${colors.bold(`${stats.cpuUsage}%`)}`,
210
+ `RAM Usage: ${colors.bold(`${stats.usedMemGB} GB / ${stats.totalMemGB} GB`)} (${stats.memUsagePct}%)`,
211
+ `System Uptime: ${Math.floor(stats.uptimeSeconds / 3600)}h ${Math.floor((stats.uptimeSeconds % 3600) / 60)}m`,
212
+ `Playit Tunnel: ${tunnelStatus.status === 'Online' ? colors.green('Online') : colors.red('Offline')}`,
213
+ ];
214
+ if (server) {
215
+ const diskMB = ((0, helpers_1.getDirSize)(server.path) / (1024 * 1024)).toFixed(1);
216
+ out.push('');
217
+ out.push(colors.bold(colors.cyan(`Server: ${server.name}`)));
218
+ out.push(`Status: ${info ? colors.green('Running') : colors.red('Offline')}`);
219
+ out.push(`Disk Size: ${colors.bold(`${diskMB} MB`)}`);
220
+ if (info) {
221
+ const usage = await this.processUsage(info.pid);
222
+ out.push(`PID: ${info.pid}`);
223
+ out.push(`CPU: ${colors.bold(`${usage.cpu}%`)}`);
224
+ out.push(`RAM: ${colors.bold(`${usage.ramMB} MB`)}`);
225
+ out.push(`Uptime: ${Math.floor((Date.now() - info.startTime) / 60000)}m`);
226
+ }
227
+ }
228
+ out.push('');
229
+ return out.join('\n');
230
+ }
231
+ /** Cross-platform per-process CPU%/RAM via pidusage; safe on failure. */
232
+ async processUsage(pid) {
233
+ try {
234
+ // pidusage needs two samples to compute a CPU delta — the first reads 0,
235
+ // so prime it, wait briefly, then read the real value.
236
+ await (0, pidusage_1.default)(pid);
237
+ await new Promise((r) => setTimeout(r, 250));
238
+ const stat = await (0, pidusage_1.default)(pid);
239
+ return { cpu: stat.cpu.toFixed(1), ramMB: (stat.memory / (1024 * 1024)).toFixed(1) };
240
+ }
241
+ catch {
242
+ return { cpu: 'N/A', ramMB: 'N/A' };
243
+ }
244
+ }
245
+ /**
246
+ * Executes /folder — opens the server's directory in the OS file explorer.
247
+ */
248
+ executeFolder() {
249
+ const server = this.configManager.getServer();
250
+ if (!server) {
251
+ return colors.failure('No server connected.');
252
+ }
253
+ if (!fs.existsSync(server.path)) {
254
+ return colors.failure(`Server directory does not exist: ${server.path}`);
255
+ }
256
+ const opened = (0, helpers_1.openInFileExplorer)(server.path);
257
+ return opened
258
+ ? colors.success(`Opening folder for "${server.name}": ${server.path}`)
259
+ : colors.warning(`Could not launch a file explorer. Path: ${server.path}`);
260
+ }
261
+ /**
262
+ * Executes /backup create
263
+ */
264
+ executeBackupCreate() {
265
+ const server = this.configManager.getServer();
266
+ if (!server) {
267
+ return colors.failure('No server connected.');
268
+ }
269
+ if (this.processManager.getActiveServer(server.name)) {
270
+ return colors.failure(`Server "${server.name}" is currently running. Stop it first to prevent world corruption.`);
271
+ }
272
+ try {
273
+ const meta = this.backupManager.createBackup();
274
+ return colors.success(`Backup created: ${meta.name} (${(meta.sizeBytes / 1024 / 1024).toFixed(2)} MB)`);
275
+ }
276
+ catch (err) {
277
+ return colors.failure(`Backup failed: ${err.message}`);
278
+ }
279
+ }
280
+ /**
281
+ * Executes /backup list
282
+ */
283
+ executeBackupList() {
284
+ const list = this.backupManager.listBackups();
285
+ if (list.length === 0) {
286
+ return colors.warning('No backups found.');
287
+ }
288
+ const rows = [
289
+ colors.bold(`${'Backup ID (Filename)'.padEnd(50)}${'Server'.padEnd(15)}${'Size (MB)'.padEnd(12)}${'Created At'.padEnd(20)}`),
290
+ colors.gray('─'.repeat(97))
291
+ ];
292
+ for (const b of list) {
293
+ const sizeMB = (b.sizeBytes / (1024 * 1024)).toFixed(2);
294
+ const shortId = b.id.length > 47 ? b.id.substring(0, 44) + '...' : b.id;
295
+ rows.push(`${shortId.padEnd(50)}${b.serverName.padEnd(15)}${sizeMB.padEnd(12)}${new Date(b.createdAt).toLocaleDateString().padEnd(20)}`);
296
+ }
297
+ return '\n' + rows.join('\n') + '\n';
298
+ }
299
+ /**
300
+ * Executes /backup restore <backup-id>
301
+ */
302
+ executeBackupRestore(backupId) {
303
+ const server = this.configManager.getServer();
304
+ if (!server) {
305
+ return colors.failure('No server connected.');
306
+ }
307
+ if (this.processManager.getActiveServer(server.name)) {
308
+ return colors.failure(`Server "${server.name}" is currently running. Stop it first before restoring.`);
309
+ }
310
+ try {
311
+ this.backupManager.restoreBackup(backupId);
312
+ return colors.success(`Backup successfully restored to "${server.name}".`);
313
+ }
314
+ catch (err) {
315
+ return colors.failure(`Restoration failed: ${err.message}`);
316
+ }
317
+ }
318
+ /**
319
+ * Executes /plugins list
320
+ */
321
+ executePluginsList() {
322
+ const server = this.configManager.getServer();
323
+ if (!server) {
324
+ return colors.failure('No server connected.');
325
+ }
326
+ const pluginsDir = path.join(server.path, 'plugins');
327
+ if (!fs.existsSync(pluginsDir)) {
328
+ return colors.warning(`No plugins folder found. Start the server once to generate it, or create it at: ${pluginsDir}`);
329
+ }
330
+ const plugins = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.jar'));
331
+ if (plugins.length === 0) {
332
+ return colors.warning(`No plugins (.jar) installed on "${server.name}".`);
333
+ }
334
+ const rows = ['\n' + colors.bold(colors.cyan(`Plugins installed on ${server.name}:`))];
335
+ plugins.forEach((p, idx) => rows.push(` ${idx + 1}. ${p}`));
336
+ return rows.join('\n') + '\n';
337
+ }
338
+ /**
339
+ * Executes /plugins install <plugin-url>
340
+ */
341
+ async executePluginsInstall(url) {
342
+ const server = this.configManager.getServer();
343
+ if (!server) {
344
+ return colors.failure('No server connected.');
345
+ }
346
+ const pluginsDir = path.join(server.path, 'plugins');
347
+ if (!fs.existsSync(pluginsDir)) {
348
+ fs.mkdirSync(pluginsDir, { recursive: true });
349
+ }
350
+ let fileName;
351
+ try {
352
+ fileName = path.basename(new URL(url).pathname);
353
+ }
354
+ catch {
355
+ return colors.failure('Invalid plugin URL.');
356
+ }
357
+ if (!fileName || !fileName.endsWith('.jar')) {
358
+ fileName = 'downloaded-plugin.jar';
359
+ }
360
+ const destPath = path.join(pluginsDir, fileName);
361
+ console.log(colors.cyan(`Downloading plugin to ${destPath}...`));
362
+ try {
363
+ await (0, downloadService_1.downloadFile)(url, destPath);
364
+ return colors.success(`Plugin "${fileName}" installed successfully to "${server.name}".`);
365
+ }
366
+ catch (err) {
367
+ return colors.failure(`Failed to install plugin: ${err.message}`);
368
+ }
369
+ }
370
+ /**
371
+ * Executes /plugins remove <plugin-jar-name>
372
+ */
373
+ executePluginsRemove(pluginName) {
374
+ const server = this.configManager.getServer();
375
+ if (!server) {
376
+ return colors.failure('No server connected.');
377
+ }
378
+ const pluginsDir = path.join(server.path, 'plugins');
379
+ if (!fs.existsSync(pluginsDir)) {
380
+ return colors.failure('No plugins folder exists.');
381
+ }
382
+ let targetFile = pluginName;
383
+ if (!targetFile.endsWith('.jar')) {
384
+ targetFile += '.jar';
385
+ }
386
+ // Security sanitization (basename) to prevent directory traversal.
387
+ const cleanFileName = path.basename(targetFile);
388
+ const targetPath = path.join(pluginsDir, cleanFileName);
389
+ if (fs.existsSync(targetPath)) {
390
+ fs.unlinkSync(targetPath);
391
+ return colors.success(`Plugin "${cleanFileName}" removed from "${server.name}".`);
392
+ }
393
+ return colors.failure(`Plugin "${cleanFileName}" was not found on "${server.name}".`);
394
+ }
395
+ /**
396
+ * Executes /tunnel stop
397
+ */
398
+ executeTunnelStop() {
399
+ const success = this.playitManager.stopTunnel();
400
+ return success
401
+ ? colors.success('Playit tunnel agent stopped.')
402
+ : colors.warning('Playit tunnel is not currently running.');
403
+ }
404
+ /**
405
+ * Executes /tunnel status
406
+ */
407
+ executeTunnelStatus() {
408
+ const status = this.playitManager.getStatus();
409
+ let statusStr = colors.red('Offline');
410
+ if (status.status === 'Online') {
411
+ statusStr = colors.green('Online');
412
+ }
413
+ else if (status.status === 'Connecting') {
414
+ statusStr = colors.yellow('Connecting');
415
+ }
416
+ const output = [
417
+ '\n' + colors.bold(colors.cyan('Playit.gg Tunnel Status')),
418
+ `Tunnel Address: ${colors.bold(status.address)}`,
419
+ `Port: ${status.port}`,
420
+ `Status: ${statusStr}`,
421
+ `Latency: ${status.latency}`
422
+ ];
423
+ if (status.type) {
424
+ output.push(`Type: ${status.type === 'java' ? 'Minecraft Java' : 'Minecraft Bedrock'}`);
425
+ }
426
+ if (status.address && status.address !== 'None') {
427
+ output.push(`\n🎮 Connect at: ${colors.bold(colors.green(`${status.address}:${status.port}`))}`);
428
+ }
429
+ output.push('');
430
+ return output.join('\n');
431
+ }
432
+ /**
433
+ * Executes /java [path] — shows the current Java, lists detected JVMs, or
434
+ * (with an argument) validates and sets the Java used to launch the server.
435
+ * Newer Minecraft versions require newer Java (e.g. MC 26.x needs Java 25).
436
+ */
437
+ executeJava(newPath) {
438
+ if (!newPath) {
439
+ const current = this.configManager.getConfig().defaultJavaPath;
440
+ const info = (0, helpers_1.checkJava)(current);
441
+ const lines = [
442
+ '\n' + colors.bold(colors.cyan('Java Runtime')),
443
+ `Current: ${colors.bold(current)}`,
444
+ `Version: ${info.installed ? colors.green(info.version) : colors.red('not found')}`,
445
+ ];
446
+ const detected = (0, helpers_1.findInstalledJavas)();
447
+ if (detected.length > 0) {
448
+ lines.push('');
449
+ lines.push(colors.bold(colors.cyan('Detected JVMs:')));
450
+ for (const j of detected) {
451
+ lines.push(` ${colors.green(j.version.padEnd(10))} ${j.path}`);
452
+ }
453
+ lines.push('');
454
+ lines.push(colors.gray('Switch with: /java <path>'));
455
+ }
456
+ lines.push('');
457
+ return lines.join('\n');
458
+ }
459
+ const cleanPath = newPath.trim().replace(/^['"]|['"]$/g, '');
460
+ const info = (0, helpers_1.checkJava)(cleanPath);
461
+ if (!info.installed) {
462
+ return colors.failure(`No working Java found at "${cleanPath}".`);
463
+ }
464
+ this.configManager.updateSettings({ defaultJavaPath: cleanPath });
465
+ return colors.success(`Java set to "${cleanPath}" (version ${info.version}). It will be used on the next /start.`);
466
+ }
467
+ /**
468
+ * Executes /config
469
+ */
470
+ executeConfig() {
471
+ const cfg = this.configManager.getConfig();
472
+ return '\n' + colors.bold(colors.cyan('Active Application Configuration:')) + '\n' + JSON.stringify(cfg, null, 2) + '\n';
473
+ }
474
+ /**
475
+ * Executes /tunnel <java|bedrock> (and the legacy /tunnel create alias).
476
+ *
477
+ * Fully automated: ensures the binary + agent secret (claiming once if needed),
478
+ * creates the tunnel, starts the agent, and returns the live connect address.
479
+ */
480
+ async executeTunnelCreate(type) {
481
+ try {
482
+ const firstRun = !this.playitManager.getSecret();
483
+ if (firstRun) {
484
+ console.log(colors.cyan('First-time setup: the playit agent must be claimed to your account once.'));
485
+ }
486
+ else {
487
+ console.log(colors.cyan(`Creating ${type} tunnel...`));
488
+ }
489
+ const status = await this.playitManager.setupAndStart(type, {
490
+ onClaimUrl: (url) => {
491
+ const opened = (0, helpers_1.openInBrowser)(url);
492
+ console.log(`\n🔗 ${colors.bold('One-time setup: approve the agent in your browser.')}`);
493
+ if (opened) {
494
+ console.log(colors.gray('Your browser was opened automatically — just sign in and click Approve.'));
495
+ }
496
+ else {
497
+ console.log(colors.gray('Open this link, sign in (or create a free account), and click Approve:'));
498
+ }
499
+ console.log(colors.underline(colors.cyan(url)));
500
+ console.log(colors.gray('Everything after this is automatic, and you will never be asked again.\n'));
501
+ },
502
+ onStatus: (msg) => console.log(colors.info(msg)),
503
+ });
504
+ return [
505
+ colors.success(`${type === 'java' ? 'Java' : 'Bedrock'} tunnel is online!`),
506
+ `\n🎮 Connect at: ${colors.bold(colors.green(`${status.address}:${status.port}`))}`,
507
+ colors.gray('Share this address with players. The tunnel stays up while MCPANEL is running.'),
508
+ ].join('\n');
509
+ }
510
+ catch (err) {
511
+ if (err.message && err.message.includes('NotAllowedWithReadOnly')) {
512
+ return colors.failure('The agent secret is read-only. Run /tunnel reset and try again to re-claim it.');
513
+ }
514
+ return colors.failure(`Failed to create tunnel: ${err.message}`);
515
+ }
516
+ }
517
+ /**
518
+ * Executes /tunnel reset — clears the saved secret so the agent can be re-claimed.
519
+ */
520
+ async executeTunnelReset() {
521
+ await this.playitManager.resetSecret();
522
+ return colors.success('Playit agent secret cleared. The next tunnel command will re-claim the agent.');
523
+ }
524
+ }
525
+ exports.CommandRouter = CommandRouter;