@mudlet/mudlet-web 0.3.1 → 0.4.1

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 (97) hide show
  1. package/README.md +8 -0
  2. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  3. package/dist-lib/branding.d.ts +1 -1
  4. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  5. package/dist-lib/hooks/useOutput.d.ts +3 -1
  6. package/dist-lib/import/defaultPackages.d.ts +35 -2
  7. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  8. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  9. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  10. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  11. package/dist-lib/import/packageExport.d.ts +71 -0
  12. package/dist-lib/import/packageInstaller.d.ts +4 -1
  13. package/dist-lib/index.js +45929 -40688
  14. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  15. package/dist-lib/map/MapStore.d.ts +32 -4
  16. package/dist-lib/map/mapImageExport.d.ts +1 -1
  17. package/dist-lib/map/mapZoom.d.ts +71 -0
  18. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  19. package/dist-lib/mud/MudSession.d.ts +113 -11
  20. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  21. package/dist-lib/mud/commandSplit.d.ts +16 -0
  22. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  23. package/dist-lib/mud/connection/MudClient.d.ts +120 -16
  24. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  25. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  26. package/dist-lib/mud/events.d.ts +19 -4
  27. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  28. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  29. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  30. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  31. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  32. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  33. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  34. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  35. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  36. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  37. package/dist-lib/mud/protocol/index.d.ts +1 -1
  38. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  39. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  40. package/dist-lib/mud/text/Console.d.ts +64 -0
  41. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  42. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  43. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  44. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  45. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  46. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  47. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  48. package/dist-lib/scripting/IScriptingRuntime.d.ts +16 -0
  49. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  50. package/dist-lib/scripting/ScriptingAPI.d.ts +323 -54
  51. package/dist-lib/scripting/ScriptingEngine.d.ts +180 -23
  52. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  53. package/dist-lib/scripting/lua/LuaRuntime.d.ts +151 -2
  54. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  55. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  56. package/dist-lib/storage/index.d.ts +1 -1
  57. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  58. package/dist-lib/storage/schema.d.ts +116 -0
  59. package/dist-lib/styles.css +1 -1
  60. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  61. package/dist-lib/ui/CommandBar.d.ts +7 -1
  62. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  63. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  64. package/dist-lib/ui/HelpModal.d.ts +11 -0
  65. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  66. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  67. package/dist-lib/ui/Toolbar.d.ts +2 -1
  68. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  69. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  70. package/dist-lib/ui/components/index.d.ts +1 -0
  71. package/dist-lib/ui/helpTopics.d.ts +19 -0
  72. package/dist-lib/ui/labels/LabelManager.d.ts +7 -0
  73. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  74. package/dist-lib/ui/markdown.d.ts +10 -1
  75. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  76. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  77. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  78. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  79. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  80. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  81. package/dist-lib/ui/search/matcher.d.ts +15 -0
  82. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  83. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  84. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  85. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  86. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  87. package/dist-lib/ui/windows/WindowManager.d.ts +132 -2
  88. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  89. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  90. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  91. package/dist-lib/ui/windows/types.d.ts +35 -0
  92. package/dist-lib/utils/describeThrown.d.ts +22 -0
  93. package/dist-lib/utils/fontLoader.d.ts +5 -0
  94. package/dist-lib/vite.js +22 -0
  95. package/package.json +10 -6
  96. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  97. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -3,7 +3,7 @@ import { AnsiAwareBuffer } from "../text/FormatState";
3
3
  import { type ChunkProcessor } from "../triggers/ChunkProcessor";
4
4
  import type { MudClientEvents } from "../events";
5
5
  export type { MudClientEvents } from "../events";
6
- export { SUPPORTED_SERVER_ENCODINGS } from "../protocol";
6
+ export { SUPPORTED_SERVER_ENCODINGS, DEFAULT_SERVER_ENCODING, canonicalServerEncoding, canEncodeForServer } from "../protocol";
7
7
  export interface MudClientOptions {
8
8
  url: string;
9
9
  mccpEnabled?: boolean;
@@ -88,6 +88,51 @@ export interface MudClientOptions {
88
88
  * of each GA-terminated data block — the IRE-server bug Mudlet patches in
89
89
  * `cTelnet::gotPrompt`. See LineAssembler. */
90
90
  fixUnnecessaryLinebreaks?: boolean;
91
+ /** Mudlet's `Host::mUndoServerWrap` — rejoin the lines the game wrapped
92
+ * itself, so triggers and rendering see whole logical lines. Default false.
93
+ * See LineAssembler and mud/text/serverWrap.ts. */
94
+ undoServerWrap?: boolean;
95
+ /** The column that game wraps at (`Host::mUndoServerWrapWidth`, default 80).
96
+ * Only consulted while {@link undoServerWrap} is on. */
97
+ undoServerWrapWidth?: number;
98
+ /** Mudlet's `setConfig("inputLineStrictUnixEndings", …)` (host flag
99
+ * `mUSE_UNIX_EOL`). Default false. When true a submitted command is
100
+ * terminated with a bare `\n` instead of the telnet-standard `\r\n`
101
+ * (`cTelnet::sendData` appends the CR only when the flag is off) — some
102
+ * Unix-y servers treat the CR as part of the command. */
103
+ inputLineStrictUnixEndings?: boolean;
104
+ /** Mudlet's `setConfig("specialForceGAOff", …)` (host flag `mFORCE_GA_OFF`).
105
+ * Default false. When true an inbound IAC GA / IAC EOR is *not* treated as
106
+ * a prompt marker: the session never latches into GA-driven mode and the
107
+ * marker becomes a plain newline in the data stream, exactly as
108
+ * `cTelnet::processSocketData` does in its `else` branch. For servers whose
109
+ * GA placement is wrong often enough that prompt detection does more harm
110
+ * than good. Read once per connect (Mudlet snapshots it in `connectIt`), so
111
+ * a mid-session change applies on the next dial. */
112
+ specialForceGAOff?: boolean;
113
+ /** Mudlet's `setConfig("promptForVersionInTTYPE", …)` (host flag
114
+ * `mPromptedForVersionInTTYPE`). Default false. Latches once the KaVir
115
+ * auto-detect has had its say for this profile, so a user who then turns
116
+ * `versionInTTYPE` back off isn't overridden on the next connect. */
117
+ promptForVersionInTTYPE?: boolean;
118
+ /** Mudlet's `setConfig("promptForMXPProcessorOn", …)` (host flag
119
+ * `mPromptedForMXPProcessorOn`). Default false. Latches once the in-band
120
+ * MXP auto-detect has fired for this profile; together with
121
+ * `specialForceMXPProcessorOn` it decides whether an `ESC[<n>z` from a
122
+ * server that never negotiated option 91 may still start MXP. */
123
+ promptForMXPProcessorOn?: boolean;
124
+ /** Mudlet's `setConfig("specialForceMXPProcessorOn", …)` (host flag
125
+ * `mForceMXPProcessorOn`). Default false. Only consulted here for the
126
+ * in-band-detection gate above — the parser-side effect lives in
127
+ * ScriptingEngine, which owns the MXP processor. */
128
+ specialForceMXPProcessorOn?: boolean;
129
+ /** Mudlet's `setConfig("versionInTTYPE", …)` (host flag `mVersionInTTYPE`).
130
+ * Default false. When true the first TTYPE cycle value carries our version
131
+ * after the client name (`MUDLET-WEB 1.2.3`). Off by default because the
132
+ * period is not a legal TTYPE character per RFC 1091 — but servers running
133
+ * KaVir's protocol snippet parse a version out of it and fall back to 16
134
+ * colours without one, so it's opt-in rather than absent. */
135
+ versionInTTYPE?: boolean;
91
136
  /** WebSocket subprotocols to advertise in the opening handshake's
92
137
  * `Sec-WebSocket-Protocol` header (RFC 6455), in preference order — the
93
138
  * server selects at most one. Mutually-exclusive stream *modes*, not layers:
@@ -117,6 +162,8 @@ export declare const MUD_TELNET_SUBPROTOCOL = "telnet.mudstandards.org";
117
162
  * GMCP/MSDP/MSSP subnegotiation streams.
118
163
  */
119
164
  export declare class MudClient {
165
+ /** The live WebSocket, or null when there is none — before the first
166
+ * connect, and from the moment disconnect() lets go of one. */
120
167
  private socket;
121
168
  private readonly eventBus;
122
169
  private readonly chunkProcessor;
@@ -130,8 +177,12 @@ export declare class MudClient {
130
177
  private readonly negotiator;
131
178
  /** Latches once `sysCharacterModeDetected` has fired for this connection so
132
179
  * the warning isn't repeated. Reset on each connect(). See
133
- * {@link maybeDetectCharacterMode}. */
180
+ * {@link checkCharacterModePattern}. */
134
181
  private charModeDetected;
182
+ /** Pending character-at-a-time verdict, armed by a submitted game command
183
+ * while ECHO+SGA are active and cancelled if the server releases ECHO
184
+ * first. Null when nothing is in flight. */
185
+ private charModeTimer;
135
186
  /** Byte↔char codec for the session (inbound streaming decoder + outgoing
136
187
  * encoding). Swapped between encodings by the CHARSET handler. */
137
188
  private readonly codec;
@@ -164,21 +215,68 @@ export declare class MudClient {
164
215
  private readonly mspParser;
165
216
  /** WebSocket subprotocols advertised on connect (see MudClientOptions). */
166
217
  private readonly subprotocols;
167
- constructor({ url, mccpEnabled, commandEcho, chunkProcessor, promptTimeoutMs, gmcpEnabled, mttsEnabled, msdpEnabled, msspEnabled, charsetEnabled, mspEnabled, mxpEnabled, mnesEnabled, newEnvironEnabled, secureTransport, screenReaderAdvertised, nawsEnabled, fixUnnecessaryLinebreaks, subprotocols, }: MudClientOptions, eventBus: EventBus<MudClientEvents>);
168
- /** Mudlet-parity character-at-a-time detection (cTelnet::
169
- * checkCharacterModePattern). When the server has both asked to suppress
170
- * go-ahead (IAC WILL SGA — which we refuse) *and* enabled server-side echo,
171
- * it's driving a character-at-a-time session that mudix, a line-based
172
- * client, can't handle well. Raise `sysCharacterModeDetected` once so the
173
- * UI / scripts can warn the user. Checked on both trigger edges: an SGA
174
- * request arriving while echo is already on, and echo committing while SGA
175
- * was already requested. */
176
- private maybeDetectCharacterMode;
218
+ /** Mudlet `mUSE_UNIX_EOL` terminate submitted commands with a bare `\n`.
219
+ * Read on every send, so a change applies to the next command (Mudlet reads
220
+ * the host flag in `sendData` too). */
221
+ private strictUnixEndings;
222
+ /** Mudlet `mFORCE_GA_OFF` see MudClientOptions.specialForceGAOff. A live
223
+ * session keeps parsing with the value it connected with (Mudlet snapshots
224
+ * the host flag into cTelnet at `connectIt`), which here falls out of
225
+ * MudSession building a fresh client per connect. An unconnected one takes
226
+ * the change straight away — see {@link setForceGaOff}. */
227
+ private forceGaOff;
228
+ /** Handed to the telnet sequence parser and mutated in place, so the parser
229
+ * reads the current setting rather than the one it was built with. */
230
+ private readonly telnetParserOpts;
231
+ constructor({ url, mccpEnabled, commandEcho, chunkProcessor, promptTimeoutMs, gmcpEnabled, mttsEnabled, msdpEnabled, msspEnabled, charsetEnabled, mspEnabled, mxpEnabled, mnesEnabled, newEnvironEnabled, secureTransport, screenReaderAdvertised, nawsEnabled, fixUnnecessaryLinebreaks, undoServerWrap, undoServerWrapWidth, inputLineStrictUnixEndings, specialForceGAOff, versionInTTYPE, promptForVersionInTTYPE, promptForMXPProcessorOn, specialForceMXPProcessorOn, subprotocols, }: MudClientOptions, eventBus: EventBus<MudClientEvents>);
232
+ /** Mudlet-parity character-at-a-time detection (cTelnet::sendData's
233
+ * `armCharacterModeDetection`). ECHO+SGA on its own is *not* the signature:
234
+ * an ordinary line-mode server masking a password negotiates exactly the
235
+ * same pair. The two are told apart by behaviour, so detection is armed
236
+ * only by a submitted game command and only decides {@link
237
+ * CHARACTER_MODE_DETECT_MS} later — a password mask sends WONT ECHO right
238
+ * after the masked line and cancels it first.
239
+ *
240
+ * Re-armed on every game command so the window always measures from the
241
+ * most recent submission. */
242
+ private armCharacterModeDetection;
243
+ private cancelCharacterModeDetection;
244
+ /** The armed window elapsed with ECHO+SGA still active — a full input line
245
+ * was submitted without the server releasing echo, which a password prompt
246
+ * never does. Raise `sysCharacterModeDetected` once per connection so the
247
+ * UI / scripts can warn the user.
248
+ *
249
+ * Accepted limitation (same as Mudlet's): this can't tell genuine
250
+ * character-at-a-time from the rarer cases of a buggy password prompt that
251
+ * never sends WONT ECHO, or a line-mode server using persistent
252
+ * server-side echo. We stay in line mode regardless and the message is
253
+ * only advisory. */
254
+ private checkCharacterModePattern;
177
255
  setMccpEnabled(enabled: boolean): void;
178
256
  isMccpEnabled(): boolean;
179
257
  /** Mudlet `setConfig("fixUnnecessaryLinebreaks", …)`. Takes effect on the
180
258
  * next GA-driven block; never retroactive. */
181
259
  setFixUnnecessaryLinebreaks(enabled: boolean): void;
260
+ /**
261
+ * Mudlet `setConfig("specialForceGAOff", …)`. Only an UNCONNECTED client
262
+ * takes the change: a live session has to keep parsing telnet the way it
263
+ * connected, or a GA mid-stream would suddenly mean something else. Mudlet
264
+ * draws the same line — `cacheHostSettings()` is re-run from setConfig only
265
+ * while the socket is unconnected, and otherwise at the next connect.
266
+ */
267
+ setForceGaOff(enabled: boolean): void;
268
+ /** Mudlet `setConfig("undoServerWrap", …)`. Live: the next server line is
269
+ * judged under the new setting. */
270
+ setUndoServerWrap(enabled: boolean): void;
271
+ /** Mudlet `setConfig("undoServerWrapWidth", …)`. */
272
+ setUndoServerWrapWidth(width: number): void;
273
+ /** Commit a line held for a wrap continuation whose flush delay has passed.
274
+ * See {@link LineAssembler.pumpServerWrapDue}. */
275
+ pumpServerWrapDue(now?: number): boolean;
276
+ /** Mudlet `setConfig("inputLineStrictUnixEndings", …)`. Live: `cTelnet::
277
+ * sendData` reads the host flag per send, so the next command submitted
278
+ * uses the new terminator. */
279
+ setInputLineStrictUnixEndings(enabled: boolean): void;
182
280
  /** Mudlet `addSupportedTelnetOption(option)`. See TelnetNegotiator. */
183
281
  addSupportedTelnetOption(option: number): boolean;
184
282
  setPromptTimeoutMs(ms: number): void;
@@ -200,7 +298,11 @@ export declare class MudClient {
200
298
  isSocketOpen(): boolean;
201
299
  isPasswordMode(): boolean;
202
300
  shouldEchoCommand(): boolean;
203
- send(message: string): void;
301
+ /** `isGameCommand` mirrors `cTelnet::sendData`'s third argument: only a real
302
+ * game command (typed or from a script's `send()`) may arm
303
+ * character-at-a-time detection. Credentials and internal protocol replies
304
+ * route through here too and must not. */
305
+ send(message: string, isGameCommand?: boolean): void;
204
306
  private sendRaw;
205
307
  /** Encodes a Latin-1 byte-string to raw bytes and sends it as a binary
206
308
  * WebSocket frame. The proxy worker expects binary, not base64. Every
@@ -245,9 +347,11 @@ export declare class MudClient {
245
347
  * (the zMUD generic out-of-band channel) and sends it raw. Returns false
246
348
  * when the socket isn't open. */
247
349
  sendTelnetChannel102(msg: string): boolean;
248
- /** Frame a raw `IAC SB <opt> <payload> IAC SE` subnegotiation and send it
249
- * with no encoding conversion (each char → one byte), like the other
250
- * telnet negotiations. Shared by sendATCP / sendTelnetChannel102. */
350
+ /** Frame an `IAC SB <opt> <payload> IAC SE` subnegotiation and send it.
351
+ * `payload` must already be a Latin-1 byte-string (each char → one byte)
352
+ * with any IAC handled the framing is all this does, since ATCP and
353
+ * channel 102 need different byte treatments. Shared by sendATCP /
354
+ * sendTelnetChannel102. */
251
355
  private sendSubnegotiation;
252
356
  /** Mudlet `getServerEncoding()`. The IANA name of the decoder currently
253
357
  * applied to the inbound stream — 'utf-8' until CHARSET negotiation (or an
@@ -18,6 +18,25 @@ export interface TelnetNegotiatorFlags {
18
18
  * key. Default false; some MUDs adjust their output (e.g. suppress ASCII
19
19
  * art, add extra room-description detail) when this is set. */
20
20
  screenReaderAdvertised: boolean;
21
+ /** Whether the first TTYPE cycle value carries our version after the client
22
+ * name — Mudlet's `versionInTTYPE` config key (`mVersionInTTYPE`). Default
23
+ * false: RFC 1091 doesn't permit the period, so Mudlet stopped sending it
24
+ * in 2024. Servers running KaVir's protocol snippet want it anyway (without
25
+ * a version they assume 1.0 and cap colour support at 16), which is what
26
+ * the KaVir auto-detect below turns it on for. */
27
+ versionInTTYPE: boolean;
28
+ /** Whether this profile has already been through the KaVir auto-detect —
29
+ * Mudlet's `promptForVersionInTTYPE` config key (`mPromptedForVersionInTTYPE`).
30
+ * When true the detector below is disabled, so a user who turned
31
+ * `versionInTTYPE` back off is not overridden on every reconnect. */
32
+ versionInTTYPEPrompted: boolean;
33
+ /** Whether an in-band `ESC[<n>z` may still auto-start MXP on a server that
34
+ * never negotiated telnet option 91. Mudlet gates its equivalent scan on
35
+ * `mForceMXPProcessorOn || !mPromptedForMXPProcessorOn`
36
+ * (`cTelnet::gotRest`): once the auto-detect has fired for a profile, only
37
+ * the forced-on state keeps it live — so a user who then turns
38
+ * `specialForceMXPProcessorOn` off is not re-overridden every connect. */
39
+ mxpInBandDetectionEnabled: boolean;
21
40
  }
22
41
  export interface TelnetNegotiatorHooks {
23
42
  sendRaw(data: string): void;
@@ -30,6 +49,10 @@ export interface TelnetNegotiatorHooks {
30
49
  /** Current inbound encoding (IANA label) — drives the MTTS UTF-8 bit and
31
50
  * the MNES/NEW-ENVIRON CHARSET variable. */
32
51
  getEncoding(): string;
52
+ /** The server's option-negotiation order matched KaVir's protocol snippet —
53
+ * it wants a version in our TTYPE reply. Fired at most once per connection;
54
+ * the owner turns `versionInTTYPE` on and redials. */
55
+ onKaVirProtocolDetected(): void;
33
56
  }
34
57
  /**
35
58
  * Telnet option negotiation for a MUD session. Owns the WILL/WONT/DO/DONT
@@ -76,6 +99,15 @@ export declare class TelnetNegotiator {
76
99
  * telnet handshake, so the in-band signal is the reliable trigger. Reset on
77
100
  * each connect(). */
78
101
  private mxpStarted;
102
+ /** Rolling window of the last {@link KAVIR_NEGOTIATION_ORDER}`.length`
103
+ * options the server sent us a WILL/DO for, oldest first — Mudlet's
104
+ * `mNegotiationOrder`. Compared against the KaVir fingerprint after each
105
+ * push. Reset on each connect(). */
106
+ private negotiationOrder;
107
+ /** Latches once the KaVir fingerprint has matched this connection, so the
108
+ * detector fires the hook at most once even though a server may keep
109
+ * re-offering options. */
110
+ private kaVirDetected;
79
111
  /** True once we've sent IAC WILL NAWS this session (proactively on connect,
80
112
  * or in response to a server-initiated IAC DO NAWS), so we don't re-offer. */
81
113
  private nawsWillSent;
@@ -141,6 +173,13 @@ export declare class TelnetNegotiator {
141
173
  * supported-options registry answers every occurrence; anything left is
142
174
  * surfaced as a `telnet.event`. */
143
175
  private handleNegotiationCommand;
176
+ /** Mudlet `cTelnet::trackKaVirNegotiation`. Records the option of each
177
+ * inbound WILL/DO in a rolling window and fires the hook the first time the
178
+ * window equals the KaVir fingerprint. Recording happens before the
179
+ * per-frame response dedupe, so the order seen here is the order the server
180
+ * actually sent — the same thing Mudlet feeds its own tracker from
181
+ * `processTelnetCommand`. */
182
+ private trackKaVirNegotiation;
144
183
  private respondToKnownOption;
145
184
  /** Raise `telnet.event` for an SB of an option we neither handle natively
146
185
  * nor auto-negotiate (type 5 in Mudlet's sysTelnetEvent mapping). */
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Mudlet's `<XX>` byte placeholders for `feedTelnet(data)` —
3
+ * `TLuaInterpreter::decodeBytes`.
4
+ *
5
+ * Lua strings are text, and the bytes a telnet stream is made of mostly are not:
6
+ * `\xff\xf9` typed into a script is awkward to read and easy to get wrong, and a
7
+ * profile that saves scripts as XML has to survive round-tripping them. So
8
+ * Mudlet lets the data name its control bytes instead — `<T_IAC><T_GA>` for a
9
+ * prompt marker, `<ESC>` for the start of an ANSI sequence — and decodes them
10
+ * here before the bytes reach the parser.
11
+ *
12
+ * Anything that isn't a known tag is passed through unchanged, so ordinary text
13
+ * containing `<` survives; `<<` and `>>` are the escapes for a literal one.
14
+ */
15
+ /**
16
+ * Version of the tag table, as Mudlet's `decodeBytes` reports it: feeding the
17
+ * empty string yields this number as text rather than nothing, so a script can
18
+ * ask which tags it can count on.
19
+ */
20
+ export declare const TELNET_BYTE_TAG_TABLE_VERSION = 1;
21
+ /**
22
+ * Decode Mudlet's `<XX>` byte placeholders into the bytes they name, as
23
+ * `feedTelnet` does before handing data to the telnet parser.
24
+ *
25
+ * The result is a byte-string (each code unit ≤ 0xFF), which is what the inbound
26
+ * pipeline takes — the same shape a decoded WebSocket frame arrives in.
27
+ *
28
+ * - `<<` yields a literal `<`, `>>` a literal `>`.
29
+ * - An unknown `<…>` is left exactly as written, so prose and markup pass
30
+ * through: `<b>bold</b>` is text, not a failed decode.
31
+ * - An unclosed `<` is likewise just a `<`.
32
+ * - The empty string yields the tag table's version number as text, which is how
33
+ * a script asks what this build understands.
34
+ */
35
+ export declare function decodeTelnetByteTags(input: string): string;
@@ -189,11 +189,21 @@ export type MudClientEvents = {
189
189
  * (`'SUPPRESS_GO_AHEAD'` or `'LINEMODE'`). */
190
190
  'protocol.rejected': [protocol: string];
191
191
  /** Mudlet `sysCharacterModeDetected`. Fires once per connection when the
192
- * server has both asked to suppress go-ahead (IAC WILL SGA) *and* enabled
193
- * server-side echo the classic character-at-a-time signature that mudix,
194
- * a line-based client, can't drive well. Lets scripts / the UI warn the
195
- * user that input may not behave as expected. */
192
+ * server has both asked to suppress go-ahead (IAC WILL SGA) *and* kept
193
+ * server-side echo on across a submitted game command — the
194
+ * character-at-a-time signature that mudix, a line-based client, can't
195
+ * drive well. The three-second delay is what separates it from an ordinary
196
+ * password mask, which negotiates the same pair but releases echo as soon
197
+ * as the masked line is in. Lets scripts / the UI warn the user that input
198
+ * may not behave as expected. */
196
199
  'charmode.detected': void;
200
+ /** The server's telnet option-negotiation order matched KaVir's protocol
201
+ * snippet (Mudlet `cTelnet::trackKaVirNegotiation`). Such servers read a
202
+ * decimal version out of our TTYPE client-name reply and fall back to 16
203
+ * colours without one, so the owner switches `versionInTTYPE` on and
204
+ * redials. Fires at most once per connection, and never once the profile's
205
+ * `promptForVersionInTTYPE` latch is set. */
206
+ 'kavir.detected': void;
197
207
  } & Record<string, any>;
198
208
  export type MudEvents = MudClientEvents & {
199
209
  'status': [status: SessionStatus];
@@ -207,6 +217,11 @@ export type MudEvents = MudClientEvents & {
207
217
  'script.clearcmd': void;
208
218
  'script.selectcmd': void;
209
219
  'script.cmdlinesuggestions': [items: string[]];
220
+ /** Words Tab completion must never offer, whatever list they came from. */
221
+ 'script.cmdlineblacklist': [items: string[]];
222
+ /** Whether the main command bar's history is persisted at all
223
+ * (setSaveCommandHistory); the size cap stays a separate setting. */
224
+ 'script.savecommandhistory': [save: boolean];
210
225
  'script.openvfs': [path: string];
211
226
  /** Fired by ScriptingAPI when Lua calls `invokeFileDialog(...)`. The UI
212
227
  * (ProfileSession) shows the in-app VFS picker and resolves the request
@@ -0,0 +1,29 @@
1
+ /** One entry of Mudlet's bundled-game catalogue (its C++ `GameDetail`). */
2
+ export interface BundledGame {
3
+ name: string;
4
+ hostUrl: string;
5
+ port: number;
6
+ tlsEnabled: boolean;
7
+ /** Small HTML fragment of website/forum/Discord links. */
8
+ websiteInfo: string;
9
+ /** Qt resource path of the game's icon, kept as the upstream identifier. */
10
+ icon: string;
11
+ /** Filename of the copy vendored in ./icons/, when one was available.
12
+ * Resolve it with `gameIconUrl()` rather than building a path by hand —
13
+ * the bundler only emits assets it can see referenced. */
14
+ iconFile?: string;
15
+ description: string;
16
+ /** The game's own loader installs its full interface, so the generic
17
+ * starter UI is not preinstalled for it. */
18
+ providesOwnUi?: boolean;
19
+ /** Other hostnames the game answers on. */
20
+ alternateHostUrls?: string[];
21
+ }
22
+ export declare const BUNDLED_GAMES: readonly BundledGame[];
23
+ /** The bundled game called `name`, matched the way Mudlet matches it
24
+ * (TGameDetails::findGame — exact, case-sensitive). */
25
+ export declare function findBundledGame(name: string): BundledGame | null;
26
+ /** Whether the game reachable at `hostUrl` installs its own full interface,
27
+ * in which case the generic starter UI is not preinstalled (Mudlet's
28
+ * TGameDetails::gameProvidesOwnUi). */
29
+ export declare function gameProvidesOwnUi(hostUrl: string): boolean;
@@ -0,0 +1,5 @@
1
+ import { type BundledGame } from './bundledGames';
2
+ /** URL of a bundled game's logo, or null when it has none vendored. Accepts the
3
+ * entry or its exact name, so a caller holding only a profile name (which is
4
+ * the game's name for a profile made from the catalogue) can ask too. */
5
+ export declare function gameIconUrl(game: BundledGame | string | undefined): string | null;
@@ -1,3 +1,4 @@
1
+ import { ItemIdSequence } from '../ItemIdSequence';
1
2
  import type { KeyNode } from '../../storage/schema';
2
3
  export type { KeyNode };
3
4
  type TempFn = () => void;
@@ -9,8 +10,11 @@ export interface KeyCodeInfo {
9
10
  export declare class KeyEngine {
10
11
  private readonly temp;
11
12
  private perm;
12
- private nextId;
13
- /** Number of live session-scoped temp keys (Mudlet `getProfileStats` temp count). */
13
+ /** Shared with every other engine in the profile — see ItemIdSequence. */
14
+ private idSeq;
15
+ setIdSequence(seq: ItemIdSequence): void;
16
+ /** Number of live session-scoped temp keys (Mudlet `getProfileStats` temp
17
+ * count). Killed-but-unreaped keys are not live and don't count. */
14
18
  get tempCount(): number;
15
19
  addTemp(key: string, modifiers: string[], fn: TempFn, qt?: {
16
20
  keyCode: number;
@@ -29,6 +33,9 @@ export declare class KeyEngine {
29
33
  */
30
34
  getKeyCode(idOrName: number | string): KeyCodeInfo | null;
31
35
  killKey(id: number): boolean;
36
+ /** Free every key killed since the last call. Runs once per processed line
37
+ * batch, mirroring the deferred cleanup Mudlet's TKeyUnit does. */
38
+ reapKilled(): void;
32
39
  processTemp(event: KeyboardEvent): boolean;
33
40
  loadPerm(keybindings: KeyNode[]): void;
34
41
  matchPerm(event: KeyboardEvent): KeyNode | null;
@@ -25,7 +25,11 @@ export interface ReservedKeyWarning {
25
25
  }
26
26
  /** Platform accelerator — browser shortcuts use Cmd (meta) on macOS, Ctrl elsewhere. */
27
27
  export type Accel = 'ctrl' | 'meta';
28
- /** Detect the platform accelerator modifier. Falls back to Ctrl off-browser (tests). */
28
+ /** Detect the platform accelerator modifier, from `navigator.platform` on every
29
+ * call. Note that a `navigator` exists under vitest's node environment too, and
30
+ * reports the real host — so this is not a Ctrl fallback in tests, and a test
31
+ * that hard-codes one accelerator will pass on CI and fail on a Mac. Callers
32
+ * that need a fixed platform should pass `accel` explicitly. */
29
33
  export declare function detectAccel(): Accel;
30
34
  /** Render a keybinding as a display combo like "Ctrl+Shift+I". */
31
35
  export declare function formatKeyCombo(node: Pick<KeyNode, 'key' | 'modifiers'>): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Whether a keydown belongs to the focused text widget rather than to the
3
+ * keybinding engine.
4
+ *
5
+ * The MUD command line is a `<textarea class="command-input">` (an `<input>`
6
+ * of the same class in password mode) and holds focus for essentially the
7
+ * whole session. So the usual "don't hijack keys while the user is typing"
8
+ * guard — `tagName === 'TEXTAREA'` — cannot be applied to it: it is true
9
+ * almost always, and it silently disables every keybinding in the profile.
10
+ * Typing a command is the app's resting state, not a text-entry mode that
11
+ * should own every hotkey.
12
+ *
13
+ * Real text entry elsewhere — the Lua script editor (CodeMirror, so
14
+ * `contentEditable`), a modal's form field, the script tree's filter box —
15
+ * does keep the event to itself.
16
+ */
17
+ export declare function isTextEntryTarget(target: EventTarget | null): boolean;
@@ -1,5 +1,7 @@
1
1
  /** Qt::KeypadModifier — set by Mudlet when a binding came from the numpad. */
2
2
  export declare const QT_KEYPAD_MODIFIER = 536870912;
3
+ /** Qt::Key_unknown — what Mudlet stores for a key item with nothing bound to it. */
4
+ export declare const QT_KEY_UNKNOWN = 33554431;
3
5
  /**
4
6
  * Translate a Qt::Key integer (or already-translated DOM `KeyboardEvent.code`
5
7
  * string) into a DOM `KeyboardEvent.code`. Mudlet `tempKey` accepts both;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * GMCP `Char.Login` flow decisions.
3
+ *
4
+ * The server sends `Char.Login.Default` to ask for credentials and then waits,
5
+ * withholding its text login prompt until the client answers. Answering is
6
+ * therefore mandatory — every path here ends in either a credentials reply or a
7
+ * popup that will produce one. `Char.Login.Result` reports the outcome, and a
8
+ * server that rejects an attempt commonly re-sends `Char.Login.Default` to ask
9
+ * again, so these rules run repeatedly within one connection.
10
+ *
11
+ * Mirrors Mudlet's `GMCPAuthenticator` (GMCPAuthenticator.cpp). Kept free of
12
+ * React and session plumbing so the rules can be tested directly; the caller
13
+ * supplies the per-connection state.
14
+ */
15
+ /** What to do about a `Char.Login.Default` request. */
16
+ export type CharLoginAction =
17
+ /** Reply with the empty `{}` form — "no credentials, use your next method". */
18
+ {
19
+ kind: 'decline';
20
+ }
21
+ /** Send stored credentials without troubling the player. */
22
+ | {
23
+ kind: 'autofill';
24
+ account: string;
25
+ password: string;
26
+ }
27
+ /** Raise the credentials popup. */
28
+ | {
29
+ kind: 'prompt';
30
+ };
31
+ export interface CharLoginRequestState {
32
+ /** The `type` list from `Char.Login.Default`; empty when the server sent none. */
33
+ methods: string[];
34
+ /** The player chose "Use text login" earlier this connection. */
35
+ declined: boolean;
36
+ /** Credentials have already been sent once this connection (stored or typed). */
37
+ attempted: boolean;
38
+ /** Stored/in-memory credentials, if any. */
39
+ account?: string;
40
+ password?: string;
41
+ }
42
+ /**
43
+ * Decide how to answer a `Char.Login.Default`.
44
+ *
45
+ * The one automatic attempt per connection is the load-bearing rule: it makes a
46
+ * saved login silent when it works, and guarantees that a *wrong* saved login
47
+ * turns into a visible popup instead of a silent send-reject-send loop against
48
+ * the server's repeated asks.
49
+ */
50
+ export declare function decideCharLoginRequest(state: CharLoginRequestState): CharLoginAction;
51
+ /**
52
+ * The console line for a rejected login, in Mudlet's wording
53
+ * (`GMCPAuthenticator::handleAuthResult`). Servers doing GMCP login usually send
54
+ * no text of their own, so this is often the only trace of the failure.
55
+ */
56
+ /**
57
+ * The console line for credentials the game never answered at all: it closed the
58
+ * connection instead of sending `Char.Login.Result`.
59
+ *
60
+ * Measured against Achaea (2026-08-02): a rejected `Char.Login.Credentials` gets
61
+ * no result, no text, no close reason — the socket simply ends. The same wrong
62
+ * password typed into its *text* login prints "Password incorrect. For recovery,
63
+ * see …" before dropping, so the silence is specific to the GMCP path. Mudlet
64
+ * only reports `Char.Login.Result`, so it is equally silent here; without this
65
+ * line a mistyped password is indistinguishable from a network failure.
66
+ */
67
+ export declare const CHAR_LOGIN_SILENT_DROP_MESSAGE: string;
68
+ export declare function charLoginFailureMessage(message?: string): string;
@@ -20,7 +20,26 @@ export declare function normalizeCharsetName(raw: string): string | null;
20
20
  * via `getServerEncodingsList()`. Every entry round-trips through
21
21
  * {@link normalizeCharsetName}, so any name here is a valid `setServerEncoding`
22
22
  * argument. ("ASCII" maps to the UTF-8 decoder, which handles it byte-for-byte.) */
23
+ /** What `getServerEncoding()` reports before anything has changed it. UTF-8
24
+ * rather than Mudlet's ASCII: the decoder handles ASCII byte-for-byte anyway,
25
+ * and a browser stream is far more likely to be UTF-8 than not. */
26
+ export declare const DEFAULT_SERVER_ENCODING = "UTF-8";
23
27
  export declare const SUPPORTED_SERVER_ENCODINGS: readonly string[];
28
+ /** The {@link SUPPORTED_SERVER_ENCODINGS} entry a caller's spelling means, or
29
+ * null when mudix cannot decode it. Dash/space/case differences are all the
30
+ * same encoding — the profile XML, the wire, and Mudlet's own list disagree
31
+ * about which to use — so the answer is always the list's own spelling and
32
+ * `getServerEncoding()` reports one canonical name whatever was set. */
33
+ export declare function canonicalServerEncoding(raw: string): string | null;
34
+ /**
35
+ * Whether every character of `text` survives a trip to the game under
36
+ * `serverEncoding` — Mudlet's TEncodingHelper::canEncode, which is what decides
37
+ * whether a send gets the "unlikely to understand it" warning.
38
+ *
39
+ * Anything mudix cannot judge (an unknown label) is called encodable: the point
40
+ * is to warn about a loss that will certainly happen, not to guess.
41
+ */
42
+ export declare function canEncodeForServer(text: string, serverEncoding: string): boolean;
24
43
  /**
25
44
  * Parse an `IAC SB CHARSET REQUEST ...` subnegotiation body (leading byte is
26
45
  * the option code 42, then subcommand byte 1, then optional `[TTABLE]<ver>`
@@ -61,11 +80,14 @@ export declare class SessionCodec {
61
80
  decode(byteString: string): string;
62
81
  /** Convert a user-typed JS string (UTF-16) into the Latin-1 byte-string the
63
82
  * socket layer expects, using the currently negotiated outgoing encoding.
64
- * For UTF-8 we run it through TextEncoder so multi-byte chars survive;
65
- * for other encodings we fall back to a per-char `& 0xff` truncation,
66
- * which is lossless for ASCII and acceptable for Latin-1-family inputs.
67
- * TextEncoder has no API for non-UTF-8 outputs, so a full per-encoding
68
- * outbound table isn't worth the bytes given how rare non-UTF-8 MUDs are. */
83
+ * UTF-8 goes through TextEncoder so multi-byte chars survive; every other
84
+ * encoding here is single-byte and goes through the inverted decode table,
85
+ * which puts the codepage's own byte on the wire. (A plain `& 0xff`
86
+ * truncation used to stand in for that, and was right only for the
87
+ * Latin-1 range every Polish, Cyrillic, or Greek character above it went
88
+ * out as a byte meaning something else entirely.) A character the codepage
89
+ * has no byte for becomes '?', as Qt's encoder does it — and the send path
90
+ * warns before it comes to that. */
69
91
  encodeOutgoing(text: string): string;
70
92
  }
71
93
  export interface CharsetHandlerHooks {
@@ -1,4 +1,5 @@
1
1
  export declare const TELNET_OPTION_REGEX: RegExp;
2
+ export declare const TELNET_OPTION_REGEX_NO_SB: RegExp;
2
3
  export declare const TELNET_GA = "\u00FF\u00F9";
3
4
  export declare const TELNET_EOR = "\u00FF\u00EF";
4
5
  export declare const TELOPT_EOR = "\u0019";
@@ -3,12 +3,28 @@ export interface GmcpEnvelope {
3
3
  value: unknown;
4
4
  }
5
5
  export type TelnetOptionHandler = (data: string) => string;
6
- export declare const createTelnetOptionParser: (onSubnegotiation: (data: string) => void) => TelnetOptionHandler;
6
+ /** UTF-8-encode into a Latin-1 byte-string, for MudClient.sendBytes (which
7
+ * writes `charCodeAt(i) & 0xff` per char). The inverse of fromByteString. */
8
+ export declare const toByteString: (s: string) => string;
9
+ export declare const createTelnetOptionParser: (onSubnegotiation: (data: string) => void, opts?: {
10
+ /** When true, an inbound IAC GA / IAC EOR is replaced by a newline
11
+ * rather than stripped — Mudlet's `mFORCE_GA_OFF` behaviour
12
+ * (`cTelnet::processSocketData` pushes `'\n'` in place of the marker
13
+ * instead of treating it as a prompt). Doing the substitution here,
14
+ * inside the sequence parser, keeps it positional: the newline lands
15
+ * exactly where the marker was, and a `\xFF\xF9` byte pair inside a
16
+ * subnegotiation payload is never mistaken for one. */
17
+ promptMarkerAsNewline?: boolean;
18
+ }) => TelnetOptionHandler;
7
19
  export declare const stripTelnetSequences: (data: string, handler: TelnetOptionHandler) => string;
8
20
  export declare const encodeGmcp: (path: string, payload: unknown) => string;
9
21
  /** Encode a GMCP frame from a single pre-formatted body (e.g. `"Module.Sub args"`).
10
- * Matches Mudlet's `sendGMCP` semantics — the caller controls the exact bytes
11
- * between IAC SB GMCP and IAC SE. */
22
+ * Mudlet's `sendGMCP` semantics — the caller controls the body between IAC SB
23
+ * GMCP and IAC SE — except that the body is transcoded to UTF-8 rather than to
24
+ * the session's outgoing encoding, so it can't be used to place arbitrary raw
25
+ * bytes on the wire. UTF-8 is what the GMCP spec asks for, and it's also what
26
+ * keeps a 0xFF byte (which would need IAC-escaping inside a subnegotiation)
27
+ * out of the body in the first place. */
12
28
  export declare const encodeGmcpRaw: (message: string) => string;
13
29
  export interface GmcpStreamOptions {
14
30
  onEnvelope: (payload: GmcpEnvelope) => void;
@@ -8,4 +8,4 @@ export { MspParser, type MspCommand, type MspKind } from "./msp";
8
8
  export { MxpParser, splitMxpResultLines, type MxpLink, type MxpLineResult } from "./mxp";
9
9
  export { parseMnesRequest, encodeMnesIs, selectMnesVars, buildNewEnvironVars, CLIENT_NAME, CLIENT_VERSION, TERMINAL_TYPE, type MnesVar, type MnesRequest, type NewEnvironState } from "./mnes";
10
10
  export { encodeNaws } from "./naws";
11
- export { SessionCodec, CharsetHandler, normalizeCharsetName, pickCharsetFromRequest, SUPPORTED_SERVER_ENCODINGS, type CharsetHandlerHooks } from "./charset";
11
+ export { SessionCodec, CharsetHandler, normalizeCharsetName, pickCharsetFromRequest, SUPPORTED_SERVER_ENCODINGS, DEFAULT_SERVER_ENCODING, canonicalServerEncoding, canEncodeForServer, type CharsetHandlerHooks } from "./charset";