minercon 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +439 -0
- package/LICENSE +22 -0
- package/README.md +401 -0
- package/images/icon.png +0 -0
- package/out/ansi.js +123 -0
- package/out/argumentHint.js +43 -0
- package/out/bukkitHelpParsing.js +62 -0
- package/out/cli.js +253 -0
- package/out/cliConfig.js +141 -0
- package/out/commandLine.js +28 -0
- package/out/commandSuggestions.js +202 -0
- package/out/commandTree.js +46 -0
- package/out/commandTreeCache.js +171 -0
- package/out/commandTreeCrawler.js +583 -0
- package/out/commandTreeParsingBrigadier.js +426 -0
- package/out/commandTreeParsingBukkit.js +116 -0
- package/out/commandTreeSuggestions.js +142 -0
- package/out/completionBackend.js +94 -0
- package/out/completionEngine.js +376 -0
- package/out/completionQueries.js +86 -0
- package/out/completionsBackend.js +97 -0
- package/out/connectionManager.js +209 -0
- package/out/displayArgumentHint.js +43 -0
- package/out/displayCommandTree.js +115 -0
- package/out/displaySuggestion.js +282 -0
- package/out/extension.js +190 -0
- package/out/helpTextParsing.js +445 -0
- package/out/historySearch.js +46 -0
- package/out/historyStore.js +126 -0
- package/out/lineEditor.js +525 -0
- package/out/localCommandTree.js +541 -0
- package/out/logger.js +14 -0
- package/out/minercon +253 -0
- package/out/pager.js +168 -0
- package/out/pagination.js +142 -0
- package/out/rconClient.js +97 -0
- package/out/rconConnectionManager.js +238 -0
- package/out/rconProtocol.js +421 -0
- package/out/rconSession.js +920 -0
- package/out/rconTerminal.js +80 -0
- package/out/suggestionDisplay.js +286 -0
- package/out/terminalOutput.js +110 -0
- package/out/unpaginate.js +30 -0
- package/package.json +138 -0
package/out/cli.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// src/cli.ts — standalone CLI entry point for the Minercon terminal.
|
|
4
|
+
// Compiles to out/cli.js; the build script copies it to out/minercon.
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const util_1 = require("util");
|
|
43
|
+
const consola_1 = require("consola");
|
|
44
|
+
const prompts_1 = require("@clack/prompts");
|
|
45
|
+
const rconClient_1 = require("./rconClient");
|
|
46
|
+
const rconSession_1 = require("./rconSession");
|
|
47
|
+
const cliConfig_1 = require("./cliConfig");
|
|
48
|
+
// ── Config file ──────────────────────────────────────────────────────────────
|
|
49
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'minercon');
|
|
50
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
51
|
+
// ── Prompt cancellation ──────────────────────────────────────────────────────
|
|
52
|
+
/** Unwraps a clack prompt result, exiting cleanly if the user cancelled (Ctrl+C/Esc). */
|
|
53
|
+
function unwrap(result) {
|
|
54
|
+
if ((0, prompts_1.isCancel)(result)) {
|
|
55
|
+
(0, prompts_1.cancel)('Cancelled.');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
61
|
+
async function main() {
|
|
62
|
+
const { values, positionals } = (0, util_1.parseArgs)({
|
|
63
|
+
args: process.argv.slice(2),
|
|
64
|
+
options: {
|
|
65
|
+
password: { type: 'string', short: 'p' },
|
|
66
|
+
save: { type: 'boolean', default: false },
|
|
67
|
+
'log-file': { type: 'string' },
|
|
68
|
+
'log-level': { type: 'string' },
|
|
69
|
+
'history-size': { type: 'string' },
|
|
70
|
+
'no-plugin': { type: 'boolean', default: false },
|
|
71
|
+
'no-unpaginate': { type: 'boolean', default: false },
|
|
72
|
+
'no-pager': { type: 'boolean', default: false },
|
|
73
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
74
|
+
version: { type: 'boolean', short: 'V', default: false },
|
|
75
|
+
},
|
|
76
|
+
allowPositionals: true,
|
|
77
|
+
strict: false,
|
|
78
|
+
});
|
|
79
|
+
if (values.version) {
|
|
80
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
81
|
+
process.stdout.write(`minercon ${pkg.version}\n`);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
if (values.help) {
|
|
85
|
+
process.stdout.write([
|
|
86
|
+
'Usage: minercon [host] [port] [options]',
|
|
87
|
+
'',
|
|
88
|
+
'Options:',
|
|
89
|
+
' -p, --password <pw> RCON password (also: MCRCON_PASSWORD env var)',
|
|
90
|
+
' --save Save host/port/history-size to ~/.config/minercon/config.json',
|
|
91
|
+
' --log-file <path> Write log output to a file instead of the console',
|
|
92
|
+
' --log-level <level> consola log level, e.g. debug, info, warn, error (default: info)',
|
|
93
|
+
' --history-size <n> Number of commands to remember in history (default: 100)',
|
|
94
|
+
' --no-plugin Skip the server-side tab-complete plugin probe (manual testing only;',
|
|
95
|
+
' not persisted to config)',
|
|
96
|
+
' --no-unpaginate Do not request unpaginated output via the plugin (keep server pages)',
|
|
97
|
+
' --no-pager Do not page tall output; print it all at once',
|
|
98
|
+
' -V, --version Print version and exit',
|
|
99
|
+
' -h, --help Show this help',
|
|
100
|
+
'',
|
|
101
|
+
'Environment:',
|
|
102
|
+
' MCRCON_PASSWORD RCON password (used if --password is not given)',
|
|
103
|
+
' MCRCON_LOG_FILE Log file path (used if --log-file is not given)',
|
|
104
|
+
' MCRCON_LOG_LEVEL Log level (used if --log-level is not given)',
|
|
105
|
+
' MCRCON_HISTORY_SIZE History size (used if --history-size is not given)',
|
|
106
|
+
' MCRCON_UNPAGINATE Set to 0 to disable unpaginated output (default on)',
|
|
107
|
+
' MCRCON_PAGER Set to 0 to disable the output pager (default on)',
|
|
108
|
+
'',
|
|
109
|
+
].join('\n'));
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
if (!process.stdin.isTTY) {
|
|
113
|
+
process.stderr.write('Error: minercon is an interactive terminal and does not support piped input\n');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const logLevelResolution = (0, cliConfig_1.resolveLogLevel)(values['log-level'], process.env['MCRCON_LOG_LEVEL']);
|
|
117
|
+
if ('error' in logLevelResolution) {
|
|
118
|
+
process.stderr.write(`Error: ${logLevelResolution.error}\n`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const logFilePath = values['log-file'] ?? process.env['MCRCON_LOG_FILE'];
|
|
122
|
+
const logStream = logFilePath
|
|
123
|
+
? fs.createWriteStream(logFilePath, { flags: 'a' })
|
|
124
|
+
: undefined;
|
|
125
|
+
(0, prompts_1.updateSettings)({ withGuide: false });
|
|
126
|
+
const logger = (0, consola_1.createConsola)({
|
|
127
|
+
level: logLevelResolution.level,
|
|
128
|
+
...(logStream ? { stdout: logStream, stderr: logStream } : {}),
|
|
129
|
+
});
|
|
130
|
+
const savedConfig = (0, cliConfig_1.readConfig)(CONFIG_FILE);
|
|
131
|
+
// Resolve host
|
|
132
|
+
let host = (0, cliConfig_1.resolveHost)(positionals[0], savedConfig);
|
|
133
|
+
if (!host) {
|
|
134
|
+
host = unwrap(await (0, prompts_1.text)({
|
|
135
|
+
message: 'RCON host (e.g. 127.0.0.1):',
|
|
136
|
+
validate: (value) => {
|
|
137
|
+
if (!value || value.trim() === '') {
|
|
138
|
+
return 'host is required';
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
})).trim();
|
|
142
|
+
}
|
|
143
|
+
// Resolve port
|
|
144
|
+
let port;
|
|
145
|
+
const portResolution = (0, cliConfig_1.resolvePort)(positionals[1], savedConfig);
|
|
146
|
+
if (portResolution && 'error' in portResolution) {
|
|
147
|
+
process.stderr.write(`Error: ${portResolution.error}\n`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
else if (portResolution) {
|
|
151
|
+
port = portResolution.port;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const raw = unwrap(await (0, prompts_1.text)({
|
|
155
|
+
message: 'RCON port',
|
|
156
|
+
placeholder: '25575',
|
|
157
|
+
defaultValue: '25575',
|
|
158
|
+
validate: (value) => {
|
|
159
|
+
if (value && (0, cliConfig_1.parsePort)(value) === null) {
|
|
160
|
+
return `invalid port: ${value}`;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
}));
|
|
164
|
+
const parsed = (0, cliConfig_1.parsePort)(raw);
|
|
165
|
+
if (parsed === null) {
|
|
166
|
+
process.stderr.write(`Error: invalid port: ${raw}\n`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
port = parsed;
|
|
170
|
+
}
|
|
171
|
+
// Resolve password — never saved to disk
|
|
172
|
+
let password = (0, cliConfig_1.resolvePassword)(values.password, process.env['MCRCON_PASSWORD']);
|
|
173
|
+
if (!password) {
|
|
174
|
+
password = unwrap(await (0, prompts_1.password)({ message: `RCON password for ${host}:${port}:` }));
|
|
175
|
+
}
|
|
176
|
+
// Resolve history size
|
|
177
|
+
const historySizeResolution = (0, cliConfig_1.resolveHistorySize)(values['history-size'], process.env['MCRCON_HISTORY_SIZE'], savedConfig);
|
|
178
|
+
if ('error' in historySizeResolution) {
|
|
179
|
+
process.stderr.write(`Error: ${historySizeResolution.error}\n`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
const historySize = historySizeResolution.historySize;
|
|
183
|
+
if (values.save) {
|
|
184
|
+
(0, cliConfig_1.writeConfig)(CONFIG_FILE, { host, port, historySize });
|
|
185
|
+
logger.info(`Saved ${host}:${port} (history size ${historySize}) to ${CONFIG_FILE}`);
|
|
186
|
+
}
|
|
187
|
+
// ── Establish connection ──────────────────────────────────────────────────
|
|
188
|
+
const controller = new rconClient_1.RconController(host, port, password, logger);
|
|
189
|
+
logger.info(`Connecting to ${host}:${port}...`);
|
|
190
|
+
try {
|
|
191
|
+
await controller.connect();
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
logger.error(`Failed to connect: ${err}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
// ── Build session host ────────────────────────────────────────────────────
|
|
198
|
+
let pasteboard = '';
|
|
199
|
+
// Cache lives in the same config dir as config.json
|
|
200
|
+
const cacheDir = CONFIG_DIR;
|
|
201
|
+
const sessionHost = {
|
|
202
|
+
write: (text) => process.stdout.write(text),
|
|
203
|
+
close: (code) => {
|
|
204
|
+
teardown();
|
|
205
|
+
process.exit(code);
|
|
206
|
+
},
|
|
207
|
+
clipboard: {
|
|
208
|
+
readText: () => Promise.resolve(pasteboard),
|
|
209
|
+
writeText: (text) => { pasteboard = text; return Promise.resolve(); },
|
|
210
|
+
},
|
|
211
|
+
cacheDir,
|
|
212
|
+
dimensions: () => process.stdout.columns && process.stdout.rows
|
|
213
|
+
? { columns: process.stdout.columns, rows: process.stdout.rows }
|
|
214
|
+
: undefined,
|
|
215
|
+
historySize,
|
|
216
|
+
disablePlugin: values['no-plugin'],
|
|
217
|
+
// Both default-on: disabled only by their --no-* flag or env var set to '0'.
|
|
218
|
+
unpaginateOutput: !values['no-unpaginate'] && process.env['MCRCON_UNPAGINATE'] !== '0',
|
|
219
|
+
terminalPager: !values['no-pager'] && process.env['MCRCON_PAGER'] !== '0',
|
|
220
|
+
logToFile: logFilePath !== undefined,
|
|
221
|
+
};
|
|
222
|
+
const session = new rconSession_1.RconSession(controller, host, port, password, logger, sessionHost);
|
|
223
|
+
// ── TTY / signal setup ────────────────────────────────────────────────────
|
|
224
|
+
let tornDown = false;
|
|
225
|
+
function teardown() {
|
|
226
|
+
if (tornDown) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
tornDown = true;
|
|
230
|
+
process.stdin.setRawMode(false);
|
|
231
|
+
process.stdin.pause();
|
|
232
|
+
session.close();
|
|
233
|
+
}
|
|
234
|
+
process.on('exit', teardown);
|
|
235
|
+
process.on('SIGINT', () => { teardown(); process.exit(0); });
|
|
236
|
+
process.on('SIGTERM', () => { teardown(); process.exit(0); });
|
|
237
|
+
process.stdin.setRawMode(true);
|
|
238
|
+
process.stdin.resume();
|
|
239
|
+
process.stdin.setEncoding('utf8');
|
|
240
|
+
process.stdout.on('resize', () => {
|
|
241
|
+
// dimensions() reads process.stdout.columns/rows live — nothing to do here
|
|
242
|
+
});
|
|
243
|
+
process.stdin.on('data', (chunk) => {
|
|
244
|
+
session.handleInput(chunk);
|
|
245
|
+
});
|
|
246
|
+
// Open the session (writes the welcome banner and starts the plugin probe)
|
|
247
|
+
session.open();
|
|
248
|
+
}
|
|
249
|
+
main().catch((err) => {
|
|
250
|
+
process.stderr.write(`Error: ${err}\n`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
});
|
|
253
|
+
//# sourceMappingURL=cli.js.map
|
package/out/cliConfig.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/cliConfig.ts
|
|
3
|
+
//
|
|
4
|
+
// Pure helpers for the CLI's connection-config resolution: the saved
|
|
5
|
+
// host/port config file, port validation, and the precedence rules for
|
|
6
|
+
// host/port/password (CLI args → saved config / env var → prompt). Kept
|
|
7
|
+
// separate from cli.ts (which wires these into argv parsing, prompts, and
|
|
8
|
+
// process I/O) so the precedence logic can be unit-tested without spawning a
|
|
9
|
+
// process or touching stdin/stdout.
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.readConfig = readConfig;
|
|
45
|
+
exports.writeConfig = writeConfig;
|
|
46
|
+
exports.parsePort = parsePort;
|
|
47
|
+
exports.resolveHost = resolveHost;
|
|
48
|
+
exports.resolvePort = resolvePort;
|
|
49
|
+
exports.resolvePassword = resolvePassword;
|
|
50
|
+
exports.parseHistorySize = parseHistorySize;
|
|
51
|
+
exports.resolveHistorySize = resolveHistorySize;
|
|
52
|
+
exports.resolveLogLevel = resolveLogLevel;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const consola_1 = require("consola");
|
|
56
|
+
function readConfig(configFile) {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function writeConfig(configFile, cfg) {
|
|
65
|
+
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
66
|
+
fs.writeFileSync(configFile, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
67
|
+
}
|
|
68
|
+
/** Parses a port string, returning null if it isn't a valid TCP port (1-65535). */
|
|
69
|
+
function parsePort(raw) {
|
|
70
|
+
const port = parseInt(raw, 10);
|
|
71
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return port;
|
|
75
|
+
}
|
|
76
|
+
/** Resolves the RCON host from a positional CLI arg and the saved config, or undefined if the user must be prompted. */
|
|
77
|
+
function resolveHost(positionalHost, savedConfig) {
|
|
78
|
+
return positionalHost || savedConfig.host;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolves the RCON port from a positional CLI arg and the saved config.
|
|
82
|
+
* - `{ port }` if resolved from the arg or the saved config
|
|
83
|
+
* - `{ error }` if a positional port arg was given but isn't a valid port
|
|
84
|
+
* - `undefined` if the user must be prompted (with a default of 25575)
|
|
85
|
+
*/
|
|
86
|
+
function resolvePort(positionalPort, savedConfig) {
|
|
87
|
+
if (positionalPort !== undefined) {
|
|
88
|
+
const port = parsePort(positionalPort);
|
|
89
|
+
return port !== null ? { port } : { error: `invalid port: ${positionalPort}` };
|
|
90
|
+
}
|
|
91
|
+
if (savedConfig.port !== undefined) {
|
|
92
|
+
return { port: savedConfig.port };
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/** Resolves the RCON password from the --password flag and MCRCON_PASSWORD env var, or undefined if the user must be prompted. */
|
|
97
|
+
function resolvePassword(flagPassword, envPassword) {
|
|
98
|
+
return flagPassword || envPassword || undefined;
|
|
99
|
+
}
|
|
100
|
+
/** Parses a history size string, returning null if it isn't a positive integer. */
|
|
101
|
+
function parseHistorySize(raw) {
|
|
102
|
+
const size = parseInt(raw, 10);
|
|
103
|
+
if (isNaN(size) || size < 1 || String(size) !== raw.trim()) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return size;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolves the number of history entries to remember from the --history-size
|
|
110
|
+
* flag, MCRCON_HISTORY_SIZE env var, and saved config, in that order, falling
|
|
111
|
+
* back to 100 if none are set. Returns `{ error }` if a value was given but
|
|
112
|
+
* isn't a positive integer.
|
|
113
|
+
*/
|
|
114
|
+
function resolveHistorySize(flagValue, envValue, savedConfig) {
|
|
115
|
+
const raw = flagValue || envValue;
|
|
116
|
+
if (raw !== undefined) {
|
|
117
|
+
const parsed = parseHistorySize(raw);
|
|
118
|
+
return parsed !== null ? { historySize: parsed } : { error: `invalid history size: ${raw}` };
|
|
119
|
+
}
|
|
120
|
+
if (savedConfig.historySize !== undefined) {
|
|
121
|
+
return { historySize: savedConfig.historySize };
|
|
122
|
+
}
|
|
123
|
+
return { historySize: 100 };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolves the consola log level from the --log-level flag or
|
|
127
|
+
* MCRCON_LOG_LEVEL env var, in that order, falling back to "info" if neither
|
|
128
|
+
* is set. Returns `{ error }` if a value was given but isn't one of
|
|
129
|
+
* consola's LogTypes.
|
|
130
|
+
*/
|
|
131
|
+
function resolveLogLevel(flagValue, envValue) {
|
|
132
|
+
const raw = flagValue || envValue;
|
|
133
|
+
if (raw === undefined) {
|
|
134
|
+
return { level: consola_1.LogLevels.info };
|
|
135
|
+
}
|
|
136
|
+
if (raw in consola_1.LogLevels) {
|
|
137
|
+
return { level: consola_1.LogLevels[raw] };
|
|
138
|
+
}
|
|
139
|
+
return { error: `invalid log level: ${raw} (expected one of: ${Object.keys(consola_1.LogLevels).join(', ')})` };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=cliConfig.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/commandLine.ts
|
|
3
|
+
//
|
|
4
|
+
// Splitting a *typed command invocation* into its space-separated words —
|
|
5
|
+
// the shared counterpart to commandTreeParsingBrigadier's
|
|
6
|
+
// `tokenizeParameterString`, which tokenizes a *usage/grammar string*.
|
|
7
|
+
//
|
|
8
|
+
// The distinction matters: a usage string uses `<>`/`[]`/`()` as structural
|
|
9
|
+
// metacharacters (and `tokenizeParameterString` keeps those groups whole),
|
|
10
|
+
// whereas a command line the user is typing carries those same characters as
|
|
11
|
+
// literal argument data — target selectors (`@e[type=cow]`), NBT (`{...}`),
|
|
12
|
+
// coordinates — where only whitespace separates arguments and a half-typed
|
|
13
|
+
// bracket is just text. So command lines are split here, plainly on
|
|
14
|
+
// whitespace, and never run through the grammar tokenizer.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.splitCommandLine = splitCommandLine;
|
|
17
|
+
/**
|
|
18
|
+
* Split a typed command line into its words, dropping a single leading `/`
|
|
19
|
+
* if present. Callers that care whether the input *is* a command (starts with
|
|
20
|
+
* `/`) should check that themselves; this only parses the words out.
|
|
21
|
+
*/
|
|
22
|
+
function splitCommandLine(input) {
|
|
23
|
+
const hasTrailingSpace = input.endsWith(' ');
|
|
24
|
+
const body = input.trim().replace(/^\//, '');
|
|
25
|
+
const parts = body.split(/\s+/).filter(p => p.length > 0);
|
|
26
|
+
return { parts, hasTrailingSpace };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=commandLine.js.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/commandSuggestions.ts
|
|
3
|
+
//
|
|
4
|
+
// Pure suggestion generation: given the command tree `LocalCommandTree`
|
|
5
|
+
// builds and the user's current input line, work out what to suggest next
|
|
6
|
+
// and what argument-help text to show. No state, no IO — a deterministic
|
|
7
|
+
// function of the tree and the input.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getSuggestions = getSuggestions;
|
|
10
|
+
const commandTree_1 = require("./commandTree");
|
|
11
|
+
/**
|
|
12
|
+
* Get suggestions based on current input
|
|
13
|
+
*/
|
|
14
|
+
function getSuggestions(rootCommands, isReady, input) {
|
|
15
|
+
if (!isReady) {
|
|
16
|
+
return { suggestions: [], argumentHelp: undefined };
|
|
17
|
+
}
|
|
18
|
+
const trimmed = input.trim();
|
|
19
|
+
if (!trimmed.startsWith('/')) {
|
|
20
|
+
return { suggestions: [], argumentHelp: undefined };
|
|
21
|
+
}
|
|
22
|
+
const hasTrailingSpace = input.endsWith(' ');
|
|
23
|
+
const parts = trimmed.slice(1).split(' ').filter(p => p.length > 0);
|
|
24
|
+
const commandName = parts[0];
|
|
25
|
+
// Handle root command suggestions
|
|
26
|
+
if (parts.length === 0 || (parts.length === 1 && !hasTrailingSpace)) {
|
|
27
|
+
const suggestions = Array.from(rootCommands.keys())
|
|
28
|
+
.filter(cmd => cmd.startsWith(commandName || ''))
|
|
29
|
+
.sort();
|
|
30
|
+
return { suggestions, argumentHelp: undefined };
|
|
31
|
+
}
|
|
32
|
+
// Find the command node
|
|
33
|
+
const rootNode = rootCommands.get(commandName);
|
|
34
|
+
if (!rootNode) {
|
|
35
|
+
return { suggestions: [], argumentHelp: undefined };
|
|
36
|
+
}
|
|
37
|
+
// Navigate through the parameter tree
|
|
38
|
+
let currentParameters = rootNode.members || [];
|
|
39
|
+
let paramIndex = 1; // Start after the command name
|
|
40
|
+
// The command path consumed so far (root command + any literal/subcommand
|
|
41
|
+
// tokens navigated past) - see SuggestionResult.commandPath.
|
|
42
|
+
const pathParts = [commandName];
|
|
43
|
+
// Navigate through completed parts (not including what we're currently typing)
|
|
44
|
+
const partsToNavigate = hasTrailingSpace ? parts.length : parts.length - 1;
|
|
45
|
+
while (paramIndex < partsToNavigate && currentParameters.length > 0) {
|
|
46
|
+
const currentPart = parts[paramIndex];
|
|
47
|
+
let navigated = false;
|
|
48
|
+
// Get the first parameter at this position
|
|
49
|
+
const firstParam = currentParameters[0];
|
|
50
|
+
if (firstParam.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
51
|
+
// Direct subcommand
|
|
52
|
+
if (firstParam.name === currentPart || firstParam.literal === currentPart) {
|
|
53
|
+
currentParameters = firstParam.members || [];
|
|
54
|
+
navigated = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (firstParam.type === commandTree_1.ParameterType.CHOICE_LIST && firstParam.choices) {
|
|
58
|
+
// Choice list - find matching choice and navigate into it
|
|
59
|
+
for (const choice of firstParam.choices) {
|
|
60
|
+
if (choice.type === commandTree_1.ParameterType.SUBCOMMAND &&
|
|
61
|
+
(choice.name === currentPart || choice.literal === currentPart)) {
|
|
62
|
+
// IMPORTANT: Navigate into the selected choice's members
|
|
63
|
+
currentParameters = choice.members || [];
|
|
64
|
+
navigated = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
else if (choice.type === commandTree_1.ParameterType.LITERAL && choice.literal === currentPart) {
|
|
68
|
+
// For literal choices, move to next parameter position
|
|
69
|
+
currentParameters = currentParameters.slice(1);
|
|
70
|
+
navigated = true;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (firstParam.type === commandTree_1.ParameterType.LITERAL && firstParam.literal === currentPart) {
|
|
76
|
+
// Literal parameter
|
|
77
|
+
currentParameters = currentParameters.slice(1);
|
|
78
|
+
navigated = true;
|
|
79
|
+
}
|
|
80
|
+
paramIndex++;
|
|
81
|
+
if (navigated) {
|
|
82
|
+
pathParts.push(currentPart);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// It's an argument value, skip to next position
|
|
86
|
+
currentParameters = currentParameters.slice(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Build argument help from current position
|
|
90
|
+
const argumentHelp = buildArgumentHelp(currentParameters);
|
|
91
|
+
const commandPath = pathParts.join(' ');
|
|
92
|
+
// Generate suggestions based on current position
|
|
93
|
+
let suggestions = [];
|
|
94
|
+
if (hasTrailingSpace) {
|
|
95
|
+
// We want suggestions for the NEXT parameter
|
|
96
|
+
suggestions = generateSuggestionsForNextPosition(currentParameters);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// We're typing something, get matching suggestions
|
|
100
|
+
const currentPart = parts[parts.length - 1] || '';
|
|
101
|
+
suggestions = generateSuggestionsForCurrentPart(currentParameters, currentPart);
|
|
102
|
+
}
|
|
103
|
+
return { suggestions, argumentHelp, commandPath };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generate suggestions for what we're currently typing
|
|
107
|
+
* Must handle CHOICE_LIST parameters properly
|
|
108
|
+
*/
|
|
109
|
+
function generateSuggestionsForCurrentPart(parameters, currentPart) {
|
|
110
|
+
const suggestions = [];
|
|
111
|
+
for (const param of parameters) {
|
|
112
|
+
if (param.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
113
|
+
// Direct subcommand
|
|
114
|
+
const name = param.name || param.literal || '';
|
|
115
|
+
if (name.startsWith(currentPart)) {
|
|
116
|
+
suggestions.push(name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (param.type === commandTree_1.ParameterType.CHOICE_LIST && param.choices) {
|
|
120
|
+
// Choice list - add all matching choices
|
|
121
|
+
for (const choice of param.choices) {
|
|
122
|
+
if (choice.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
123
|
+
const name = choice.name || choice.literal || '';
|
|
124
|
+
if (name.startsWith(currentPart)) {
|
|
125
|
+
suggestions.push(name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (choice.type === commandTree_1.ParameterType.LITERAL) {
|
|
129
|
+
const literal = choice.literal || '';
|
|
130
|
+
if (literal.startsWith(currentPart)) {
|
|
131
|
+
suggestions.push(literal);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (param.type === commandTree_1.ParameterType.LITERAL) {
|
|
137
|
+
const literal = param.literal || '';
|
|
138
|
+
if (literal.startsWith(currentPart)) {
|
|
139
|
+
suggestions.push(literal);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// We only process the first parameter position
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
return suggestions.sort();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate suggestions for the next parameter position
|
|
149
|
+
* Must handle CHOICE_LIST parameters properly
|
|
150
|
+
*/
|
|
151
|
+
function generateSuggestionsForNextPosition(parameters) {
|
|
152
|
+
const suggestions = [];
|
|
153
|
+
if (parameters.length === 0) {
|
|
154
|
+
return suggestions;
|
|
155
|
+
}
|
|
156
|
+
const firstParam = parameters[0];
|
|
157
|
+
if (firstParam.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
158
|
+
// Direct subcommand
|
|
159
|
+
suggestions.push(firstParam.name || firstParam.literal || '');
|
|
160
|
+
}
|
|
161
|
+
else if (firstParam.type === commandTree_1.ParameterType.CHOICE_LIST && firstParam.choices) {
|
|
162
|
+
// Choice list - add all choices as suggestions
|
|
163
|
+
for (const choice of firstParam.choices) {
|
|
164
|
+
if (choice.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
165
|
+
suggestions.push(choice.name || choice.literal || '');
|
|
166
|
+
}
|
|
167
|
+
else if (choice.type === commandTree_1.ParameterType.LITERAL) {
|
|
168
|
+
suggestions.push(choice.literal || '');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (firstParam.type === commandTree_1.ParameterType.LITERAL) {
|
|
173
|
+
suggestions.push(firstParam.literal || '');
|
|
174
|
+
}
|
|
175
|
+
// Don't suggest anything for ARGUMENT types
|
|
176
|
+
return suggestions.sort();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build argument help string from parameters
|
|
180
|
+
*/
|
|
181
|
+
function buildArgumentHelp(parameters) {
|
|
182
|
+
if (parameters.length === 0) {
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
return parameters.map(param => {
|
|
186
|
+
if (param.type === commandTree_1.ParameterType.ARGUMENT) {
|
|
187
|
+
return param.optional ? `[<${param.name}>]` : `<${param.name}>`;
|
|
188
|
+
}
|
|
189
|
+
else if (param.type === commandTree_1.ParameterType.CHOICE_LIST && param.choices) {
|
|
190
|
+
const choices = param.choices.map((c) => c.literal).join('|');
|
|
191
|
+
return `(${choices})`;
|
|
192
|
+
}
|
|
193
|
+
else if (param.type === commandTree_1.ParameterType.LITERAL) {
|
|
194
|
+
return param.literal;
|
|
195
|
+
}
|
|
196
|
+
else if (param.type === commandTree_1.ParameterType.SUBCOMMAND) {
|
|
197
|
+
return param.name; // Show subcommand name
|
|
198
|
+
}
|
|
199
|
+
return '';
|
|
200
|
+
}).join(' ');
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=commandSuggestions.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/commandTree.ts
|
|
3
|
+
//
|
|
4
|
+
// The command tree model: a discriminated union `Parameter`, used both for
|
|
5
|
+
// root commands (the entries in `CommandTreeCrawler`'s `rootCommands` map,
|
|
6
|
+
// aliased here as `CommandNode`) and for every argument/subcommand/choice
|
|
7
|
+
// nested beneath them. The `type` tag selects the variant, and each variant
|
|
8
|
+
// carries exactly the fields that apply to it - so consumers `switch` on
|
|
9
|
+
// `type` and get the right fields narrowed, with no `param.choices!` /
|
|
10
|
+
// `param.name || param.literal` defensiveness.
|
|
11
|
+
//
|
|
12
|
+
// Everything that builds it lives in commandTreeParsingBrigadier.ts/
|
|
13
|
+
// commandTreeParsingBukkit.ts; everything that reads it lives in
|
|
14
|
+
// commandTreeSuggestions.ts/displayArgumentHint.ts; `commandTreeCrawler.ts` is the
|
|
15
|
+
// stateful orchestration that ties construction and the cache together.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ParameterType = void 0;
|
|
18
|
+
exports.newCommandNode = newCommandNode;
|
|
19
|
+
exports.parameterLabel = parameterLabel;
|
|
20
|
+
var ParameterType;
|
|
21
|
+
(function (ParameterType) {
|
|
22
|
+
ParameterType["ARGUMENT"] = "argument";
|
|
23
|
+
ParameterType["LITERAL"] = "literal";
|
|
24
|
+
ParameterType["CHOICE_LIST"] = "choice_list";
|
|
25
|
+
ParameterType["SUBCOMMAND"] = "subcommand"; // subcommand with its own members
|
|
26
|
+
})(ParameterType || (exports.ParameterType = ParameterType = {}));
|
|
27
|
+
/** A fresh, not-yet-loaded root command node for `name`. */
|
|
28
|
+
function newCommandNode(name) {
|
|
29
|
+
return { type: ParameterType.SUBCOMMAND, name, optional: false, position: 0, members: [], isComplete: false };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The bare display label of a parameter: an argument/subcommand name, a
|
|
33
|
+
* literal's text, or a choice list joined with `|`. The single source of
|
|
34
|
+
* truth both display sites (`commandTreeSuggestions`, `displayCommandTree`)
|
|
35
|
+
* use to render a parameter's inner token, so neither has to narrow the
|
|
36
|
+
* union by hand.
|
|
37
|
+
*/
|
|
38
|
+
function parameterLabel(p) {
|
|
39
|
+
switch (p.type) {
|
|
40
|
+
case ParameterType.ARGUMENT: return p.name;
|
|
41
|
+
case ParameterType.LITERAL: return p.literal;
|
|
42
|
+
case ParameterType.SUBCOMMAND: return p.name;
|
|
43
|
+
case ParameterType.CHOICE_LIST: return p.choices.map(parameterLabel).join('|');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=commandTree.js.map
|