@powernukkitx/cli 0.0.1

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.
Files changed (41) hide show
  1. package/dist/bundle.js +18871 -0
  2. package/dist/commands/backup.d.ts +1 -0
  3. package/dist/commands/backup.js +74 -0
  4. package/dist/commands/config.d.ts +1 -0
  5. package/dist/commands/config.js +114 -0
  6. package/dist/commands/doctor.d.ts +1 -0
  7. package/dist/commands/doctor.js +119 -0
  8. package/dist/commands/info.d.ts +1 -0
  9. package/dist/commands/info.js +36 -0
  10. package/dist/commands/init.d.ts +4 -0
  11. package/dist/commands/init.js +73 -0
  12. package/dist/commands/plugin.d.ts +12 -0
  13. package/dist/commands/plugin.js +385 -0
  14. package/dist/commands/start.d.ts +7 -0
  15. package/dist/commands/start.js +108 -0
  16. package/dist/commands/update.d.ts +5 -0
  17. package/dist/commands/update.js +55 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +260 -0
  20. package/dist/lang/en.json +178 -0
  21. package/dist/lang/fr.json +178 -0
  22. package/dist/lang/index.d.ts +7 -0
  23. package/dist/lang/index.js +83 -0
  24. package/dist/plugins.json +66 -0
  25. package/dist/types.d.ts +61 -0
  26. package/dist/types.js +1 -0
  27. package/dist/utils/api.d.ts +18 -0
  28. package/dist/utils/api.js +74 -0
  29. package/dist/utils/github.d.ts +27 -0
  30. package/dist/utils/github.js +187 -0
  31. package/dist/utils/logger.d.ts +15 -0
  32. package/dist/utils/logger.js +72 -0
  33. package/dist/utils/pause.d.ts +1 -0
  34. package/dist/utils/pause.js +6 -0
  35. package/dist/utils/plugins.d.ts +4 -0
  36. package/dist/utils/plugins.js +34 -0
  37. package/dist/utils/server.d.ts +3 -0
  38. package/dist/utils/server.js +39 -0
  39. package/dist/utils/updater.d.ts +1 -0
  40. package/dist/utils/updater.js +86 -0
  41. package/package.json +63 -0
@@ -0,0 +1,83 @@
1
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import en from './en.json' with { type: 'json' };
5
+ import fr from './fr.json' with { type: 'json' };
6
+ const translations = { en, fr };
7
+ const CONFIG_DIR = join(homedir(), '.pnx-cli');
8
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
9
+ let _code = 'en';
10
+ let _strings = translations.en;
11
+ function detectLanguage() {
12
+ const envLang = process.env.PNX_LANG || process.env.LANG || '';
13
+ if (envLang.startsWith('fr'))
14
+ return 'fr';
15
+ return 'en';
16
+ }
17
+ function loadSavedLanguage() {
18
+ try {
19
+ if (existsSync(CONFIG_FILE)) {
20
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
21
+ const cfg = JSON.parse(raw);
22
+ if (cfg.lang === 'en' || cfg.lang === 'fr')
23
+ return cfg.lang;
24
+ }
25
+ }
26
+ catch { /* ignore */ }
27
+ return null;
28
+ }
29
+ export function saveLanguage(code) {
30
+ try {
31
+ if (!existsSync(CONFIG_DIR))
32
+ mkdirSync(CONFIG_DIR, { recursive: true });
33
+ const existing = existsSync(CONFIG_FILE) ? JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')) : {};
34
+ existing.lang = code;
35
+ writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2), 'utf-8');
36
+ }
37
+ catch { /* ignore */ }
38
+ }
39
+ export function hasSavedLanguage() {
40
+ return loadSavedLanguage() !== null;
41
+ }
42
+ export function setLanguage(code) {
43
+ _code = code;
44
+ _strings = translations[code] || translations.en;
45
+ }
46
+ export function initLanguage(cliLang) {
47
+ const code = (cliLang ||
48
+ loadSavedLanguage() ||
49
+ detectLanguage());
50
+ setLanguage(code);
51
+ return _code;
52
+ }
53
+ export function getLanguage() {
54
+ return _code;
55
+ }
56
+ export function t(key, ...args) {
57
+ const keys = key.split('.');
58
+ let value = _strings;
59
+ for (const k of keys) {
60
+ if (value && typeof value === 'object' && k in value) {
61
+ value = value[k];
62
+ }
63
+ else {
64
+ value = undefined;
65
+ break;
66
+ }
67
+ }
68
+ if (typeof value === 'string') {
69
+ return value.replace(/{(\d+)}/g, (_, i) => String(args[parseInt(i)] ?? `{${i}}`));
70
+ }
71
+ // fallback to English
72
+ let fallback = translations.en;
73
+ for (const k of keys) {
74
+ if (fallback && typeof fallback === 'object' && k in fallback) {
75
+ fallback = fallback[k];
76
+ }
77
+ else {
78
+ fallback = undefined;
79
+ break;
80
+ }
81
+ }
82
+ return typeof fallback === 'string' ? fallback : key;
83
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "essentials": {
3
+ "name": "EssentialsPNX",
4
+ "description": "Essential commands pour votre serveur PNX (tpa, warp, home, etc.)",
5
+ "author": "PowerNukkitX",
6
+ "source": "github",
7
+ "repo": "PowerNukkitX/EssentialsPNX",
8
+ "asset": "EssentialsPNX.jar"
9
+ },
10
+ "economyapi": {
11
+ "name": "EconomyAPI",
12
+ "description": "Système économique complet pour votre serveur",
13
+ "author": "onebone",
14
+ "source": "github",
15
+ "repo": "onebone/EconomyAPI",
16
+ "asset": "EconomyAPI.jar"
17
+ },
18
+ "lands": {
19
+ "name": "Lands",
20
+ "description": "Système de protection de terrains et claims",
21
+ "author": "Angelic47",
22
+ "source": "github",
23
+ "repo": "Angelic47/Lands",
24
+ "asset": "Lands.jar"
25
+ },
26
+ "worldprotect": {
27
+ "name": "WorldProtect",
28
+ "description": "Protection et gestion avancée des mondes",
29
+ "author": "PowerNukkitX",
30
+ "source": "github",
31
+ "repo": "PowerNukkitX/WorldProtect",
32
+ "asset": "WorldProtect.jar"
33
+ },
34
+ "motd": {
35
+ "name": "MOTD",
36
+ "description": "Personnalisation du message du jour sur votre serveur",
37
+ "author": "PowerNukkitX",
38
+ "source": "github",
39
+ "repo": "PowerNukkitX/MOTD",
40
+ "asset": "MOTD.jar"
41
+ },
42
+ "fawe": {
43
+ "name": "FastAsyncWorldEdit",
44
+ "description": "WorldEdit ultra-rapide pour PNX",
45
+ "author": "IntellectualSites",
46
+ "source": "github",
47
+ "repo": "IntellectualSites/FastAsyncWorldEdit",
48
+ "asset": "FastAsyncWorldEdit-Bukkit.jar"
49
+ },
50
+ "tpsoptimizer": {
51
+ "name": "TPSOptimizer",
52
+ "description": "Optimise les performances et le TPS de votre serveur",
53
+ "author": "Creeperface01",
54
+ "source": "github",
55
+ "repo": "Creeperface01/TPSOptimizer",
56
+ "asset": "TPSOptimizer.jar"
57
+ },
58
+ "pets": {
59
+ "name": "Pets",
60
+ "description": "Ajoute des animaux de compagnie personnalisés",
61
+ "author": "PetteriM1",
62
+ "source": "github",
63
+ "repo": "PetteriM1/Pets",
64
+ "asset": "Pets.jar"
65
+ }
66
+ }
@@ -0,0 +1,61 @@
1
+ export interface PluginListItem {
2
+ name: string;
3
+ slug: string;
4
+ description: string;
5
+ tags: string[];
6
+ version: string;
7
+ downloads: number;
8
+ stars: number;
9
+ author: string;
10
+ }
11
+ export interface PluginFile {
12
+ id: string;
13
+ name: string;
14
+ url: string;
15
+ size: number;
16
+ }
17
+ export interface PluginRelease {
18
+ id: string;
19
+ version: string;
20
+ is_stable: boolean;
21
+ created_at: string;
22
+ tag: string;
23
+ files: PluginFile[];
24
+ }
25
+ export interface PluginDetail {
26
+ name: string;
27
+ slug: string;
28
+ description: string;
29
+ long_description: string;
30
+ tags: string[];
31
+ downloads: number;
32
+ stars: number;
33
+ author: string;
34
+ author_id: string;
35
+ icon_url: string | null;
36
+ banner_url: string | null;
37
+ repository_url: string | null;
38
+ documentation_url: string | null;
39
+ license_url: string | null;
40
+ website_url: string | null;
41
+ created_at: string;
42
+ updated_at: string;
43
+ latest_version: string;
44
+ releases: PluginRelease[];
45
+ }
46
+ export interface PluginListResponse {
47
+ plugins: PluginListItem[];
48
+ count: number;
49
+ }
50
+ export interface ServerConfig {
51
+ jarPath: string;
52
+ javaArgs: string[];
53
+ autoRestart: boolean;
54
+ }
55
+ export interface InstalledPlugin {
56
+ slug: string;
57
+ name: string;
58
+ version: string;
59
+ installedAt: string;
60
+ fileName: string;
61
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { PluginListResponse, PluginDetail } from '../types.js';
2
+ export declare function fetchJSON(url: string): Promise<any>;
3
+ export interface ListPluginsParams {
4
+ sort?: 'newest' | 'updated' | 'stars';
5
+ limit?: number;
6
+ page?: number;
7
+ q?: string;
8
+ }
9
+ export declare function fetchPluginsList(params?: ListPluginsParams): Promise<PluginListResponse>;
10
+ export declare function fetchPluginDetail(slug: string): Promise<PluginDetail>;
11
+ export interface ResolvedDownload {
12
+ url: string;
13
+ name: string;
14
+ size: number;
15
+ version: string;
16
+ }
17
+ export declare function resolveDownload(slug: string, version?: string): Promise<ResolvedDownload>;
18
+ export declare function proxyDownloadUrl(slug: string, version?: string): string;
@@ -0,0 +1,74 @@
1
+ import https from 'node:https';
2
+ import http from 'node:http';
3
+ import { t } from '../lang/index.js';
4
+ let API_BASE = process.env.PNX_API_URL || 'http://localhost:3000';
5
+ if (API_BASE.endsWith('/'))
6
+ API_BASE = API_BASE.slice(0, -1);
7
+ function agentGet(url, redirects = 5) {
8
+ if (redirects <= 0)
9
+ return Promise.reject(new Error('Too many redirects'));
10
+ return new Promise((resolve, reject) => {
11
+ const mod = url.startsWith('https') ? https : http;
12
+ mod.get(url, { timeout: 15_000, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
13
+ const code = res.statusCode ?? 0;
14
+ if ([301, 302, 303, 307, 308].includes(code)) {
15
+ if (res.headers.location) {
16
+ resolve(agentGet(res.headers.location, redirects - 1));
17
+ return;
18
+ }
19
+ }
20
+ if (code !== 200) {
21
+ reject(new Error(`HTTP ${code}`));
22
+ return;
23
+ }
24
+ let data = '';
25
+ res.on('data', (chunk) => (data += chunk));
26
+ res.on('end', () => resolve(data));
27
+ })
28
+ .on('error', reject)
29
+ .on('timeout', function () { this?.destroy(); reject(new Error(t('errors.timeout'))); });
30
+ });
31
+ }
32
+ export async function fetchJSON(url) {
33
+ const raw = await agentGet(url);
34
+ return JSON.parse(raw);
35
+ }
36
+ export async function fetchPluginsList(params) {
37
+ const qs = new URLSearchParams();
38
+ if (params?.sort)
39
+ qs.set('sort', params.sort);
40
+ if (params?.limit)
41
+ qs.set('limit', String(params.limit));
42
+ if (params?.page)
43
+ qs.set('page', String(params.page));
44
+ if (params?.q)
45
+ qs.set('q', params.q);
46
+ const query = qs.toString();
47
+ const url = `${API_BASE}/api/plugins/cli${query ? `?${query}` : ''}`;
48
+ return fetchJSON(url);
49
+ }
50
+ export async function fetchPluginDetail(slug) {
51
+ const url = `${API_BASE}/api/plugins/cli/${encodeURIComponent(slug)}`;
52
+ return fetchJSON(url);
53
+ }
54
+ export async function resolveDownload(slug, version) {
55
+ const qs = new URLSearchParams();
56
+ qs.set('resolve', '1');
57
+ if (version)
58
+ qs.set('version', version);
59
+ const url = `${API_BASE}/api/plugins/${encodeURIComponent(slug)}/download?${qs.toString()}`;
60
+ const data = await fetchJSON(url);
61
+ return {
62
+ url: data.url || data.download_url,
63
+ name: data.name || data.file_name || `${slug}.jar`,
64
+ size: data.size || 0,
65
+ version: data.version || version || 'latest',
66
+ };
67
+ }
68
+ export function proxyDownloadUrl(slug, version) {
69
+ const qs = new URLSearchParams();
70
+ qs.set('proxy', '1');
71
+ if (version)
72
+ qs.set('version', version);
73
+ return `${API_BASE}/api/plugins/${encodeURIComponent(slug)}/download?${qs.toString()}`;
74
+ }
@@ -0,0 +1,27 @@
1
+ interface GitHubAsset {
2
+ name: string;
3
+ browser_download_url: string;
4
+ size: number;
5
+ }
6
+ interface GitHubRelease {
7
+ tag_name: string;
8
+ assets: GitHubAsset[];
9
+ published_at: string;
10
+ body: string;
11
+ }
12
+ export declare function fetchJSON(url: string, redirects?: number): Promise<any>;
13
+ export declare function getLatestRelease(): Promise<GitHubRelease>;
14
+ export declare function findAsset(release: GitHubRelease, name: string): Promise<GitHubAsset | undefined>;
15
+ export declare function findPluginAsset(repo: string, assetName: string): Promise<{
16
+ url: string;
17
+ size: number;
18
+ } | null>;
19
+ export declare function downloadFile(url: string, dest: string, label?: string): Promise<void>;
20
+ export declare function generateServerId(): string;
21
+ export declare function getDefaultJavaArgs(): string[];
22
+ export declare function getJavaEnv(): Record<string, string>;
23
+ export declare function getStartScripts(): {
24
+ url: string;
25
+ name: string;
26
+ }[];
27
+ export {};
@@ -0,0 +1,187 @@
1
+ import https from 'node:https';
2
+ import http from 'node:http';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { createWriteStream, existsSync } from 'node:fs';
5
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
6
+ import { homedir, platform } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import chalk from 'chalk';
9
+ import { step } from './logger.js';
10
+ import { t } from '../lang/index.js';
11
+ const CACHE_DIR = join(homedir(), '.pnx-cli');
12
+ const CACHE_FILE = join(CACHE_DIR, 'latest-release.json');
13
+ const CACHE_TTL = 3_600_000;
14
+ export async function fetchJSON(url, redirects = 5) {
15
+ if (redirects <= 0)
16
+ return Promise.reject(new Error('Too many redirects'));
17
+ return new Promise((resolve, reject) => {
18
+ const mod = url.startsWith('https') ? https : http;
19
+ mod.get(url, { timeout: 15_000, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
20
+ const code = res.statusCode ?? 0;
21
+ if ([301, 302, 303, 307, 308].includes(code)) {
22
+ if (res.headers.location) {
23
+ resolve(fetchJSON(res.headers.location, redirects - 1));
24
+ return;
25
+ }
26
+ }
27
+ if (code !== 200) {
28
+ reject(new Error(`HTTP ${code}`));
29
+ return;
30
+ }
31
+ let data = '';
32
+ res.on('data', (chunk) => (data += chunk));
33
+ res.on('end', () => {
34
+ try {
35
+ resolve(JSON.parse(data));
36
+ }
37
+ catch {
38
+ reject(new Error('Invalid JSON response'));
39
+ }
40
+ });
41
+ })
42
+ .on('error', reject)
43
+ .on('timeout', function () {
44
+ this?.destroy();
45
+ reject(new Error(t('errors.timeout')));
46
+ });
47
+ });
48
+ }
49
+ async function getCachedRelease() {
50
+ try {
51
+ if (existsSync(CACHE_FILE)) {
52
+ const raw = await readFile(CACHE_FILE, 'utf-8');
53
+ const cached = JSON.parse(raw);
54
+ if (Date.now() - cached.fetchedAt < CACHE_TTL) {
55
+ return cached.data;
56
+ }
57
+ }
58
+ }
59
+ catch { /* ignore */ }
60
+ return null;
61
+ }
62
+ async function setCachedRelease(data) {
63
+ try {
64
+ await mkdir(CACHE_DIR, { recursive: true });
65
+ await writeFile(CACHE_FILE, JSON.stringify({ fetchedAt: Date.now(), data }), 'utf-8');
66
+ }
67
+ catch { /* ignore */ }
68
+ }
69
+ export async function getLatestRelease() {
70
+ const cached = await getCachedRelease();
71
+ if (cached)
72
+ return cached;
73
+ const data = await fetchJSON('https://api.github.com/repos/PowerNukkitX/PowerNukkitX/releases/latest');
74
+ await setCachedRelease(data);
75
+ return data;
76
+ }
77
+ export async function findAsset(release, name) {
78
+ return release.assets.find((a) => a.name === name);
79
+ }
80
+ export async function findPluginAsset(repo, assetName) {
81
+ try {
82
+ const data = await fetchJSON(`https://api.github.com/repos/${repo}/releases/latest`);
83
+ const asset = data.assets.find((a) => a.name === assetName);
84
+ if (asset)
85
+ return { url: asset.browser_download_url, size: asset.size };
86
+ }
87
+ catch { /* not found */ }
88
+ return null;
89
+ }
90
+ export function downloadFile(url, dest, label) {
91
+ const fileName = label || dest.split(/[/\\]/).pop() || 'file';
92
+ step(chalk.cyan(fileName));
93
+ function doGet(targetUrl, redirects = 5) {
94
+ if (redirects <= 0)
95
+ return Promise.reject(new Error('Too many redirects'));
96
+ return new Promise((resolve, reject) => {
97
+ const file = createWriteStream(dest);
98
+ const mod = targetUrl.startsWith('https') ? https : http;
99
+ mod.get(targetUrl, { timeout: 120_000, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
100
+ const code = res.statusCode ?? 0;
101
+ if ([301, 302, 303, 307, 308].includes(code)) {
102
+ if (res.headers.location) {
103
+ file.close();
104
+ resolve(doGet(res.headers.location, redirects - 1));
105
+ return;
106
+ }
107
+ }
108
+ if (code !== 200) {
109
+ file.close();
110
+ reject(new Error(`HTTP ${code}`));
111
+ return;
112
+ }
113
+ const total = parseInt(res.headers['content-length'] || '0', 10);
114
+ let downloaded = 0;
115
+ let lastPercent = -1;
116
+ res.on('data', (chunk) => {
117
+ downloaded += chunk.length;
118
+ if (total > 0) {
119
+ const percent = Math.round((downloaded / total) * 100);
120
+ if (percent !== lastPercent && percent % 5 === 0) {
121
+ const done = '━'.repeat(Math.floor(percent / 5));
122
+ const left = '─'.repeat(20 - Math.floor(percent / 5));
123
+ process.stdout.write(`\r ${chalk.hex('#4ECDC4')(done)}${chalk.dim(left)} ${chalk.bold(String(percent))}%`);
124
+ lastPercent = percent;
125
+ }
126
+ }
127
+ });
128
+ res.pipe(file);
129
+ file.on('finish', () => {
130
+ if (total > 0)
131
+ process.stdout.write('\n');
132
+ file.close();
133
+ resolve();
134
+ });
135
+ })
136
+ .on('error', (err) => {
137
+ file.close();
138
+ reject(err);
139
+ })
140
+ .on('timeout', function () {
141
+ this?.destroy();
142
+ file.close();
143
+ reject(new Error(t('errors.timeout')));
144
+ });
145
+ });
146
+ }
147
+ return doGet(url);
148
+ }
149
+ export function generateServerId() {
150
+ return randomBytes(4).toString('hex');
151
+ }
152
+ export function getDefaultJavaArgs() {
153
+ return [
154
+ '-Dfile.encoding=UTF-8',
155
+ '-Dstdout.encoding=UTF-8',
156
+ '-Dstderr.encoding=UTF-8',
157
+ '-Djansi.passthrough=true',
158
+ '-Dterminal.ansi=true',
159
+ '-XX:+UseZGC',
160
+ '-XX:+ZGenerational',
161
+ '-XX:+UseStringDeduplication',
162
+ '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
163
+ '--add-opens', 'java.base/java.io=ALL-UNNAMED',
164
+ '--add-opens', 'java.base/java.net=ALL-UNNAMED',
165
+ ];
166
+ }
167
+ export function getJavaEnv() {
168
+ return {
169
+ ...process.env,
170
+ JAVA_TOOL_OPTIONS: '-Dfile.encoding=UTF-8',
171
+ LANG: process.env.LANG || 'en_US.UTF-8',
172
+ };
173
+ }
174
+ const RAW_BASE = 'https://raw.githubusercontent.com/PowerNukkitX/scripts/master';
175
+ export function getStartScripts() {
176
+ const os = platform();
177
+ if (os === 'win32') {
178
+ return [
179
+ { url: `${RAW_BASE}/start.bat`, name: 'start.bat' },
180
+ { url: `${RAW_BASE}/start.ps1`, name: 'start.ps1' },
181
+ ];
182
+ }
183
+ // linux, darwin, etc.
184
+ return [
185
+ { url: `${RAW_BASE}/start.sh`, name: 'start.sh' },
186
+ ];
187
+ }
@@ -0,0 +1,15 @@
1
+ export declare const logo: string;
2
+ export declare function success(msg: string): void;
3
+ export declare function error(msg: string): void;
4
+ export declare function warn(msg: string): void;
5
+ export declare function info(msg: string): void;
6
+ export declare function step(msg: string): void;
7
+ export declare function header(title: string): void;
8
+ export declare function subheader(title: string): void;
9
+ export declare function table(rows: [string, string][]): void;
10
+ export declare function bullet(items: string[]): void;
11
+ export declare function divider(): void;
12
+ export declare const highlight: import("chalk").ChalkInstance;
13
+ export declare const dim: import("chalk").ChalkInstance;
14
+ export declare const bold: import("chalk").ChalkInstance;
15
+ export declare const code: (s: string) => string;
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ // ─── palette ──────────────────────────────────────────────────
3
+ const c = {
4
+ primary: chalk.hex('#FF6B35'),
5
+ accent: chalk.hex('#FF8C42'),
6
+ gold: chalk.hex('#FFB347'),
7
+ blue: chalk.hex('#6C8EBF'),
8
+ cyan: chalk.hex('#4ECDC4'),
9
+ green: chalk.hex('#4CAF50'),
10
+ red: chalk.hex('#E53935'),
11
+ yellow: chalk.hex('#FFC107'),
12
+ dim: chalk.dim,
13
+ bold: chalk.bold,
14
+ };
15
+ // ─── logo ─────────────────────────────────────────────────────
16
+ export const logo = `
17
+ ${c.primary.bold(' ╭──────────────────────────────────────╮')}
18
+ ${c.primary.bold(' │')} ${c.accent.bold('██████╗ ███╗ ██╗██╗ ██╗')} ${c.primary.bold('│')}
19
+ ${c.primary.bold(' │')} ${c.accent.bold('██╔══██╗████╗ ██║╚██╗██╔╝')} ${c.primary.bold('│')}
20
+ ${c.primary.bold(' │')} ${c.accent.bold('██████╔╝██╔██╗ ██║ ╚███╔╝')} ${c.primary.bold('│')}
21
+ ${c.primary.bold(' │')} ${c.accent.bold('██╔═══╝ ██║╚██╗██║ ██╔██╗')} ${c.primary.bold('│')}
22
+ ${c.primary.bold(' │')} ${c.accent.bold('██║ ██║ ╚████║██╔╝ ██╗')} ${c.primary.bold('│')}
23
+ ${c.primary.bold(' │')} ${c.accent.bold('╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝')} ${c.primary.bold('│')}
24
+ ${c.primary.bold(' ╰──────────────────────────────────────╯')}
25
+ ${c.gold(' PowerNukkitX · Minecraft Bedrock')}
26
+ ${c.dim(' Server Management CLI')}
27
+ `;
28
+ // ─── helpers ──────────────────────────────────────────────────
29
+ const pad = (s) => ` ${s}`;
30
+ export function success(msg) {
31
+ console.log(pad(`${c.green('✔')} ${c.bold(msg)}`));
32
+ }
33
+ export function error(msg) {
34
+ console.log(pad(`${c.red('✘')} ${c.bold(msg)}`));
35
+ }
36
+ export function warn(msg) {
37
+ console.log(pad(`${c.yellow('⚠')} ${msg}`));
38
+ }
39
+ export function info(msg) {
40
+ console.log(pad(`${c.blue('ℹ')} ${msg}`));
41
+ }
42
+ export function step(msg) {
43
+ console.log(pad(`${c.dim('▸')} ${c.dim(msg)}`));
44
+ }
45
+ export function header(title) {
46
+ const line = c.primary('─'.repeat(50));
47
+ console.log(`\n ${line}`);
48
+ console.log(` ${c.accent.bold(title)}`);
49
+ console.log(` ${line}`);
50
+ }
51
+ export function subheader(title) {
52
+ console.log(`\n ${c.accent('┃')} ${c.bold(title)}`);
53
+ console.log(` ${c.accent('┃')} ${c.dim('─'.repeat(Math.min(title.length, 40)))}`);
54
+ }
55
+ export function table(rows) {
56
+ const max = Math.max(...rows.map(([l]) => l.length));
57
+ for (const [label, value] of rows) {
58
+ console.log(pad(`${c.accent(label.padEnd(max + 2))}${value}`));
59
+ }
60
+ }
61
+ export function bullet(items) {
62
+ for (const item of items) {
63
+ console.log(pad(`${c.cyan('●')} ${item}`));
64
+ }
65
+ }
66
+ export function divider() {
67
+ console.log(pad(c.dim('─'.repeat(50))));
68
+ }
69
+ export const highlight = c.gold;
70
+ export const dim = c.dim;
71
+ export const bold = c.bold;
72
+ export const code = (s) => c.cyan(s);
@@ -0,0 +1 @@
1
+ export declare function pauseForExit(): Promise<void>;
@@ -0,0 +1,6 @@
1
+ import { input } from '@inquirer/prompts';
2
+ import { dim } from './logger.js';
3
+ export async function pauseForExit() {
4
+ console.log();
5
+ await input({ message: dim('Press Enter to continue...') });
6
+ }
@@ -0,0 +1,4 @@
1
+ import type { InstalledPlugin } from '../types.js';
2
+ export declare function getInstalledPlugins(): Promise<InstalledPlugin[]>;
3
+ export declare function saveInstalledPlugin(plugin: InstalledPlugin): Promise<void>;
4
+ export declare function getPluginVersion(slug: string): Promise<string | null>;
@@ -0,0 +1,34 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const CACHE_DIR = join(homedir(), '.pnx-cli');
6
+ const PLUGINS_FILE = join(CACHE_DIR, 'installed-plugins.json');
7
+ export async function getInstalledPlugins() {
8
+ try {
9
+ if (!existsSync(PLUGINS_FILE))
10
+ return [];
11
+ const raw = await readFile(PLUGINS_FILE, 'utf-8');
12
+ return JSON.parse(raw);
13
+ }
14
+ catch {
15
+ return [];
16
+ }
17
+ }
18
+ export async function saveInstalledPlugin(plugin) {
19
+ await mkdir(CACHE_DIR, { recursive: true });
20
+ const list = await getInstalledPlugins();
21
+ const idx = list.findIndex(p => p.slug === plugin.slug);
22
+ if (idx >= 0) {
23
+ list[idx] = plugin;
24
+ }
25
+ else {
26
+ list.push(plugin);
27
+ }
28
+ await writeFile(PLUGINS_FILE, JSON.stringify(list, null, 2), 'utf-8');
29
+ }
30
+ export async function getPluginVersion(slug) {
31
+ const list = await getInstalledPlugins();
32
+ const p = list.find(x => x.slug === slug);
33
+ return p ? p.version : null;
34
+ }
@@ -0,0 +1,3 @@
1
+ export declare function detectServer(startPath?: string): string | null;
2
+ export declare function detectAllServers(startPath?: string): string[];
3
+ export declare function resolveServerDir(label?: string): Promise<string | null>;