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
|
@@ -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
|