minercon 3.0.3 → 3.0.4
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/package.json +1 -1
- package/out/argumentHint.js +0 -43
- package/out/bukkitHelpParsing.js +0 -62
- package/out/commandSuggestions.js +0 -202
- package/out/completionsBackend.js +0 -97
- package/out/connectionManager.js +0 -209
- package/out/helpTextParsing.js +0 -445
- package/out/historySearch.js +0 -46
- package/out/localCommandTree.js +0 -541
- package/out/rconTerminal.js +0 -80
- package/out/suggestionDisplay.js +0 -286
- package/out/terminalOutput.js +0 -110
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "minercon",
|
|
3
3
|
"displayName": "Minercon",
|
|
4
4
|
"description": "A powerful RCON client for Minecraft servers with fancy tab completion",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.4",
|
|
6
6
|
"author": "xton <thisischriston@gmail.com>",
|
|
7
7
|
"icon": "images/icon.png",
|
|
8
8
|
"license": "MIT",
|
package/out/argumentHint.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// src/argumentHint.ts
|
|
3
|
-
//
|
|
4
|
-
// Pure formatting for the "argument hint" display: given a command's usage
|
|
5
|
-
// string (e.g. "gamemode <mode> [<target>]") and the line the user has typed
|
|
6
|
-
// so far, work out which argument position they're at and which token in the
|
|
7
|
-
// usage string corresponds to it (so it can be highlighted).
|
|
8
|
-
//
|
|
9
|
-
// This is presentation logic, not decision logic — it has no notion of
|
|
10
|
-
// fetching, timing, or staleness — so it lives apart from completionEngine's
|
|
11
|
-
// state machine, as its own pure, independently-testable function.
|
|
12
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.formatArgumentHint = formatArgumentHint;
|
|
14
|
-
const ARGUMENT_TOKEN_PATTERN = /(<[^>]+>|\[[^\]]+\]|\([^)]+\))/g;
|
|
15
|
-
/** Returns null when there's no usage text to show anything for. */
|
|
16
|
-
function formatArgumentHint(usage, line) {
|
|
17
|
-
if (!usage) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const tokens = usage.match(ARGUMENT_TOKEN_PATTERN) || [];
|
|
21
|
-
// The literal words before the first argument token are the command path —
|
|
22
|
-
// derived straight from the usage string so we don't need a separate
|
|
23
|
-
// "commandPath" concept threaded in from wherever the usage came from.
|
|
24
|
-
const firstTokenStart = usage.search(ARGUMENT_TOKEN_PATTERN);
|
|
25
|
-
const literalPrefix = (firstTokenStart >= 0 ? usage.slice(0, firstTokenStart) : usage).trim();
|
|
26
|
-
const commandPrefixWordCount = literalPrefix.length > 0 ? literalPrefix.split(/\s+/).length : 0;
|
|
27
|
-
const commandPrefixText = '/' + literalPrefix;
|
|
28
|
-
const parts = line.trim().split(' ').filter(p => p.length > 0);
|
|
29
|
-
const hasTrailingSpace = line.endsWith(' ');
|
|
30
|
-
const argumentCount = Math.max(0, parts.length - commandPrefixWordCount);
|
|
31
|
-
let currentArgIndex;
|
|
32
|
-
if (argumentCount === 0 && !hasTrailingSpace) {
|
|
33
|
-
currentArgIndex = -1; // still typing the command/subcommand itself
|
|
34
|
-
}
|
|
35
|
-
else if (hasTrailingSpace) {
|
|
36
|
-
currentArgIndex = argumentCount; // ready for the next argument
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
currentArgIndex = argumentCount - 1; // currently typing this argument
|
|
40
|
-
}
|
|
41
|
-
return { commandPrefixText, tokens, currentArgIndex };
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=argumentHint.js.map
|
package/out/bukkitHelpParsing.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// src/bukkitHelpParsing.ts
|
|
3
|
-
//
|
|
4
|
-
// Pure parsing of Bukkit's hand-written `/help <command>` pages - a
|
|
5
|
-
// "§e--------- §fHelp: /<cmd> ----...§e" banner line followed by
|
|
6
|
-
// `Description:`/`Usage:`/`Aliases:` lines. This is a different grammar from
|
|
7
|
-
// the flat Brigadier `/cmd <args>` blobs `helpTextParsing.ts` handles
|
|
8
|
-
// (`minecraft:help`, vanilla's plain `help`): on a server where the
|
|
9
|
-
// `minecraft:` namespace is supported (Paper/Spigot), `help <path>` is
|
|
10
|
-
// *always* one of these pages, for every command - vanilla-backed or
|
|
11
|
-
// Bukkit-added alike - so no shape-sniffing is needed to decide which parser
|
|
12
|
-
// applies, only `LocalCommandTree.supportsMinecraftNamespace`.
|
|
13
|
-
//
|
|
14
|
-
// No state, no IO — every export here is a deterministic function of its
|
|
15
|
-
// arguments.
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.extractBukkitUsageLines = extractBukkitUsageLines;
|
|
18
|
-
exports.extractBukkitAliases = extractBukkitAliases;
|
|
19
|
-
const ansi_1 = require("./ansi");
|
|
20
|
-
/**
|
|
21
|
-
* Extract the `Usage: ...` line(s) from a Bukkit-style `/help <command>`
|
|
22
|
-
* response (e.g. "Description: ...\nUsage: /version [plugin name]\nAliases:
|
|
23
|
-
* ..."), normalized so each reads as `<commandPath> ...` for `parseHelpLines`.
|
|
24
|
-
* Returns `[]` if there's no Usage line, or if its content is just the bare
|
|
25
|
-
* command name with nothing after it — the generic response Bukkit gives for
|
|
26
|
-
* Brigadier-backed (vanilla) commands ("Description: A Mojang provided
|
|
27
|
-
* command.\nUsage: <name>"), which carries no argument info.
|
|
28
|
-
*/
|
|
29
|
-
function extractBukkitUsageLines(helpText, commandPath) {
|
|
30
|
-
const lines = (0, ansi_1.stripColors)(helpText).split('\n').map(line => line.trim());
|
|
31
|
-
const usageIndex = lines.findIndex(line => /^Usage:\s*/i.test(line));
|
|
32
|
-
if (usageIndex === -1) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
const result = [lines[usageIndex].replace(/^Usage:\s*/i, '')];
|
|
36
|
-
for (let i = usageIndex + 1; i < lines.length; i++) {
|
|
37
|
-
const line = lines[i];
|
|
38
|
-
if (!line || /^[A-Za-z][A-Za-z ]*:/.test(line) || line.startsWith('---')) {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
result.push(line);
|
|
42
|
-
}
|
|
43
|
-
const normalizedPath = commandPath.toLowerCase();
|
|
44
|
-
return result.filter(line => line.replace(/^\//, '').toLowerCase() !== normalizedPath);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Extract alias names from a Bukkit-style `/help <command>` response's
|
|
48
|
-
* `Aliases: a, b, c` line (e.g. "Description: ...\nUsage: ...\nAliases: ver,
|
|
49
|
-
* about"). Returns `[]` if there's no Aliases line.
|
|
50
|
-
*/
|
|
51
|
-
function extractBukkitAliases(helpText) {
|
|
52
|
-
const lines = (0, ansi_1.stripColors)(helpText).split('\n').map(line => line.trim());
|
|
53
|
-
const aliasesLine = lines.find(line => /^Aliases:\s*/i.test(line));
|
|
54
|
-
if (!aliasesLine) {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
return aliasesLine.replace(/^Aliases:\s*/i, '')
|
|
58
|
-
.split(',')
|
|
59
|
-
.map(alias => alias.trim())
|
|
60
|
-
.filter(alias => alias.length > 0);
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=bukkitHelpParsing.js.map
|
|
@@ -1,202 +0,0 @@
|
|
|
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
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// src/completionsBackend.ts
|
|
3
|
-
//
|
|
4
|
-
// The completionEngine state machine knows it needs completions and usage
|
|
5
|
-
// text for a given input line — it doesn't know or care whether those come
|
|
6
|
-
// from a network round trip or an in-memory lookup. A CompletionsBackend is
|
|
7
|
-
// that boundary: one implementation asks the server-side TabComplete
|
|
8
|
-
// plugin over RCON, the other asks the locally-built command tree.
|
|
9
|
-
//
|
|
10
|
-
// Driving both modes through the same backend interface (and therefore the
|
|
11
|
-
// same engine, the same dispatch plumbing, and the same rendering) is what
|
|
12
|
-
// lets RconSession be entirely mode-blind — "which backend" is decided once,
|
|
13
|
-
// not branched on at every call site.
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.LocalCompletionsBackend = exports.RconCompletionsBackend = void 0;
|
|
16
|
-
const completionEngine_1 = require("./completionEngine");
|
|
17
|
-
/**
|
|
18
|
-
* Server-side completions via the TabComplete plugin's `tabcomplete`/`cmdusage` commands.
|
|
19
|
-
*
|
|
20
|
-
* Takes a `controller` thunk rather than a fixed `RconController` — the
|
|
21
|
-
* connection manager replaces its controller wholesale on every reconnect, so
|
|
22
|
-
* capturing one instance up front would silently start sending through a dead
|
|
23
|
-
* (disconnected) controller after the first reconnect, with every fetch
|
|
24
|
-
* throwing "Not connected" (swallowed by the caller as "no completions").
|
|
25
|
-
* Looking it up fresh on each call always reaches whatever's live.
|
|
26
|
-
*/
|
|
27
|
-
class RconCompletionsBackend {
|
|
28
|
-
getController;
|
|
29
|
-
constructor(getController) {
|
|
30
|
-
this.getController = getController;
|
|
31
|
-
}
|
|
32
|
-
async fetchCompletions(line) {
|
|
33
|
-
const query = (0, completionEngine_1.buildCompletionsQuery)(line);
|
|
34
|
-
if (query === null) {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
const response = await this.getController().send(`tabcomplete ${query}`);
|
|
38
|
-
return (0, completionEngine_1.parseCompletionsResponse)(response ?? undefined);
|
|
39
|
-
}
|
|
40
|
-
async fetchUsage(line) {
|
|
41
|
-
const query = (0, completionEngine_1.buildUsageQuery)(line);
|
|
42
|
-
if (query === null) {
|
|
43
|
-
return '';
|
|
44
|
-
}
|
|
45
|
-
const response = await this.getController().send(`cmdusage ${query}`);
|
|
46
|
-
return (0, completionEngine_1.parseUsageResponse)(response ?? undefined);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
exports.RconCompletionsBackend = RconCompletionsBackend;
|
|
50
|
-
/**
|
|
51
|
-
* Local completions via the command tree LocalCommandTree builds at
|
|
52
|
-
* startup — a synchronous in-memory lookup, wrapped in `async` so it flows
|
|
53
|
-
* through exactly the same dispatch path as the RCON backend (the `await`
|
|
54
|
-
* still yields to the microtask queue, so a `dispatchToEngine` call made from
|
|
55
|
-
* here lands *after* the effect loop that triggered it has finished — same
|
|
56
|
-
* ordering guarantee the real async backend gets for free from the network).
|
|
57
|
-
*/
|
|
58
|
-
class LocalCompletionsBackend {
|
|
59
|
-
commandTree;
|
|
60
|
-
// getSuggestions sometimes returns incomplete argument help for inputs that
|
|
61
|
-
// are mid-command (e.g. once the user is typing a free-form argument like a
|
|
62
|
-
// player name, the locally-built tree may not have anything to say). Stick
|
|
63
|
-
// with the first full version we saw for this command rather than flicker
|
|
64
|
-
// between that and "(nothing)" — mirrors the original local-mode behavior.
|
|
65
|
-
cachedCommand = null;
|
|
66
|
-
cachedHelp = null;
|
|
67
|
-
constructor(commandTree) {
|
|
68
|
-
this.commandTree = commandTree;
|
|
69
|
-
}
|
|
70
|
-
async fetchCompletions(line) {
|
|
71
|
-
return this.commandTree.getSuggestions(line).suggestions;
|
|
72
|
-
}
|
|
73
|
-
async fetchUsage(line) {
|
|
74
|
-
const result = this.commandTree.getSuggestions(line);
|
|
75
|
-
// Everything up to the first space, including any "namespace:" prefix -
|
|
76
|
-
// \S rather than \w so "minecraft:clear" isn't truncated to "minecraft".
|
|
77
|
-
const commandName = (line.match(/^\/?(\S+)/) || [])[1] || '';
|
|
78
|
-
if (commandName !== this.cachedCommand) {
|
|
79
|
-
this.cachedCommand = commandName;
|
|
80
|
-
this.cachedHelp = null;
|
|
81
|
-
}
|
|
82
|
-
if (result.argumentHelp !== undefined) {
|
|
83
|
-
// Match the shape of the server's `cmdusage` response (e.g. "clear
|
|
84
|
-
// [<targets>] [<item>]") - formatArgumentHint derives the command
|
|
85
|
-
// prefix from this leading commandPath, so it must be present even
|
|
86
|
-
// when there's no argument help (e.g. "reload").
|
|
87
|
-
const usage = result.argumentHelp ? `${result.commandPath} ${result.argumentHelp}` : result.commandPath;
|
|
88
|
-
if (this.cachedHelp === null) {
|
|
89
|
-
this.cachedHelp = usage;
|
|
90
|
-
}
|
|
91
|
-
return this.cachedHelp;
|
|
92
|
-
}
|
|
93
|
-
return this.cachedHelp ?? '';
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
exports.LocalCompletionsBackend = LocalCompletionsBackend;
|
|
97
|
-
//# sourceMappingURL=completionsBackend.js.map
|
package/out/connectionManager.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// src/connectionManager.ts
|
|
3
|
-
//
|
|
4
|
-
// Owns the RCON connection lifecycle: the live `RconController` (recreated on
|
|
5
|
-
// each reconnect attempt), connection/reconnection status, and the
|
|
6
|
-
// exponential-backoff retry loop. Pulled out of RconTerminal as part of the
|
|
7
|
-
// mega-module split — see lineEditor.ts / suggestionDisplay.ts for the sibling
|
|
8
|
-
// extractions and their rationale.
|
|
9
|
-
//
|
|
10
|
-
// `RconSession` keeps `detectAndInitialize`/`initializeCommands` (they're
|
|
11
|
-
// about the autocomplete command-tree and `pluginMode`, a different concern
|
|
12
|
-
// that happens to run at similar times) but reads connection status and reaches
|
|
13
|
-
// the live controller through this class, and is notified via `onReconnected`
|
|
14
|
-
// when it should reload the command tree.
|
|
15
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
-
if (k2 === undefined) k2 = k;
|
|
17
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
-
}
|
|
21
|
-
Object.defineProperty(o, k2, desc);
|
|
22
|
-
}) : (function(o, m, k, k2) {
|
|
23
|
-
if (k2 === undefined) k2 = k;
|
|
24
|
-
o[k2] = m[k];
|
|
25
|
-
}));
|
|
26
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
-
}) : function(o, v) {
|
|
29
|
-
o["default"] = v;
|
|
30
|
-
});
|
|
31
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
-
var ownKeys = function(o) {
|
|
33
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
-
var ar = [];
|
|
35
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
-
return ar;
|
|
37
|
-
};
|
|
38
|
-
return ownKeys(o);
|
|
39
|
-
};
|
|
40
|
-
return function (mod) {
|
|
41
|
-
if (mod && mod.__esModule) return mod;
|
|
42
|
-
var result = {};
|
|
43
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
-
__setModuleDefault(result, mod);
|
|
45
|
-
return result;
|
|
46
|
-
};
|
|
47
|
-
})();
|
|
48
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
-
exports.ConnectionManager = void 0;
|
|
50
|
-
const rconClient_1 = require("./rconClient");
|
|
51
|
-
const logger_1 = require("./logger");
|
|
52
|
-
const ansi = __importStar(require("./ansi"));
|
|
53
|
-
const defaultControllerFactory = (host, port, password, logger) => new rconClient_1.RconController(host, port, password, logger);
|
|
54
|
-
class ConnectionManager {
|
|
55
|
-
serverHost;
|
|
56
|
-
serverPort;
|
|
57
|
-
password;
|
|
58
|
-
logger;
|
|
59
|
-
host;
|
|
60
|
-
controllerFactory;
|
|
61
|
-
_controller;
|
|
62
|
-
_isConnected = true;
|
|
63
|
-
_isReconnecting = false;
|
|
64
|
-
reconnectAttempts = 0;
|
|
65
|
-
maxReconnectAttempts = 5;
|
|
66
|
-
reconnectDelay = 2000;
|
|
67
|
-
reconnectTimeout = null;
|
|
68
|
-
constructor(serverHost, serverPort, password, logger, controller, host, controllerFactory = defaultControllerFactory) {
|
|
69
|
-
this.serverHost = serverHost;
|
|
70
|
-
this.serverPort = serverPort;
|
|
71
|
-
this.password = password;
|
|
72
|
-
this.logger = logger;
|
|
73
|
-
this.host = host;
|
|
74
|
-
this.controllerFactory = controllerFactory;
|
|
75
|
-
this._controller = controller;
|
|
76
|
-
}
|
|
77
|
-
get controller() {
|
|
78
|
-
return this._controller;
|
|
79
|
-
}
|
|
80
|
-
get isConnected() {
|
|
81
|
-
return this._isConnected;
|
|
82
|
-
}
|
|
83
|
-
get isReconnecting() {
|
|
84
|
-
return this._isReconnecting;
|
|
85
|
-
}
|
|
86
|
-
/** Resets the reconnect-attempt counter, backoff delay, and any pending reconnect timer back to their initial state. */
|
|
87
|
-
resetReconnectState() {
|
|
88
|
-
this.reconnectAttempts = 0;
|
|
89
|
-
this.reconnectDelay = 2000;
|
|
90
|
-
if (this.reconnectTimeout) {
|
|
91
|
-
clearTimeout(this.reconnectTimeout);
|
|
92
|
-
this.reconnectTimeout = null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Records that an in-flight command discovered the connection is gone, and
|
|
97
|
-
* schedules an auto-reconnect attempt shortly after. The "Connection
|
|
98
|
-
* lost..." message itself stays with the caller (`executeCommand` accounts
|
|
99
|
-
* for it in its output-line bookkeeping).
|
|
100
|
-
*/
|
|
101
|
-
reportConnectionLost() {
|
|
102
|
-
this._isConnected = false;
|
|
103
|
-
this.resetReconnectState();
|
|
104
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
105
|
-
this.reconnectTimeout = null;
|
|
106
|
-
this.attemptReconnect();
|
|
107
|
-
}, 1000);
|
|
108
|
-
}
|
|
109
|
-
disconnect() {
|
|
110
|
-
// No key-chord echo here: this runs for the typed /disconnect built-in.
|
|
111
|
-
// Ctrl+D echoes its own ^D in RconSession.handleCtrlD before closing.
|
|
112
|
-
this.host.write('Disconnecting...\r\n');
|
|
113
|
-
// Clear any pending reconnect
|
|
114
|
-
if (this.reconnectTimeout) {
|
|
115
|
-
clearTimeout(this.reconnectTimeout);
|
|
116
|
-
this.reconnectTimeout = null;
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
this._controller.disconnect();
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
this.logger.error(`Error during disconnect: ${err}`);
|
|
123
|
-
}
|
|
124
|
-
this._isConnected = false;
|
|
125
|
-
this._isReconnecting = false;
|
|
126
|
-
this.host.write('Connection closed. Type ' + ansi.yellow('/reconnect') + ' to reconnect.\r\n\r\n');
|
|
127
|
-
this.host.showPrompt();
|
|
128
|
-
}
|
|
129
|
-
async manualReconnect() {
|
|
130
|
-
if (this._isReconnecting) {
|
|
131
|
-
this.host.write(ansi.yellow('Already reconnecting...') + '\r\n\r\n');
|
|
132
|
-
this.host.showPrompt();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this.resetReconnectState();
|
|
136
|
-
await this.attemptReconnect();
|
|
137
|
-
}
|
|
138
|
-
async attemptReconnect() {
|
|
139
|
-
if (this._isReconnecting) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if (this._isConnected) {
|
|
143
|
-
this.host.write(ansi.green('Already connected.') + '\r\n\r\n');
|
|
144
|
-
this.host.showPrompt();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
this._isReconnecting = true;
|
|
148
|
-
this.reconnectAttempts++;
|
|
149
|
-
const attemptText = this.reconnectAttempts > 1 ? ` (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})` : '';
|
|
150
|
-
this.host.write(ansi.yellow('Reconnecting to ' + this.serverHost + ':' + this.serverPort + attemptText + '...') + '\r\n');
|
|
151
|
-
try {
|
|
152
|
-
// Disconnect existing controller
|
|
153
|
-
try {
|
|
154
|
-
await this._controller.disconnect();
|
|
155
|
-
}
|
|
156
|
-
catch (err) {
|
|
157
|
-
// Ignore disconnect errors during reconnect
|
|
158
|
-
}
|
|
159
|
-
// Create new controller
|
|
160
|
-
this._controller = this.controllerFactory(this.serverHost, this.serverPort, this.password, this.logger);
|
|
161
|
-
await this._controller.connect();
|
|
162
|
-
this._isConnected = true;
|
|
163
|
-
this._isReconnecting = false;
|
|
164
|
-
this.resetReconnectState();
|
|
165
|
-
this.host.write(ansi.boldGreen('✓ Reconnected successfully!') + '\r\n\r\n');
|
|
166
|
-
// Reload commands after reconnection
|
|
167
|
-
this.host.onReconnected();
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
this._isReconnecting = false;
|
|
171
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
172
|
-
this.host.write(ansi.red('✗ Connection failed: ' + (0, logger_1.errorMessage)(err)) + '\r\n');
|
|
173
|
-
this.host.write(ansi.yellow('Retrying in ' + (this.reconnectDelay / 1000) + ' seconds...') + '\r\n');
|
|
174
|
-
// Clear any existing timeout
|
|
175
|
-
if (this.reconnectTimeout) {
|
|
176
|
-
clearTimeout(this.reconnectTimeout);
|
|
177
|
-
}
|
|
178
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
179
|
-
this.reconnectTimeout = null;
|
|
180
|
-
this.attemptReconnect();
|
|
181
|
-
}, this.reconnectDelay);
|
|
182
|
-
// Exponential backoff with max delay
|
|
183
|
-
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 32000);
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// Max attempts reached
|
|
187
|
-
this.host.write(ansi.boldRed('✗ Reconnection failed after ' + this.maxReconnectAttempts + ' attempts.') + '\r\n');
|
|
188
|
-
this.host.write('Type ' + ansi.yellow('/reconnect') + ' to try again.\r\n\r\n');
|
|
189
|
-
this.resetReconnectState();
|
|
190
|
-
this.host.showPrompt();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/** Tears down any pending reconnect timer and disconnects the controller — used by `RconSession.close()`. */
|
|
195
|
-
dispose() {
|
|
196
|
-
if (this.reconnectTimeout) {
|
|
197
|
-
clearTimeout(this.reconnectTimeout);
|
|
198
|
-
this.reconnectTimeout = null;
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
this._controller.disconnect();
|
|
202
|
-
}
|
|
203
|
-
catch (err) {
|
|
204
|
-
this.logger.error(`Error during close: ${err}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
exports.ConnectionManager = ConnectionManager;
|
|
209
|
-
//# sourceMappingURL=connectionManager.js.map
|