@ariane-emory/must-have-plugin 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ariane Emory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # A MUST-have plugin
2
+
3
+ Automatically replaces text patterns in your prompts before they're sent to the LLM.
4
+
5
+ ## Installation
6
+
7
+ ### From npm
8
+
9
+ Add the published package to your OpenCode config:
10
+
11
+ ```json
12
+ {
13
+ "$schema": "https://opencode.ai/config.json",
14
+ "plugin": ["@ariane-emory/must-have-plugin"]
15
+ }
16
+ ```
17
+
18
+ OpenCode installs npm plugins automatically at startup.
19
+
20
+ ### From Source
21
+
22
+ ```bash
23
+ npm install
24
+ npm run build
25
+ ```
26
+
27
+ Then copy the built file into your OpenCode plugins directory:
28
+
29
+ ```bash
30
+ mkdir -p ~/.config/opencode/plugins
31
+ cp dist/index.js ~/.config/opencode/plugins/MUST-have-plugin.js
32
+ ```
33
+
34
+ ## What It Does
35
+
36
+ Performs case-insensitive string replacements on user-typed prompts. The primary use case is auto-capitalizing RFC2119 keywords (MUST, SHOULD, MAY, etc.) in technical specifications.
37
+
38
+ **Example**: Typing `"the system must validate input"` becomes `"the system MUST validate input"`.
39
+
40
+ ### Features
41
+
42
+ - **Case-insensitive matching**: `must`, `Must`, and `MUST` all match
43
+ - **Word boundary aware**: Won't replace `may` inside `maybe`
44
+ - **Multi-word phrases**: `must not` is matched as a unit (before `must` alone)
45
+ - **Hot reload**: Config changes take effect immediately (no restart needed)
46
+ - **JSONC support**: Comments and trailing commas allowed in config file
47
+
48
+ ### Scope
49
+
50
+ - Only replaces text in user-typed prompts
51
+ - Does NOT modify file content attached via `@` mentions
52
+ - Does NOT modify slash command output
53
+
54
+ ## Configuration
55
+
56
+ **Config file**: `~/.config/opencode/MUST-have-plugin.jsonc`
57
+
58
+ If the config file doesn't exist, it's automatically created with RFC2119 defaults.
59
+
60
+ ### Default Configuration
61
+
62
+ ```jsonc
63
+ {
64
+ // Uncomment to enable debug logging (logs appear in OpenCode's log file)
65
+ // "debug": true,
66
+
67
+ "replacements": {
68
+ "must": "MUST",
69
+ "must not": "MUST NOT",
70
+ "required": "REQUIRED",
71
+ "shall": "SHALL",
72
+ "shall not": "SHALL NOT",
73
+ "should": "SHOULD",
74
+ "should not": "SHOULD NOT",
75
+ "recommended": "RECOMMENDED",
76
+ "not recommended": "NOT RECOMMENDED",
77
+ "may": "MAY",
78
+ "optional": "OPTIONAL"
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### Custom Replacements
84
+
85
+ Add your own replacement pairs to the `replacements` object:
86
+
87
+ ```jsonc
88
+ {
89
+ // Uncomment to enable debug logging (view with: tail -f /tmp/opencode-replacer-debug.log)
90
+ // "debug": true,
91
+
92
+ "replacements": {
93
+ "bl.md": "~/.config/opencode/supplemental/md/branch-list.md",
94
+ "dfp": "Diagnose and fix this problem: ",
95
+ "mnm": "Make no mistakes!",
96
+ "rfc!": "The key words \"**MUST**\", \"**MUST NOT**\", \"**REQUIRED**\", \"**SHALL**\", \"**SHALL NOT**\", \"**SHOULD**\", \"**SHOULD NOT**\", \"**RECOMMENDED**\", \"**MAY**\", and \"**OPTIONAL**\" in this message are to be interpreted as described in RFC2119.\n\n",
97
+
98
+ "pto": "push that to origin,",
99
+ "mb": "return to the initial branch and merge the new branch back in.",
100
+
101
+ "always": "**ALWAYS**",
102
+ "ever": "**EVER**",
103
+ "head": "HEAD",
104
+ "may not": "**MAY NOT**",
105
+ "may": "**MAY**",
106
+ "must always": "**MUST ALWAYS**",
107
+ "must never" : "**MUST NEVER**",
108
+ "must not" : "**MUST NOT**",
109
+ "must": "**MUST**",
110
+ "mustn't" : "**MUST NOT**",
111
+ "never": "**NEVER**",
112
+ "not recommended": "**NOT RECOMMENDED**",
113
+ "nothing": "**NOTHING**",
114
+ "not": "**NOT**",
115
+ "optional": "**OPTIONAL**",
116
+ "ought": "**SHOULD**",
117
+ "oughtn't": "**SHOULD NOT**",
118
+ "recommended": "**RECOMMENDED**",
119
+ "required": "**REQUIRED**",
120
+ "shall not": "**SHALL NOT**",
121
+ "shan't": "**SHALL NOT**",
122
+ "shall": "**SHALL**",
123
+ "should not": "**SHOULD NOT**",
124
+ "shouldn't": "**SHOULD NOT**",
125
+ "should": "**SHOULD**",
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Configuration Options
131
+
132
+ | Option | Type | Default | Description |
133
+ |--------|------|---------|-------------|
134
+ | `debug` | boolean | `false` | Enable debug logging to OpenCode's log file |
135
+ | `replacements` | object | RFC2119 keywords | Key-value pairs for text replacement |
136
+
137
+ ## Debug Logging
138
+
139
+ Logs are written to OpenCode's unified log file using the SDK logging system.
140
+
141
+ **Log location**: `~/.local/share/opencode/log/dev.log`
142
+
143
+ Enable debug mode to see what replacements are being made:
144
+
145
+ 1. Edit `~/.config/opencode/MUST-have-plugin.jsonc`
146
+ 2. Uncomment or add `"debug": true`
147
+ 3. View logs in real-time (filtering by this plugin):
148
+
149
+ ```bash
150
+ tail -f ~/.local/share/opencode/log/dev.log | grep "MUST-have-plugin"
151
+ ```
152
+
153
+ Or view all recent plugin logs:
154
+
155
+ ```bash
156
+ grep "MUST-have-plugin" ~/.local/share/opencode/log/dev.log | tail -20
157
+ ```
158
+
159
+ ### Log Format
160
+
161
+ Logs use OpenCode's standard format with structured metadata:
162
+
163
+ ```
164
+ INFO 2026-01-20T15:30:42 +2ms service=MUST-have-plugin Plugin loaded
165
+ INFO 2026-01-20T15:31:05 +5ms service=MUST-have-plugin Applied 3 replacement(s) replacements={"must":{"value":"MUST","count":2},"should":{"value":"SHOULD","count":1}}
166
+ ```
167
+
168
+ ## RFC2119 Keywords
169
+
170
+ The default configuration includes all keywords from [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119), which defines requirement levels for use in technical specifications:
171
+
172
+ | Keyword | Meaning |
173
+ |---------|---------|
174
+ | MUST / REQUIRED / SHALL | Absolute requirement |
175
+ | MUST NOT / SHALL NOT | Absolute prohibition |
176
+ | SHOULD / RECOMMENDED | Recommended, but valid reasons may exist to ignore |
177
+ | SHOULD NOT / NOT RECOMMENDED | Not recommended, but may be acceptable in some cases |
178
+ | MAY / OPTIONAL | Truly optional |
179
+
180
+ ## Troubleshooting
181
+
182
+ ### Replacements not working
183
+
184
+ 1. Check that the config file exists: `cat ~/.config/opencode/MUST-have-plugin.jsonc`
185
+ 2. Verify JSONC syntax is valid (comments and trailing commas are allowed)
186
+ 3. Enable debug mode and check the log file
187
+
188
+ ### Unexpected replacements
189
+
190
+ - Replacements use word boundaries, so `must` won't match inside `customer`
191
+ - Multi-word phrases are matched first, so `must not` won't become `MUST not`
192
+ - Check for typos in your replacement keys
193
+
194
+ ### Config changes not taking effect
195
+
196
+ The plugin re-reads the config on every message, so changes should be immediate. If not:
197
+
198
+ 1. Verify you saved the config file
199
+ 2. Check for JSONC syntax errors
200
+ 3. Restart OpenCode as a last resort
201
+
202
+ ## Publishing
203
+
204
+ ```bash
205
+ npm install
206
+ npm login
207
+ npm run build
208
+ npm publish --access public
209
+ ```
210
+
211
+ `npm publish` does not install dev dependencies for you. Run `npm install` first in a fresh checkout so `typescript` and `@types/node` are available for the `prepublishOnly` build.
212
+
213
+ For a preflight check before publishing, run `npm publish --dry-run`.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MUST-have-plugin (Replacer Plugin)
3
+ *
4
+ * Performs case-insensitive string replacements on user-typed prompts
5
+ * before they're sent to the LLM.
6
+ *
7
+ * Config: ~/.config/opencode/MUST-have-plugin.jsonc (JSONC format)
8
+ * Logs: ~/.local/share/opencode/log/dev.log (filter by service=MUST-have-plugin)
9
+ *
10
+ * Features:
11
+ * - Case-insensitive matching with word boundaries
12
+ * - Multi-word phrase support (longest-first matching)
13
+ * - Hot reload: config re-read on every message
14
+ * - Auto-generates RFC2119 defaults if no config exists
15
+ * - JSONC support (comments and trailing commas allowed in config)
16
+ *
17
+ * Scope:
18
+ * - Only replaces in user-typed prompts
19
+ * - Does NOT modify file content attached via @ mentions
20
+ * - Does NOT modify slash command output
21
+ */
22
+ import type { Plugin } from "@opencode-ai/plugin";
23
+ /**
24
+ * MUST-have-plugin (Replacer Plugin)
25
+ */
26
+ export declare const MustHavePlugin: Plugin;
27
+ export default MustHavePlugin;
package/dist/index.js ADDED
@@ -0,0 +1,270 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "fs";
2
+ import { resolve } from "path";
3
+ const CONFIG_PATH = resolve(process.env.HOME || "", ".config", "opencode", "MUST-have-plugin.jsonc");
4
+ const SERVICE_NAME = "MUST-have-plugin";
5
+ // Track debug state (loaded from config)
6
+ let debugEnabled = false;
7
+ // SDK client reference for logging (set during plugin init)
8
+ let sdkClient = null;
9
+ // Track sessions that just executed a slash command (to skip replacement)
10
+ const sessionsWithCommand = new Set();
11
+ /**
12
+ * RFC2119 default replacements
13
+ * These keywords are used in technical specifications to indicate requirement levels.
14
+ * See: https://datatracker.ietf.org/doc/html/rfc2119
15
+ */
16
+ const RFC2119_DEFAULTS = {
17
+ "must": "MUST",
18
+ "must not": "MUST NOT",
19
+ "required": "REQUIRED",
20
+ "shall": "SHALL",
21
+ "shall not": "SHALL NOT",
22
+ "should": "SHOULD",
23
+ "should not": "SHOULD NOT",
24
+ "recommended": "RECOMMENDED",
25
+ "not recommended": "NOT RECOMMENDED",
26
+ "may": "MAY",
27
+ "optional": "OPTIONAL",
28
+ };
29
+ /**
30
+ * Default config file content (JSONC format)
31
+ */
32
+ const DEFAULT_CONFIG = `{
33
+ // Uncomment to enable debug logging (view in: ~/.local/share/opencode/log/dev.log)
34
+ // Filter with: grep "service=${SERVICE_NAME}" ~/.local/share/opencode/log/dev.log
35
+ // "debug": true,
36
+
37
+ "replacements": {
38
+ "must": "MUST",
39
+ "must not": "MUST NOT",
40
+ "required": "REQUIRED",
41
+ "shall": "SHALL",
42
+ "shall not": "SHALL NOT",
43
+ "should": "SHOULD",
44
+ "should not": "SHOULD NOT",
45
+ "recommended": "RECOMMENDED",
46
+ "not recommended": "NOT RECOMMENDED",
47
+ "may": "MAY",
48
+ "optional": "OPTIONAL"
49
+ }
50
+ }
51
+ `;
52
+ /**
53
+ * Log a message using the OpenCode SDK logging system
54
+ * Logs appear in ~/.local/share/opencode/log/dev.log
55
+ */
56
+ async function log(level, message, extra) {
57
+ if (!debugEnabled && level === "debug")
58
+ return;
59
+ if (!sdkClient)
60
+ return;
61
+ try {
62
+ await sdkClient.app.log({
63
+ body: {
64
+ service: SERVICE_NAME,
65
+ level,
66
+ message,
67
+ extra,
68
+ },
69
+ });
70
+ }
71
+ catch {
72
+ // Silently fail - logging should never break the plugin
73
+ }
74
+ }
75
+ /**
76
+ * Parse JSONC (JSON with Comments) by stripping comments before parsing
77
+ */
78
+ function parseJSONC(content) {
79
+ // Strip single-line comments (// ...) - but not inside strings
80
+ // This is a simplified approach that works for typical config files
81
+ const lines = content.split("\n");
82
+ const strippedLines = lines.map((line) => {
83
+ // Find // that's not inside a string
84
+ let inString = false;
85
+ let escapeNext = false;
86
+ for (let i = 0; i < line.length; i++) {
87
+ const char = line[i];
88
+ if (escapeNext) {
89
+ escapeNext = false;
90
+ continue;
91
+ }
92
+ if (char === "\\") {
93
+ escapeNext = true;
94
+ continue;
95
+ }
96
+ if (char === '"') {
97
+ inString = !inString;
98
+ continue;
99
+ }
100
+ if (!inString && char === "/" && line[i + 1] === "/") {
101
+ return line.substring(0, i);
102
+ }
103
+ }
104
+ return line;
105
+ });
106
+ const stripped = strippedLines.join("\n");
107
+ // Also strip multi-line comments /* ... */
108
+ const noMultiLine = stripped.replace(/\/\*[\s\S]*?\*\//g, "");
109
+ // Strip trailing commas before ] or } (for JSONC compatibility)
110
+ const noTrailingCommas = noMultiLine.replace(/,(\s*[}\]])/g, "$1");
111
+ return JSON.parse(noTrailingCommas);
112
+ }
113
+ /**
114
+ * Create default config file with RFC2119 keywords if it doesn't exist
115
+ */
116
+ function ensureConfigExists() {
117
+ if (!existsSync(CONFIG_PATH)) {
118
+ try {
119
+ writeFileSync(CONFIG_PATH, DEFAULT_CONFIG, "utf-8");
120
+ // Fire-and-forget log - don't block init
121
+ log("info", "Created default config", { path: CONFIG_PATH });
122
+ }
123
+ catch (error) {
124
+ // Fire-and-forget log - don't block init
125
+ log("error", "Failed to create default config", { error: String(error) });
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Load and parse the config file
131
+ * Returns debug flag and replacements map
132
+ */
133
+ function loadConfig() {
134
+ const defaultConfig = {
135
+ debug: false,
136
+ replacements: RFC2119_DEFAULTS,
137
+ };
138
+ try {
139
+ if (!existsSync(CONFIG_PATH)) {
140
+ return defaultConfig;
141
+ }
142
+ const content = readFileSync(CONFIG_PATH, "utf-8");
143
+ const parsed = parseJSONC(content);
144
+ return {
145
+ debug: parsed.debug === true,
146
+ replacements: parsed.replacements || {},
147
+ };
148
+ }
149
+ catch (error) {
150
+ // Log to stderr since SDK client might not be ready yet
151
+ process.stderr.write(`MUST-have-plugin: [WARN] Failed to parse config: ${error}\n`);
152
+ return defaultConfig;
153
+ }
154
+ }
155
+ /**
156
+ * Escape special regex characters in a string
157
+ */
158
+ function escapeRegex(str) {
159
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
160
+ }
161
+ /**
162
+ * Apply all replacements to the given text
163
+ * - Case-insensitive matching
164
+ * - Word boundary aware (won't replace "must" inside "customer")
165
+ * - Won't match inside markdown formatting (won't replace MUST inside **MUST**)
166
+ * - Single-pass: all patterns matched at once to prevent re-replacement
167
+ */
168
+ function applyReplacements(text, replacements) {
169
+ if (Object.keys(replacements).length === 0) {
170
+ return { result: text, counts: new Map() };
171
+ }
172
+ // Sort keys by length descending (longest first) for proper matching priority
173
+ const sortedKeys = Object.keys(replacements).sort((a, b) => b.length - a.length);
174
+ // Build a single regex that matches any of the patterns
175
+ // Uses negative lookbehind/lookahead to exclude matches inside markdown formatting
176
+ // (?<![a-zA-Z*_'']) = not preceded by letter, asterisk, underscore, or apostrophe
177
+ // (?![a-zA-Z*_'']) = not followed by letter, asterisk, underscore, or apostrophe
178
+ const patternStrings = sortedKeys.map((key) => `(?<![a-zA-Z*_''])${escapeRegex(key)}(?![a-zA-Z*_''])`);
179
+ const combinedPattern = new RegExp(`(${patternStrings.join("|")})`, "gi");
180
+ // Build a case-insensitive lookup map
181
+ const lookup = new Map();
182
+ for (const key of sortedKeys) {
183
+ lookup.set(key.toLowerCase(), replacements[key]);
184
+ }
185
+ const counts = new Map();
186
+ // Single-pass replacement: each match is replaced exactly once
187
+ let lastIndex = 0;
188
+ let result = "";
189
+ let match;
190
+ while ((match = combinedPattern.exec(text)) !== null) {
191
+ const replacement = lookup.get(match[0].toLowerCase());
192
+ if (replacement) {
193
+ counts.set(match[0].toLowerCase(), (counts.get(match[0].toLowerCase()) || 0) + 1);
194
+ result += text.slice(lastIndex, match.index) + replacement;
195
+ lastIndex = match.index + match[0].length;
196
+ // If replacement ends with whitespace, consume trailing space from original text
197
+ if (/\s$/.test(replacement) && text[lastIndex] === " ") {
198
+ lastIndex++;
199
+ }
200
+ }
201
+ else {
202
+ result += text.slice(lastIndex, match.index) + match[0];
203
+ lastIndex = match.index + match[0].length;
204
+ }
205
+ }
206
+ result += text.slice(lastIndex);
207
+ return { result, counts };
208
+ }
209
+ /**
210
+ * MUST-have-plugin (Replacer Plugin)
211
+ */
212
+ export const MustHavePlugin = async ({ client }) => {
213
+ // Store SDK client for logging
214
+ sdkClient = client;
215
+ // Ensure default config exists (synchronous)
216
+ ensureConfigExists();
217
+ // Initial config load to get debug state
218
+ const initialConfig = loadConfig();
219
+ debugEnabled = initialConfig.debug;
220
+ // Fire-and-forget log during init - don't await to avoid blocking startup
221
+ log("info", "Plugin loaded", {
222
+ configPath: CONFIG_PATH,
223
+ replacementCount: Object.keys(initialConfig.replacements).length,
224
+ });
225
+ return {
226
+ "command.execute.before": async (input) => {
227
+ sessionsWithCommand.add(input.sessionID);
228
+ await log("debug", "Tracking command execution", { sessionID: input.sessionID, command: input.command });
229
+ },
230
+ "chat.message": async (input, output) => {
231
+ // Skip processing if this message came from a slash command execution
232
+ if (sessionsWithCommand.has(input.sessionID)) {
233
+ sessionsWithCommand.delete(input.sessionID);
234
+ await log("debug", "Skipping replacements - message from slash command", { sessionID: input.sessionID });
235
+ return;
236
+ }
237
+ // Hot reload: re-read config on every message
238
+ const config = loadConfig();
239
+ debugEnabled = config.debug;
240
+ if (Object.keys(config.replacements).length === 0) {
241
+ await log("debug", "No replacements configured, skipping");
242
+ return;
243
+ }
244
+ let totalReplacements = 0;
245
+ const allCounts = new Map();
246
+ // Apply to user-typed text only (not file content, not slash command output, not synthetic)
247
+ // User-typed content: type === "text" && synthetic !== true
248
+ for (const part of output.parts) {
249
+ if (part.type === "text" && "text" in part && typeof part.text === "string" && !part.synthetic) {
250
+ const { result, counts } = applyReplacements(part.text, config.replacements);
251
+ part.text = result;
252
+ // Merge counts
253
+ for (const [key, count] of counts) {
254
+ allCounts.set(key, (allCounts.get(key) || 0) + count);
255
+ totalReplacements += count;
256
+ }
257
+ }
258
+ }
259
+ // Log replacements made
260
+ if (totalReplacements > 0) {
261
+ const replacements = {};
262
+ for (const [key, count] of allCounts) {
263
+ replacements[key] = { value: config.replacements[key], count };
264
+ }
265
+ await log("info", `Applied ${totalReplacements} replacement(s)`, { replacements });
266
+ }
267
+ },
268
+ };
269
+ };
270
+ export default MustHavePlugin;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@ariane-emory/must-have-plugin",
4
+ "version": "1.0.0",
5
+ "description": "OpenCode plugin that applies configurable prompt text replacements, including RFC2119 keyword capitalization.",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "typecheck": "tsc --noEmit",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "opencode",
27
+ "opencode-plugin",
28
+ "plugin",
29
+ "rfc2119",
30
+ "text-replacement"
31
+ ],
32
+ "author": "Ariane Emory",
33
+ "license": "MIT",
34
+ "homepage": "https://github.com/ariane-emory/MUST-have-plugin#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/ariane-emory/MUST-have-plugin.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/ariane-emory/MUST-have-plugin/issues"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "peerDependencies": {
46
+ "@opencode-ai/plugin": ">=1.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@opencode-ai/plugin": "^1.4.3",
50
+ "@types/node": "^22.13.9",
51
+ "typescript": "^5.8.2"
52
+ }
53
+ }