creepercli 1.0.0 → 1.1.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/src/formatters.js CHANGED
@@ -1,134 +1,120 @@
1
- /*
2
- * Copyright 2026 DemonZDevelopment
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- 'use strict';
18
-
19
- function humanSize(bytes) {
20
- if (bytes == null || bytes < 0) return '-';
21
- const units = ['B', 'K', 'M', 'G', 'T'];
22
- let v = bytes;
23
- let i = 0;
24
- while (v >= 1024 && i < units.length - 1) {
25
- v /= 1024;
26
- i++;
27
- }
28
- return (i === 0 ? String(bytes) : v.toFixed(1)) + units[i];
29
- }
30
-
31
- function formatDate(ms) {
32
- if (!ms) return '-';
33
- const d = new Date(ms);
34
- const p = (n) => String(n).padStart(2, '0');
35
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
36
- }
37
-
38
- function formatLs(entries, { longFormat }) {
39
- if (!entries.length) return '(empty)';
40
- if (!longFormat) {
41
- return wrap(entries.map((e) => e.name + (e.isDir ? '/' : '')));
42
- }
43
- return entries.map((e) => {
44
- const perms = e.perms || '----------';
45
- const size = e.isDir ? '-' : humanSize(e.size);
46
- return `${perms} ${String(size).padStart(8)} ${formatDate(e.mtime)} ${e.name}${e.isDir ? '/' : ''}${e.link ? ' -> ' + e.link : ''}`;
47
- }).join('\n');
48
- }
49
-
50
- function wrap(items, width = 100) {
51
- let line = '';
52
- const out = [];
53
- for (const it of items) {
54
- if (line && line.length + it.length + 2 > width) {
55
- out.push(line);
56
- line = '';
57
- }
58
- line = line ? line + ' ' + it : it;
59
- }
60
- if (line) out.push(line);
61
- return out.join('\n');
62
- }
63
-
64
- function formatTree(nodes, prefix = '') {
65
- const out = [];
66
- nodes.forEach((node, i) => {
67
- const last = i === nodes.length - 1;
68
- const branch = last ? '└── ' : '├── ';
69
- out.push(prefix + branch + node.name + (node.isDir ? '/' : ''));
70
- if (node.children && node.children.length) {
71
- out.push(...formatTree(node.children, prefix + (last ? ' ' : '│ ')));
72
- }
73
- });
74
- return out.join('\n');
75
- }
76
-
77
- function formatUptime(ms) {
78
- const total = Math.floor(ms / 1000);
79
- const d = Math.floor(total / 86400);
80
- const h = Math.floor((total % 86400) / 3600);
81
- const m = Math.floor((total % 3600) / 60);
82
- return `${d}d ${h}h ${m}m`;
83
- }
84
-
85
- function formatStats(s) {
86
- const mem = s.memory || {};
87
- const cpu = s.cpu || {};
88
- const disk = s.disk || {};
89
- const load = cpu.load >= 0 ? cpu.load.toFixed(2) : 'n/a';
90
- const proc = cpu.processCpuPercent >= 0 ? cpu.processCpuPercent.toFixed(1) + '%' : 'n/a';
91
- const rows = [
92
- `Uptime: ${formatUptime(s.uptimeMs || 0)}`,
93
- `CPU load: ${load} process: ${proc}`,
94
- `Memory heap: ${humanSize(mem.heapUsed)} / ${humanSize(mem.heapTotal)} (max ${humanSize(mem.heapMax)})`,
95
- `Disk (root): ${humanSize(disk.used)} / ${humanSize(disk.total)} (usable ${humanSize(disk.usable)})`,
96
- ];
97
- if (s.jvm && s.jvm.javaVersion) rows.push(`Java: ${s.jvm.javaVersion} (${s.jvm.availableProcessors} cores)`);
98
- return rows.join('\n');
99
- }
100
-
101
- function tpsBar(tps) {
102
- const filled = Math.max(0, Math.min(20, Math.round(tps)));
103
- return '█'.repeat(filled) + '░'.repeat(20 - filled);
104
- }
105
-
106
- function formatTps(t) {
107
- return `TPS: 1m ${t.tps1m.toFixed(1)} | 5m ${t.tps5m.toFixed(1)} | 15m ${t.tps15m.toFixed(1)} [${tpsBar(t.tps1m)}] tick ${t.tickMs}ms`;
108
- }
109
-
110
- function formatMatches(matches) {
111
- return matches.map((m) => `${m.path}${m.line ? ':' + m.line : ''}: ${m.text}`).join('\n');
112
- }
113
-
114
- function formatInfo(e) {
115
- return [
116
- `Path: ${e.path}`,
117
- `Type: ${e.isDir ? 'directory' : 'file'}${e.link ? ' (symlink -> ' + e.link + ')' : ''}`,
118
- `Perms: ${e.perms || '-'}`,
119
- `Size: ${e.isDir ? '-' : humanSize(e.size)}`,
120
- `Modified: ${formatDate(e.mtime)}`,
121
- ].join('\n');
122
- }
123
-
124
- module.exports = {
125
- humanSize,
126
- formatDate,
127
- formatLs,
128
- formatTree,
129
- formatStats,
130
- formatTps,
131
- formatMatches,
132
- formatInfo,
133
- wrap,
134
- };
1
+
2
+
3
+ 'use strict';
4
+
5
+ function humanSize(bytes) {
6
+ if (bytes == null || bytes < 0) return '-';
7
+ const units = ['B', 'K', 'M', 'G', 'T'];
8
+ let v = bytes;
9
+ let i = 0;
10
+ while (v >= 1024 && i < units.length - 1) {
11
+ v /= 1024;
12
+ i++;
13
+ }
14
+ return (i === 0 ? String(bytes) : v.toFixed(1)) + units[i];
15
+ }
16
+
17
+ function formatDate(ms) {
18
+ if (!ms) return '-';
19
+ const d = new Date(ms);
20
+ const p = (n) => String(n).padStart(2, '0');
21
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
22
+ }
23
+
24
+ function formatLs(entries, { longFormat }) {
25
+ if (!entries.length) return '(empty)';
26
+ if (!longFormat) {
27
+ return wrap(entries.map((e) => e.name + (e.isDir ? '/' : '')));
28
+ }
29
+ return entries.map((e) => {
30
+ const perms = e.perms || '----------';
31
+ const size = e.isDir ? '-' : humanSize(e.size);
32
+ return `${perms} ${String(size).padStart(8)} ${formatDate(e.mtime)} ${e.name}${e.isDir ? '/' : ''}${e.link ? ' -> ' + e.link : ''}`;
33
+ }).join('\n');
34
+ }
35
+
36
+ function wrap(items, width = 100) {
37
+ let line = '';
38
+ const out = [];
39
+ for (const it of items) {
40
+ if (line && line.length + it.length + 2 > width) {
41
+ out.push(line);
42
+ line = '';
43
+ }
44
+ line = line ? line + ' ' + it : it;
45
+ }
46
+ if (line) out.push(line);
47
+ return out.join('\n');
48
+ }
49
+
50
+ function formatTree(nodes, prefix = '') {
51
+ const out = [];
52
+ nodes.forEach((node, i) => {
53
+ const last = i === nodes.length - 1;
54
+ const branch = last ? '└── ' : '├── ';
55
+ out.push(prefix + branch + node.name + (node.isDir ? '/' : ''));
56
+ if (node.children && node.children.length) {
57
+ out.push(...formatTree(node.children, prefix + (last ? ' ' : '│ ')));
58
+ }
59
+ });
60
+ return out.join('\n');
61
+ }
62
+
63
+ function formatUptime(ms) {
64
+ const total = Math.floor(ms / 1000);
65
+ const d = Math.floor(total / 86400);
66
+ const h = Math.floor((total % 86400) / 3600);
67
+ const m = Math.floor((total % 3600) / 60);
68
+ return `${d}d ${h}h ${m}m`;
69
+ }
70
+
71
+ function formatStats(s) {
72
+ const mem = s.memory || {};
73
+ const cpu = s.cpu || {};
74
+ const disk = s.disk || {};
75
+ const load = cpu.load >= 0 ? cpu.load.toFixed(2) : 'n/a';
76
+ const proc = cpu.processCpuPercent >= 0 ? cpu.processCpuPercent.toFixed(1) + '%' : 'n/a';
77
+ const rows = [
78
+ `Uptime: ${formatUptime(s.uptimeMs || 0)}`,
79
+ `CPU load: ${load} process: ${proc}`,
80
+ `Memory heap: ${humanSize(mem.heapUsed)} / ${humanSize(mem.heapTotal)} (max ${humanSize(mem.heapMax)})`,
81
+ `Disk (root): ${humanSize(disk.used)} / ${humanSize(disk.total)} (usable ${humanSize(disk.usable)})`,
82
+ ];
83
+ if (s.jvm && s.jvm.javaVersion) rows.push(`Java: ${s.jvm.javaVersion} (${s.jvm.availableProcessors} cores)`);
84
+ return rows.join('\n');
85
+ }
86
+
87
+ function tpsBar(tps) {
88
+ const filled = Math.max(0, Math.min(20, Math.round(tps)));
89
+ return '█'.repeat(filled) + '░'.repeat(20 - filled);
90
+ }
91
+
92
+ function formatTps(t) {
93
+ return `TPS: 1m ${t.tps1m.toFixed(1)} | 5m ${t.tps5m.toFixed(1)} | 15m ${t.tps15m.toFixed(1)} [${tpsBar(t.tps1m)}] tick ${t.tickMs}ms`;
94
+ }
95
+
96
+ function formatMatches(matches) {
97
+ return matches.map((m) => `${m.path}${m.line ? ':' + m.line : ''}: ${m.text}`).join('\n');
98
+ }
99
+
100
+ function formatInfo(e) {
101
+ return [
102
+ `Path: ${e.path}`,
103
+ `Type: ${e.isDir ? 'directory' : 'file'}${e.link ? ' (symlink -> ' + e.link + ')' : ''}`,
104
+ `Perms: ${e.perms || '-'}`,
105
+ `Size: ${e.isDir ? '-' : humanSize(e.size)}`,
106
+ `Modified: ${formatDate(e.mtime)}`,
107
+ ].join('\n');
108
+ }
109
+
110
+ module.exports = {
111
+ humanSize,
112
+ formatDate,
113
+ formatLs,
114
+ formatTree,
115
+ formatStats,
116
+ formatTps,
117
+ formatMatches,
118
+ formatInfo,
119
+ wrap,
120
+ };
package/src/index.js CHANGED
@@ -1,186 +1,175 @@
1
- /*
2
- * Copyright 2026 DemonZDevelopment
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- 'use strict';
18
-
19
- const { CreeperClient } = require('./client');
20
- const { load: loadConfig, applyFlags } = require('./config');
21
- const session = require('./session');
22
- const { startRepl } = require('./repl');
23
- const { runCommand } = require('./commands');
24
- const { doLogin } = require('./commands/auth');
25
- const { friendly } = require('./protocol');
1
+
2
+
3
+ 'use strict';
4
+
5
+ const { CreeperClient } = require('./client');
6
+ const { load: loadConfig, applyFlags } = require('./config');
7
+ const session = require('./session');
8
+ const { startRepl } = require('./repl');
9
+ const { runCommand } = require('./commands');
10
+ const { doLogin } = require('./commands/auth');
11
+ const { friendly } = require('./protocol');
26
12
  const { checkUpdate } = require('./update');
27
-
13
+ const pkg = require('../package.json');
14
+
28
15
  async function main(argv) {
29
- checkUpdate();
30
16
  const flags = parseFlags(argv);
31
- const cfg = applyFlags(loadConfig(), flags);
32
17
  if (flags.help) {
33
18
  printUsage();
34
19
  return 0;
35
20
  }
36
- const args = flags.args;
37
- const cmd = args[0];
38
-
39
- if (cmd === 'login') {
40
- const client = await connect(cfg);
41
- const ctx = { client, cfg, username: null, cwd: '/', exit: false, flags };
42
- try {
43
- const res = await doLogin(ctx);
44
- if (res && res.token && process.stdout.isTTY) {
45
- console.log('Entering interactive REPL shell (type "help" for commands, "exit" or "q" to quit)...\n');
46
- startRepl(ctx);
47
- return 0;
48
- }
21
+ checkUpdate();
22
+ const cfg = applyFlags(loadConfig(), flags);
23
+ const args = flags.args;
24
+ const cmd = args[0];
25
+
26
+ if (cmd === 'login') {
27
+ const client = await connect(cfg);
28
+ const ctx = { client, cfg, username: null, cwd: '/', exit: false, flags };
29
+ try {
30
+ const res = await doLogin(ctx);
31
+ if (res && res.token && process.stdout.isTTY) {
32
+ console.log('Entering interactive REPL shell (type "help" for commands, "exit" or "q" to quit)...\n');
33
+ startRepl(ctx);
34
+ return 0;
35
+ }
49
36
  } catch (err) {
50
37
  console.error(`\nError: ${friendly(err)}\n`);
51
38
  client.close();
52
- process.exit(1);
53
- } finally {
54
- if (!ctx.exit && !process.stdout.isTTY) {
55
- client.close();
56
- }
57
- }
58
- return 0;
59
- }
60
-
61
- if (cmd === 'logout') {
62
- const client = await connect(cfg);
63
- try {
64
- const creds = session.load(cfg.host, cfg.port);
65
- if (creds) {
66
- await runCommand({ client, cfg, username: creds.username, cwd: '/' }, 'logout');
67
- } else {
68
- console.log('No local session to clear.');
69
- }
70
- } finally {
71
- client.close();
72
- }
73
- return 0;
74
- }
75
-
76
- const client = await connect(cfg);
77
- const ctx = { client, cfg, username: null, cwd: '/', exit: false, flags };
78
- let creds = session.load(cfg.host, cfg.port);
79
- if (creds) {
80
- ctx.username = creds.username;
81
- try {
39
+ return 1;
40
+ } finally {
41
+ if (!ctx.exit && !process.stdout.isTTY) {
42
+ client.close();
43
+ }
44
+ }
45
+ return 0;
46
+ }
47
+
48
+ if (cmd === 'logout') {
49
+ const client = await connect(cfg);
50
+ try {
51
+ const creds = session.load(cfg.host, cfg.port);
52
+ if (creds) {
53
+ await runCommand({ client, cfg, username: creds.username, cwd: '/' }, 'logout');
54
+ } else {
55
+ console.log('No local session to clear.');
56
+ }
57
+ } finally {
58
+ client.close();
59
+ }
60
+ return 0;
61
+ }
62
+
63
+ const client = await connect(cfg);
64
+ const ctx = { client, cfg, username: null, cwd: '/', exit: false, flags };
65
+ let creds = session.load(cfg.host, cfg.port);
66
+ if (creds) {
67
+ ctx.username = creds.username;
68
+ try {
82
69
  const resume = await client.request('auth.resume', { token: creds.token }, { timeoutMs: 15000 });
83
70
  ctx.username = resume.username;
84
71
  ctx.cwd = resume.cwd || '/';
85
- } catch (err) {
86
- if (err.code === 'E_SESSION_EXPIRED' || err.code === 'E_UNAUTHORIZED') {
87
- session.clear();
88
- creds = null;
89
- console.log('Session expired, please log in.');
90
- } else {
91
- throw err;
92
- }
93
- }
72
+ session.save({ ...creds, expiresAt: Date.now() + (resume.expiresInSeconds || 0) * 1000 });
73
+ } catch (err) {
74
+ if (err.code === 'E_SESSION_EXPIRED' || err.code === 'E_UNAUTHORIZED') {
75
+ session.clear();
76
+ creds = null;
77
+ console.log('Session expired, please log in.');
78
+ } else {
79
+ throw err;
80
+ }
81
+ }
82
+ }
83
+ if (!creds) {
84
+ if (!process.stdout.isTTY) {
85
+ console.error('Not authenticated and stdin is not a TTY. Run "creepercli login" first.');
86
+ client.close();
87
+ return 1;
88
+ }
89
+ await doLogin(ctx);
90
+ }
91
+ if (!cmd || cmd === 'repl' || cmd === 'shell') {
92
+ startRepl(ctx);
93
+ return 0;
94
+ }
95
+ try {
96
+ return await runCommand(ctx, args.join(' '));
97
+ } finally {
98
+ client.close();
99
+ }
100
+ }
101
+
102
+ function parseFlags(argv) {
103
+ const flags = { args: [], host: null, port: null, editor: null, refresh: null, yes: false, help: false };
104
+ for (let i = 0; i < argv.length; i++) {
105
+ const a = argv[i];
106
+ if (a === '--host') {
107
+ if (i + 1 < argv.length) flags.host = argv[++i];
108
+ } else if (a === '--port') {
109
+ if (i + 1 < argv.length) flags.port = parseInt(argv[++i], 10);
110
+ } else if (a === '--editor') {
111
+ if (i + 1 < argv.length) flags.editor = argv[++i];
112
+ } else if (a === '--refresh') {
113
+ if (i + 1 < argv.length) flags.refresh = parseInt(argv[++i], 10);
114
+ } else if (a === '--yes' || a === '-y') {
115
+ flags.yes = true;
116
+ } else if (a === '--help' || a === '-h') {
117
+ flags.help = true;
118
+ } else {
119
+ flags.args.push(a);
120
+ }
121
+ }
122
+ return flags;
123
+ }
124
+
125
+ async function connect(cfg) {
126
+ const client = new CreeperClient({ host: cfg.host, port: cfg.port });
127
+ try {
128
+ await client.connect();
129
+ return client;
130
+ } catch (err) {
131
+ const codeStr = err.code || err.message;
132
+ console.error(`\nError: Cannot connect to server at ${cfg.host}:${cfg.port} (${codeStr})\n`);
133
+ console.error(`--------------------------------------------------------------------------------`);
134
+ console.error(`CREEPER CLI SETUP & TROUBLESHOOTING GUIDE`);
135
+ console.error(`--------------------------------------------------------------------------------\n`);
136
+ console.error(`1. INSTALL THE PLUGIN ON YOUR MINECRAFT SERVER:`);
137
+ console.error(` * Place CreeperCLI-${pkg.version}.jar inside your server's 'plugins/' folder.`);
138
+ console.error(` * Start or restart your server (Paper / Spigot 1.21+).\n`);
139
+ console.error(`2. CREATE YOUR ADMIN USER IN SERVER CONSOLE:`);
140
+ console.error(` * Open your server console (or run in-game as OP):`);
141
+ console.error(` /creepercli user add <username> <password>\n`);
142
+ console.error(`3. VERIFY NETWORK & PORT FORWARDING:`);
143
+ console.error(` * CreeperCLI listens on TCP port ${cfg.port} by default.`);
144
+ console.error(` * If connecting to a remote server, specify host & port flags:`);
145
+ console.error(` creepercli login --host <your-server-ip> --port ${cfg.port}`);
146
+ console.error(` * Or set up an SSH tunnel:`);
147
+ console.error(` ssh -L ${cfg.port}:127.0.0.1:${cfg.port} user@<your-server-ip>\n`);
148
+ console.error(`--------------------------------------------------------------------------------`);
149
+ console.error(`Documentation: https://github.com/DemonZ-Development/creepercli\n`);
150
+ err.creepercliReported = true;
151
+ throw err;
94
152
  }
95
- if (!creds) {
96
- if (!process.stdout.isTTY) {
97
- console.error('Not authenticated and stdin is not a TTY. Run "creepercli login" first.');
98
- client.close();
99
- return 1;
100
- }
101
- await doLogin(ctx);
102
- }
103
- if (!cmd || cmd === 'repl' || cmd === 'shell') {
104
- startRepl(ctx);
105
- return 0;
106
- }
107
- try {
108
- return await runCommand(ctx, args.join(' '));
109
- } finally {
110
- client.close();
111
- }
112
- }
113
-
114
- function parseFlags(argv) {
115
- const flags = { args: [], host: null, port: null, editor: null, refresh: null, yes: false, help: false };
116
- for (let i = 0; i < argv.length; i++) {
117
- const a = argv[i];
118
- if (a === '--host') {
119
- if (i + 1 < argv.length) flags.host = argv[++i];
120
- } else if (a === '--port') {
121
- if (i + 1 < argv.length) flags.port = parseInt(argv[++i], 10);
122
- } else if (a === '--editor') {
123
- if (i + 1 < argv.length) flags.editor = argv[++i];
124
- } else if (a === '--refresh') {
125
- if (i + 1 < argv.length) flags.refresh = parseInt(argv[++i], 10);
126
- } else if (a === '--yes' || a === '-y') {
127
- flags.yes = true;
128
- } else if (a === '--help' || a === '-h') {
129
- flags.help = true;
130
- } else {
131
- flags.args.push(a);
132
- }
133
- }
134
- return flags;
135
- }
136
-
137
- async function connect(cfg) {
138
- const client = new CreeperClient({ host: cfg.host, port: cfg.port });
139
- try {
140
- await client.connect();
141
- return client;
142
- } catch (err) {
143
- const codeStr = err.code || err.message;
144
- console.error(`\nError: Cannot connect to server at ${cfg.host}:${cfg.port} (${codeStr})\n`);
145
- console.error(`--------------------------------------------------------------------------------`);
146
- console.error(`CREEPER CLI SETUP & TROUBLESHOOTING GUIDE`);
147
- console.error(`--------------------------------------------------------------------------------\n`);
148
- console.error(`1. INSTALL THE PLUGIN ON YOUR MINECRAFT SERVER:`);
149
- console.error(` * Place CreeperCLI-1.0.0.jar inside your server's 'plugins/' folder.`);
150
- console.error(` * Start or restart your server (Paper / Spigot 1.21+).\n`);
151
- console.error(`2. CREATE YOUR ADMIN USER IN SERVER CONSOLE:`);
152
- console.error(` * Open your server console (or run in-game as OP):`);
153
- console.error(` /creepercli user add <username> <password>\n`);
154
- console.error(`3. VERIFY NETWORK & PORT FORWARDING:`);
155
- console.error(` * CreeperCLI listens on TCP port ${cfg.port} by default.`);
156
- console.error(` * If connecting to a remote server, specify host & port flags:`);
157
- console.error(` creepercli login --host <your-server-ip> --port ${cfg.port}`);
158
- console.error(` * Or set up an SSH tunnel:`);
159
- console.error(` ssh -L ${cfg.port}:127.0.0.1:${cfg.port} user@<your-server-ip>\n`);
160
- console.error(`--------------------------------------------------------------------------------`);
161
- console.error(`Documentation: https://github.com/DemonZDevelopment/creepercli\n`);
162
- process.exit(1);
163
- }
164
- }
165
-
166
- function printUsage() {
167
- console.log(`CreeperCLI v1.0.0 — remote Minecraft administration
168
-
169
- Usage:
170
- creepercli login Authenticate with the server
171
- creepercli logout Clear the local session
172
- creepercli <command> [args...] Run a single command
173
- creepercli repl Interactive shell (default)
174
-
175
- Options:
176
- --host <ip> server address (default 127.0.0.1)
177
- --port <port> server port (default 45678)
178
- --editor <cmd> editor for "edit" (default $EDITOR or vi)
179
- --refresh <ms> dashboard refresh rate for "top"
180
- --yes / -y auto-confirm transfers (csync)
181
- --help / -h show this help
182
-
183
- Run "help" inside the REPL for the full command reference.`);
184
153
  }
185
-
186
- module.exports = { main };
154
+
155
+ function printUsage() {
156
+ console.log(`CreeperCLI v${pkg.version} — remote Minecraft administration
157
+
158
+ Usage:
159
+ creepercli login Authenticate with the server
160
+ creepercli logout Clear the local session
161
+ creepercli <command> [args...] Run a single command
162
+ creepercli repl Interactive shell (default)
163
+
164
+ Options:
165
+ --host <ip> server address (default 127.0.0.1)
166
+ --port <port> server port (default 45678)
167
+ --editor <cmd> editor for "edit" (default $EDITOR or vi)
168
+ --refresh <ms> dashboard refresh rate for "top"
169
+ --yes / -y auto-confirm transfers (csync)
170
+ --help / -h show this help
171
+
172
+ Run "help" inside the REPL for the full command reference.`);
173
+ }
174
+
175
+ module.exports = { main };