@scopewatch/capability-inference 0.0.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.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +3 -0
- package/dist/infer.d.ts.map +1 -0
- package/dist/infer.js +231 -0
- package/dist/infer.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/infer.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["../src/infer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAsB,eAAe,EAAE,MAAM,YAAY,CAAC;AAsO/E,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,eAAe,CAmBtF"}
|
package/dist/infer.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real (not real-time-negotiated) capability inference for MCP tools that
|
|
3
|
+
* lack a declared manifest. Every result is tagged provenance: 'inferred' -
|
|
4
|
+
* this is a best-effort heuristic, not ground truth, and callers must render
|
|
5
|
+
* it with the hedged "appears to..." language Phase B already built for
|
|
6
|
+
* exactly this case.
|
|
7
|
+
*
|
|
8
|
+
* Design validated against 14 real tools from the actual, live
|
|
9
|
+
* @modelcontextprotocol/server-filesystem (see test/fixtures/real-filesystem-server-tools.json,
|
|
10
|
+
* captured directly from a real tools/list response, not invented).
|
|
11
|
+
*
|
|
12
|
+
* TWO STRUCTURAL DEFENSES against the noun/verb ambiguity inherent to
|
|
13
|
+
* scanning natural-language tool descriptions (a word like "modified" or
|
|
14
|
+
* "messages" can be a verb or a noun/adjective depending on context a
|
|
15
|
+
* keyword regex can't see):
|
|
16
|
+
*
|
|
17
|
+
* 1. Verb inference checks the tool's NAME first for the "which flavor"
|
|
18
|
+
* decision (e.g. delete vs execute vs write within the destructive
|
|
19
|
+
* pool) - terse, verb-led names carry none of the incidental nouns/
|
|
20
|
+
* adjectives flowing description prose does.
|
|
21
|
+
*
|
|
22
|
+
* 2. The severity-override rule (see inferVerb, matchDangerousKeyword)
|
|
23
|
+
* scans the FULL name+description text regardless of what the name
|
|
24
|
+
* alone found - its purpose is catching a genuinely more dangerous
|
|
25
|
+
* signal that a calm-sounding NAME might otherwise suppress (a tool
|
|
26
|
+
* named "benign_reader" whose description says it "permanently
|
|
27
|
+
* deletes" a file) - but it is deliberately narrowed to ONLY the
|
|
28
|
+
* delete/execute patterns, not all six categories. The override's job
|
|
29
|
+
* is specifically "is this secretly destructive," not "does the full
|
|
30
|
+
* text contain any keyword at all" - scanning against all six was
|
|
31
|
+
* strictly broader than that job required, and that excess breadth was
|
|
32
|
+
* the exact path a stray 'send' match ("detailed error messages")
|
|
33
|
+
* reappeared through even with name-first in place and no other bug
|
|
34
|
+
* present.
|
|
35
|
+
*
|
|
36
|
+
* Confirmed directly, not assumed: with both defenses in place, temporarily
|
|
37
|
+
* reverting every word-specific exclusion in KEYWORD_PATTERNS below (the
|
|
38
|
+
* 'messag'/'creat'/'modif' exclusions and the 'sent'/'found' additions) and
|
|
39
|
+
* re-running the full fixture matrix - all 12 tests, including the ones
|
|
40
|
+
* that broke earlier iterations - passed with none of those patches in
|
|
41
|
+
* place. That's the actual signal that this closes the class of bug
|
|
42
|
+
* structurally rather than requiring an ever-growing patch list: the
|
|
43
|
+
* patches are kept anyway as defense-in-depth for the one path they still
|
|
44
|
+
* matter on (a generically-named tool with no name-first match still falls
|
|
45
|
+
* back to a full six-category scan for its default classification), but
|
|
46
|
+
* they are no longer load-bearing for the two cases that originally broke.
|
|
47
|
+
*
|
|
48
|
+
* This is not claimed as a complete solution to keyword-heuristic ambiguity
|
|
49
|
+
* over natural language - it structurally closed the specific interaction
|
|
50
|
+
* discovered so far. A future real server could still surface a new
|
|
51
|
+
* instance through the remaining fallback path; treat that as an
|
|
52
|
+
* interesting new fixture to add, not evidence of a regression.
|
|
53
|
+
*/
|
|
54
|
+
const SEVERITY = { read: 0, fetch: 1, send: 1, write: 2, delete: 3, execute: 3 };
|
|
55
|
+
// Checked most-severe-first, so if a description contains multiple signal
|
|
56
|
+
// words, the more alarming one is the one that gets detected.
|
|
57
|
+
// NOTE: 'move'/'rename' are deliberately NOT in the write list - a relocation
|
|
58
|
+
// both creates at the destination and vacates the source, which is closer to
|
|
59
|
+
// a delete-shaped effect at the origin than a confident write signal. Left
|
|
60
|
+
// unmatched, it correctly falls through to the destructive-pool default.
|
|
61
|
+
//
|
|
62
|
+
// Patterns are left-anchored word STEMS (\b before, no \b after), not whole
|
|
63
|
+
// words - natural-language tool descriptions use conjugated forms
|
|
64
|
+
// ("deletes", "deleting", "created") far more often than bare infinitives,
|
|
65
|
+
// and \bdelete\b does not match "deletes" (no word boundary between "delete"
|
|
66
|
+
// and the trailing "s"). Left-anchoring on the stem catches all common
|
|
67
|
+
// inflections without needing to enumerate every conjugation.
|
|
68
|
+
// Longer, distinctive words use a left-anchored stem (catches conjugations
|
|
69
|
+
// like "deletes"/"deleting" without enumerating every form, since \bdelete\b
|
|
70
|
+
// does not match "deletes" - no word boundary between "delete" and "s").
|
|
71
|
+
// Short or common words (run, call, get) use exact whole-word matches
|
|
72
|
+
// instead, since a left-anchored stem on a short word risks false positives
|
|
73
|
+
// on unrelated words that merely start with it (e.g. "runtime", "callback",
|
|
74
|
+
// "getter").
|
|
75
|
+
// Audited for the conjugation gap that broke read_text_file's classification
|
|
76
|
+
// (\bdelete\b does not match "deletes"): every stem below is left-anchored
|
|
77
|
+
// (no trailing \b) unless the bare stem itself risks colliding with an
|
|
78
|
+
// unrelated word (run->runtime, call->callback, get->getter, sent->sentence/
|
|
79
|
+
// sentiment, found->foundation), in which case exact whole-word forms are
|
|
80
|
+
// enumerated instead - including irregular past tenses (sent, found) that a
|
|
81
|
+
// simple stem would miss entirely. Known residual gap, documented rather
|
|
82
|
+
// than chased further: rarer irregulars (ran, got, gotten, ate) are not
|
|
83
|
+
// exhaustively enumerated - see the module-level doc comment.
|
|
84
|
+
const KEYWORD_PATTERNS = [
|
|
85
|
+
['delete', /\b(delete|remov|destroy|purg|drop)/],
|
|
86
|
+
['execute', /\b(execut|invok|spawn)|\b(run|runs|running|call|calls|calling|called)\b/],
|
|
87
|
+
// 'messag' deliberately excluded: "error messages", "log messages" etc. are
|
|
88
|
+
// extremely common boilerplate in tool descriptions that have nothing to do
|
|
89
|
+
// with the tool sending anything - a noun/verb ambiguity that produced a
|
|
90
|
+
// real false positive (read_text_file's "detailed error messages" phrase)
|
|
91
|
+
// during validation against real tool data. 'sent' added as an exact
|
|
92
|
+
// whole-word form since "send" the bare stem doesn't cover this irregular
|
|
93
|
+
// past tense, and a bare 'sent' stem would collide with "sentence"/
|
|
94
|
+
// "sentiment".
|
|
95
|
+
['send', /\b(send|post|publish|notif|email)|\bsent\b/],
|
|
96
|
+
['fetch', /\b(fetch|download|pull|scrap)/],
|
|
97
|
+
// 'creat' and 'modif' as bare stems are deliberately avoided: "creation
|
|
98
|
+
// time"/"last modified time" are extremely common metadata-field phrases
|
|
99
|
+
// (nouns/adjectives describing timestamps, as in get_file_info's own
|
|
100
|
+
// description) with nothing to do with the tool creating or modifying
|
|
101
|
+
// anything - the same noun/verb ambiguity as 'messag' above. Bare
|
|
102
|
+
// "modified" is excluded too (it's the specific form that appears in
|
|
103
|
+
// "last modified" boilerplate); "modify"/"modifies"/"modifying" - active
|
|
104
|
+
// verb forms far more likely to describe a real action - are kept.
|
|
105
|
+
['write', /\b(writ|updat|edit|sav|upload)|\b(create|creates|created|creating|modify|modifies|modifying)\b/],
|
|
106
|
+
// 'found' added as an exact whole-word form (irregular past tense of
|
|
107
|
+
// "find" - the bare 'find' stem doesn't cover it), guarded against
|
|
108
|
+
// colliding with "foundation".
|
|
109
|
+
['read', /\b(read|list|search|view|inspect|quer|find)|\b(get|gets|getting|found)\b/],
|
|
110
|
+
];
|
|
111
|
+
function normalize(text) {
|
|
112
|
+
return text.replace(/[_-]/g, ' ').toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
function matchKeyword(text) {
|
|
115
|
+
for (const [verb, pattern] of KEYWORD_PATTERNS) {
|
|
116
|
+
if (pattern.test(text))
|
|
117
|
+
return verb;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// The severity-override's actual job is narrow: "does the full text contain
|
|
122
|
+
// a delete or execute signal the name alone missed" - not "does the full
|
|
123
|
+
// text contain ANY keyword at all." A name-first match to read/write/send/
|
|
124
|
+
// fetch that isn't contradicted by an actual delete/execute signal has
|
|
125
|
+
// nothing for the override to correct; scanning against all six categories
|
|
126
|
+
// was strictly broader than that job requires, and that excess breadth was
|
|
127
|
+
// exactly the path "detailed error messages" used to reappear as a false
|
|
128
|
+
// 'send' override even with name-first in place and no other bug present.
|
|
129
|
+
// Checking only the two categories the override actually exists to catch
|
|
130
|
+
// removes that path entirely, rather than patching around it per word.
|
|
131
|
+
const DANGEROUS_PATTERNS = KEYWORD_PATTERNS.filter(([verb]) => verb === 'delete' || verb === 'execute');
|
|
132
|
+
function matchDangerousKeyword(text) {
|
|
133
|
+
for (const [verb, pattern] of DANGEROUS_PATTERNS) {
|
|
134
|
+
if (pattern.test(text))
|
|
135
|
+
return verb;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
function inferVerb(tool) {
|
|
140
|
+
// Name-first: a tool's NAME is terse and verb-led by convention
|
|
141
|
+
// (read_file, write_file, delete_record) with none of the incidental
|
|
142
|
+
// nouns/adjectives that flowing description prose carries (a description
|
|
143
|
+
// saying "creation time" or "detailed error messages" is never mirrored
|
|
144
|
+
// in a tool's own name). Checking the name alone first for the "which
|
|
145
|
+
// flavor" decision (e.g. within the destructive pool: delete vs execute
|
|
146
|
+
// vs write), and only falling through to the noisier description text
|
|
147
|
+
// when the name itself gives no signal, structurally avoids the whole
|
|
148
|
+
// CLASS of noun/verb ambiguity that produced two real false positives
|
|
149
|
+
// (read_text_file, get_file_info) during validation - not just those two
|
|
150
|
+
// specific words.
|
|
151
|
+
//
|
|
152
|
+
// This alone is NOT sufficient, though: it must not let a calm-sounding
|
|
153
|
+
// NAME suppress a genuinely more dangerous signal sitting in the
|
|
154
|
+
// description (e.g. a tool named "benign_reader" whose description says
|
|
155
|
+
// it "permanently deletes" something). So dangerousKeywordVerb below
|
|
156
|
+
// ALWAYS scans the full name+description text, independent of what the
|
|
157
|
+
// name-first check found - but ONLY against the delete/execute patterns
|
|
158
|
+
// specifically (see matchDangerousKeyword), not all six categories. The
|
|
159
|
+
// override's actual job is narrow ("is this secretly destructive"), and
|
|
160
|
+
// scanning against all six was strictly broader than that job needed -
|
|
161
|
+
// that excess breadth was the exact path a stray 'send' match ("detailed
|
|
162
|
+
// error messages") used to reappear even with name-first in place.
|
|
163
|
+
const nameOnlyVerb = matchKeyword(normalize(tool.name));
|
|
164
|
+
const fullText = normalize(`${tool.name} ${tool.description}`);
|
|
165
|
+
const dangerousKeywordVerb = matchDangerousKeyword(fullText);
|
|
166
|
+
const keywordVerb = nameOnlyVerb ?? matchKeyword(fullText);
|
|
167
|
+
const ann = tool.annotations;
|
|
168
|
+
const warnings = [];
|
|
169
|
+
if (ann?.destructiveHint === true && ann?.readOnlyHint === true) {
|
|
170
|
+
warnings.push(`Tool '${tool.name}' declares contradictory annotations (destructiveHint and readOnlyHint both true); resolving to the more cautious (destructive) classification.`);
|
|
171
|
+
}
|
|
172
|
+
let hintVerb;
|
|
173
|
+
if (ann?.destructiveHint === true) {
|
|
174
|
+
hintVerb = keywordVerb === 'delete' || keywordVerb === 'execute' || keywordVerb === 'write' ? keywordVerb : 'delete';
|
|
175
|
+
}
|
|
176
|
+
else if (ann?.readOnlyHint === true) {
|
|
177
|
+
hintVerb = keywordVerb === 'fetch' ? 'fetch' : 'read';
|
|
178
|
+
}
|
|
179
|
+
else if (ann && ann.destructiveHint === false && ann.readOnlyHint === false) {
|
|
180
|
+
hintVerb = keywordVerb ?? 'write';
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
hintVerb = keywordVerb ?? 'execute';
|
|
184
|
+
}
|
|
185
|
+
let finalVerb = hintVerb;
|
|
186
|
+
if (dangerousKeywordVerb && SEVERITY[dangerousKeywordVerb] > SEVERITY[hintVerb]) {
|
|
187
|
+
warnings.push(`Tool '${tool.name}': keyword evidence ('${dangerousKeywordVerb}') found in its name or description is more severe than the annotation/name-based classification ('${hintVerb}'); using the more cautious classification.`);
|
|
188
|
+
finalVerb = dangerousKeywordVerb;
|
|
189
|
+
}
|
|
190
|
+
return { verb: finalVerb, warnings };
|
|
191
|
+
}
|
|
192
|
+
const RESOURCE_PATTERNS = [
|
|
193
|
+
['filesystem:*', /\b(path|paths|file|filepath|directory|dir)\b/],
|
|
194
|
+
['repo:*', /\b(repo|repository)\b/],
|
|
195
|
+
['http:*', /\b(url|uri|endpoint)\b/],
|
|
196
|
+
];
|
|
197
|
+
function inferResource(tool) {
|
|
198
|
+
// Deliberately scans ONLY the input schema's parameter names/descriptions,
|
|
199
|
+
// not the tool's free-text description - a description can casually
|
|
200
|
+
// mention path-shaped words ("...their nested paths...") without the tool
|
|
201
|
+
// actually taking any such parameter, which would otherwise produce a
|
|
202
|
+
// falsely specific resource guess instead of the honest wildcard fallback.
|
|
203
|
+
const props = tool.inputSchema?.properties ?? {};
|
|
204
|
+
const paramText = Object.entries(props)
|
|
205
|
+
.map(([key, val]) => `${key} ${val.description ?? ''}`)
|
|
206
|
+
.join(' ');
|
|
207
|
+
const text = normalize(paramText);
|
|
208
|
+
for (const [resource, pattern] of RESOURCE_PATTERNS) {
|
|
209
|
+
if (pattern.test(text))
|
|
210
|
+
return resource;
|
|
211
|
+
}
|
|
212
|
+
return '*';
|
|
213
|
+
}
|
|
214
|
+
export function inferCapabilities(server_id, tools) {
|
|
215
|
+
const capabilities = [];
|
|
216
|
+
const warnings = [];
|
|
217
|
+
for (const tool of tools) {
|
|
218
|
+
const { verb, warnings: toolWarnings } = inferVerb(tool);
|
|
219
|
+
const resource = inferResource(tool);
|
|
220
|
+
capabilities.push({
|
|
221
|
+
tool_id: tool.name,
|
|
222
|
+
verb,
|
|
223
|
+
resource,
|
|
224
|
+
provenance: 'inferred',
|
|
225
|
+
description: `inferred from tool name/description/schema (server: ${server_id})`,
|
|
226
|
+
});
|
|
227
|
+
warnings.push(...toolWarnings);
|
|
228
|
+
}
|
|
229
|
+
return { capabilities, warnings };
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=infer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer.js","sourceRoot":"","sources":["../src/infer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A tool as returned by a real MCP server's tools/list response. */
|
|
2
|
+
export type McpTool = {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema?: {
|
|
6
|
+
properties?: Record<string, {
|
|
7
|
+
type?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
/** Optional per the MCP spec - self-reported by the server author, not independently verified. */
|
|
12
|
+
annotations?: {
|
|
13
|
+
readOnlyHint?: boolean;
|
|
14
|
+
destructiveHint?: boolean;
|
|
15
|
+
idempotentHint?: boolean;
|
|
16
|
+
openWorldHint?: boolean;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type InferredCapability = {
|
|
20
|
+
tool_id: string;
|
|
21
|
+
verb: 'read' | 'fetch' | 'send' | 'write' | 'delete' | 'execute';
|
|
22
|
+
resource: string;
|
|
23
|
+
provenance: 'inferred';
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
export type InferenceResult = {
|
|
27
|
+
capabilities: InferredCapability[];
|
|
28
|
+
/** Non-fatal signals worth surfacing in diagnostics - contradictory self-reported
|
|
29
|
+
* annotations, or cases where keyword evidence overrode a declared hint. */
|
|
30
|
+
warnings: string[];
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtE,CAAC;IACF,kGAAkG;IAClG,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC;gFAC4E;IAC5E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scopewatch/capability-inference",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Heuristic capability inference from real MCP tools/list data, for servers without declared capability metadata",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Sathvik Putta <puttasathvik16@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/PuttaSathvik16/scopewatch.git",
|
|
10
|
+
"directory": "packages/capability-inference"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"test": "node --import tsx --test 'test/**/*.test.ts'",
|
|
27
|
+
"clean": "rm -rf dist"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"typescript": "^5.7.0",
|
|
32
|
+
"tsx": "^4.19.0"
|
|
33
|
+
}
|
|
34
|
+
}
|