@j0hanz/filesystem-mcp 1.19.0 → 2.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/README.md +333 -838
- package/dist/cli-help.d.ts +3 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +169 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +168 -123
- package/dist/cli.js.map +1 -0
- package/dist/core/concurrency.d.ts +70 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +173 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cursor.d.ts +23 -0
- package/dist/core/cursor.d.ts.map +1 -0
- package/dist/core/cursor.js +62 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/errors.d.ts +96 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +356 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-uri.d.ts +24 -0
- package/dist/core/file-uri.d.ts.map +1 -0
- package/dist/core/file-uri.js +52 -0
- package/dist/core/file-uri.js.map +1 -0
- package/dist/core/fmt.d.ts +38 -0
- package/dist/core/fmt.d.ts.map +1 -0
- package/dist/core/fmt.js +109 -0
- package/dist/core/fmt.js.map +1 -0
- package/dist/core/fs.d.ts +108 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +243 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/glob.d.ts +41 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +399 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/input-required.d.ts +147 -0
- package/dist/core/input-required.d.ts.map +1 -0
- package/dist/core/input-required.js +261 -0
- package/dist/core/input-required.js.map +1 -0
- package/dist/core/mime.d.ts +15 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +188 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/observability.d.ts +10 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/observability.js +76 -0
- package/dist/core/observability.js.map +1 -0
- package/dist/core/page-store.d.ts +41 -0
- package/dist/core/page-store.d.ts.map +1 -0
- package/dist/core/page-store.js +74 -0
- package/dist/core/page-store.js.map +1 -0
- package/dist/core/path-completer.d.ts +10 -0
- package/dist/core/path-completer.d.ts.map +1 -0
- package/dist/core/path-completer.js +227 -0
- package/dist/core/path-completer.js.map +1 -0
- package/dist/core/path-discovery.d.ts +8 -0
- package/dist/core/path-discovery.d.ts.map +1 -0
- package/dist/core/path-discovery.js +105 -0
- package/dist/core/path-discovery.js.map +1 -0
- package/dist/core/path-utils.d.ts +9 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +121 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/path.d.ts +148 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +674 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/primitives.d.ts +23 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +54 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/read.d.ts +74 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +562 -0
- package/dist/core/read.js.map +1 -0
- package/dist/core/schema.d.ts +139 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +345 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.d.ts +100 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +248 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/sensitive.d.ts +6 -0
- package/dist/core/sensitive.d.ts.map +1 -0
- package/dist/core/sensitive.js +142 -0
- package/dist/core/sensitive.js.map +1 -0
- package/dist/core/store.d.ts +57 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/util.d.ts +26 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +66 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/watcher-registry.d.ts +68 -0
- package/dist/core/watcher-registry.d.ts.map +1 -0
- package/dist/core/watcher-registry.js +312 -0
- package/dist/core/watcher-registry.js.map +1 -0
- package/dist/http-policy.d.ts +121 -0
- package/dist/http-policy.d.ts.map +1 -0
- package/dist/http-policy.js +340 -0
- package/dist/http-policy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -41
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +71 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.d.ts +8 -6
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +74 -168
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +73 -9
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +392 -163
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +62 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +139 -1
- package/dist/server.js.map +1 -0
- package/dist/tools/batch.d.ts +89 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +157 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/create.d.ts +2 -0
- package/dist/tools/create.d.ts.map +1 -0
- package/dist/tools/create.js +142 -0
- package/dist/tools/create.js.map +1 -0
- package/dist/tools/define.d.ts +110 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +394 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/delete-file.d.ts +2 -4
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +330 -87
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/diff.d.ts +2 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +94 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +442 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +26 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list.d.ts +2 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +291 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +321 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.d.ts.map +1 -0
- package/dist/tools/patch.js +155 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/progress.d.ts +52 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +172 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/tools/read.d.ts +2 -4
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +398 -152
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replace-in-files.d.ts +2 -4
- package/dist/tools/replace-in-files.d.ts.map +1 -0
- package/dist/tools/replace-in-files.js +435 -264
- package/dist/tools/replace-in-files.js.map +1 -0
- package/dist/tools/roots.d.ts +2 -4
- package/dist/tools/roots.d.ts.map +1 -0
- package/dist/tools/roots.js +29 -52
- package/dist/tools/roots.js.map +1 -0
- package/dist/tools/search-content.d.ts +2 -4
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +262 -228
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +2 -4
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +162 -161
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/stat.d.ts +2 -4
- package/dist/tools/stat.d.ts.map +1 -0
- package/dist/tools/stat.js +202 -62
- package/dist/tools/stat.js.map +1 -0
- package/dist/transport/http.d.ts +5 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +276 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/shared.d.ts +45 -0
- package/dist/transport/shared.d.ts.map +1 -0
- package/dist/transport/shared.js +65 -0
- package/dist/transport/shared.js.map +1 -0
- package/dist/transport/stdio.d.ts +32 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +279 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/package.json +37 -46
- package/dist/assets/logo.svg +0 -35
- package/dist/completions.d.ts +0 -2
- package/dist/completions.js +0 -572
- package/dist/config.d.ts +0 -119
- package/dist/config.js +0 -31
- package/dist/lib/abort.d.ts +0 -7
- package/dist/lib/abort.js +0 -81
- package/dist/lib/constants.d.ts +0 -42
- package/dist/lib/constants.js +0 -313
- package/dist/lib/errors.d.ts +0 -28
- package/dist/lib/errors.js +0 -301
- package/dist/lib/file-operations/core.d.ts +0 -53
- package/dist/lib/file-operations/core.js +0 -144
- package/dist/lib/file-operations/metadata.d.ts +0 -77
- package/dist/lib/file-operations/metadata.js +0 -878
- package/dist/lib/file-operations/search.d.ts +0 -46
- package/dist/lib/file-operations/search.js +0 -1194
- package/dist/lib/file-operations/traversal.d.ts +0 -40
- package/dist/lib/file-operations/traversal.js +0 -310
- package/dist/lib/fs-helpers.d.ts +0 -46
- package/dist/lib/fs-helpers.js +0 -606
- package/dist/lib/globs.d.ts +0 -2
- package/dist/lib/globs.js +0 -19
- package/dist/lib/logger.d.ts +0 -27
- package/dist/lib/logger.js +0 -91
- package/dist/lib/observability.d.ts +0 -33
- package/dist/lib/observability.js +0 -373
- package/dist/lib/paths.d.ts +0 -35
- package/dist/lib/paths.js +0 -634
- package/dist/lib/resource-store.d.ts +0 -28
- package/dist/lib/resource-store.js +0 -195
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.js +0 -37
- package/dist/lib/zod-codecs.d.ts +0 -2
- package/dist/lib/zod-codecs.js +0 -18
- package/dist/pkg-info.d.ts +0 -7
- package/dist/pkg-info.js +0 -9
- package/dist/resources/generated-instructions.d.ts +0 -1
- package/dist/resources/generated-instructions.js +0 -105
- package/dist/resources/tool-catalog.d.ts +0 -2
- package/dist/resources/tool-catalog.js +0 -78
- package/dist/resources/tool-info.d.ts +0 -9
- package/dist/resources/tool-info.js +0 -206
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/resources/workflows.js +0 -72
- package/dist/schemas.d.ts +0 -505
- package/dist/schemas.js +0 -676
- package/dist/server/bootstrap.d.ts +0 -6
- package/dist/server/bootstrap.js +0 -563
- package/dist/server/event-store.d.ts +0 -18
- package/dist/server/event-store.js +0 -71
- package/dist/server/roots-manager.d.ts +0 -28
- package/dist/server/roots-manager.js +0 -227
- package/dist/server/task-store.d.ts +0 -10
- package/dist/server/task-store.js +0 -91
- package/dist/tools/apply-patch.d.ts +0 -4
- package/dist/tools/apply-patch.js +0 -235
- package/dist/tools/calculate-hash.d.ts +0 -4
- package/dist/tools/calculate-hash.js +0 -187
- package/dist/tools/contract.d.ts +0 -50
- package/dist/tools/contract.js +0 -1
- package/dist/tools/create-directory.d.ts +0 -4
- package/dist/tools/create-directory.js +0 -68
- package/dist/tools/diff-files.d.ts +0 -4
- package/dist/tools/diff-files.js +0 -136
- package/dist/tools/edit-file.d.ts +0 -4
- package/dist/tools/edit-file.js +0 -256
- package/dist/tools/icons.d.ts +0 -15
- package/dist/tools/icons.js +0 -24
- package/dist/tools/list-directory.d.ts +0 -4
- package/dist/tools/list-directory.js +0 -218
- package/dist/tools/move-file.d.ts +0 -4
- package/dist/tools/move-file.js +0 -178
- package/dist/tools/read-multiple.d.ts +0 -4
- package/dist/tools/read-multiple.js +0 -207
- package/dist/tools/shared.d.ts +0 -172
- package/dist/tools/shared.js +0 -599
- package/dist/tools/stat-many.d.ts +0 -4
- package/dist/tools/stat-many.js +0 -104
- package/dist/tools/task-support.d.ts +0 -29
- package/dist/tools/task-support.js +0 -472
- package/dist/tools/tree.d.ts +0 -4
- package/dist/tools/tree.js +0 -102
- package/dist/tools/write-file.d.ts +0 -4
- package/dist/tools/write-file.js +0 -60
- package/dist/tools.d.ts +0 -5
- package/dist/tools.js +0 -54
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ErrorCode, FsError } from './errors.js';
|
|
3
|
+
import { Logger } from './observability.js';
|
|
4
|
+
import { MIB } from './util.js';
|
|
5
|
+
const MAX_ENTRIES = 64;
|
|
6
|
+
const MAX_TOTAL_BYTES = 25 * MIB;
|
|
7
|
+
const MAX_ENTRY_BYTES = 10 * MIB;
|
|
8
|
+
const ENTRY_TTL_MS = 60 * 1000;
|
|
9
|
+
function isExpired(entry, now = Date.now()) {
|
|
10
|
+
const expiresAt = Date.parse(entry.expiresAt);
|
|
11
|
+
if (!Number.isFinite(expiresAt)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return expiresAt <= now;
|
|
15
|
+
}
|
|
16
|
+
export class ResourceStore {
|
|
17
|
+
byUri = new Map();
|
|
18
|
+
_totalBytes = 0;
|
|
19
|
+
onListChanged;
|
|
20
|
+
constructor(onListChanged) {
|
|
21
|
+
this.onListChanged = onListChanged;
|
|
22
|
+
}
|
|
23
|
+
emitListChanged(changed) {
|
|
24
|
+
if (changed)
|
|
25
|
+
this.onListChanged?.();
|
|
26
|
+
}
|
|
27
|
+
bumpLru(uri, entry) {
|
|
28
|
+
if (!this.byUri.has(uri)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.byUri.delete(uri);
|
|
32
|
+
this.byUri.set(uri, entry);
|
|
33
|
+
}
|
|
34
|
+
removeEntry(uri) {
|
|
35
|
+
const existing = this.byUri.get(uri);
|
|
36
|
+
if (!existing) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
this._totalBytes -= existing.size;
|
|
40
|
+
this.byUri.delete(uri);
|
|
41
|
+
return existing;
|
|
42
|
+
}
|
|
43
|
+
evictOldest() {
|
|
44
|
+
const first = this.byUri.values().next();
|
|
45
|
+
if (first.done) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.removeEntry(first.value.uri);
|
|
49
|
+
}
|
|
50
|
+
pruneExpiredEntries(now = Date.now()) {
|
|
51
|
+
const toRemove = [];
|
|
52
|
+
for (const entry of this.byUri.values()) {
|
|
53
|
+
if (isExpired(entry, now)) {
|
|
54
|
+
toRemove.push(entry.uri);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const uri of toRemove) {
|
|
58
|
+
this.removeEntry(uri);
|
|
59
|
+
}
|
|
60
|
+
return toRemove.length > 0;
|
|
61
|
+
}
|
|
62
|
+
enforceLimits() {
|
|
63
|
+
while (this.byUri.size > MAX_ENTRIES) {
|
|
64
|
+
this.evictOldest();
|
|
65
|
+
}
|
|
66
|
+
while (this._totalBytes > MAX_TOTAL_BYTES) {
|
|
67
|
+
if (this.byUri.size === 0) {
|
|
68
|
+
Logger.error(`[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
this.evictOldest();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
getExisting(uri) {
|
|
75
|
+
const existing = this.byUri.get(uri);
|
|
76
|
+
if (!existing) {
|
|
77
|
+
throw new FsError(ErrorCode.NOT_FOUND, `Resource not found: ${uri}. Re-run the tool to regenerate.`);
|
|
78
|
+
}
|
|
79
|
+
if (isExpired(existing)) {
|
|
80
|
+
this.removeEntry(uri);
|
|
81
|
+
this.emitListChanged(true);
|
|
82
|
+
throw new FsError(ErrorCode.NOT_FOUND, `Resource expired: ${uri}. Re-run the tool to regenerate.`);
|
|
83
|
+
}
|
|
84
|
+
this.bumpLru(uri, existing);
|
|
85
|
+
return existing;
|
|
86
|
+
}
|
|
87
|
+
checkBeforePut(entryBytes) {
|
|
88
|
+
const changed = this.pruneExpiredEntries();
|
|
89
|
+
if (entryBytes > MAX_ENTRY_BYTES) {
|
|
90
|
+
this.emitListChanged(changed);
|
|
91
|
+
throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
putText(params) {
|
|
95
|
+
const entryBytes = Buffer.byteLength(params.text, 'utf8');
|
|
96
|
+
this.checkBeforePut(entryBytes);
|
|
97
|
+
const storedAt = new Date();
|
|
98
|
+
const entry = {
|
|
99
|
+
uri: `filesystem-mcp://result/${randomUUID()}`,
|
|
100
|
+
name: params.name,
|
|
101
|
+
mimeType: params.mimeType ?? 'text/plain',
|
|
102
|
+
size: entryBytes,
|
|
103
|
+
storedAt: storedAt.toISOString(),
|
|
104
|
+
expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),
|
|
105
|
+
text: params.text,
|
|
106
|
+
};
|
|
107
|
+
this.byUri.set(entry.uri, entry);
|
|
108
|
+
this._totalBytes += entryBytes;
|
|
109
|
+
this.enforceLimits();
|
|
110
|
+
this.emitListChanged(true);
|
|
111
|
+
return entry;
|
|
112
|
+
}
|
|
113
|
+
getEntry(uri) {
|
|
114
|
+
return this.getExisting(uri);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns live URIs currently in the store.
|
|
118
|
+
* Side effect: prunes expired entries before returning.
|
|
119
|
+
*
|
|
120
|
+
* Deliberately silent: this is the read path `resources/list` runs through,
|
|
121
|
+
* and the caller is already receiving the post-prune list. Emitting here
|
|
122
|
+
* would tell the client its list changed while handing it that same list.
|
|
123
|
+
*/
|
|
124
|
+
keys() {
|
|
125
|
+
this.pruneExpiredEntries();
|
|
126
|
+
return Array.from(this.byUri.keys());
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Owner of the externalize-a-payload rule: a tool whose inline response is
|
|
131
|
+
* truncated publishes the full value to the resource store as pretty-printed
|
|
132
|
+
* JSON and hands back the URI to reach it by, plus the link block that offers
|
|
133
|
+
* it to the user.
|
|
134
|
+
*/
|
|
135
|
+
export function putJsonResource(store, name,
|
|
136
|
+
// `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:
|
|
137
|
+
// JSON.stringify returns undefined for undefined and for functions, and `null`
|
|
138
|
+
// is excluded so non-string payloads cannot enter the store's text entry.
|
|
139
|
+
value) {
|
|
140
|
+
const entry = store.putText({
|
|
141
|
+
name,
|
|
142
|
+
mimeType: 'application/json',
|
|
143
|
+
text: JSON.stringify(value, null, 2),
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
entry: {
|
|
147
|
+
uri: entry.uri,
|
|
148
|
+
size: entry.size,
|
|
149
|
+
mimeType: entry.mimeType,
|
|
150
|
+
expiresAt: entry.expiresAt,
|
|
151
|
+
},
|
|
152
|
+
link: {
|
|
153
|
+
type: 'resource_link',
|
|
154
|
+
uri: entry.uri,
|
|
155
|
+
name: entry.name,
|
|
156
|
+
mimeType: entry.mimeType,
|
|
157
|
+
size: entry.size,
|
|
158
|
+
annotations: { audience: ['user'] },
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { Logger } from './observability.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n storedAt: string;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const toRemove: string[] = [];\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) {\n toRemove.push(entry.uri);\n }\n }\n for (const uri of toRemove) {\n this.removeEntry(uri);\n }\n return toRemove.length > 0;\n }\n\n private enforceLimits(): void {\n while (this.byUri.size > MAX_ENTRIES) {\n this.evictOldest();\n }\n while (this._totalBytes > MAX_TOTAL_BYTES) {\n if (this.byUri.size === 0) {\n Logger.error(\n `[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`,\n );\n break;\n }\n this.evictOldest();\n }\n }\n\n private getExisting(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n storedAt: storedAt.toISOString(),\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n getEntry(uri: string): ResourceEntry {\n return this.getExisting(uri);\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const KIB = 1024;
|
|
2
|
+
export declare const MIB: number;
|
|
3
|
+
export declare const GIB: number;
|
|
4
|
+
export declare function parseEnvInt(envVar: string, defaultValue: number, min: number, max: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Split a comma-separated config value into trimmed, non-empty entries. Empty
|
|
7
|
+
* entries are dropped so "," or " " reads as unset rather than as a list of
|
|
8
|
+
* blanks. Every comma-separated env var this server accepts parses through
|
|
9
|
+
* here, so they all agree on what "configured" means.
|
|
10
|
+
*/
|
|
11
|
+
export declare function splitCsvList(value: string | undefined): string[];
|
|
12
|
+
export declare const PARALLEL_CONCURRENCY: number;
|
|
13
|
+
export declare const ROOTS_TIMEOUT_MS = 5000;
|
|
14
|
+
export declare function getMaxTextFileSize(): number;
|
|
15
|
+
export declare function getDefaultReadManyMaxTotalSize(): number;
|
|
16
|
+
/** Default line chunk size for read continuation when no explicit range was given. */
|
|
17
|
+
export declare const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;
|
|
18
|
+
export declare const DEFAULT_SEARCH_TIMEOUT_MS: number;
|
|
19
|
+
export declare const MAX_TREE_DEPTH = 50;
|
|
20
|
+
export declare const DEFAULT_TREE_ENTRIES = 1000;
|
|
21
|
+
export declare const MAX_LIST_ENTRIES = 20000;
|
|
22
|
+
export declare const MAX_SEARCH_RESULTS = 10000;
|
|
23
|
+
export declare const DEFAULT_SEARCH_RESULTS = 100;
|
|
24
|
+
export declare const MAX_SEARCH_DEPTH = 100;
|
|
25
|
+
export declare const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
|
|
26
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,GAAG,OAAO,CAAC;AACxB,eAAO,MAAM,GAAG,QAAa,CAAC;AAC9B,eAAO,MAAM,GAAG,QAAa,CAAC;AAoB9B,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,CAER;AA6BD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAKhE;AAED,eAAO,MAAM,oBAAoB,QAAoD,CAAC;AAEtF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,wBAAgB,kBAAkB,IAAI,MAAM,CAQ3C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,eAAO,MAAM,yBAAyB,QAAwD,CAAC;AAE/F,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AACxC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { availableParallelism } from 'node:os';
|
|
2
|
+
import { cli } from './config.js';
|
|
3
|
+
import { Logger } from './observability.js';
|
|
4
|
+
export const KIB = 1024;
|
|
5
|
+
export const MIB = 1024 * KIB;
|
|
6
|
+
export const GIB = 1024 * MIB;
|
|
7
|
+
const loggedWarns = new Set();
|
|
8
|
+
function logInvalidEnvValue(envVar, value, expected, defaultValue) {
|
|
9
|
+
const key = `${envVar}:${value}:${expected}`;
|
|
10
|
+
if (loggedWarns.has(key)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
loggedWarns.add(key);
|
|
14
|
+
Logger.warn(`Invalid ${envVar} value: ${value} (must be ${expected}). Using default: ${String(defaultValue)}`);
|
|
15
|
+
}
|
|
16
|
+
export function parseEnvInt(envVar, defaultValue, min, max) {
|
|
17
|
+
return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);
|
|
18
|
+
}
|
|
19
|
+
/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */
|
|
20
|
+
function parseIntSetting(name, value, defaultValue, min, max) {
|
|
21
|
+
if (!value) {
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
const parsed = Number(trimmed);
|
|
26
|
+
if (trimmed === '' ||
|
|
27
|
+
Number.isNaN(parsed) ||
|
|
28
|
+
!Number.isInteger(parsed) ||
|
|
29
|
+
parsed < min ||
|
|
30
|
+
parsed > max) {
|
|
31
|
+
logInvalidEnvValue(name, value, `${String(min)}-${String(max)}`, defaultValue);
|
|
32
|
+
return defaultValue;
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Split a comma-separated config value into trimmed, non-empty entries. Empty
|
|
38
|
+
* entries are dropped so "," or " " reads as unset rather than as a list of
|
|
39
|
+
* blanks. Every comma-separated env var this server accepts parses through
|
|
40
|
+
* here, so they all agree on what "configured" means.
|
|
41
|
+
*/
|
|
42
|
+
export function splitCsvList(value) {
|
|
43
|
+
return (value ?? '')
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((entry) => entry.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
export const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);
|
|
49
|
+
export const ROOTS_TIMEOUT_MS = 5000;
|
|
50
|
+
export function getMaxTextFileSize() {
|
|
51
|
+
return parseIntSetting('FS_MAX_FILE_SIZE', cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'], 10 * MIB, MIB, 100 * MIB);
|
|
52
|
+
}
|
|
53
|
+
export function getDefaultReadManyMaxTotalSize() {
|
|
54
|
+
return parseEnvInt('FS_MAX_READ_MANY_BYTES', 512 * KIB, 10 * KIB, 100 * MIB);
|
|
55
|
+
}
|
|
56
|
+
/** Default line chunk size for read continuation when no explicit range was given. */
|
|
57
|
+
export const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;
|
|
58
|
+
export const DEFAULT_SEARCH_TIMEOUT_MS = parseEnvInt('FS_SEARCH_TIMEOUT_MS', 5000, 100, 60000);
|
|
59
|
+
export const MAX_TREE_DEPTH = 50;
|
|
60
|
+
export const DEFAULT_TREE_ENTRIES = 1000;
|
|
61
|
+
export const MAX_LIST_ENTRIES = 20000;
|
|
62
|
+
export const MAX_SEARCH_RESULTS = 10000;
|
|
63
|
+
export const DEFAULT_SEARCH_RESULTS = 100;
|
|
64
|
+
export const MAX_SEARCH_DEPTH = 100;
|
|
65
|
+
export const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
|
|
66
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/core/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { availableParallelism } from 'node:os';\n\nimport { cli } from './config.js';\nimport { Logger } from './observability.js';\n\nexport const KIB = 1024;\nexport const MIB = 1024 * KIB;\nexport const GIB = 1024 * MIB;\n\nconst loggedWarns = new Set<string>();\n\nfunction logInvalidEnvValue(\n envVar: string,\n value: string,\n expected: string,\n defaultValue: number | boolean,\n): void {\n const key = `${envVar}:${value}:${expected}`;\n if (loggedWarns.has(key)) {\n return;\n }\n loggedWarns.add(key);\n Logger.warn(\n `Invalid ${envVar} value: ${value} (must be ${expected}). Using default: ${String(defaultValue)}`,\n );\n}\n\nexport function parseEnvInt(\n envVar: string,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n return parseIntSetting(envVar, process.env[envVar], defaultValue, min, max);\n}\n\n/** Validate a raw integer setting (env var or CLI override) with a logged fallback. */\nfunction parseIntSetting(\n name: string,\n value: string | undefined,\n defaultValue: number,\n min: number,\n max: number,\n): number {\n if (!value) {\n return defaultValue;\n }\n\n const trimmed = value.trim();\n const parsed = Number(trimmed);\n if (\n trimmed === '' ||\n Number.isNaN(parsed) ||\n !Number.isInteger(parsed) ||\n parsed < min ||\n parsed > max\n ) {\n logInvalidEnvValue(name, value, `${String(min)}-${String(max)}`, defaultValue);\n return defaultValue;\n }\n return parsed;\n}\n\n/**\n * Split a comma-separated config value into trimmed, non-empty entries. Empty\n * entries are dropped so \",\" or \" \" reads as unset rather than as a list of\n * blanks. Every comma-separated env var this server accepts parses through\n * here, so they all agree on what \"configured\" means.\n */\nexport function splitCsvList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n\nexport const PARALLEL_CONCURRENCY = Math.min(Math.max(availableParallelism(), 4), 32);\n\nexport const ROOTS_TIMEOUT_MS = 5000;\n\nexport function getMaxTextFileSize(): number {\n return parseIntSetting(\n 'FS_MAX_FILE_SIZE',\n cli.maxFileSize ?? process.env['FS_MAX_FILE_SIZE'],\n 10 * MIB,\n MIB,\n 100 * MIB,\n );\n}\n\nexport function getDefaultReadManyMaxTotalSize(): number {\n return parseEnvInt('FS_MAX_READ_MANY_BYTES', 512 * KIB, 10 * KIB, 100 * MIB);\n}\n\n/** Default line chunk size for read continuation when no explicit range was given. */\nexport const DEFAULT_CONTINUATION_CHUNK_SIZE = 200;\n\nexport const DEFAULT_SEARCH_TIMEOUT_MS = parseEnvInt('FS_SEARCH_TIMEOUT_MS', 5000, 100, 60000);\n\nexport const MAX_TREE_DEPTH = 50;\nexport const DEFAULT_TREE_ENTRIES = 1000;\n\nexport const MAX_LIST_ENTRIES = 20000;\n\nexport const MAX_SEARCH_RESULTS = 10000;\nexport const DEFAULT_SEARCH_RESULTS = 100;\nexport const MAX_SEARCH_DEPTH = 100;\n\nexport const DEFAULT_SEARCH_CONTENT_RESULTS = 500;\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { PathGuard } from './path.js';
|
|
2
|
+
export declare const MAX_WATCHERS: number;
|
|
3
|
+
/**
|
|
4
|
+
* `ok` means a watcher is live for this uri and `notify` is registered. Every
|
|
5
|
+
* other outcome names why, and only `invalid-path` carries what
|
|
6
|
+
* `validateExistingPath` threw — so `error` is unreachable on a branch that has
|
|
7
|
+
* none.
|
|
8
|
+
*/
|
|
9
|
+
export type WatcherAttachResult = {
|
|
10
|
+
ok: true;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed';
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
reason: 'invalid-path';
|
|
17
|
+
error: unknown;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Owns the uri → FSWatcher map and the subscription bookkeeping around it:
|
|
21
|
+
* notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —
|
|
22
|
+
* plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits
|
|
23
|
+
* path validation midway and re-checks `isStale` and `hasWatcher` itself
|
|
24
|
+
* afterwards, so callers do not: they take a lease and release it. The
|
|
25
|
+
* individual primitives stay public for the state-machine tests.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createWatcherRegistry(): {
|
|
28
|
+
hasWatcher: (uri: string) => boolean;
|
|
29
|
+
isAtCap: () => boolean;
|
|
30
|
+
/** Live watcher count, for pre-checking a batched listen against remaining capacity. */
|
|
31
|
+
size: () => number;
|
|
32
|
+
isStale: (uri: string) => boolean;
|
|
33
|
+
startSubscribe: (uri: string) => void;
|
|
34
|
+
cancelSubscribe: (uri: string) => void;
|
|
35
|
+
addCallback: (uri: string, notify: (uri: string) => void) => void;
|
|
36
|
+
retain: (uri: string) => void;
|
|
37
|
+
release: (uri: string) => void;
|
|
38
|
+
/**
|
|
39
|
+
* The one attach ladder both watcher entry points run: `resources/subscribe`
|
|
40
|
+
* (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and
|
|
41
|
+
* stdio). Never throws — it reports the outcome and lets each caller decide
|
|
42
|
+
* what that is worth: subscribe owes its caller a precise error, and listen
|
|
43
|
+
* (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing
|
|
44
|
+
* every lease it already took and rejecting the request. Idempotent per URI
|
|
45
|
+
* — a second call for an already-watched URI re-registers the notify
|
|
46
|
+
* callback (one watcher per URI).
|
|
47
|
+
*
|
|
48
|
+
* `markSubscribe` is the one branch that differs: only `resources/subscribe`
|
|
49
|
+
* declares desired state (what `isStale` aborts against). The listen path
|
|
50
|
+
* must not, or a rejected attach past that point strands a `'subscribing'`
|
|
51
|
+
* entry nothing settles. Whoever declares it, this function settles it:
|
|
52
|
+
* every failing exit past that point cancels the declaration, so no uri is
|
|
53
|
+
* poisoned for a later attach.
|
|
54
|
+
*
|
|
55
|
+
* Every `ok` return takes one lease; lifetime is the caller's to manage.
|
|
56
|
+
* HTTP releases when the listen response closes. Stdio tracks the listen
|
|
57
|
+
* request ID and releases on cancellation, SDK rejection, or graceful
|
|
58
|
+
* completion. In both legs the ref-count is by URI, so a watcher another
|
|
59
|
+
* stream still holds survives.
|
|
60
|
+
*/
|
|
61
|
+
acquire(pathGuard: PathGuard, uri: string, notify: (uri: string) => void, { markSubscribe }?: {
|
|
62
|
+
markSubscribe?: boolean;
|
|
63
|
+
}): Promise<WatcherAttachResult>;
|
|
64
|
+
attach: (uri: string, resolvedPath: string) => boolean;
|
|
65
|
+
destroy(): void;
|
|
66
|
+
};
|
|
67
|
+
export type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;
|
|
68
|
+
//# sourceMappingURL=watcher-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAmGV,MAAM,KAAG,OAAO;mBAErB,OAAO;IAwFzB,wFAAwF;gBAC9E,MAAM;mBAtFI,MAAM,KAAG,OAAO;0BAET,MAAM,KAAG,IAAI;2BAcZ,MAAM,KAAG,IAAI;uBAIjB,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAG,IAAI;kBAUjD,MAAM,KAAG,IAAI;mBASZ,MAAM,KAAG,IAAI;IA6DjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;uBAEU,SAAS,OACf,MAAM,UACH,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,sBACF;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,mBAAmB,CAAC;kBAhFZ,MAAM,gBAAgB,MAAM,KAAG,OAAO;eA+I9C,IAAI;EAmBlB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|