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.
Files changed (44) hide show
  1. package/CHANGELOG.md +439 -0
  2. package/LICENSE +22 -0
  3. package/README.md +401 -0
  4. package/images/icon.png +0 -0
  5. package/out/ansi.js +123 -0
  6. package/out/argumentHint.js +43 -0
  7. package/out/bukkitHelpParsing.js +62 -0
  8. package/out/cli.js +253 -0
  9. package/out/cliConfig.js +141 -0
  10. package/out/commandLine.js +28 -0
  11. package/out/commandSuggestions.js +202 -0
  12. package/out/commandTree.js +46 -0
  13. package/out/commandTreeCache.js +171 -0
  14. package/out/commandTreeCrawler.js +583 -0
  15. package/out/commandTreeParsingBrigadier.js +426 -0
  16. package/out/commandTreeParsingBukkit.js +116 -0
  17. package/out/commandTreeSuggestions.js +142 -0
  18. package/out/completionBackend.js +94 -0
  19. package/out/completionEngine.js +376 -0
  20. package/out/completionQueries.js +86 -0
  21. package/out/completionsBackend.js +97 -0
  22. package/out/connectionManager.js +209 -0
  23. package/out/displayArgumentHint.js +43 -0
  24. package/out/displayCommandTree.js +115 -0
  25. package/out/displaySuggestion.js +282 -0
  26. package/out/extension.js +190 -0
  27. package/out/helpTextParsing.js +445 -0
  28. package/out/historySearch.js +46 -0
  29. package/out/historyStore.js +126 -0
  30. package/out/lineEditor.js +525 -0
  31. package/out/localCommandTree.js +541 -0
  32. package/out/logger.js +14 -0
  33. package/out/minercon +253 -0
  34. package/out/pager.js +168 -0
  35. package/out/pagination.js +142 -0
  36. package/out/rconClient.js +97 -0
  37. package/out/rconConnectionManager.js +238 -0
  38. package/out/rconProtocol.js +421 -0
  39. package/out/rconSession.js +920 -0
  40. package/out/rconTerminal.js +80 -0
  41. package/out/suggestionDisplay.js +286 -0
  42. package/out/terminalOutput.js +110 -0
  43. package/out/unpaginate.js +30 -0
  44. 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
@@ -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