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