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
@@ -0,0 +1,583 @@
1
+ "use strict";
2
+ // src/commandTreeCrawler.ts
3
+ //
4
+ // The command tree for "local" (no-plugin) mode: built by crawling a
5
+ // server's `/help` and `minecraft:help` output, cached to disk between runs
6
+ // via `CommandTreeCache`, and consumed by `LocalCompletionBackend` (which
7
+ // turns it into completions/usage text via `commandTreeSuggestions.ts`'s
8
+ // `getSuggestions`). All the text parsing this crawl relies on is in
9
+ // `commandTreeParsingBrigadier.ts` as pure functions - this file is the
10
+ // stateful orchestration: deciding what to fetch, in what order, and how to
11
+ // merge and store the results. See docs/NO_PLUGIN_HELP_CRAWL.md.
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.CommandTreeCrawler = void 0;
47
+ const path = __importStar(require("path"));
48
+ const ansi_1 = require("./ansi");
49
+ const commandTree_1 = require("./commandTree");
50
+ const commandTreeParsingBrigadier_1 = require("./commandTreeParsingBrigadier");
51
+ const commandTreeParsingBukkit_1 = require("./commandTreeParsingBukkit");
52
+ const commandTreeCache_1 = require("./commandTreeCache");
53
+ const commandTreeSuggestions_1 = require("./commandTreeSuggestions");
54
+ class CommandTreeCrawler {
55
+ sendCommand;
56
+ logger;
57
+ rootCommands = new Map();
58
+ get commands() { return this.rootCommands; }
59
+ // Per-command send/recv log, populated during a fresh crawl (not cache hits).
60
+ // Keyed by root command name (no slash). Only recorded while currentLogKey is set.
61
+ commandLogs = new Map();
62
+ currentLogKey = undefined;
63
+ isLoading = false;
64
+ loadingProgress = 0;
65
+ totalCommands = 0;
66
+ isReady = false;
67
+ // Whether the `minecraft:` command namespace prefix is registered on this
68
+ // server. Paper/Spigot (Bukkit-based) accept `minecraft:help ...` and use
69
+ // it to get Brigadier-accurate `<args>` syntax for vanilla commands; pure
70
+ // Vanilla/Fabric reject it as an unknown namespace (Brigadier syntax
71
+ // error), but their plain `/help [<cmd>]` already returns full `<args>`
72
+ // syntax directly. Detected once in fetchRootCommands() and reused for
73
+ // every per-command detail fetch. See docs/NO_PLUGIN_HELP_CRAWL.md.
74
+ supportsMinecraftNamespace = true;
75
+ // For each root command, whether its summary line in the root
76
+ // `minecraft:help` dump carried no real Brigadier info (empty or the
77
+ // generic `[<args>]` placeholder). When true, `loadCommandDetails` tries
78
+ // Bukkit's `help <command>` first, since `minecraft:help <command>` is
79
+ // unlikely to do better; when false (or absent), it tries
80
+ // `minecraft:help <command>` first. Either way, the other source is only
81
+ // fetched if the first one turns out insufficient. Populated once during
82
+ // fetchRootCommands(), not persisted to the cache.
83
+ rootSummaryIsPlaceholder = new Map();
84
+ cache;
85
+ // Set for the duration of `initialize()`: receives the crawl's blow-by-blow
86
+ // narration ("Loading details for command: X", ...). When provided (no log
87
+ // file - the narration would otherwise corrupt the console's progress bar),
88
+ // narration is routed here instead of to `this.logger`. See `report()`.
89
+ onMessage;
90
+ constructor(sendCommand, logger, cacheDir, serverHost, serverPort) {
91
+ this.sendCommand = sendCommand;
92
+ this.logger = logger;
93
+ this.cache = new commandTreeCache_1.CommandTreeCache(path.join(cacheDir, 'command-cache'), serverHost, serverPort, logger, (message) => this.report(message));
94
+ }
95
+ /** Returns the send/recv pairs recorded while crawling `name`'s command details.
96
+ * Falls back to a namespaced sibling key (e.g. `minecraft:gamemode` for `gamemode`)
97
+ * since namespaced commands are crawled first and bare commands reuse their results. */
98
+ getCommandLog(name) {
99
+ const direct = this.commandLogs.get(name);
100
+ if (direct && direct.length > 0) {
101
+ return direct;
102
+ }
103
+ if (!name.includes(':')) {
104
+ const suffix = `:${name}`;
105
+ for (const [key, log] of this.commandLogs) {
106
+ if (key.endsWith(suffix) && log.length > 0) {
107
+ return log;
108
+ }
109
+ }
110
+ }
111
+ return [];
112
+ }
113
+ /** Calls `sendCommand` and records the pair under `currentLogKey` if set. */
114
+ async loggedSend(command) {
115
+ const recv = await this.sendCommand(command);
116
+ if (this.currentLogKey !== undefined) {
117
+ let log = this.commandLogs.get(this.currentLogKey);
118
+ if (!log) {
119
+ log = [];
120
+ this.commandLogs.set(this.currentLogKey, log);
121
+ }
122
+ log.push({ send: command, recv });
123
+ }
124
+ return recv;
125
+ }
126
+ /** Reports crawl narration: to `onMessage` (e.g. a progress bar's message) if set, else logged. */
127
+ report(message) {
128
+ if (this.onMessage) {
129
+ this.onMessage(message);
130
+ }
131
+ else {
132
+ this.logger.info(message);
133
+ }
134
+ }
135
+ /**
136
+ * Initialize command database
137
+ */
138
+ async initialize(onProgress, onMessage, forceRefresh = false) {
139
+ if (this.isLoading) {
140
+ return;
141
+ }
142
+ this.isLoading = true;
143
+ this.loadingProgress = 0;
144
+ this.onMessage = onMessage;
145
+ try {
146
+ // Try to load from cache first
147
+ if (!forceRefresh) {
148
+ const loaded = this.cache.load();
149
+ if (loaded) {
150
+ this.rootCommands = loaded;
151
+ onProgress?.(100, 'cache-hit');
152
+ this.isReady = true;
153
+ return;
154
+ }
155
+ }
156
+ this.commandLogs.clear();
157
+ // Aliases discovered while crawling (`<alias> -> <target>` redirect
158
+ // lines, Bukkit `Aliases:` lines) - resolved into rootCommands once
159
+ // their targets have been fully loaded, below.
160
+ const pendingAliases = new Map();
161
+ // Fetch commands from server
162
+ onProgress?.(10, 'fetching');
163
+ await this.fetchRootCommands(pendingAliases);
164
+ // Load details for each command. Namespaced commands (`minecraft:foo`,
165
+ // `bukkit:foo`, ...) are loaded first, so that their bare counterparts
166
+ // (`foo`) can reuse the already-fetched details instead of issuing a
167
+ // second, near-identical pair of `help`/`minecraft:help` round trips
168
+ // (see loadCommandDetails).
169
+ const commands = Array.from(this.rootCommands.keys())
170
+ .sort((a, b) => Number(b.includes(':')) - Number(a.includes(':')));
171
+ this.report(`Loading details for ${commands.length} commands...`);
172
+ for (let i = 0; i < commands.length; i++) {
173
+ const progress = 10 + (80 * (i / commands.length));
174
+ onProgress?.(progress, 'loading');
175
+ const node = this.rootCommands.get(commands[i]);
176
+ this.currentLogKey = commands[i];
177
+ try {
178
+ await this.loadCommandDetails(node, node.members, pendingAliases);
179
+ }
180
+ catch (error) {
181
+ this.logger.warn(`Warning: Failed to load details for ${commands[i]}: ${error}`);
182
+ }
183
+ finally {
184
+ this.currentLogKey = undefined;
185
+ }
186
+ }
187
+ // Expand aliases into rootCommands now that their targets are fully
188
+ // loaded, sharing the target's node so alias entries stay in sync.
189
+ for (const [alias, target] of pendingAliases) {
190
+ const targetNode = this.rootCommands.get(target);
191
+ if (targetNode && !this.rootCommands.has(alias)) {
192
+ this.rootCommands.set(alias, targetNode);
193
+ // Share the target's log so /tree <alias> shows the same send/recv pairs.
194
+ if (!this.commandLogs.has(alias)) {
195
+ const targetLog = this.getCommandLog(target);
196
+ if (targetLog.length > 0) {
197
+ this.commandLogs.set(alias, targetLog);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ // Save to cache
203
+ this.cache.save(this.rootCommands);
204
+ onProgress?.(100, 'complete');
205
+ this.isReady = true;
206
+ }
207
+ catch (error) {
208
+ this.logger.error(`Error initializing commands: ${error}`);
209
+ // Try to provide basic functionality even if initialization fails
210
+ this.isReady = true; // Mark as ready with whatever we have
211
+ throw error;
212
+ }
213
+ finally {
214
+ this.isLoading = false;
215
+ }
216
+ }
217
+ async fetchPaginatedCommand(command) {
218
+ let output = await this.loggedSend(command);
219
+ const match = output.match(/Help:\s+.*?\((\d+)\/(\d+)\)/i);
220
+ if (!match) {
221
+ return output;
222
+ }
223
+ else {
224
+ const pageCount = parseInt(match[2]);
225
+ this.report(`Detected paginated output: ${pageCount} pages total`);
226
+ for (let page = 2; page <= pageCount; page++) {
227
+ const pageOutput = await this.loggedSend(`${command} ${page}`);
228
+ if (pageOutput) {
229
+ this.report(`Fetched page ${page}/${pageCount} (${pageOutput.length} bytes)`);
230
+ output += pageOutput;
231
+ }
232
+ }
233
+ return output;
234
+ }
235
+ }
236
+ /**
237
+ * Fetch root commands from server
238
+ */
239
+ async fetchRootCommands(pendingAliases) {
240
+ try {
241
+ this.report('Fetching root commands with /help...');
242
+ const mcResponse = await this.sendCommand('minecraft:help');
243
+ if ((0, commandTreeParsingBrigadier_1.isUnsupportedNamespaceError)(mcResponse)) {
244
+ // Vanilla/Fabric: the `minecraft:` namespace prefix isn't
245
+ // registered. Plain `/help` (paginated) already gives a complete,
246
+ // accurate, one-shot command list with full <args> syntax.
247
+ this.report('minecraft: namespace not supported; using /help for root commands');
248
+ this.supportsMinecraftNamespace = false;
249
+ const response = await this.fetchPaginatedCommand('help');
250
+ this.report(`Help response received: ${response.length} bytes`);
251
+ if (!response || response.length === 0) {
252
+ throw new Error('Unable to fetch command list from server');
253
+ }
254
+ this.ingestHelpResponse(response, pendingAliases);
255
+ return;
256
+ }
257
+ this.supportsMinecraftNamespace = true;
258
+ this.report(`Help response received: ${mcResponse.length} bytes`);
259
+ if (!mcResponse || mcResponse.length === 0) {
260
+ this.logger.warn('Warning: Empty response from help command');
261
+ // Try alternative help format
262
+ const altResponse = await this.sendCommand('?');
263
+ if (altResponse && altResponse.length > 0) {
264
+ this.report('Using alternative help command (?)');
265
+ this.ingestHelpResponse(altResponse, pendingAliases);
266
+ }
267
+ else {
268
+ throw new Error('Unable to fetch command list from server');
269
+ }
270
+ }
271
+ else {
272
+ this.ingestHelpResponse(mcResponse, pendingAliases);
273
+ }
274
+ }
275
+ catch (error) {
276
+ this.logger.error(`Error fetching root commands: ${error}`);
277
+ // Provide fallback common commands if help fails
278
+ this.addFallbackCommands();
279
+ throw error;
280
+ }
281
+ }
282
+ /**
283
+ * Parse a help response and merge the commands/aliases it describes into
284
+ * `rootCommands`, `rootSummaryIsPlaceholder`, and `pendingAliases`.
285
+ */
286
+ ingestHelpResponse(response, pendingAliases) {
287
+ const { commands, aliases } = (0, commandTreeParsingBrigadier_1.parseHelpResponse)(response);
288
+ for (const { alias, target } of aliases) {
289
+ pendingAliases.set(alias, target);
290
+ }
291
+ for (const { name, isPlaceholder } of commands) {
292
+ this.rootCommands.set(name, (0, commandTree_1.newCommandNode)(name));
293
+ this.rootSummaryIsPlaceholder.set(name, isPlaceholder);
294
+ }
295
+ this.report(`Found ${this.rootCommands.size} root commands`);
296
+ if (commands.length === 0) {
297
+ this.logger.warn('Warning: No commands found in help response');
298
+ this.report('First few lines of response:');
299
+ (0, commandTreeParsingBrigadier_1.splitConcatenatedHelpLines)(response).split('\n').slice(0, 10).forEach(line => {
300
+ this.report(` > ${(0, ansi_1.stripColors)(line)}`);
301
+ });
302
+ // Add fallback commands
303
+ this.addFallbackCommands();
304
+ }
305
+ }
306
+ /**
307
+ * Add fallback commands if help parsing fails
308
+ */
309
+ addFallbackCommands() {
310
+ this.report('Adding common Minecraft commands as fallback...');
311
+ const commonCommands = [
312
+ 'gamemode', 'give', 'tp', 'teleport', 'kill', 'kick', 'ban', 'pardon',
313
+ 'op', 'deop', 'whitelist', 'reload', 'save-all', 'save-on', 'save-off',
314
+ 'stop', 'list', 'say', 'tell', 'msg', 'w', 'me', 'trigger', 'scoreboard',
315
+ 'effect', 'enchant', 'xp', 'experience', 'clear', 'difficulty', 'gamerule',
316
+ 'defaultgamemode', 'setworldspawn', 'spawnpoint', 'time', 'weather', 'worldborder',
317
+ 'locate', 'particle', 'playsound', 'stopsound', 'title', 'tellraw', 'help',
318
+ 'seed', 'clone', 'fill', 'setblock', 'summon', 'execute',
319
+ 'recipe', 'advancement', 'function', 'tag', 'team',
320
+ 'bossbar', 'data', 'datapack', 'debug', 'forceload', 'locatebiome', 'loot',
321
+ 'publish', 'schedule', 'spreadplayers', 'spectate'
322
+ ];
323
+ for (const cmd of commonCommands) {
324
+ if (!this.rootCommands.has(cmd)) {
325
+ this.rootCommands.set(cmd, (0, commandTree_1.newCommandNode)(cmd));
326
+ }
327
+ }
328
+ this.report(`Added ${commonCommands.length} fallback commands`);
329
+ }
330
+ /**
331
+ * Find an already-loaded root command that's a namespaced form of the bare
332
+ * `commandPath` (e.g. "minecraft:version" or "bukkit:version" for
333
+ * "version"), if any. Returns the first match in `rootCommands`'
334
+ * iteration order, or `undefined` if `commandPath` itself is namespaced or
335
+ * no such sibling exists yet.
336
+ */
337
+ findNamespacedSibling(commandPath) {
338
+ if (commandPath.includes(':')) {
339
+ return undefined;
340
+ }
341
+ for (const [name, node] of this.rootCommands) {
342
+ if (node.isComplete && name.endsWith(`:${commandPath}`) && node.members.length) {
343
+ return node;
344
+ }
345
+ }
346
+ return undefined;
347
+ }
348
+ /**
349
+ * Merge the two help sources for `commandPath`'s syntax into one
350
+ * `HelpLinesResult`. `mcResponse` (from `minecraft:help <commandPath>`,
351
+ * only fetched when `supportsMinecraftNamespace`, and sometimes skipped
352
+ * even then as an optimization - see `loadCommandDetails`) wins unless
353
+ * it's empty or the generic `[<args>]` placeholder Bukkit emits for
354
+ * non-Brigadier commands, in which case `helpResponse` (from `help
355
+ * <commandPath>`) is used instead.
356
+ *
357
+ * Which shape `helpResponse` is in follows directly from
358
+ * `this.supportsMinecraftNamespace`, not from sniffing its content: when
359
+ * `minecraft:` is supported, Paper/Spigot's `help <path>` is *always* a
360
+ * Bukkit `Description:`/`Usage:`/`Aliases:` page (even for vanilla-backed
361
+ * commands), extracted via `extractBukkitUsageLines`; when it isn't,
362
+ * vanilla/fabric's `help <path>` is *always* a flat Brigadier blob. See
363
+ * docs/NO_PLUGIN_HELP_CRAWL.md.
364
+ */
365
+ mergeHelpSources(helpResponse, mcResponse, commandPath) {
366
+ // mcResponse is always a flat Brigadier blob - never a Bukkit help page.
367
+ const mc = mcResponse !== null
368
+ ? (0, commandTreeParsingBrigadier_1.parseHelpLines)((0, commandTreeParsingBrigadier_1.splitConcatenatedHelpLines)(mcResponse), commandPath)
369
+ : { variants: new Map(), direct: null };
370
+ let result;
371
+ if (mc.direct !== null && !(0, commandTreeParsingBrigadier_1.isGenericArgsPlaceholder)(mc.direct)) {
372
+ result = { variants: new Map(), direct: mc.direct };
373
+ }
374
+ else if (mc.variants.size > 0) {
375
+ result = { variants: mc.variants, direct: null };
376
+ }
377
+ else if (this.supportsMinecraftNamespace) {
378
+ // helpResponse is always a Bukkit Description:/Usage:/Aliases: page -
379
+ // extract its Usage line(s).
380
+ result = (0, commandTreeParsingBrigadier_1.parseHelpLines)((0, commandTreeParsingBukkit_1.extractBukkitUsageLines)(helpResponse, commandPath).join('\n'), commandPath);
381
+ }
382
+ else {
383
+ // helpResponse is always a flat Brigadier blob. Like `mcResponse`,
384
+ // vanilla's `help <path>` responses for commands with multiple
385
+ // variants (team, gamerule, debug, ...) pack each `/path ...` onto one
386
+ // line with no separators - re-split the same way.
387
+ result = (0, commandTreeParsingBrigadier_1.parseHelpLines)((0, commandTreeParsingBrigadier_1.splitConcatenatedHelpLines)(helpResponse), commandPath);
388
+ }
389
+ // The generic `[<args>]` placeholder (from either source) means "no
390
+ // further arguments", not a real <args> token.
391
+ if (result.direct !== null && (0, commandTreeParsingBrigadier_1.isGenericArgsPlaceholder)(result.direct)) {
392
+ result = { variants: result.variants, direct: [] };
393
+ }
394
+ return result;
395
+ }
396
+ /**
397
+ * Load details for a command or subcommand
398
+ */
399
+ async loadCommandDetails(parent, parameters, pendingAliases) {
400
+ const commandPath = parent.name;
401
+ // A bare command (e.g. "version") and its namespaced counterparts (e.g.
402
+ // "bukkit:version", "minecraft:version") describe the same underlying
403
+ // command and have identical `help`/`minecraft:help` output - fetching
404
+ // both is wasteful. Namespaced commands are loaded first (see
405
+ // initialize()), so if one is already complete, reuse its parameters
406
+ // instead of repeating the round trips.
407
+ const sibling = this.findNamespacedSibling(commandPath);
408
+ if (sibling) {
409
+ parameters.length = 0;
410
+ parameters.push(...sibling.members);
411
+ parent.isComplete = true;
412
+ this.report(`Reusing ${sibling.name}'s details for ${commandPath}`);
413
+ return;
414
+ }
415
+ // Reported before the round trips below (rather than after) so it's
416
+ // visible while they're in flight, instead of being instantly
417
+ // overwritten by the next command's report.
418
+ this.report(`Loading details for command: ${commandPath}`);
419
+ try {
420
+ let helpResponse = '';
421
+ let mcResponse = null;
422
+ if (!this.supportsMinecraftNamespace) {
423
+ helpResponse = await this.fetchPaginatedCommand(`help ${commandPath}`);
424
+ }
425
+ else if (this.rootSummaryIsPlaceholder.get(commandPath)) {
426
+ // The root `minecraft:help` summary for this command had no real
427
+ // syntax (empty or `[<args>]`) - `minecraft:help <commandPath>` is
428
+ // unlikely to do better, so try Bukkit's `help <commandPath>` first
429
+ // and only fall back to `minecraft:help` if it doesn't pan out.
430
+ helpResponse = await this.fetchPaginatedCommand(`help ${commandPath}`);
431
+ const fromHelp = (0, commandTreeParsingBrigadier_1.parseHelpLines)((0, commandTreeParsingBukkit_1.extractBukkitUsageLines)(helpResponse, commandPath).join('\n'), commandPath);
432
+ if (!(0, commandTreeParsingBrigadier_1.hasRealUsage)(fromHelp)) {
433
+ mcResponse = await this.fetchPaginatedCommand(`minecraft:help ${commandPath}`);
434
+ }
435
+ }
436
+ else {
437
+ // The root summary already had real syntax info - `minecraft:help
438
+ // <commandPath>` is at least as detailed, so try it first and only
439
+ // fall back to Bukkit's `help <commandPath>` if it doesn't pan out.
440
+ mcResponse = await this.fetchPaginatedCommand(`minecraft:help ${commandPath}`);
441
+ const fromMc = (0, commandTreeParsingBrigadier_1.parseHelpLines)((0, commandTreeParsingBrigadier_1.splitConcatenatedHelpLines)(mcResponse), commandPath);
442
+ if (!(0, commandTreeParsingBrigadier_1.hasRealUsage)(fromMc)) {
443
+ helpResponse = await this.fetchPaginatedCommand(`help ${commandPath}`);
444
+ }
445
+ }
446
+ // Check if we got a valid response from at least one source
447
+ if (!helpResponse && !mcResponse) {
448
+ this.logger.warn(`Empty help response for: ${commandPath}`);
449
+ return;
450
+ }
451
+ // Bukkit `/help <command>` pages list aliases on their own
452
+ // `Aliases: a, b, c` line - returns [] for Brigadier blobs, which
453
+ // have no such line.
454
+ for (const alias of (0, commandTreeParsingBukkit_1.extractBukkitAliases)(helpResponse)) {
455
+ pendingAliases.set(alias, commandPath);
456
+ }
457
+ const { variants, direct } = this.mergeHelpSources(helpResponse, mcResponse, commandPath);
458
+ parameters.length = 0;
459
+ if (direct !== null) {
460
+ parameters.push(...direct);
461
+ }
462
+ else {
463
+ parameters.push(...(0, commandTreeParsingBrigadier_1.buildParameterStructureFromVariants)(variants));
464
+ }
465
+ this.report(`Loaded ${parameters.length} parameter(s) for ${commandPath}`);
466
+ // Mark as complete
467
+ parent.isComplete = true;
468
+ // Recursively load details for all subcommands
469
+ await this.loadSubcommandsIn(commandPath, parameters);
470
+ }
471
+ catch (error) {
472
+ this.logger.error(`Error loading details for ${commandPath}: ${error}`);
473
+ // Mark as complete even on error to avoid infinite loops
474
+ parent.isComplete = true;
475
+ }
476
+ }
477
+ /**
478
+ * Load details for a subcommand by fetching its help
479
+ */
480
+ async loadSubcommandDetails(parentPath, subcommand) {
481
+ if (subcommand.type !== commandTree_1.ParameterType.SUBCOMMAND || !subcommand.name) {
482
+ return;
483
+ }
484
+ // Build the full command path for this subcommand
485
+ const fullPath = `${parentPath} ${subcommand.name}`;
486
+ // The variant line that introduced this subcommand (e.g. "/gamerule
487
+ // doDaylightCycle [<value>]") already gave us its argument list - trust
488
+ // it instead of spending a `help`/`minecraft:help` round trip per
489
+ // subcommand (e.g. once per gamerule, scoreboard objectives subcommand,
490
+ // ...). Only fall through to fetching when that usage is empty or just
491
+ // the generic `<args>` placeholder, i.e. we don't actually know its args.
492
+ if ((0, commandTreeParsingBrigadier_1.hasUsableArguments)(subcommand.members)) {
493
+ subcommand.isComplete = true;
494
+ await this.loadSubcommandsIn(fullPath, subcommand.members);
495
+ return;
496
+ }
497
+ // A variant whose name came from a `[bracketed]` token with no further
498
+ // arguments (e.g. "[peaceful]" in "/minecraft:difficulty [peaceful]")
499
+ // is one literal value of an enum-style argument, not a subcommand verb
500
+ // - "peaceful" has no syntax of its own to discover, so
501
+ // "help"/"minecraft:help <fullPath>" would just be wasted round trips
502
+ // (confirmed: minecraft:help returns empty, help returns "not found").
503
+ if (subcommand.members.length === 0 && subcommand.optional) {
504
+ subcommand.isComplete = true;
505
+ return;
506
+ }
507
+ try {
508
+ const helpResponse = await this.fetchPaginatedCommand(`help ${fullPath}`);
509
+ const mcResponse = this.supportsMinecraftNamespace
510
+ ? await this.fetchPaginatedCommand(`minecraft:help ${fullPath}`)
511
+ : null;
512
+ // Check for a valid response from at least one source
513
+ if (!helpResponse && !mcResponse) {
514
+ subcommand.isComplete = true;
515
+ return;
516
+ }
517
+ const { variants, direct } = this.mergeHelpSources(helpResponse, mcResponse, fullPath);
518
+ if (variants.size === 0 && direct === null) {
519
+ // Neither source described `fullPath` directly (e.g. it's an enum
520
+ // value like a gamerule name, not a queryable command path) - keep
521
+ // whatever members were already known from the parent's syntax line.
522
+ subcommand.isComplete = true;
523
+ return;
524
+ }
525
+ // Clear existing members to avoid duplicates
526
+ subcommand.members.length = 0;
527
+ if (direct !== null) {
528
+ subcommand.members.push(...direct);
529
+ }
530
+ else {
531
+ subcommand.members.push(...(0, commandTreeParsingBrigadier_1.buildParameterStructureFromVariants)(variants));
532
+ }
533
+ subcommand.isComplete = true;
534
+ // Recursively load any nested subcommands
535
+ await this.loadSubcommandsIn(fullPath, subcommand.members);
536
+ }
537
+ catch {
538
+ // Subcommand might not have its own help, that's okay
539
+ subcommand.isComplete = true;
540
+ }
541
+ }
542
+ /**
543
+ * Recursively loads details for any not-yet-complete SUBCOMMAND parameters
544
+ * in `parameters` — both direct ones and those nested inside CHOICE_LIST
545
+ * choices — fetching each via `loadSubcommandDetails(path, ...)`.
546
+ */
547
+ async loadSubcommandsIn(path, parameters) {
548
+ for (const param of parameters) {
549
+ if (param.type === commandTree_1.ParameterType.CHOICE_LIST) {
550
+ // For choice lists, recurse into each subcommand choice
551
+ for (const choice of param.choices) {
552
+ if (choice.type === commandTree_1.ParameterType.SUBCOMMAND && !choice.isComplete) {
553
+ await this.loadSubcommandDetails(path, choice);
554
+ }
555
+ }
556
+ }
557
+ else if (param.type === commandTree_1.ParameterType.SUBCOMMAND && !param.isComplete) {
558
+ // Direct subcommand parameter
559
+ await this.loadSubcommandDetails(path, param);
560
+ }
561
+ }
562
+ }
563
+ /**
564
+ * Get suggestions based on current input
565
+ */
566
+ getSuggestions(input) {
567
+ return (0, commandTreeSuggestions_1.getSuggestions)(this.rootCommands, this.isReady, input);
568
+ }
569
+ /**
570
+ * Get cache information
571
+ */
572
+ getCacheInfo() {
573
+ return this.cache.getInfo();
574
+ }
575
+ /**
576
+ * Clear the cache
577
+ */
578
+ clearCache() {
579
+ this.cache.clear();
580
+ }
581
+ }
582
+ exports.CommandTreeCrawler = CommandTreeCrawler;
583
+ //# sourceMappingURL=commandTreeCrawler.js.map