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.
@@ -1,152 +1,138 @@
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 { formatLs, formatTree, humanSize, formatInfo } = require('../formatters');
20
- const { editFile } = require('../editor');
21
-
22
- async function pwd(ctx) {
23
- const res = await ctx.client.request('fs.pwd');
24
- ctx.cwd = res.cwd;
25
- console.log(res.cwd);
26
- }
27
-
28
- async function ls(ctx, args) {
29
- const flags = args.filter((a) => a.startsWith('-') && a !== '-');
30
- const longFormat = flags.some((f) => f.includes('l'));
31
- const all = flags.some((f) => f.includes('a'));
32
- const path = args.find((a) => !a.startsWith('-')) || '.';
33
- const res = await ctx.client.request('fs.ls', { path, long: longFormat, all });
34
- ctx.cwd = res.cwd;
35
- console.log(formatLs(res.entries, { longFormat }));
36
- }
37
-
38
- async function cd(ctx, args) {
39
- let path = args[0] || '/';
40
- if (path === '~' || path.startsWith('~/')) {
41
- path = '/' + path.slice(1).replace(/^\/+/, '');
42
- }
43
- const res = await ctx.client.request('fs.cd', { path });
44
- ctx.cwd = res.cwd;
45
- console.log(`Changed directory to ${res.cwd}`);
46
- }
47
-
48
- async function tree(ctx, args) {
49
- let depth = 3;
50
- const pathArgs = [];
51
- for (let i = 0; i < args.length; i++) {
52
- if (args[i] === '-d' || args[i] === '--depth') {
53
- depth = parseInt(args[++i], 10) || 3;
54
- } else {
55
- pathArgs.push(args[i]);
56
- }
57
- }
58
- const res = await ctx.client.request('fs.tree', { path: pathArgs[0] || '.', depth });
59
- console.log(formatTree(res.children || []));
60
- if (res.truncated) console.log('(tree truncated)');
61
- }
62
-
63
- async function cat(ctx, args) {
64
- if (!args[0]) throw new Error('Usage: cat <path>');
65
- const res = await ctx.client.request('fs.cat', { path: args[0] });
66
- process.stdout.write(res.content);
67
- if (!res.content.endsWith('\n')) process.stdout.write('\n');
68
- if (res.truncated) console.error(`(truncated: file is ${humanSize(res.size)})`);
69
- }
70
-
71
- async function head(ctx, args) {
72
- const { lines, path } = parseTailArgs(args, 10);
73
- const res = await ctx.client.request('fs.head', { path, lines });
74
- console.log(res.lines.join('\n'));
75
- if (res.truncated) console.error('(truncated)');
76
- }
77
-
78
- async function tail(ctx, args) {
79
- if (args.includes('-f') || args.includes('--follow')) {
80
- throw new Error('Live follow is available via the "log" command: creepercli log [--grep <regex>]');
81
- }
82
- const { lines, path } = parseTailArgs(args, 10);
83
- const res = await ctx.client.request('fs.tail', { path, lines });
84
- console.log(res.lines.join('\n'));
85
- if (res.truncated) console.error('(truncated)');
86
- }
87
-
88
- function parseTailArgs(args, def) {
89
- let n = def;
90
- let path = null;
91
- for (let i = 0; i < args.length; i++) {
92
- if (args[i] === '-n') n = parseInt(args[++i], 10) || def;
93
- else path = args[i];
94
- }
95
- if (!path) throw new Error('Usage: <command> [-n N] <path>');
96
- return { lines: n, path };
97
- }
98
-
99
- async function wc(ctx, args) {
100
- if (!args[0]) throw new Error('Usage: wc <path>');
101
- const res = await ctx.client.request('fs.wc', { path: args[0] });
102
- console.log(`${res.lines} ${res.words} ${res.chars} ${humanSize(res.bytes)} ${args[0]}`);
103
- }
104
-
105
- async function touch(ctx, args) {
106
- if (!args[0]) throw new Error('Usage: touch <path>');
107
- const res = await ctx.client.request('fs.touch', { path: args[0] });
108
- console.log(`touched ${res.path}`);
109
- }
110
-
111
- async function mkdir(ctx, args) {
112
- const parents = args.includes('-p') || args.includes('--parents');
113
- const path = args.find((a) => !a.startsWith('-'));
114
- if (!path) throw new Error('Usage: mkdir [-p] <path>');
115
- const res = await ctx.client.request('fs.mkdir', { path, parents });
116
- console.log(`created ${res.path}`);
117
- }
118
-
119
- async function rm(ctx, args) {
120
- const recursive = args.includes('-r') || args.includes('-R') || args.includes('--recursive');
121
- const path = args.find((a) => !a.startsWith('-'));
122
- if (!path) throw new Error('Usage: rm [-r] <path>');
123
- const res = await ctx.client.request('fs.rm', { path, recursive });
124
- console.log(`removed ${res.path}`);
125
- }
126
-
127
- async function cp(ctx, args) {
128
- const recursive = args.includes('-r') || args.includes('-R') || args.includes('--recursive');
129
- const rest = args.filter((a) => !a.startsWith('-'));
130
- if (rest.length < 2) throw new Error('Usage: cp [-r] <src> <dst>');
131
- const res = await ctx.client.request('fs.cp', { src: rest[0], dst: rest[1], recursive });
132
- console.log(`copied ${res.src} -> ${res.dst}`);
133
- }
134
-
135
- async function mv(ctx, args) {
136
- if (args.length < 2) throw new Error('Usage: mv <src> <dst>');
137
- const res = await ctx.client.request('fs.mv', { src: args[0], dst: args[1] });
138
- console.log(`moved ${res.src} -> ${res.dst}`);
139
- }
140
-
141
- async function info(ctx, args) {
142
- if (!args[0]) throw new Error('Usage: info <path>');
143
- const res = await ctx.client.request('fs.info', { path: args[0] });
144
- console.log(formatInfo(res));
145
- }
146
-
147
- async function edit(ctx, args) {
148
- if (!args[0]) throw new Error('Usage: edit <path>');
149
- return editFile(ctx, args[0]);
150
- }
151
-
152
- module.exports = { pwd, ls, cd, tree, cat, head, tail, wc, touch, mkdir, rm, cp, mv, info, edit };
1
+
2
+
3
+ 'use strict';
4
+
5
+ const { formatLs, formatTree, humanSize, formatInfo } = require('../formatters');
6
+ const { editFile } = require('../editor');
7
+
8
+ async function pwd(ctx) {
9
+ const res = await ctx.client.request('fs.pwd');
10
+ ctx.cwd = res.cwd;
11
+ console.log(res.cwd);
12
+ }
13
+
14
+ async function ls(ctx, args) {
15
+ const flags = args.filter((a) => a.startsWith('-') && a !== '-');
16
+ const longFormat = flags.some((f) => f.includes('l'));
17
+ const all = flags.some((f) => f.includes('a'));
18
+ const path = args.find((a) => !a.startsWith('-')) || '.';
19
+ const res = await ctx.client.request('fs.ls', { path, long: longFormat, all });
20
+ ctx.cwd = res.cwd;
21
+ console.log(formatLs(res.entries, { longFormat }));
22
+ }
23
+
24
+ async function cd(ctx, args) {
25
+ let path = args[0] || '/';
26
+ if (path === '~' || path.startsWith('~/')) {
27
+ path = '/' + path.slice(1).replace(/^\/+/, '');
28
+ }
29
+ const res = await ctx.client.request('fs.cd', { path });
30
+ ctx.cwd = res.cwd;
31
+ console.log(`Changed directory to ${res.cwd}`);
32
+ }
33
+
34
+ async function tree(ctx, args) {
35
+ let depth = 3;
36
+ const pathArgs = [];
37
+ for (let i = 0; i < args.length; i++) {
38
+ if (args[i] === '-d' || args[i] === '--depth') {
39
+ depth = parseInt(args[++i], 10) || 3;
40
+ } else {
41
+ pathArgs.push(args[i]);
42
+ }
43
+ }
44
+ const res = await ctx.client.request('fs.tree', { path: pathArgs[0] || '.', depth });
45
+ console.log(formatTree(res.children || []));
46
+ if (res.truncated) console.log('(tree truncated)');
47
+ }
48
+
49
+ async function cat(ctx, args) {
50
+ if (!args[0]) throw new Error('Usage: cat <path>');
51
+ const res = await ctx.client.request('fs.cat', { path: args[0] });
52
+ process.stdout.write(res.content);
53
+ if (!res.content.endsWith('\n')) process.stdout.write('\n');
54
+ if (res.truncated) console.error(`(truncated: file is ${humanSize(res.size)})`);
55
+ }
56
+
57
+ async function head(ctx, args) {
58
+ const { lines, path } = parseTailArgs(args, 10);
59
+ const res = await ctx.client.request('fs.head', { path, lines });
60
+ console.log(res.lines.join('\n'));
61
+ if (res.truncated) console.error('(truncated)');
62
+ }
63
+
64
+ async function tail(ctx, args) {
65
+ if (args.includes('-f') || args.includes('--follow')) {
66
+ throw new Error('Live follow is available via the "log" command: creepercli log [--grep <regex>]');
67
+ }
68
+ const { lines, path } = parseTailArgs(args, 10);
69
+ const res = await ctx.client.request('fs.tail', { path, lines });
70
+ console.log(res.lines.join('\n'));
71
+ if (res.truncated) console.error('(truncated)');
72
+ }
73
+
74
+ function parseTailArgs(args, def) {
75
+ let n = def;
76
+ let path = null;
77
+ for (let i = 0; i < args.length; i++) {
78
+ if (args[i] === '-n') n = parseInt(args[++i], 10) || def;
79
+ else path = args[i];
80
+ }
81
+ if (!path) throw new Error('Usage: <command> [-n N] <path>');
82
+ return { lines: n, path };
83
+ }
84
+
85
+ async function wc(ctx, args) {
86
+ if (!args[0]) throw new Error('Usage: wc <path>');
87
+ const res = await ctx.client.request('fs.wc', { path: args[0] });
88
+ console.log(`${res.lines} ${res.words} ${res.chars} ${humanSize(res.bytes)} ${args[0]}`);
89
+ }
90
+
91
+ async function touch(ctx, args) {
92
+ if (!args[0]) throw new Error('Usage: touch <path>');
93
+ const res = await ctx.client.request('fs.touch', { path: args[0] });
94
+ console.log(`touched ${res.path}`);
95
+ }
96
+
97
+ async function mkdir(ctx, args) {
98
+ const parents = args.includes('-p') || args.includes('--parents');
99
+ const path = args.find((a) => !a.startsWith('-'));
100
+ if (!path) throw new Error('Usage: mkdir [-p] <path>');
101
+ const res = await ctx.client.request('fs.mkdir', { path, parents });
102
+ console.log(`created ${res.path}`);
103
+ }
104
+
105
+ async function rm(ctx, args) {
106
+ const recursive = args.includes('-r') || args.includes('-R') || args.includes('--recursive');
107
+ const path = args.find((a) => !a.startsWith('-'));
108
+ if (!path) throw new Error('Usage: rm [-r] <path>');
109
+ const res = await ctx.client.request('fs.rm', { path, recursive });
110
+ console.log(`removed ${res.path}`);
111
+ }
112
+
113
+ async function cp(ctx, args) {
114
+ const recursive = args.includes('-r') || args.includes('-R') || args.includes('--recursive');
115
+ const rest = args.filter((a) => !a.startsWith('-'));
116
+ if (rest.length < 2) throw new Error('Usage: cp [-r] <src> <dst>');
117
+ const res = await ctx.client.request('fs.cp', { src: rest[0], dst: rest[1], recursive });
118
+ console.log(`copied ${res.src} -> ${res.dst}`);
119
+ }
120
+
121
+ async function mv(ctx, args) {
122
+ if (args.length < 2) throw new Error('Usage: mv <src> <dst>');
123
+ const res = await ctx.client.request('fs.mv', { src: args[0], dst: args[1] });
124
+ console.log(`moved ${res.src} -> ${res.dst}`);
125
+ }
126
+
127
+ async function info(ctx, args) {
128
+ if (!args[0]) throw new Error('Usage: info <path>');
129
+ const res = await ctx.client.request('fs.info', { path: args[0] });
130
+ console.log(formatInfo(res));
131
+ }
132
+
133
+ async function edit(ctx, args) {
134
+ if (!args[0]) throw new Error('Usage: edit <path>');
135
+ return editFile(ctx, args[0]);
136
+ }
137
+
138
+ module.exports = { pwd, ls, cd, tree, cat, head, tail, wc, touch, mkdir, rm, cp, mv, info, edit };
@@ -1,139 +1,129 @@
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 fsCmds = require('./fs');
20
- const searchCmds = require('./search');
21
- const execCmds = require('./exec');
22
- const monitorCmds = require('./monitor');
23
- const authCmds = require('./auth');
24
- const { cpush, cpull, csync } = require('../transfer');
25
- const { friendly } = require('../protocol');
26
-
27
- const COMMANDS = [
28
- { name: 'pwd', run: fsCmds.pwd, desc: 'Print working directory' },
29
- { name: 'ls', run: fsCmds.ls, desc: 'List directory (flags: -l, -a)' },
30
- { name: 'cd', run: fsCmds.cd, desc: 'Change directory' },
31
- { name: 'tree', run: fsCmds.tree, desc: 'Show directory tree (flag: --depth N)' },
32
- { name: 'cat', run: fsCmds.cat, desc: 'Print file contents' },
33
- { name: 'head', run: fsCmds.head, desc: 'Print first lines of a file (-n N)' },
34
- { name: 'tail', run: fsCmds.tail, desc: 'Print last lines of a file (-n N)' },
35
- { name: 'wc', run: fsCmds.wc, desc: 'Count lines/words/chars of a file' },
36
- { name: 'touch', run: fsCmds.touch, desc: 'Create or update a file' },
37
- { name: 'mkdir', run: fsCmds.mkdir, desc: 'Create directory (flag: -p)' },
38
- { name: 'rm', run: fsCmds.rm, desc: 'Remove file/directory (flag: -r)' },
39
- { name: 'cp', run: fsCmds.cp, desc: 'Copy file/directory (flag: -r)' },
40
- { name: 'mv', run: fsCmds.mv, desc: 'Move/rename a file' },
41
- { name: 'info', run: fsCmds.info, desc: 'Show file info (perms, size, mtime)' },
42
- { name: 'edit', run: fsCmds.edit, desc: 'Edit a remote file in your local $EDITOR' },
43
- { name: 'grep', run: searchCmds.grep, desc: 'Search file contents (flags: -i, --max-depth N)' },
44
- { name: 'find', run: searchCmds.find, desc: 'Find files by glob (flag: --glob "**/*.jar")' },
45
- { name: 'exec', run: execCmds.exec, desc: 'Run a Minecraft console command (allowlisted)' },
46
- { name: 'say', run: execCmds.say, desc: 'Shortcut for "exec say ..."' },
47
- { name: 'restart', run: execCmds.restart, desc: 'Shortcut for "exec restart"' },
48
- { name: 'stats', run: monitorCmds.stats, desc: 'Server CPU/RAM/disk stats' },
49
- { name: 'tps', run: monitorCmds.tps, desc: 'Server TPS (1m/5m/15m)' },
50
- { name: 'log', run: monitorCmds.log, desc: 'Follow the server console (flag: --grep <regex>)' },
51
- { name: 'top', run: monitorCmds.top, desc: 'Live dashboard (q to quit)' },
52
- { name: 'cpush', run: cpush, desc: 'Upload a local file to the server' },
53
- { name: 'cpull', run: cpull, desc: 'Download a server file locally' },
54
- { name: 'csync', run: csync, desc: 'Synchronize local/remote directories (flag: --yes)' },
55
- { name: 'login', run: authCmds.login, desc: 'Authenticate and store a session token' },
56
- { name: 'logout', run: authCmds.logout, desc: 'Invalidate session and clear local credentials' },
57
- { name: 'passwd', run: authCmds.passwd, desc: 'Change your password' },
58
- { name: 'totp', run: authCmds.totp, desc: 'Manage 2FA: totp setup | totp disable' },
59
- { name: 'whoami', run: authCmds.whoami, desc: 'Show current session info' },
60
- { name: 'ping', run: authCmds.ping, desc: 'Check server connectivity' },
61
- { name: 'help', run: help, desc: 'Show this help' },
62
- { name: 'exit', run: exitCmd, desc: 'Leave the REPL' },
63
- { name: 'quit', run: exitCmd, desc: 'Leave the REPL' },
64
- { name: 'q', run: exitCmd, desc: 'Leave the REPL' },
65
- ];
66
-
67
- function find(name) {
68
- return COMMANDS.find((c) => c.name === name);
69
- }
70
-
1
+
2
+
3
+ 'use strict';
4
+
5
+ const fsCmds = require('./fs');
6
+ const searchCmds = require('./search');
7
+ const execCmds = require('./exec');
8
+ const monitorCmds = require('./monitor');
9
+ const authCmds = require('./auth');
10
+ const { cpush, cpull, csync } = require('../transfer');
11
+ const { friendly } = require('../protocol');
12
+
13
+ const COMMANDS = [
14
+ { name: 'pwd', run: fsCmds.pwd, desc: 'Print working directory' },
15
+ { name: 'ls', run: fsCmds.ls, desc: 'List directory (flags: -l, -a)' },
16
+ { name: 'cd', run: fsCmds.cd, desc: 'Change directory' },
17
+ { name: 'tree', run: fsCmds.tree, desc: 'Show directory tree (flag: --depth N)' },
18
+ { name: 'cat', run: fsCmds.cat, desc: 'Print file contents' },
19
+ { name: 'head', run: fsCmds.head, desc: 'Print first lines of a file (-n N)' },
20
+ { name: 'tail', run: fsCmds.tail, desc: 'Print last lines of a file (-n N)' },
21
+ { name: 'wc', run: fsCmds.wc, desc: 'Count lines/words/chars of a file' },
22
+ { name: 'touch', run: fsCmds.touch, desc: 'Create or update a file' },
23
+ { name: 'mkdir', run: fsCmds.mkdir, desc: 'Create directory (flag: -p)' },
24
+ { name: 'rm', run: fsCmds.rm, desc: 'Remove file/directory (flag: -r)' },
25
+ { name: 'cp', run: fsCmds.cp, desc: 'Copy file/directory (flag: -r)' },
26
+ { name: 'mv', run: fsCmds.mv, desc: 'Move/rename a file' },
27
+ { name: 'info', run: fsCmds.info, desc: 'Show file info (perms, size, mtime)' },
28
+ { name: 'edit', run: fsCmds.edit, desc: 'Edit a remote file in your local $EDITOR' },
29
+ { name: 'grep', run: searchCmds.grep, desc: 'Search file contents (flags: -i, --max-depth N)' },
30
+ { name: 'find', run: searchCmds.find, desc: 'Find files by glob (flag: --glob "**/*.jar")' },
31
+ { name: 'exec', run: execCmds.exec, desc: 'Run a Minecraft console command (allowlisted)' },
32
+ { name: 'say', run: execCmds.say, desc: 'Shortcut for "exec say ..."' },
33
+ { name: 'restart', run: execCmds.restart, desc: 'Shortcut for "exec restart"' },
34
+ { name: 'stats', run: monitorCmds.stats, desc: 'Server CPU/RAM/disk stats' },
35
+ { name: 'tps', run: monitorCmds.tps, desc: 'Server TPS (1m/5m/15m)' },
36
+ { name: 'log', run: monitorCmds.log, desc: 'Follow the server console (flag: --grep <regex>)' },
37
+ { name: 'top', run: monitorCmds.top, desc: 'Live dashboard (q to quit)' },
38
+ { name: 'cpush', run: cpush, desc: 'Upload a local file to the server' },
39
+ { name: 'cpull', run: cpull, desc: 'Download a server file locally' },
40
+ { name: 'csync', run: csync, desc: 'Synchronize local/remote directories (flag: --yes)' },
41
+ { name: 'login', run: authCmds.login, desc: 'Authenticate and store a session token' },
42
+ { name: 'logout', run: authCmds.logout, desc: 'Invalidate session and clear local credentials' },
43
+ { name: 'passwd', run: authCmds.passwd, desc: 'Change your password' },
44
+ { name: 'totp', run: authCmds.totp, desc: 'Manage 2FA: totp setup | totp disable' },
45
+ { name: 'whoami', run: authCmds.whoami, desc: 'Show current session info' },
46
+ { name: 'ping', run: authCmds.ping, desc: 'Check server connectivity' },
47
+ { name: 'help', run: help, desc: 'Show this help' },
48
+ { name: 'exit', run: exitCmd, desc: 'Leave the REPL' },
49
+ { name: 'quit', run: exitCmd, desc: 'Leave the REPL' },
50
+ { name: 'q', run: exitCmd, desc: 'Leave the REPL' },
51
+ ];
52
+
53
+ function find(name) {
54
+ return COMMANDS.find((c) => c.name === name);
55
+ }
56
+
71
57
  function splitArgs(input) {
72
- const args = [];
73
- let cur = '';
74
- let quote = null;
75
- let escaped = false;
76
- for (const ch of input) {
77
- if (escaped) {
78
- cur += ch;
79
- escaped = false;
58
+ const args = [];
59
+ let cur = '';
60
+ let quote = null;
61
+ let escaped = false;
62
+ for (let i = 0; i < input.length; i++) {
63
+ const ch = input[i];
64
+ if (escaped) {
65
+ cur += ch;
66
+ escaped = false;
80
67
  } else if (ch === '\\') {
81
- escaped = true;
82
- } else if (quote) {
83
- if (ch === quote) quote = null;
68
+ const next = input[i + 1];
69
+ const escapable = next != null && (next === '\\' || next === '"' || next === "'" || /\s/.test(next));
70
+ if (escapable) escaped = true;
84
71
  else cur += ch;
85
- } else if (ch === '"' || ch === "'") {
86
- quote = ch;
87
- } else if (/\s/.test(ch)) {
88
- if (cur) {
89
- args.push(cur);
90
- cur = '';
91
- }
92
- } else {
93
- cur += ch;
94
- }
95
- }
96
- if (escaped) cur += '\\';
97
- if (cur) args.push(cur);
98
- return args;
99
- }
100
-
101
- async function runCommand(ctx, input) {
102
- let trimmed = input.trim();
103
- if (!trimmed || trimmed.startsWith('#')) return 0;
104
-
105
- // Automatically strip redundant "creepercli" or "creepercli.exe" prefix if typed inside the REPL
106
- if (trimmed.startsWith('creepercli.exe ')) {
107
- trimmed = trimmed.slice(15).trim();
108
- } else if (trimmed.startsWith('creepercli ')) {
109
- trimmed = trimmed.slice(11).trim();
110
- }
111
-
112
- const args = splitArgs(trimmed);
113
- const cmd = find(args[0]);
114
- if (!cmd) {
115
- console.error(`Unknown command: ${args[0]} (try "help")`);
116
- return 1;
117
- }
118
- try {
119
- const rc = await cmd.run(ctx, args.slice(1));
120
- return rc == null ? 0 : rc;
121
- } catch (err) {
122
- console.error(`Error: ${friendly(err)}`);
123
- return 1;
124
- }
125
- }
126
-
127
- function help() {
128
- const width = Math.max(...COMMANDS.map((c) => c.name.length)) + 2;
129
- for (const c of COMMANDS) {
130
- console.log(c.name.padEnd(width) + c.desc);
131
- }
132
- }
133
-
134
- function exitCmd(ctx) {
135
- ctx.exit = true;
136
- return 0;
137
- }
138
-
139
- module.exports = { COMMANDS, find, splitArgs, runCommand, help };
72
+ } else if (quote) {
73
+ if (ch === quote) quote = null;
74
+ else cur += ch;
75
+ } else if (ch === '"' || ch === "'") {
76
+ quote = ch;
77
+ } else if (/\s/.test(ch)) {
78
+ if (cur) {
79
+ args.push(cur);
80
+ cur = '';
81
+ }
82
+ } else {
83
+ cur += ch;
84
+ }
85
+ }
86
+ if (escaped) cur += '\\';
87
+ if (cur) args.push(cur);
88
+ return args;
89
+ }
90
+
91
+ async function runCommand(ctx, input) {
92
+ let trimmed = input.trim();
93
+ if (!trimmed || trimmed.startsWith('#')) return 0;
94
+
95
+
96
+ if (trimmed.startsWith('creepercli.exe ')) {
97
+ trimmed = trimmed.slice(15).trim();
98
+ } else if (trimmed.startsWith('creepercli ')) {
99
+ trimmed = trimmed.slice(11).trim();
100
+ }
101
+
102
+ const args = splitArgs(trimmed);
103
+ const cmd = find(args[0]);
104
+ if (!cmd) {
105
+ console.error(`Unknown command: ${args[0]} (try "help")`);
106
+ return 1;
107
+ }
108
+ try {
109
+ const rc = await cmd.run(ctx, args.slice(1));
110
+ return rc == null ? 0 : rc;
111
+ } catch (err) {
112
+ console.error(`Error: ${friendly(err)}`);
113
+ return 1;
114
+ }
115
+ }
116
+
117
+ function help() {
118
+ const width = Math.max(...COMMANDS.map((c) => c.name.length)) + 2;
119
+ for (const c of COMMANDS) {
120
+ console.log(c.name.padEnd(width) + c.desc);
121
+ }
122
+ }
123
+
124
+ function exitCmd(ctx) {
125
+ ctx.exit = true;
126
+ return 0;
127
+ }
128
+
129
+ module.exports = { COMMANDS, find, splitArgs, runCommand, help };