@akaanakbaik/pterodactyl-gateway 0.3.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/cli.js ADDED
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+ import { createPtero, explainError } from "./index.js";
3
+ const rawArgs = process.argv.slice(2);
4
+ const jsonMode = rawArgs.includes("--json");
5
+ const yesMode = rawArgs.includes("--yes") || rawArgs.includes("-y");
6
+ const dryRunMode = rawArgs.includes("--dry-run");
7
+ const allowAnyPath = rawArgs.includes("--allow-any-path");
8
+ const args = rawArgs.filter(arg => arg !== "--json" && arg !== "--yes" && arg !== "-y" && arg !== "--dry-run" && arg !== "--allow-any-path");
9
+ const command = args[0] ?? "help";
10
+ async function main() {
11
+ if (command === "help" || command === "--help" || command === "-h") {
12
+ printHelp();
13
+ return;
14
+ }
15
+ const ptero = createPtero.fromEnv();
16
+ if (command === "doctor") {
17
+ const report = await ptero.doctor();
18
+ output(report, formatDoctor(report));
19
+ process.exitCode = asRecord(report).ok ? 0 : 1;
20
+ return;
21
+ }
22
+ if (command === "connect") {
23
+ const result = await ptero.connect();
24
+ output(result, formatConnect(result));
25
+ return;
26
+ }
27
+ if (command === "ids") {
28
+ const nestIndex = args.indexOf("--nest");
29
+ const nestId = nestIndex >= 0 ? Number(args[nestIndex + 1]) : undefined;
30
+ const result = await ptero.listIds(nestId);
31
+ output(result);
32
+ return;
33
+ }
34
+ if (command === "servers") {
35
+ const raw = await ptero.raw.client.get("/");
36
+ const servers = formatClientServers(raw);
37
+ output(servers, table(servers, ["identifier", "name", "node", "serverOwner"]));
38
+ return;
39
+ }
40
+ if (command === "admin") {
41
+ const scope = args[1];
42
+ if (!scope)
43
+ throw new Error("Format: ptero-gateway admin <users|servers|server|create-user|create-server>");
44
+ if (scope === "users") {
45
+ const result = await ptero.raw.application.get("/users?per_page=100");
46
+ output(result, formatAdminUsers(result));
47
+ return;
48
+ }
49
+ if (scope === "servers") {
50
+ const result = await ptero.raw.application.get("/servers?per_page=100");
51
+ output(result, formatAdminServers(result));
52
+ return;
53
+ }
54
+ if (scope === "create-user") {
55
+ if (!dryRunMode)
56
+ requireYes("create user");
57
+ const username = getRequiredOption("--username");
58
+ const email = getRequiredOption("--email");
59
+ const password = getOption("--password") ?? "auto";
60
+ const administrator = parseBooleanOption("--admin", false);
61
+ const result = await ptero.users.createSmart({ username, email, password, administrator }, { dryRun: dryRunMode });
62
+ output(result, dryRunMode ? formatDryRun("Create user", result) : formatCreatedUser(result));
63
+ return;
64
+ }
65
+ if (scope === "create-server") {
66
+ if (!dryRunMode)
67
+ requireYes("create server");
68
+ const input = buildCreateServerInput();
69
+ const result = await ptero.servers.createSmart(input, { dryRun: dryRunMode });
70
+ output(result, dryRunMode ? formatDryRun("Create server", result) : formatCreatedServer(result));
71
+ return;
72
+ }
73
+ if (scope === "server") {
74
+ const serverId = args[2];
75
+ const action = args[3];
76
+ if (!serverId || !action)
77
+ throw new Error("Format: ptero-gateway admin server <serverId> <detail|limits|update-limits|suspend|unsuspend|reinstall>");
78
+ if (action === "detail") {
79
+ const result = await ptero.raw.application.get(`/servers/${serverId}`);
80
+ output(result, formatAdminServerDetail(result));
81
+ return;
82
+ }
83
+ if (action === "limits") {
84
+ const result = await ptero.raw.application.get(`/servers/${serverId}`);
85
+ output(result, formatAdminServerLimits(result));
86
+ return;
87
+ }
88
+ if (action === "update-limits") {
89
+ requireYes("update server limits");
90
+ const current = await ptero.raw.application.get(`/servers/${serverId}`);
91
+ const payload = buildLimitPayload(current);
92
+ const result = await ptero.raw.application.patch(`/servers/${serverId}/build`, payload);
93
+ output(result, "Limit server berhasil di-update.");
94
+ return;
95
+ }
96
+ if (action === "suspend") {
97
+ requireYes("suspend server");
98
+ output(await ptero.raw.application.post(`/servers/${serverId}/suspend`), `Server ${serverId} berhasil di-suspend.`);
99
+ return;
100
+ }
101
+ if (action === "unsuspend") {
102
+ requireYes("unsuspend server");
103
+ output(await ptero.raw.application.post(`/servers/${serverId}/unsuspend`), `Server ${serverId} berhasil di-unsuspend.`);
104
+ return;
105
+ }
106
+ if (action === "reinstall") {
107
+ requireYes("reinstall server");
108
+ output(await ptero.raw.application.post(`/servers/${serverId}/reinstall`), `Server ${serverId} berhasil diminta reinstall.`);
109
+ return;
110
+ }
111
+ }
112
+ throw new Error(`Admin command tidak dikenal: ${scope}`);
113
+ }
114
+ if (command === "probe") {
115
+ const id = args[1];
116
+ if (!id)
117
+ throw new Error("Format: ptero-gateway probe <identifier>");
118
+ if (isPlaceholderIdentifier(id))
119
+ throw new Error("IDENTIFIER_SERVER hanya placeholder. Jalankan `ptero-gateway servers`, lalu salin nilai `identifier` server yang ingin dicek.");
120
+ const report = await ptero.server(id).probe();
121
+ output(report, formatProbe(report));
122
+ process.exitCode = asRecord(report).ok ? 0 : 1;
123
+ return;
124
+ }
125
+ if (command === "server") {
126
+ const id = args[1];
127
+ const action = args[2];
128
+ if (!id || !action)
129
+ throw new Error("Format: ptero-gateway server <identifier> <action>");
130
+ if (isPlaceholderIdentifier(id))
131
+ throw new Error("IDENTIFIER_SERVER hanya placeholder. Jalankan `ptero-gateway servers`, lalu salin nilai `identifier` server yang ingin dikontrol.");
132
+ const server = ptero.server(id);
133
+ if (action === "summary") {
134
+ const [resources, startup, network, databases, backups, schedules] = await Promise.all([
135
+ server.resources().catch(errorResult),
136
+ server.startup.variables().catch(errorResult),
137
+ server.network.list().catch(errorResult),
138
+ server.databases.list().catch(errorResult),
139
+ server.backups.list().catch(errorResult),
140
+ server.schedules.list().catch(errorResult)
141
+ ]);
142
+ const result = { identifier: id, resources, startup, network, databases, backups, schedules };
143
+ output(result, formatSummary(result));
144
+ }
145
+ else if (action === "start") {
146
+ requireYes("start server");
147
+ output(await server.start(), "Power signal terkirim: start");
148
+ }
149
+ else if (action === "stop") {
150
+ requireYes("stop server");
151
+ output(await server.stop(), "Power signal terkirim: stop");
152
+ }
153
+ else if (action === "restart") {
154
+ requireYes("restart server");
155
+ output(await server.restart(), "Power signal terkirim: restart");
156
+ }
157
+ else if (action === "kill") {
158
+ requireYes("kill server");
159
+ output(await server.kill(), "Power signal terkirim: kill");
160
+ }
161
+ else if (action === "resources") {
162
+ const result = await server.resources();
163
+ output(result, formatResources(result));
164
+ }
165
+ else if (action === "command") {
166
+ requireYes("send command");
167
+ const commandToSend = args.slice(3).join(" ");
168
+ if (!commandToSend)
169
+ throw new Error("Format: ptero-gateway server <identifier> command \"npm start\" --yes");
170
+ output(await server.command(commandToSend), `Command terkirim: ${commandToSend}`);
171
+ }
172
+ else if (action === "files") {
173
+ const result = await server.files.list(args[3] ?? "/");
174
+ output(result, formatFiles(result));
175
+ }
176
+ else if (action === "read")
177
+ console.log(await server.files.read(args[3] ?? "/"));
178
+ else if (action === "write") {
179
+ requireYes("write file");
180
+ const file = args[3];
181
+ const content = args.slice(4).join(" ");
182
+ if (!file || !content)
183
+ throw new Error("Format: ptero-gateway server <identifier> write /tmp/test.txt \"isi file\" --yes");
184
+ if (!allowAnyPath && !isSafeTmpPath(file))
185
+ throw new Error("Demi keamanan, write via CLI default hanya boleh ke /tmp/. Tambahkan --allow-any-path jika benar-benar ingin menulis ke path lain.");
186
+ output(await server.files.write(file, content), `File berhasil ditulis: ${file}`);
187
+ }
188
+ else if (action === "init-node-alive") {
189
+ requireYes("init node alive template");
190
+ const packageJson = JSON.stringify({ scripts: { start: "node index.js" }, dependencies: {} });
191
+ const indexJs = 'console.log("running"); setInterval(() => console.log("tick", new Date().toISOString()), 30000);';
192
+ await server.files.write("/package.json", packageJson);
193
+ await server.files.write("/index.js", indexJs);
194
+ await server.startup.set("CMD_RUN", "node index.js");
195
+ output({ ok: true }, "Template Node alive berhasil dipasang. Jalankan: ptero-gateway server <identifier> start --yes");
196
+ }
197
+ else if (action === "startup" || action === "env") {
198
+ const result = await server.startup.variables();
199
+ output(result, formatStartup(result));
200
+ }
201
+ else if (action === "set-env") {
202
+ requireYes("set startup variable");
203
+ const key = args[3];
204
+ const value = args.slice(4).join(" ");
205
+ if (!key || !value)
206
+ throw new Error("Format: ptero-gateway server <identifier> set-env KEY VALUE --yes");
207
+ output(await server.startup.set(key, value), `Variable ${key} berhasil diubah menjadi ${maskIfSecret(key, value)}`);
208
+ }
209
+ else if (action === "network" || action === "ports") {
210
+ const result = await server.network.list();
211
+ output(result, formatNetwork(result));
212
+ }
213
+ else if (action === "databases") {
214
+ const result = await server.databases.list();
215
+ output(result, formatNamedList(result, "database"));
216
+ }
217
+ else if (action === "backups") {
218
+ const result = await server.backups.list();
219
+ output(result, formatNamedList(result, "backup"));
220
+ }
221
+ else if (action === "backup") {
222
+ const backupId = args[3];
223
+ if (!backupId)
224
+ throw new Error("Format: ptero-gateway server <identifier> backup <uuid>");
225
+ const result = await server.backups.details(backupId);
226
+ output(result, formatBackupDetail(result));
227
+ }
228
+ else if (action === "delete-backup") {
229
+ requireYes("delete backup");
230
+ const backupId = args[3];
231
+ if (!backupId)
232
+ throw new Error("Format: ptero-gateway server <identifier> delete-backup <uuid> --yes");
233
+ output(await server.backups.delete(backupId), `Backup ${backupId} berhasil dihapus.`);
234
+ }
235
+ else if (action === "create-backup") {
236
+ requireYes("create backup");
237
+ const name = getOption("--name") ?? `backup-${new Date().toISOString()}`;
238
+ output(await server.backups.create({ name }), `Backup dibuat: ${name}`);
239
+ }
240
+ else if (action === "schedules") {
241
+ const result = await server.schedules.list();
242
+ output(result, formatNamedList(result, "schedule"));
243
+ }
244
+ else
245
+ throw new Error(`Action server tidak dikenal: ${action}`);
246
+ return;
247
+ }
248
+ throw new Error(`Command tidak dikenal: ${command}`);
249
+ }
250
+ function output(raw, pretty) {
251
+ if (jsonMode || !pretty)
252
+ console.log(JSON.stringify(raw, null, 2));
253
+ else
254
+ console.log(pretty);
255
+ }
256
+ function requireYes(action) {
257
+ if (!yesMode)
258
+ throw new Error(`Aksi '${action}' mengubah server. Tambahkan --yes jika kamu yakin.`);
259
+ }
260
+ function getOption(name) {
261
+ const index = args.indexOf(name);
262
+ if (index < 0)
263
+ return undefined;
264
+ return args[index + 1];
265
+ }
266
+ function getRequiredOption(name) {
267
+ const value = getOption(name);
268
+ if (!value)
269
+ throw new Error(`Opsi ${name} wajib diisi.`);
270
+ return value;
271
+ }
272
+ function getNumberOption(name, fallback) {
273
+ const value = getOption(name);
274
+ if (value === undefined)
275
+ return fallback;
276
+ const parsed = Number(value);
277
+ if (!Number.isFinite(parsed))
278
+ throw new Error(`${name} harus berupa angka.`);
279
+ return parsed;
280
+ }
281
+ function parseBooleanOption(name, fallback) {
282
+ const value = getOption(name);
283
+ if (value === undefined)
284
+ return fallback;
285
+ if (["1", "true", "yes", "y"].includes(value.toLowerCase()))
286
+ return true;
287
+ if (["0", "false", "no", "n"].includes(value.toLowerCase()))
288
+ return false;
289
+ throw new Error(`${name} harus true/false.`);
290
+ }
291
+ function errorResult(error) {
292
+ return { error: error instanceof Error ? error.message : String(error) };
293
+ }
294
+ function buildCreateServerInput() {
295
+ const username = getOption("--username");
296
+ const password = getOption("--password");
297
+ return {
298
+ name: getRequiredOption("--name"),
299
+ email: getRequiredOption("--email"),
300
+ username,
301
+ password,
302
+ autoCreateUser: Boolean(username),
303
+ description: getOption("--description") ?? "Created by Akadev Pterodactyl Gateway",
304
+ nodeId: getNumberOption("--node", 1),
305
+ nestId: getNumberOption("--nest", 5),
306
+ eggId: getNumberOption("--egg", 18),
307
+ dockerImage: getOption("--docker-image") ?? "auto",
308
+ startup: getOption("--startup") ?? "auto",
309
+ specs: {
310
+ memory: getOption("--memory") ?? "1GB",
311
+ disk: getOption("--disk") ?? "2GB",
312
+ cpu: getOption("--cpu") ?? "100%",
313
+ databases: getNumberOption("--databases", 0),
314
+ allocations: getNumberOption("--allocations", 1),
315
+ backups: getNumberOption("--backups", 0),
316
+ swap: getNumberOption("--swap", 0),
317
+ io: getNumberOption("--io", 500)
318
+ }
319
+ };
320
+ }
321
+ function buildLimitPayload(raw) {
322
+ const attributes = getAttributes(raw);
323
+ const limits = asRecord(attributes.limits);
324
+ const featureLimits = asRecord(attributes.feature_limits);
325
+ const allocation = Number(attributes.allocation ?? 0);
326
+ if (!allocation)
327
+ throw new Error("Tidak bisa membaca default allocation server dari Application API.");
328
+ return {
329
+ allocation,
330
+ memory: getNumberOption("--memory", Number(limits.memory ?? 0)),
331
+ swap: getNumberOption("--swap", Number(limits.swap ?? 0)),
332
+ disk: getNumberOption("--disk", Number(limits.disk ?? 0)),
333
+ io: getNumberOption("--io", Number(limits.io ?? 500)),
334
+ cpu: getNumberOption("--cpu", Number(limits.cpu ?? 0)),
335
+ threads: getOption("--threads") ?? String(limits.threads ?? ""),
336
+ feature_limits: {
337
+ databases: getNumberOption("--databases", Number(featureLimits.databases ?? 0)),
338
+ allocations: getNumberOption("--allocations", Number(featureLimits.allocations ?? 0)),
339
+ backups: getNumberOption("--backups", Number(featureLimits.backups ?? 0))
340
+ }
341
+ };
342
+ }
343
+ function formatDoctor(report) {
344
+ const root = asRecord(report);
345
+ const checks = Array.isArray(root.checks) ? root.checks.map(asRecord) : [];
346
+ const lines = [`Doctor: ${root.ok ? "OK" : "FAILED"}`, `Mode: ${root.mode ?? "unknown"}`, ""];
347
+ for (const check of checks)
348
+ lines.push(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.message ?? ""}`);
349
+ return lines.join("\n");
350
+ }
351
+ function formatConnect(result) {
352
+ const root = asRecord(result);
353
+ return [`Connect: ${root.ok ? "OK" : "FAILED"}`, `Mode: ${root.mode ?? "unknown"}`, `Domain: ${root.domain ?? "-"}`, `Latency: ${root.latency ?? 0}ms`].join("\n");
354
+ }
355
+ function formatProbe(report) {
356
+ const root = asRecord(report);
357
+ const checks = asRecord(root.checks);
358
+ const lines = [`Probe ${root.identifier ?? ""}: ${root.ok ? "OK" : "FAILED"}`, ""];
359
+ for (const [name, checkValue] of Object.entries(checks)) {
360
+ const check = asRecord(checkValue);
361
+ lines.push(`${check.ok ? "✓" : "✗"} ${name}: ${check.message ?? ""}`);
362
+ }
363
+ return lines.join("\n");
364
+ }
365
+ function formatSummary(result) {
366
+ const root = asRecord(result);
367
+ const lines = [`Server Summary: ${root.identifier}`, ""];
368
+ lines.push(formatResources(root.resources));
369
+ lines.push("");
370
+ lines.push("Files/Startup/Network/DB/Backup/Schedule:");
371
+ lines.push(`startup variables: ${countCollection(root.startup)}`);
372
+ lines.push(`ports: ${countCollection(root.network)}`);
373
+ lines.push(`databases: ${countCollection(root.databases)}`);
374
+ lines.push(`backups: ${countCollection(root.backups)}`);
375
+ lines.push(`schedules: ${countCollection(root.schedules)}`);
376
+ return lines.join("\n");
377
+ }
378
+ function formatResources(raw) {
379
+ const attributes = asRecord(asRecord(raw).attributes);
380
+ const resources = asRecord(attributes.resources);
381
+ return [
382
+ `State: ${attributes.current_state ?? "unknown"}`,
383
+ `Suspended: ${attributes.is_suspended ? "yes" : "no"}`,
384
+ `Memory: ${formatBytes(Number(resources.memory_bytes ?? 0))}`,
385
+ `Disk: ${formatBytes(Number(resources.disk_bytes ?? 0))}`,
386
+ `CPU: ${Number(resources.cpu_absolute ?? 0).toFixed(2)}%`,
387
+ `Network RX: ${formatBytes(Number(resources.network_rx_bytes ?? 0))}`,
388
+ `Network TX: ${formatBytes(Number(resources.network_tx_bytes ?? 0))}`,
389
+ `Uptime: ${formatDuration(Number(resources.uptime ?? 0))}`
390
+ ].join("\n");
391
+ }
392
+ function formatFiles(raw) {
393
+ const rows = getCollection(raw).map(item => {
394
+ const attributes = asRecord(item.attributes);
395
+ return {
396
+ type: attributes.is_file ? "file" : "dir",
397
+ name: String(attributes.name ?? ""),
398
+ size: attributes.is_file ? formatBytes(Number(attributes.size ?? 0)) : "-",
399
+ modified: String(attributes.modified_at ?? "")
400
+ };
401
+ });
402
+ return rows.length ? table(rows, ["type", "name", "size", "modified"]) : "Folder kosong.";
403
+ }
404
+ function formatStartup(raw) {
405
+ const rows = getCollection(raw).map(item => {
406
+ const attributes = asRecord(item.attributes);
407
+ return {
408
+ variable: String(attributes.env_variable ?? ""),
409
+ value: maskIfSecret(String(attributes.env_variable ?? ""), String(attributes.server_value ?? "")),
410
+ editable: String(Boolean(attributes.is_editable))
411
+ };
412
+ });
413
+ return rows.length ? table(rows, ["variable", "value", "editable"]) : "Startup variable kosong.";
414
+ }
415
+ function formatNetwork(raw) {
416
+ const rows = getCollection(raw).map(item => {
417
+ const attributes = asRecord(item.attributes);
418
+ return {
419
+ id: String(attributes.id ?? ""),
420
+ ip: String(attributes.ip_alias ?? attributes.ip ?? ""),
421
+ port: String(attributes.port ?? ""),
422
+ default: attributes.is_default ? "yes" : "no",
423
+ notes: String(attributes.notes ?? "")
424
+ };
425
+ });
426
+ return rows.length ? table(rows, ["id", "ip", "port", "default", "notes"]) : "Allocation kosong.";
427
+ }
428
+ function formatNamedList(raw, label) {
429
+ const rows = getCollection(raw).map((item, index) => {
430
+ const attributes = asRecord(item.attributes);
431
+ return {
432
+ no: String(index + 1),
433
+ name: String(attributes.name ?? attributes.database ?? attributes.uuid ?? attributes.id ?? "-"),
434
+ status: String(attributes.completed_at ? "completed" : attributes.is_successful === false ? "failed" : attributes.is_active === false ? "inactive" : "active")
435
+ };
436
+ });
437
+ return rows.length ? table(rows, ["no", "name", "status"]) : `${label} kosong.`;
438
+ }
439
+ function formatClientServers(raw) {
440
+ const data = getCollection(raw);
441
+ return data.map(item => {
442
+ const attributes = asRecord(item.attributes);
443
+ return {
444
+ identifier: String(attributes.identifier ?? ""),
445
+ uuid: String(attributes.uuid ?? ""),
446
+ name: String(attributes.name ?? ""),
447
+ node: String(attributes.node ?? ""),
448
+ serverOwner: Boolean(attributes.server_owner ?? false)
449
+ };
450
+ });
451
+ }
452
+ function formatAdminUsers(raw) {
453
+ const rows = getCollection(raw).map(item => {
454
+ const attributes = asRecord(item.attributes);
455
+ return {
456
+ id: String(attributes.id ?? ""),
457
+ username: String(attributes.username ?? ""),
458
+ email: String(attributes.email ?? ""),
459
+ admin: String(Boolean(attributes.root_admin ?? false))
460
+ };
461
+ });
462
+ return rows.length ? table(rows, ["id", "username", "email", "admin"]) : "User kosong.";
463
+ }
464
+ function formatAdminServers(raw) {
465
+ const rows = getCollection(raw).map(item => {
466
+ const attributes = asRecord(item.attributes);
467
+ const limits = asRecord(attributes.limits);
468
+ const feature = asRecord(attributes.feature_limits);
469
+ return {
470
+ id: String(attributes.id ?? ""),
471
+ identifier: String(attributes.identifier ?? ""),
472
+ name: String(attributes.name ?? ""),
473
+ memory: String(limits.memory ?? ""),
474
+ disk: String(limits.disk ?? ""),
475
+ cpu: String(limits.cpu ?? ""),
476
+ backups: String(feature.backups ?? "")
477
+ };
478
+ });
479
+ return rows.length ? table(rows, ["id", "identifier", "name", "memory", "disk", "cpu", "backups"]) : "Server kosong.";
480
+ }
481
+ function formatAdminServerDetail(raw) {
482
+ const attributes = getAttributes(raw);
483
+ return [
484
+ `ID: ${attributes.id ?? "-"}`,
485
+ `Identifier: ${attributes.identifier ?? "-"}`,
486
+ `UUID: ${attributes.uuid ?? "-"}`,
487
+ `Name: ${attributes.name ?? "-"}`,
488
+ `Owner ID: ${attributes.user ?? "-"}`,
489
+ `Node ID: ${attributes.node ?? "-"}`,
490
+ `Allocation ID: ${attributes.allocation ?? "-"}`,
491
+ "",
492
+ formatAdminServerLimits(raw)
493
+ ].join("\n");
494
+ }
495
+ function formatAdminServerLimits(raw) {
496
+ const attributes = getAttributes(raw);
497
+ const limits = asRecord(attributes.limits);
498
+ const feature = asRecord(attributes.feature_limits);
499
+ const rows = [
500
+ { name: "memory", value: String(limits.memory ?? "") },
501
+ { name: "swap", value: String(limits.swap ?? "") },
502
+ { name: "disk", value: String(limits.disk ?? "") },
503
+ { name: "io", value: String(limits.io ?? "") },
504
+ { name: "cpu", value: String(limits.cpu ?? "") },
505
+ { name: "threads", value: String(limits.threads ?? "") },
506
+ { name: "databases", value: String(feature.databases ?? "") },
507
+ { name: "allocations", value: String(feature.allocations ?? "") },
508
+ { name: "backups", value: String(feature.backups ?? "") }
509
+ ];
510
+ return table(rows, ["name", "value"]);
511
+ }
512
+ function formatBackupDetail(raw) {
513
+ const attributes = getAttributes(raw);
514
+ return [
515
+ `UUID: ${attributes.uuid ?? "-"}`,
516
+ `Name: ${attributes.name ?? "-"}`,
517
+ `Successful: ${attributes.is_successful ? "yes" : "no"}`,
518
+ `Locked: ${attributes.is_locked ? "yes" : "no"}`,
519
+ `Bytes: ${formatBytes(Number(attributes.bytes ?? 0))}`,
520
+ `Checksum: ${attributes.checksum ?? "-"}`,
521
+ `Created: ${attributes.created_at ?? "-"}`,
522
+ `Completed: ${attributes.completed_at ?? "-"}`
523
+ ].join("\n");
524
+ }
525
+ function formatDryRun(title, raw) {
526
+ const root = asRecord(raw);
527
+ const payload = asRecord(root.payload);
528
+ const preview = asRecord(root.preview);
529
+ const lines = [`${title} dry-run OK`, ""];
530
+ if (payload.username || payload.email) {
531
+ lines.push(`username: ${payload.username ?? "-"}`);
532
+ lines.push(`email: ${payload.email ?? "-"}`);
533
+ }
534
+ if (payload.name || preview.dockerImage) {
535
+ lines.push(`server: ${payload.name ?? "-"}`);
536
+ lines.push(`docker: ${preview.dockerImage ?? payload.docker_image ?? "-"}`);
537
+ lines.push(`startup: ${preview.startup ?? payload.startup ?? "-"}`);
538
+ }
539
+ lines.push("");
540
+ lines.push("Tambahkan --yes untuk eksekusi asli.");
541
+ lines.push("Gunakan --json untuk melihat payload lengkap.");
542
+ return lines.join("\n");
543
+ }
544
+ function formatCreatedUser(raw) {
545
+ const root = asRecord(raw);
546
+ return [`User berhasil dibuat.`, `id: ${root.id ?? "-"}`, `username: ${root.username ?? "-"}`, `email: ${root.email ?? "-"}`, root.generatedPassword ? `password: ${root.generatedPassword}` : ""].filter(Boolean).join("\n");
547
+ }
548
+ function formatCreatedServer(raw) {
549
+ const root = asRecord(raw);
550
+ return [`Server berhasil dibuat.`, `id: ${root.id ?? "-"}`, `identifier: ${root.identifier ?? "-"}`, `uuid: ${root.uuid ?? "-"}`, `name: ${root.name ?? "-"}`].join("\n");
551
+ }
552
+ function getAttributes(raw) {
553
+ const root = asRecord(raw);
554
+ const data = asRecord(root.data);
555
+ return asRecord(data.attributes ?? root.attributes ?? raw);
556
+ }
557
+ function table(rows, columns) {
558
+ if (rows.length === 0)
559
+ return "";
560
+ const widths = columns.map(column => Math.max(column.length, ...rows.map(row => String(row[column] ?? "").length)));
561
+ const header = columns.map((column, index) => column.padEnd(widths[index] ?? column.length)).join(" ");
562
+ const divider = widths.map(width => "-".repeat(width)).join(" ");
563
+ const body = rows.map(row => columns.map((column, index) => String(row[column] ?? "").padEnd(widths[index] ?? column.length)).join(" "));
564
+ return [header, divider, ...body].join("\n");
565
+ }
566
+ function getCollection(raw) {
567
+ const root = asRecord(raw);
568
+ const data = Array.isArray(root.data) ? root.data : [];
569
+ return data.map(item => asRecord(item));
570
+ }
571
+ function countCollection(raw) {
572
+ if ("error" in asRecord(raw))
573
+ return "error";
574
+ return getCollection(raw).length;
575
+ }
576
+ function asRecord(value) {
577
+ return typeof value === "object" && value !== null ? value : {};
578
+ }
579
+ function isPlaceholderIdentifier(value) {
580
+ return value === "IDENTIFIER_SERVER" || value === "<identifier>" || value === "abc12345";
581
+ }
582
+ function isSafeTmpPath(value) {
583
+ return value === "/tmp" || value.startsWith("/tmp/");
584
+ }
585
+ function formatBytes(value) {
586
+ if (!Number.isFinite(value) || value <= 0)
587
+ return "0 B";
588
+ const units = ["B", "KB", "MB", "GB", "TB"];
589
+ let size = value;
590
+ let unit = 0;
591
+ while (size >= 1024 && unit < units.length - 1) {
592
+ size /= 1024;
593
+ unit++;
594
+ }
595
+ return `${size.toFixed(size >= 10 || unit === 0 ? 0 : 2)} ${units[unit] ?? "B"}`;
596
+ }
597
+ function formatDuration(ms) {
598
+ if (!Number.isFinite(ms) || ms <= 0)
599
+ return "0s";
600
+ const seconds = Math.floor(ms / 1000);
601
+ const days = Math.floor(seconds / 86400);
602
+ const hours = Math.floor((seconds % 86400) / 3600);
603
+ const minutes = Math.floor((seconds % 3600) / 60);
604
+ const secs = seconds % 60;
605
+ const parts = [];
606
+ if (days)
607
+ parts.push(`${days}d`);
608
+ if (hours)
609
+ parts.push(`${hours}h`);
610
+ if (minutes)
611
+ parts.push(`${minutes}m`);
612
+ if (!parts.length)
613
+ parts.push(`${secs}s`);
614
+ return parts.join(" ");
615
+ }
616
+ function maskIfSecret(key, value) {
617
+ if (!value)
618
+ return "";
619
+ if (!/(token|key|secret|password|pass|auth|credential)/i.test(key))
620
+ return value;
621
+ if (value.length <= 8)
622
+ return "********";
623
+ return `${value.slice(0, 3)}********${value.slice(-3)}`;
624
+ }
625
+ function printHelp() {
626
+ console.log(`Akadev Pterodactyl Gateway
627
+
628
+ Perintah:
629
+ ptero-gateway doctor [--json]
630
+ ptero-gateway connect [--json]
631
+ ptero-gateway ids [--nest <nestId>] [--json]
632
+ ptero-gateway servers [--json]
633
+ ptero-gateway admin users [--json]
634
+ ptero-gateway admin servers [--json]
635
+ ptero-gateway admin create-user --username aka_test --email user@example.com --password "secret" --yes
636
+ ptero-gateway admin create-server --name "aka test" --email user@example.com --node 1 --nest 5 --egg 18 --dry-run
637
+ ptero-gateway admin create-server --name "aka test" --email user@example.com --node 1 --nest 5 --egg 18 --yes
638
+ ptero-gateway admin server <serverId> detail [--json]
639
+ ptero-gateway admin server <serverId> limits [--json]
640
+ ptero-gateway admin server <serverId> update-limits --backups 1 --yes
641
+ ptero-gateway admin server <serverId> suspend --yes
642
+ ptero-gateway admin server <serverId> unsuspend --yes
643
+ ptero-gateway admin server <serverId> reinstall --yes
644
+ ptero-gateway probe <identifier> [--json]
645
+ ptero-gateway server <identifier> summary [--json]
646
+ ptero-gateway server <identifier> resources [--json]
647
+ ptero-gateway server <identifier> files [directory] [--json]
648
+ ptero-gateway server <identifier> read <file>
649
+ ptero-gateway server <identifier> startup|env [--json]
650
+ ptero-gateway server <identifier> network|ports [--json]
651
+ ptero-gateway server <identifier> databases [--json]
652
+ ptero-gateway server <identifier> backups [--json]
653
+ ptero-gateway server <identifier> backup <uuid> [--json]
654
+ ptero-gateway server <identifier> delete-backup <uuid> --yes
655
+ ptero-gateway server <identifier> schedules [--json]
656
+ ptero-gateway server <identifier> write /tmp/test.txt "isi file" --yes
657
+ ptero-gateway server <identifier> write /index.js "isi file" --yes --allow-any-path
658
+ ptero-gateway server <identifier> init-node-alive --yes
659
+ ptero-gateway server <identifier> set-env KEY VALUE --yes
660
+ ptero-gateway server <identifier> create-backup --name "backup-name" --yes
661
+ ptero-gateway server <identifier> start --yes
662
+ ptero-gateway server <identifier> stop --yes
663
+ ptero-gateway server <identifier> restart --yes
664
+ ptero-gateway server <identifier> kill --yes
665
+ ptero-gateway server <identifier> command "npm start" --yes
666
+
667
+ Env:
668
+ PTERO_DOMAIN=https://panel.example.com
669
+ PTERO_PTLA=ptla_xxx
670
+ PTERO_PTLC=ptlc_xxx`);
671
+ }
672
+ main().catch(error => {
673
+ console.error(explainError(error));
674
+ process.exitCode = 1;
675
+ });
676
+ //# sourceMappingURL=cli.js.map