@j0hanz/filesystem-mcp 2.2.0 → 2.4.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.
Files changed (194) hide show
  1. package/README.md +38 -32
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +21 -40
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/cli.d.ts +0 -2
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +24 -46
  8. package/dist/cli.js.map +1 -1
  9. package/dist/core/concurrency.d.ts +5 -17
  10. package/dist/core/concurrency.d.ts.map +1 -1
  11. package/dist/core/concurrency.js +32 -40
  12. package/dist/core/concurrency.js.map +1 -1
  13. package/dist/core/config.d.ts +2 -0
  14. package/dist/core/config.d.ts.map +1 -1
  15. package/dist/core/config.js.map +1 -1
  16. package/dist/core/cursor.d.ts +1 -12
  17. package/dist/core/cursor.d.ts.map +1 -1
  18. package/dist/core/cursor.js +2 -12
  19. package/dist/core/cursor.js.map +1 -1
  20. package/dist/core/errors.d.ts +6 -11
  21. package/dist/core/errors.d.ts.map +1 -1
  22. package/dist/core/errors.js +62 -117
  23. package/dist/core/errors.js.map +1 -1
  24. package/dist/core/file-uri.d.ts +7 -3
  25. package/dist/core/file-uri.d.ts.map +1 -1
  26. package/dist/core/file-uri.js +8 -4
  27. package/dist/core/file-uri.js.map +1 -1
  28. package/dist/core/fmt.d.ts +0 -11
  29. package/dist/core/fmt.d.ts.map +1 -1
  30. package/dist/core/fmt.js +1 -19
  31. package/dist/core/fmt.js.map +1 -1
  32. package/dist/core/fs.d.ts +34 -4
  33. package/dist/core/fs.d.ts.map +1 -1
  34. package/dist/core/fs.js +151 -12
  35. package/dist/core/fs.js.map +1 -1
  36. package/dist/core/glob.d.ts +1 -2
  37. package/dist/core/glob.d.ts.map +1 -1
  38. package/dist/core/glob.js +12 -28
  39. package/dist/core/glob.js.map +1 -1
  40. package/dist/core/input-required.d.ts +19 -29
  41. package/dist/core/input-required.d.ts.map +1 -1
  42. package/dist/core/input-required.js +58 -73
  43. package/dist/core/input-required.js.map +1 -1
  44. package/dist/core/observability.d.ts +9 -0
  45. package/dist/core/observability.d.ts.map +1 -1
  46. package/dist/core/observability.js +13 -2
  47. package/dist/core/observability.js.map +1 -1
  48. package/dist/core/path-completer.d.ts +6 -9
  49. package/dist/core/path-completer.d.ts.map +1 -1
  50. package/dist/core/path-completer.js +28 -55
  51. package/dist/core/path-completer.js.map +1 -1
  52. package/dist/core/path-discovery.d.ts.map +1 -1
  53. package/dist/core/path-discovery.js +3 -4
  54. package/dist/core/path-discovery.js.map +1 -1
  55. package/dist/core/path-utils.d.ts +28 -0
  56. package/dist/core/path-utils.d.ts.map +1 -1
  57. package/dist/core/path-utils.js +57 -2
  58. package/dist/core/path-utils.js.map +1 -1
  59. package/dist/core/path.d.ts +3 -8
  60. package/dist/core/path.d.ts.map +1 -1
  61. package/dist/core/path.js +8 -9
  62. package/dist/core/path.js.map +1 -1
  63. package/dist/core/read.d.ts +3 -22
  64. package/dist/core/read.d.ts.map +1 -1
  65. package/dist/core/read.js +46 -96
  66. package/dist/core/read.js.map +1 -1
  67. package/dist/core/schema.d.ts +8 -9
  68. package/dist/core/schema.d.ts.map +1 -1
  69. package/dist/core/schema.js +11 -14
  70. package/dist/core/schema.js.map +1 -1
  71. package/dist/core/search.d.ts +6 -1
  72. package/dist/core/search.d.ts.map +1 -1
  73. package/dist/core/search.js +21 -23
  74. package/dist/core/search.js.map +1 -1
  75. package/dist/core/sensitive.d.ts +4 -2
  76. package/dist/core/sensitive.d.ts.map +1 -1
  77. package/dist/core/sensitive.js +262 -45
  78. package/dist/core/sensitive.js.map +1 -1
  79. package/dist/core/store.d.ts +41 -16
  80. package/dist/core/store.d.ts.map +1 -1
  81. package/dist/core/store.js +147 -82
  82. package/dist/core/store.js.map +1 -1
  83. package/dist/core/util.d.ts +3 -2
  84. package/dist/core/util.d.ts.map +1 -1
  85. package/dist/core/util.js +5 -6
  86. package/dist/core/util.js.map +1 -1
  87. package/dist/core/watcher-registry.d.ts +1 -1
  88. package/dist/core/watcher-registry.d.ts.map +1 -1
  89. package/dist/core/watcher-registry.js +9 -5
  90. package/dist/core/watcher-registry.js.map +1 -1
  91. package/dist/index.js +5 -6
  92. package/dist/index.js.map +1 -1
  93. package/dist/instructions.js +2 -2
  94. package/dist/instructions.js.map +1 -1
  95. package/dist/prompts.js +1 -1
  96. package/dist/prompts.js.map +1 -1
  97. package/dist/resources.d.ts +11 -19
  98. package/dist/resources.d.ts.map +1 -1
  99. package/dist/resources.js +51 -62
  100. package/dist/resources.js.map +1 -1
  101. package/dist/server.d.ts +4 -5
  102. package/dist/server.d.ts.map +1 -1
  103. package/dist/server.js +15 -11
  104. package/dist/server.js.map +1 -1
  105. package/dist/tools/batch.d.ts +2 -2
  106. package/dist/tools/batch.js +3 -3
  107. package/dist/tools/batch.js.map +1 -1
  108. package/dist/tools/create.d.ts +1 -1
  109. package/dist/tools/create.d.ts.map +1 -1
  110. package/dist/tools/create.js +168 -17
  111. package/dist/tools/create.js.map +1 -1
  112. package/dist/tools/define.d.ts +15 -9
  113. package/dist/tools/define.d.ts.map +1 -1
  114. package/dist/tools/define.js +48 -94
  115. package/dist/tools/define.js.map +1 -1
  116. package/dist/tools/delete.d.ts +1 -1
  117. package/dist/tools/delete.d.ts.map +1 -1
  118. package/dist/tools/delete.js +23 -23
  119. package/dist/tools/delete.js.map +1 -1
  120. package/dist/tools/diff.d.ts +1 -1
  121. package/dist/tools/diff.d.ts.map +1 -1
  122. package/dist/tools/diff.js +7 -37
  123. package/dist/tools/diff.js.map +1 -1
  124. package/dist/tools/edit.d.ts +1 -1
  125. package/dist/tools/edit.d.ts.map +1 -1
  126. package/dist/tools/edit.js +101 -92
  127. package/dist/tools/edit.js.map +1 -1
  128. package/dist/tools/find-files.d.ts +1 -1
  129. package/dist/tools/find-files.d.ts.map +1 -1
  130. package/dist/tools/find-files.js +18 -37
  131. package/dist/tools/find-files.js.map +1 -1
  132. package/dist/tools/index.d.ts +10 -10
  133. package/dist/tools/index.d.ts.map +1 -1
  134. package/dist/tools/index.js +14 -13
  135. package/dist/tools/index.js.map +1 -1
  136. package/dist/tools/list-roots.d.ts +1 -1
  137. package/dist/tools/list-roots.js +3 -3
  138. package/dist/tools/list-roots.js.map +1 -1
  139. package/dist/tools/list.d.ts +1 -1
  140. package/dist/tools/list.d.ts.map +1 -1
  141. package/dist/tools/list.js +23 -29
  142. package/dist/tools/list.js.map +1 -1
  143. package/dist/tools/move.d.ts +1 -1
  144. package/dist/tools/move.d.ts.map +1 -1
  145. package/dist/tools/move.js +31 -45
  146. package/dist/tools/move.js.map +1 -1
  147. package/dist/tools/patch.d.ts +1 -1
  148. package/dist/tools/patch.js +7 -7
  149. package/dist/tools/patch.js.map +1 -1
  150. package/dist/tools/progress.d.ts +17 -28
  151. package/dist/tools/progress.d.ts.map +1 -1
  152. package/dist/tools/progress.js +42 -94
  153. package/dist/tools/progress.js.map +1 -1
  154. package/dist/tools/read.d.ts +1 -1
  155. package/dist/tools/read.d.ts.map +1 -1
  156. package/dist/tools/read.js +10 -12
  157. package/dist/tools/read.js.map +1 -1
  158. package/dist/tools/replace-text.d.ts +1 -1
  159. package/dist/tools/replace-text.d.ts.map +1 -1
  160. package/dist/tools/replace-text.js +32 -49
  161. package/dist/tools/replace-text.js.map +1 -1
  162. package/dist/tools/search-text.d.ts +1 -1
  163. package/dist/tools/search-text.d.ts.map +1 -1
  164. package/dist/tools/search-text.js +82 -45
  165. package/dist/tools/search-text.js.map +1 -1
  166. package/dist/tools/stat.d.ts +1 -1
  167. package/dist/tools/stat.d.ts.map +1 -1
  168. package/dist/tools/stat.js +9 -29
  169. package/dist/tools/stat.js.map +1 -1
  170. package/dist/transport/http-policy.js +4 -4
  171. package/dist/transport/http-policy.js.map +1 -1
  172. package/dist/transport/http.d.ts +2 -2
  173. package/dist/transport/http.d.ts.map +1 -1
  174. package/dist/transport/http.js +21 -18
  175. package/dist/transport/http.js.map +1 -1
  176. package/dist/transport/shared.d.ts +2 -2
  177. package/dist/transport/shared.js +2 -2
  178. package/dist/transport/shared.js.map +1 -1
  179. package/dist/transport/stdio.d.ts +4 -3
  180. package/dist/transport/stdio.d.ts.map +1 -1
  181. package/dist/transport/stdio.js +7 -6
  182. package/dist/transport/stdio.js.map +1 -1
  183. package/dist/transport.d.ts +2 -2
  184. package/dist/transport.js +2 -2
  185. package/dist/transport.js.map +1 -1
  186. package/package.json +6 -13
  187. package/dist/core/page-store.d.ts +0 -41
  188. package/dist/core/page-store.d.ts.map +0 -1
  189. package/dist/core/page-store.js +0 -74
  190. package/dist/core/page-store.js.map +0 -1
  191. package/dist/core/primitives.d.ts +0 -34
  192. package/dist/core/primitives.d.ts.map +0 -1
  193. package/dist/core/primitives.js +0 -63
  194. package/dist/core/primitives.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "Secure filesystem MCP server for reading, writing, searching, diffing, and patching files.",
6
6
  "author": "j0hanz",
@@ -32,12 +32,10 @@
32
32
  "format": "prettier --write .",
33
33
  "type-check": "tsc -p tsconfig.json --noEmit",
34
34
  "type-check:test": "tsc -p tsconfig.test.json",
35
- "check:static": "npm run build && npm run type-check && npm run type-check:test && eslint . && prettier --check . && knip",
35
+ "check:static": "npm run build && npm run type-check:test && npm run lint && prettier --check . && knip",
36
36
  "fix": "npm run format && npm run lint:fix && npm run check",
37
37
  "check": "npm run check:static && npm run test",
38
- "test": "node --test --import tsx",
39
- "test:inspector": "node --test --import tsx \"__tests__/inspector-*.test.ts\"",
40
- "inspector": "npm run build && npx -y @modelcontextprotocol/inspector node dist/index.js"
38
+ "test": "node --test"
41
39
  },
42
40
  "keywords": [
43
41
  "mcp",
@@ -66,13 +64,12 @@
66
64
  "@modelcontextprotocol/server": "2.0.0",
67
65
  "diff": "^9",
68
66
  "express": "^5.2.1",
69
- "ignore": "^7.0.6",
70
- "zod": "^4.4.3"
67
+ "ignore": "^7.0.9",
68
+ "zod": "^4.6.5"
71
69
  },
72
70
  "devDependencies": {
73
71
  "@eslint/js": "^10.0.1",
74
72
  "@modelcontextprotocol/client": "2.0.0",
75
- "@modelcontextprotocol/inspector": "^2.4.0",
76
73
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
77
74
  "@types/express": "^5.0.6",
78
75
  "@types/node": "^24",
@@ -80,14 +77,10 @@
80
77
  "eslint-config-prettier": "^10.1.8",
81
78
  "knip": "^6",
82
79
  "prettier": "^3.9.6",
83
- "tsx": "^4.23.12",
84
80
  "typescript": "^6",
85
- "typescript-eslint": "^8.68.0"
81
+ "typescript-eslint": "^8.70.0"
86
82
  },
87
83
  "engines": {
88
84
  "node": ">=24"
89
- },
90
- "allowScripts": {
91
- "esbuild@0.28.2": true
92
85
  }
93
86
  }
@@ -1,41 +0,0 @@
1
- import { FsError } from './errors.js';
2
- export interface PageSnapshot<T = unknown, M = unknown> {
3
- readonly items: readonly T[];
4
- readonly metadata: M;
5
- }
6
- export interface PageSnapshotStoreOptions {
7
- readonly maxSnapshots?: number;
8
- readonly ttlMs?: number;
9
- readonly now?: () => number;
10
- }
11
- /**
12
- * The one cursor rejection: a cursor whose snapshot expired, was evicted, was
13
- * already consumed past its end, or belongs to a different query. Every case
14
- * has the same remedy, so they share one message.
15
- */
16
- export declare function invalidCursor(): FsError;
17
- /**
18
- * Short-lived snapshots of a completed query's full result set, so later pages
19
- * slice a stored array instead of re-scanning and re-sorting the filesystem.
20
- *
21
- * ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000
22
- * `list` entries or 32 x 10,000 `search_text` matches, order of a few hundred MB
23
- * held for 60s, and the HTTP leg shares one store so any caller can drive it.
24
- * If that shows up as memory pressure, meter bytes the way `ResourceStore` does.
25
- */
26
- export declare class PageSnapshotStore {
27
- private readonly byId;
28
- private readonly maxSnapshots;
29
- private readonly ttlMs;
30
- private readonly now;
31
- constructor(options?: PageSnapshotStoreOptions);
32
- private pruneExpired;
33
- create(params: {
34
- queryKey: string;
35
- items: readonly unknown[];
36
- metadata?: unknown;
37
- }): string;
38
- read<T, M = undefined>(snapshotId: string, queryKey: string): PageSnapshot<T, M>;
39
- clear(): void;
40
- }
41
- //# sourceMappingURL=page-store.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"page-store.d.ts","sourceRoot":"","sources":["../../src/core/page-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,OAAO,EAAE,MAAM,aAAa,CAAC;AAYjD,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACpD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAKvC;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,OAAO,GAAE,wBAA6B;IAMlD,OAAO,CAAC,YAAY;IAOpB,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM;IAmB3F,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAYhF,KAAK,IAAI,IAAI;CAGd"}
@@ -1,74 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { ErrorCode, FsError } from './errors.js';
3
- const DEFAULT_MAX_SNAPSHOTS = 32;
4
- const DEFAULT_TTL_MS = 60 * 1000;
5
- /**
6
- * The one cursor rejection: a cursor whose snapshot expired, was evicted, was
7
- * already consumed past its end, or belongs to a different query. Every case
8
- * has the same remedy, so they share one message.
9
- */
10
- export function invalidCursor() {
11
- return new FsError(ErrorCode.INVALID_INPUT, 'Invalid cursor. Request the first page without a cursor.');
12
- }
13
- /**
14
- * Short-lived snapshots of a completed query's full result set, so later pages
15
- * slice a stored array instead of re-scanning and re-sorting the filesystem.
16
- *
17
- * ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000
18
- * `list` entries or 32 x 10,000 `search_text` matches, order of a few hundred MB
19
- * held for 60s, and the HTTP leg shares one store so any caller can drive it.
20
- * If that shows up as memory pressure, meter bytes the way `ResourceStore` does.
21
- */
22
- export class PageSnapshotStore {
23
- byId = new Map();
24
- maxSnapshots;
25
- ttlMs;
26
- now;
27
- constructor(options = {}) {
28
- this.maxSnapshots = options.maxSnapshots ?? DEFAULT_MAX_SNAPSHOTS;
29
- this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
30
- this.now = options.now ?? Date.now;
31
- }
32
- pruneExpired() {
33
- const now = this.now();
34
- for (const [snapshotId, entry] of this.byId) {
35
- if (entry.expiresAt <= now)
36
- this.byId.delete(snapshotId);
37
- }
38
- }
39
- create(params) {
40
- this.pruneExpired();
41
- const snapshotId = randomUUID();
42
- this.byId.set(snapshotId, {
43
- queryKey: params.queryKey,
44
- items: params.items,
45
- metadata: params.metadata,
46
- expiresAt: this.now() + this.ttlMs,
47
- });
48
- // Oldest first: Map keeps insertion order and `read` re-inserts, so the
49
- // first key is the least recently used snapshot.
50
- while (this.byId.size > this.maxSnapshots) {
51
- const oldest = this.byId.keys().next();
52
- if (oldest.done)
53
- break;
54
- this.byId.delete(oldest.value);
55
- }
56
- return snapshotId;
57
- }
58
- read(snapshotId, queryKey) {
59
- this.pruneExpired();
60
- const entry = this.byId.get(snapshotId);
61
- if (entry?.queryKey !== queryKey)
62
- throw invalidCursor();
63
- this.byId.delete(snapshotId);
64
- this.byId.set(snapshotId, entry);
65
- return {
66
- items: entry.items,
67
- metadata: entry.metadata,
68
- };
69
- }
70
- clear() {
71
- this.byId.clear();
72
- }
73
- }
74
- //# sourceMappingURL=page-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"page-store.js","sourceRoot":"","sources":["../../src/core/page-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC;AAoBjC;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,OAAO,CAChB,SAAS,CAAC,aAAa,EACvB,0DAA0D,CAC3D,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAiB;IACX,IAAI,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC7C,YAAY,CAAS;IACrB,KAAK,CAAS;IACd,GAAG,CAAe;IAEnC,YAAY,UAAoC,EAAE;QAChD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,qBAAqB,CAAC;QAClE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;QAC7C,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAEO,YAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,CAAC,MAA2E;QAChF,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE;YACxB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;SACnC,CAAC,CAAC;QACH,wEAAwE;QACxE,iDAAiD;QACjD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAM;YACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,CAAmB,UAAkB,EAAE,QAAgB;QACzD,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,KAAK,EAAE,QAAQ,KAAK,QAAQ;YAAE,MAAM,aAAa,EAAE,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,KAAqB;YAClC,QAAQ,EAAE,KAAK,CAAC,QAAa;SAC9B,CAAC;IACJ,CAAC;IAED,KAAK;QACH,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;CACF","sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\n\nconst DEFAULT_MAX_SNAPSHOTS = 32;\nconst DEFAULT_TTL_MS = 60 * 1000;\n\ninterface StoredPageSnapshot {\n readonly queryKey: string;\n readonly items: readonly unknown[];\n readonly metadata: unknown;\n readonly expiresAt: number;\n}\n\nexport interface PageSnapshot<T = unknown, M = unknown> {\n readonly items: readonly T[];\n readonly metadata: M;\n}\n\nexport interface PageSnapshotStoreOptions {\n readonly maxSnapshots?: number;\n readonly ttlMs?: number;\n readonly now?: () => number;\n}\n\n/**\n * The one cursor rejection: a cursor whose snapshot expired, was evicted, was\n * already consumed past its end, or belongs to a different query. Every case\n * has the same remedy, so they share one message.\n */\nexport function invalidCursor(): FsError {\n return new FsError(\n ErrorCode.INVALID_INPUT,\n 'Invalid cursor. Request the first page without a cursor.',\n );\n}\n\n/**\n * Short-lived snapshots of a completed query's full result set, so later pages\n * slice a stored array instead of re-scanning and re-sorting the filesystem.\n *\n * ponytail: bounded by snapshot count and TTL, not by bytes — 32 x 20,000\n * `list` entries or 32 x 10,000 `search_text` matches, order of a few hundred MB\n * held for 60s, and the HTTP leg shares one store so any caller can drive it.\n * If that shows up as memory pressure, meter bytes the way `ResourceStore` does.\n */\nexport class PageSnapshotStore {\n private readonly byId = new Map<string, StoredPageSnapshot>();\n private readonly maxSnapshots: number;\n private readonly ttlMs: number;\n private readonly now: () => number;\n\n constructor(options: PageSnapshotStoreOptions = {}) {\n this.maxSnapshots = options.maxSnapshots ?? DEFAULT_MAX_SNAPSHOTS;\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n this.now = options.now ?? Date.now;\n }\n\n private pruneExpired(): void {\n const now = this.now();\n for (const [snapshotId, entry] of this.byId) {\n if (entry.expiresAt <= now) this.byId.delete(snapshotId);\n }\n }\n\n create(params: { queryKey: string; items: readonly unknown[]; metadata?: unknown }): string {\n this.pruneExpired();\n const snapshotId = randomUUID();\n this.byId.set(snapshotId, {\n queryKey: params.queryKey,\n items: params.items,\n metadata: params.metadata,\n expiresAt: this.now() + this.ttlMs,\n });\n // Oldest first: Map keeps insertion order and `read` re-inserts, so the\n // first key is the least recently used snapshot.\n while (this.byId.size > this.maxSnapshots) {\n const oldest = this.byId.keys().next();\n if (oldest.done) break;\n this.byId.delete(oldest.value);\n }\n return snapshotId;\n }\n\n read<T, M = undefined>(snapshotId: string, queryKey: string): PageSnapshot<T, M> {\n this.pruneExpired();\n const entry = this.byId.get(snapshotId);\n if (entry?.queryKey !== queryKey) throw invalidCursor();\n this.byId.delete(snapshotId);\n this.byId.set(snapshotId, entry);\n return {\n items: entry.items as readonly T[],\n metadata: entry.metadata as M,\n };\n }\n\n clear(): void {\n this.byId.clear();\n }\n}\n"]}
@@ -1,34 +0,0 @@
1
- /**
2
- * Minimal shared primitives with no intra-package dependencies.
3
- * Kept separate to avoid import cycles between observability.ts and util.ts.
4
- */
5
- /** The four filesystem entry types; published as the `FileType` schema. */
6
- export declare const ENTRY_TYPES: readonly ["file", "directory", "symlink", "other"];
7
- export type EntryType = (typeof ENTRY_TYPES)[number];
8
- /** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */
9
- export interface DirentLike {
10
- isDirectory(): boolean;
11
- isFile(): boolean;
12
- isSymbolicLink(): boolean;
13
- }
14
- export declare function resolveEntryType(dirent: DirentLike): EntryType;
15
- /**
16
- * Warn once per (setting, value) that a configured value was rejected, and say
17
- * what was used instead. console.error, not Logger: this module stays
18
- * dependency-free to avoid import cycles, and a startup misconfiguration is
19
- * worth printing even when the operator asked for errors only.
20
- */
21
- export declare function warnInvalidSetting(name: string, value: string, expected: string, fallback: string | number | boolean): void;
22
- /**
23
- * Parse a boolean env flag: `true`/`1` enable, `false`/`0`/empty disable.
24
- * Any other value warns once per var (when `name` is given) and reads as
25
- * false, so a typo like `FS_ALLOW_SENSITIVE=yes` is never silent.
26
- */
27
- export declare function parseTrueEnvFlag(value: string | undefined, name?: string): boolean;
28
- export declare function escapeRegexLiteral(str: string): string;
29
- export declare function splitDirList(val: string | undefined): string[];
30
- export declare const IS_WINDOWS: boolean;
31
- export declare const isSlash: (code: number) => boolean;
32
- export declare const isAlpha: (code: number) => boolean;
33
- export declare const toPosixPath: (value: string) => string;
34
- //# sourceMappingURL=primitives.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/core/primitives.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,WAAW,oDAAqD,CAAC;AAC9E,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;IAClB,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAK9D;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAClC,IAAI,CAON;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAQlF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAM9D;AAID,eAAO,MAAM,UAAU,SAA+B,CAAC;AAEvD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAqC,CAAC;AAE7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OACoB,CAAC;AAE5D,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAAqC,CAAC"}
@@ -1,63 +0,0 @@
1
- import { delimiter } from 'node:path';
2
- /**
3
- * Minimal shared primitives with no intra-package dependencies.
4
- * Kept separate to avoid import cycles between observability.ts and util.ts.
5
- */
6
- /** The four filesystem entry types; published as the `FileType` schema. */
7
- export const ENTRY_TYPES = ['file', 'directory', 'symlink', 'other'];
8
- export function resolveEntryType(dirent) {
9
- if (dirent.isDirectory())
10
- return 'directory';
11
- if (dirent.isFile())
12
- return 'file';
13
- if (dirent.isSymbolicLink())
14
- return 'symlink';
15
- return 'other';
16
- }
17
- const warnedSettings = new Set();
18
- /**
19
- * Warn once per (setting, value) that a configured value was rejected, and say
20
- * what was used instead. console.error, not Logger: this module stays
21
- * dependency-free to avoid import cycles, and a startup misconfiguration is
22
- * worth printing even when the operator asked for errors only.
23
- */
24
- export function warnInvalidSetting(name, value, expected, fallback) {
25
- const key = `${name}:${value}`;
26
- if (warnedSettings.has(key))
27
- return;
28
- warnedSettings.add(key);
29
- console.error(`[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`);
30
- }
31
- /**
32
- * Parse a boolean env flag: `true`/`1` enable, `false`/`0`/empty disable.
33
- * Any other value warns once per var (when `name` is given) and reads as
34
- * false, so a typo like `FS_ALLOW_SENSITIVE=yes` is never silent.
35
- */
36
- export function parseTrueEnvFlag(value, name) {
37
- if (value === undefined)
38
- return false;
39
- const trimmed = value.trim().toLowerCase();
40
- if (trimmed === 'true' || trimmed === '1')
41
- return true;
42
- if (name && trimmed !== '' && trimmed !== 'false' && trimmed !== '0') {
43
- warnInvalidSetting(name, value, '"true" or "1"', false);
44
- }
45
- return false;
46
- }
47
- export function escapeRegexLiteral(str) {
48
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
- }
50
- export function splitDirList(val) {
51
- if (!val)
52
- return [];
53
- return val
54
- .split(delimiter)
55
- .map((p) => p.trim())
56
- .filter((p) => p.length > 0);
57
- }
58
- // ─── Path primitives (moved from path.ts to break the path↔sensitive cycle) ──
59
- export const IS_WINDOWS = process.platform === 'win32';
60
- export const isSlash = (code) => code === 47 || code === 92;
61
- export const isAlpha = (code) => (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
62
- export const toPosixPath = (value) => value.replaceAll('\\', '/');
63
- //# sourceMappingURL=primitives.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../src/core/primitives.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC;;;GAGG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,CAAU,CAAC;AAU9E,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,IAAI,MAAM,CAAC,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC;IAC7C,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,MAAM,CAAC;IACnC,IAAI,MAAM,CAAC,cAAc,EAAE;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,KAAa,EACb,QAAgB,EAChB,QAAmC;IAEnC,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;IAC/B,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IACpC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,OAAO,CAAC,KAAK,CACX,qBAAqB,IAAI,WAAW,KAAK,aAAa,QAAQ,qBAAqB,MAAM,CAAC,QAAQ,CAAC,EAAE,CACtG,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAyB,EAAE,IAAa;IACvE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,IAAI,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;QACrE,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAuB;IAClD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,GAAG;SACP,KAAK,CAAC,SAAS,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,gFAAgF;AAEhF,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEvD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,CAAC;AAE7E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CAC/C,CAAC,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC","sourcesContent":["import { delimiter } from 'node:path';\n\n/**\n * Minimal shared primitives with no intra-package dependencies.\n * Kept separate to avoid import cycles between observability.ts and util.ts.\n */\n\n/** The four filesystem entry types; published as the `FileType` schema. */\nexport const ENTRY_TYPES = ['file', 'directory', 'symlink', 'other'] as const;\nexport type EntryType = (typeof ENTRY_TYPES)[number];\n\n/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */\nexport interface DirentLike {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n}\n\nexport function resolveEntryType(dirent: DirentLike): EntryType {\n if (dirent.isDirectory()) return 'directory';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\nconst warnedSettings = new Set<string>();\n\n/**\n * Warn once per (setting, value) that a configured value was rejected, and say\n * what was used instead. console.error, not Logger: this module stays\n * dependency-free to avoid import cycles, and a startup misconfiguration is\n * worth printing even when the operator asked for errors only.\n */\nexport function warnInvalidSetting(\n name: string,\n value: string,\n expected: string,\n fallback: string | number | boolean,\n): void {\n const key = `${name}:${value}`;\n if (warnedSettings.has(key)) return;\n warnedSettings.add(key);\n console.error(\n `[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`,\n );\n}\n\n/**\n * Parse a boolean env flag: `true`/`1` enable, `false`/`0`/empty disable.\n * Any other value warns once per var (when `name` is given) and reads as\n * false, so a typo like `FS_ALLOW_SENSITIVE=yes` is never silent.\n */\nexport function parseTrueEnvFlag(value: string | undefined, name?: string): boolean {\n if (value === undefined) return false;\n const trimmed = value.trim().toLowerCase();\n if (trimmed === 'true' || trimmed === '1') return true;\n if (name && trimmed !== '' && trimmed !== 'false' && trimmed !== '0') {\n warnInvalidSetting(name, value, '\"true\" or \"1\"', false);\n }\n return false;\n}\n\nexport function escapeRegexLiteral(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function splitDirList(val: string | undefined): string[] {\n if (!val) return [];\n return val\n .split(delimiter)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n// ─── Path primitives (moved from path.ts to break the path↔sensitive cycle) ──\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\nexport const isSlash = (code: number): boolean => code === 47 || code === 92;\n\nexport const isAlpha = (code: number): boolean =>\n (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n\nexport const toPosixPath = (value: string): string => value.replaceAll('\\\\', '/');\n"]}