@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/README.md CHANGED
@@ -23,7 +23,7 @@ Filesystem-MCP is a [Model Context Protocol](https://modelcontextprotocol.io) se
23
23
  | **Path guarding** | Every path is validated against allowed roots; `.env`, `*.pem`, `*id_rsa*` and similar patterns are denied |
24
24
  | **Filesystem tools** | Navigate, inspect, read, and write across all major file operations |
25
25
  | **Batch operations** | Most tools accept `path`, `paths[]`, or `files[]` for parallel execution |
26
- | **Dual transport** | stdio by default; `--port` enables Streamable HTTP |
26
+ | **Dual transport** | stdio by default; `--port` enables Streamable HTTP for both 2025-era and 2026-07-28 clients |
27
27
  | **File subscriptions** | Resource subscriptions push change notifications when watched files update |
28
28
  | **Regex safety** | RE2 in all search tools: linear-time matching, so no pattern can ReDoS the server |
29
29
 
@@ -231,14 +231,14 @@ All tools are scoped to the configured roots. Call `list_roots` first to discove
231
231
 
232
232
  #### Write
233
233
 
234
- | Tool | Description |
235
- | :------------- | :------------------------------------------------------------------------------------------------ |
236
- | `create` | Create one or more files, overwriting existing content and creating parent directories as needed. |
237
- | `edit` | Apply sequential literal string replacements to one or more files (max 5 per call). |
238
- | `move` | Move, rename, or copy (`copy: true`) one or more files/directories to explicit destinations. |
239
- | `delete` | Permanently delete one or more files or directories. This action is irreversible. |
240
- | `replace_text` | Bulk search-and-replace across files matching a glob pattern. |
241
- | `patch` | Apply a single-file unified diff and write the result. |
234
+ | Tool | Description |
235
+ | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
+ | `create` | Create one or more files, creating parent directories as needed. An existing file prompts the user to confirm the overwrite; `overwrite: true` on an entry skips the prompt, `append: true` adds to the end instead. |
237
+ | `edit` | Apply sequential literal string replacements to one or more files (max 5 per call). |
238
+ | `move` | Move, rename, or copy (`copy: true`) one or more files/directories to explicit destinations. |
239
+ | `delete` | Permanently delete one or more files or directories. This action is irreversible. |
240
+ | `replace_text` | Bulk search-and-replace across files matching a glob pattern. |
241
+ | `patch` | Apply a single-file unified diff and write the result. |
242
242
 
243
243
  ### Resources
244
244
 
@@ -298,6 +298,12 @@ concrete path and approving the elicitation-backed grant. `list_roots` reports
298
298
  the roots already configured or accepted; it cannot discover an unknown
299
299
  workspace by itself.
300
300
 
301
+ Over HTTP, 2025-era clients are served statelessly: tools, resources and
302
+ prompts work. Confirmations (recursive delete, overwrite, access grants) need a
303
+ 2026-07-28 client or stdio and answer with a tool error saying so; file
304
+ subscriptions are not advertised on that leg, and a `resources/subscribe` sent
305
+ anyway is refused with method-not-found.
306
+
301
307
  ### Recommended global recipes
302
308
 
303
309
  #### VS Code / Cursor / Claude Code (primary recipe)
@@ -354,24 +360,28 @@ filesystem-mcp /path/to/project1 /path/to/project2
354
360
 
355
361
  #### CLI flags
356
362
 
357
- | Flag | Default | Purpose |
358
- | :------------------------ | :------ | :--------------------------------------------------------------------------------- |
359
- | `[dirs...]` | — | One or more allowed root directories (positional) |
360
- | `--allow-cwd` | `false` | Also allow the current working directory as a root |
361
- | `--walk-cwd` | `false` | Walk up from CWD to find a project root; implies `--allow-cwd` |
362
- | `--allow-missing-roots` | `false` | Start even if configured allowed directories do not exist |
363
- | `--port <n>` | — | Enable Streamable HTTP transport on the given port (env: `FS_PORT`) |
364
- | `--http-host <host>` | — | HTTP server bind address (env: `FS_HTTP_HOST`) |
365
- | `--api-key <key>` | — | Require this API key on HTTP requests (env: `FS_API_KEY`) |
366
- | `--read-only` | `false` | Disable write tools: `create`, `edit`, `delete`, `move`, `patch`, `replace_text` |
367
- | `--safe` | `false` | Alias for `--read-only` |
368
- | `--deny <pattern>` | — | Block paths matching this pattern; repeatable |
369
- | `--allow-sensitive` | `false` | Allow access to sensitive system paths (env: `FS_ALLOW_SENSITIVE`) |
370
- | `--root-boundary <path>` | — | Require all allowed roots to fall under this path (env: `FS_ROOT_BOUNDARY`) |
371
- | `--max-file-size <bytes>` | — | Maximum file size for reads in bytes (env: `FS_MAX_FILE_SIZE`) |
372
- | `--log-level <level>` | `info` | RFC 5424 log level, `debug` through `emergency` (env: `FS_LOG_LEVEL`) |
373
- | `--print-config` | `false` | Print the active configuration and exit (use `--json` for machine-readable output) |
374
- | `--json` | `false` | Output `--print-config` as JSON |
363
+ | Flag | Default | Purpose |
364
+ | :------------------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------ |
365
+ | `[dirs...]` | — | One or more allowed root directories (positional) |
366
+ | `--allow-cwd` | `false` | Also allow the current working directory as a root |
367
+ | `--walk-cwd` | `false` | Walk up from CWD to find a project root; implies `--allow-cwd` |
368
+ | `--allow-missing-roots` | `false` | Start even if configured allowed directories do not exist |
369
+ | `--port <n>` | — | Enable Streamable HTTP transport on the given port (env: `FS_PORT`) |
370
+ | `--http-host <host>` | — | HTTP server bind address (env: `FS_HTTP_HOST`) |
371
+ | `--api-key <key>` | — | Require this API key on HTTP requests (env: `FS_API_KEY`) |
372
+ | `--read-only` | `false` | Disable write tools: `create`, `edit`, `delete`, `move`, `patch`, `replace_text` |
373
+ | `--deny <pattern>` | — | Block paths matching this pattern; repeatable |
374
+ | `--allow <pattern>` | — | Exempt a pattern from the built-in sensitive denylist; repeatable (env: `FS_ALLOWLIST`). Does not lift `--deny`/`FS_DENYLIST` entries |
375
+ | `--allow-sensitive` | `false` | Allow access to sensitive system paths (env: `FS_ALLOW_SENSITIVE`) |
376
+ | `--root-boundary <path>` | — | Require all allowed roots to fall under this path (env: `FS_ROOT_BOUNDARY`) |
377
+ | `--max-file-size <bytes>` | — | Maximum file size for reads in bytes (env: `FS_MAX_FILE_SIZE`) |
378
+ | `--log-level <level>` | `info` | RFC 5424 log level, `debug` through `emergency` (env: `FS_LOG_LEVEL`) |
379
+ | `--print-config` | `false` | Print the active configuration as JSON and exit |
380
+
381
+ `--deny` and `--allow` patterns support `*` (any run within a segment),
382
+ `**` (any run of segments), `?`, `[...]` classes, and `{a,b}` alternation.
383
+ Dot-leading (hidden) names match like any other — `secrets/**` denies
384
+ `secrets/.env`, `*id_rsa*` denies `.id_rsa`.
375
385
 
376
386
  #### Environment variables
377
387
 
@@ -387,6 +397,7 @@ Flags take precedence when both are set.
387
397
  | `FS_ALLOW_MISSING_ROOTS` | Start even if configured directories do not exist (mirrors `--allow-missing-roots`). |
388
398
  | `FS_ALLOW_SENSITIVE` | Allow access to sensitive system paths (mirrors `--allow-sensitive`). |
389
399
  | `FS_DENYLIST` | Comma-separated list of paths or patterns to block (mirrors `--deny`). |
400
+ | `FS_ALLOWLIST` | Comma-separated patterns exempted from the built-in sensitive denylist (mirrors `--allow`). Never lifts `FS_DENYLIST`/`--deny` entries. |
390
401
  | `FS_MAX_FILE_SIZE` | Maximum file size for reads in bytes (mirrors `--max-file-size`). |
391
402
  | `FS_LOG_LEVEL` | RFC 5424 log level: `debug`, `info`, `notice`, `warn`/`warning`, `error`, `critical`, `alert`, or `emergency` (mirrors `--log-level`). |
392
403
  | `FS_PORT` | Start the Streamable HTTP transport on this port; unset = stdio (mirrors `--port`). |
@@ -398,12 +409,7 @@ Flags take precedence when both are set.
398
409
  | `FS_ALLOW_UNRESTRICTED_HOSTS` | Bind a wildcard host with no Host validation (accepts the risk). |
399
410
  | `FS_PUBLIC_URL` | Resource identifier URL for RFC 9728 discovery. |
400
411
  | `FS_RATE_LIMIT_RPM` | Per-client-IP requests/minute (default 120 with API-key authentication, 6,000 for keyless loopback; range 1–100000). |
401
- | `FS_MAX_REQUEST_BYTES` | Max HTTP request body bytes (default 4194304, 1024–268435456). |
402
- | `FS_KEEPALIVE_TIMEOUT_MS` | HTTP keep-alive timeout in ms; set above any fronting proxy's idle timeout (default 5000, 1000–600000). |
403
412
  | `FS_MAX_WATCHERS` | Max concurrent file watchers (default 256, 1–4096). |
404
- | `FS_MAX_INLINE_MATCHES` | Deprecated and ignored; `maxResults` sets the `search_text` page size. Logs a warning when set; removed in the next major. |
405
- | `FS_MAX_READ_MANY_BYTES` | Max total bytes across a batched `read` (default 524288, 10240–104857600). |
406
- | `FS_SEARCH_TIMEOUT_MS` | Search timeout in ms (default 5000, 100–60000). |
407
413
  | `NO_COLOR` | Any value disables ANSI color output. |
408
414
  | `FS_REQUEST_STATE_KEY` | HMAC key sealing `input_required` requestState across retry rounds. Optional (random per boot if unset); set it, at >=32 bytes UTF-8, to keep in-flight rounds alive across a restart. |
409
415
 
@@ -1 +1 @@
1
- {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"AAgJA,wBAAgB,gBAAgB,IAAI,KAAK,CAkCxC;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAG3C"}
1
+ {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"AAmIA,wBAAgB,gBAAgB,IAAI,KAAK,CA4BxC;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAG3C"}
package/dist/cli-help.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { styleText } from 'node:util';
1
2
  import packageJson from '../package.json' with { type: 'json' };
2
- import { cliFmt, padEndVisible } from './core/fmt.js';
3
- import { MUTATING_TOOL_NAMES } from './tools/index.js';
3
+ import { MUTATING_TOOL_NAMES } from "./tools/index.js";
4
4
  // Help text and its rendering. Split out of cli.ts so that module holds only
5
5
  // argument parsing and directory validation: this file is static copy plus a
6
6
  // formatter, and shares no state with the parser.
@@ -17,12 +17,7 @@ const OPTIONS_HELP = [
17
17
  flags: '--read-only',
18
18
  desc: `Disable write tools: ${[...MUTATING_TOOL_NAMES].sort().join(', ')}`,
19
19
  },
20
- { flags: '--safe', desc: 'Alias for --read-only' },
21
- {
22
- flags: '--print-config',
23
- desc: 'Print the active configuration and exit (use with --json for machine output)',
24
- },
25
- { flags: '--json', desc: 'Output --print-config as JSON' },
20
+ { flags: '--print-config', desc: 'Print the active configuration as JSON and exit' },
26
21
  {
27
22
  flags: '--log-level <level>',
28
23
  desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency (env: FS_LOG_LEVEL)',
@@ -46,6 +41,10 @@ const OPTIONS_HELP = [
46
41
  },
47
42
  { flags: '--walk-cwd', desc: 'Walk up from CWD to find a project root; implies --allow-cwd' },
48
43
  { flags: '--deny <pattern>', desc: 'Block paths matching this pattern; repeatable' },
44
+ {
45
+ flags: '--allow <pattern>',
46
+ desc: 'Exempt a pattern from the built-in sensitive denylist; repeatable (env: FS_ALLOWLIST)',
47
+ },
49
48
  {
50
49
  flags: '--allow-missing-roots',
51
50
  desc: 'Start even if configured allowed directories do not exist',
@@ -78,6 +77,10 @@ const ENV_HELP = [
78
77
  desc: 'Walk up from CWD to find a project root ("true" or "1" enables this)',
79
78
  },
80
79
  { flags: 'FS_DENYLIST', desc: 'Paths/patterns to block, comma-separated' },
80
+ {
81
+ flags: 'FS_ALLOWLIST',
82
+ desc: 'Patterns exempted from the built-in sensitive denylist, comma-separated; --deny/FS_DENYLIST still win',
83
+ },
81
84
  {
82
85
  flags: 'FS_ALLOW_MISSING_ROOTS',
83
86
  desc: 'Start even if configured allowed directories do not exist ("true" or "1" enables this)',
@@ -96,27 +99,10 @@ const ENV_HELP = [
96
99
  flags: 'FS_RATE_LIMIT_RPM',
97
100
  desc: 'Per-client-IP requests/min (default 120 authenticated, 6000 keyless loopback; 1–100000)',
98
101
  },
99
- {
100
- flags: 'FS_MAX_REQUEST_BYTES',
101
- desc: 'Max HTTP request body bytes (default 4194304, 1024–268435456)',
102
- },
103
- {
104
- flags: 'FS_KEEPALIVE_TIMEOUT_MS',
105
- desc: 'HTTP keep-alive timeout; set above any fronting proxy idle timeout (default 5000, 1000–600000)',
106
- },
107
102
  {
108
103
  flags: 'FS_MAX_WATCHERS',
109
104
  desc: 'Max concurrent file watchers (default 256, 1–4096)',
110
105
  },
111
- {
112
- flags: 'FS_MAX_INLINE_MATCHES',
113
- desc: 'Deprecated and ignored; maxResults sets the search_text page size. Removed in the next major',
114
- },
115
- {
116
- flags: 'FS_MAX_READ_MANY_BYTES',
117
- desc: 'Max total bytes across one batch read (default 524288, 10240–104857600)',
118
- },
119
- { flags: 'FS_SEARCH_TIMEOUT_MS', desc: 'Search timeout in ms (default 5000, 100–60000)' },
120
106
  { flags: 'NO_COLOR', desc: 'Any value disables ANSI color output' },
121
107
  {
122
108
  flags: 'FS_REQUEST_STATE_KEY',
@@ -129,31 +115,26 @@ const EXAMPLES_HELP = [
129
115
  '$ filesystem-mcp /project/src /project/tests --allow-cwd',
130
116
  '$ filesystem-mcp --port 3000 /path/to/allowed/dir',
131
117
  '$ filesystem-mcp --read-only /data/readonly',
132
- '$ filesystem-mcp --print-config --json /project',
118
+ '$ filesystem-mcp --print-config /project',
133
119
  ];
134
120
  export function printHelpAndExit() {
135
- const { bold, dim, section, flag, placeholder, cyan } = cliFmt;
136
121
  const COL = 27;
137
- const optRow = (flags, desc) => {
138
- const colored = flags
139
- .replace(/<[^>]+>/g, (m) => placeholder(m))
140
- .replace(/-{1,2}[\w-]+/g, (m) => flag(m));
141
- return ` ${padEndVisible(colored, COL)}${desc}`;
142
- };
143
- const envRow = (name, desc) => {
144
- return ` ${padEndVisible(cyan(name), COL - 1)} ${desc}`;
145
- };
122
+ // Pad first, colour after: `styleText` honours NO_COLOR, and padding the
123
+ // plain text keeps the columns aligned whether or not colour is on.
124
+ const row = (color, name, desc) => ` ${styleText(color, name.padEnd(COL))}${desc}`;
125
+ const dim = (t) => styleText('dim', t);
126
+ const section = (t) => styleText(['cyan', 'bold'], t);
146
127
  const lines = [
147
128
  '',
148
- `${bold('Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,
129
+ `${styleText('bold', 'Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,
149
130
  '',
150
131
  dim('Pass one or more directories to set the allowed access roots.'),
151
132
  '',
152
133
  section('Options:'),
153
- ...OPTIONS_HELP.map(({ flags, desc }) => optRow(flags, desc)),
134
+ ...OPTIONS_HELP.map(({ flags, desc }) => row('green', flags, desc)),
154
135
  '',
155
136
  `${section('Environment variables:')} ${dim('(flags take precedence when both are set)')}`,
156
- ...ENV_HELP.map(({ flags, desc }) => envRow(flags, desc)),
137
+ ...ENV_HELP.map(({ flags, desc }) => row('cyan', flags, desc)),
157
138
  '',
158
139
  section('Examples:'),
159
140
  ...EXAMPLES_HELP.map((ex) => ` ${dim(ex)}`),
@@ -163,7 +144,7 @@ export function printHelpAndExit() {
163
144
  process.exit(0);
164
145
  }
165
146
  export function printVersionAndExit() {
166
- process.stdout.write(`${cliFmt.cyan(SERVER_VERSION)}\n`);
147
+ process.stdout.write(`${styleText('cyan', SERVER_VERSION)}\n`);
167
148
  process.exit(0);
168
149
  }
169
150
  //# sourceMappingURL=cli-help.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"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","sourcesContent":["import packageJson from '../package.json' with { type: 'json' };\nimport { cliFmt, padEndVisible } from './core/fmt.js';\nimport { MUTATING_TOOL_NAMES } from './tools/index.js';\n\n// Help text and its rendering. Split out of cli.ts so that module holds only\n// argument parsing and directory validation: this file is static copy plus a\n// formatter, and shares no state with the parser.\n\nconst { version: SERVER_VERSION } = packageJson;\n\ninterface HelpRow {\n flags: string;\n desc: string;\n}\n\nconst OPTIONS_HELP: HelpRow[] = [\n { flags: '-h, --help', desc: 'Show this help message' },\n { flags: '-v, --version', desc: 'Show the server version' },\n { flags: '--allow-cwd', desc: 'Add the current working directory as an allowed root' },\n {\n flags: '--port <number>',\n desc: 'Start HTTP transport on this port (env: FS_PORT)',\n },\n {\n flags: '--read-only',\n desc: `Disable write tools: ${[...MUTATING_TOOL_NAMES].sort().join(', ')}`,\n },\n { flags: '--safe', desc: 'Alias for --read-only' },\n {\n flags: '--print-config',\n desc: 'Print the active configuration and exit (use with --json for machine output)',\n },\n { flags: '--json', desc: 'Output --print-config as JSON' },\n {\n flags: '--log-level <level>',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency (env: FS_LOG_LEVEL)',\n },\n { flags: '--http-host <host>', desc: 'HTTP server bind address (env: FS_HTTP_HOST)' },\n {\n flags: '--api-key <key>',\n desc: 'Require this API key on HTTP requests; prefer FS_API_KEY (argv is world-readable)',\n },\n {\n flags: '--allow-sensitive',\n desc: 'Allow access to sensitive system paths (env: FS_ALLOW_SENSITIVE)',\n },\n {\n flags: '--root-boundary <path>',\n desc: 'Require all allowed roots to fall under this path (env: FS_ROOT_BOUNDARY)',\n },\n {\n flags: '--max-file-size <bytes>',\n desc: 'Maximum file size for reads in bytes (env: FS_MAX_FILE_SIZE)',\n },\n { flags: '--walk-cwd', desc: 'Walk up from CWD to find a project root; implies --allow-cwd' },\n { flags: '--deny <pattern>', desc: 'Block paths matching this pattern; repeatable' },\n {\n flags: '--allow-missing-roots',\n desc: 'Start even if configured allowed directories do not exist',\n },\n];\n\nconst ENV_HELP: HelpRow[] = [\n {\n flags: 'FS_LOG_LEVEL',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency',\n },\n { flags: 'FS_PORT', desc: 'Start HTTP transport on this port (unset = stdio)' },\n { flags: 'FS_HTTP_HOST', desc: 'HTTP bind address' },\n { flags: 'FS_API_KEY', desc: 'HTTP API key' },\n {\n flags: 'FS_TRUST_PROXY',\n desc: 'Express trust-proxy setting: hop count or expression (unset = do not trust X-Forwarded-*)',\n },\n {\n flags: 'FS_ALLOW_SENSITIVE',\n desc: 'Allow sensitive system paths (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_ROOT_BOUNDARY', desc: 'Path prefix all allowed roots must fall under' },\n { flags: 'FS_MAX_FILE_SIZE', desc: 'Maximum file size for reads in bytes' },\n {\n flags: 'FS_ALLOWED_DIRS',\n desc: 'Allowed dirs: colon-separated (Unix), semicolon-separated (Windows)',\n },\n {\n flags: 'FS_ALLOW_CWD_WALK',\n desc: 'Walk up from CWD to find a project root (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_DENYLIST', desc: 'Paths/patterns to block, comma-separated' },\n {\n flags: 'FS_ALLOW_MISSING_ROOTS',\n desc: 'Start even if configured allowed directories do not exist (\"true\" or \"1\" enables this)',\n },\n {\n flags: 'FS_ALLOWED_HOSTS',\n desc: 'Comma-separated Host header values to accept (HTTP transport)',\n },\n { flags: 'FS_ALLOWED_ORIGINS', desc: 'Comma-separated origin hostnames for CORS' },\n {\n flags: 'FS_ALLOW_UNRESTRICTED_HOSTS',\n desc: 'Bind a wildcard host with no Host validation, accepts the risk (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_PUBLIC_URL', desc: 'Resource identifier URL for RFC 9728 discovery' },\n {\n flags: 'FS_RATE_LIMIT_RPM',\n desc: 'Per-client-IP requests/min (default 120 authenticated, 6000 keyless loopback; 1–100000)',\n },\n {\n flags: 'FS_MAX_REQUEST_BYTES',\n desc: 'Max HTTP request body bytes (default 4194304, 1024–268435456)',\n },\n {\n flags: 'FS_KEEPALIVE_TIMEOUT_MS',\n desc: 'HTTP keep-alive timeout; set above any fronting proxy idle timeout (default 5000, 1000–600000)',\n },\n {\n flags: 'FS_MAX_WATCHERS',\n desc: 'Max concurrent file watchers (default 256, 1–4096)',\n },\n {\n flags: 'FS_MAX_INLINE_MATCHES',\n desc: 'Deprecated and ignored; maxResults sets the search_text page size. Removed in the next major',\n },\n {\n flags: 'FS_MAX_READ_MANY_BYTES',\n desc: 'Max total bytes across one batch read (default 524288, 10240–104857600)',\n },\n { flags: 'FS_SEARCH_TIMEOUT_MS', desc: 'Search timeout in ms (default 5000, 100–60000)' },\n { flags: 'NO_COLOR', desc: 'Any value disables ANSI color output' },\n {\n flags: 'FS_REQUEST_STATE_KEY',\n desc: 'HMAC key for input_required state; optional outside fleet mode, shared and >=32 bytes in fleet mode',\n },\n];\n\nconst EXAMPLES_HELP = [\n '$ filesystem-mcp /path/to/allowed/dir',\n '$ filesystem-mcp --allow-cwd',\n '$ filesystem-mcp /project/src /project/tests --allow-cwd',\n '$ filesystem-mcp --port 3000 /path/to/allowed/dir',\n '$ filesystem-mcp --read-only /data/readonly',\n '$ filesystem-mcp --print-config --json /project',\n];\n\nexport function printHelpAndExit(): never {\n const { bold, dim, section, flag, placeholder, cyan } = cliFmt;\n const COL = 27;\n\n const optRow = (flags: string, desc: string): string => {\n const colored = flags\n .replace(/<[^>]+>/g, (m) => placeholder(m))\n .replace(/-{1,2}[\\w-]+/g, (m) => flag(m));\n return ` ${padEndVisible(colored, COL)}${desc}`;\n };\n\n const envRow = (name: string, desc: string): string => {\n return ` ${padEndVisible(cyan(name), COL - 1)} ${desc}`;\n };\n\n const lines = [\n '',\n `${bold('Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,\n '',\n dim('Pass one or more directories to set the allowed access roots.'),\n '',\n section('Options:'),\n ...OPTIONS_HELP.map(({ flags, desc }) => optRow(flags, desc)),\n '',\n `${section('Environment variables:')} ${dim('(flags take precedence when both are set)')}`,\n ...ENV_HELP.map(({ flags, desc }) => envRow(flags, desc)),\n '',\n section('Examples:'),\n ...EXAMPLES_HELP.map((ex) => ` ${dim(ex)}`),\n '',\n ];\n\n process.stdout.write(lines.join('\\n'));\n process.exit(0);\n}\n\nexport function printVersionAndExit(): never {\n process.stdout.write(`${cliFmt.cyan(SERVER_VERSION)}\\n`);\n process.exit(0);\n}\n"]}
1
+ {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"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","sourcesContent":["import { styleText } from 'node:util';\n\nimport packageJson from '../package.json' with { type: 'json' };\nimport { MUTATING_TOOL_NAMES } from './tools/index.ts';\n\n// Help text and its rendering. Split out of cli.ts so that module holds only\n// argument parsing and directory validation: this file is static copy plus a\n// formatter, and shares no state with the parser.\n\nconst { version: SERVER_VERSION } = packageJson;\n\ninterface HelpRow {\n flags: string;\n desc: string;\n}\n\nconst OPTIONS_HELP: HelpRow[] = [\n { flags: '-h, --help', desc: 'Show this help message' },\n { flags: '-v, --version', desc: 'Show the server version' },\n { flags: '--allow-cwd', desc: 'Add the current working directory as an allowed root' },\n {\n flags: '--port <number>',\n desc: 'Start HTTP transport on this port (env: FS_PORT)',\n },\n {\n flags: '--read-only',\n desc: `Disable write tools: ${[...MUTATING_TOOL_NAMES].sort().join(', ')}`,\n },\n { flags: '--print-config', desc: 'Print the active configuration as JSON and exit' },\n {\n flags: '--log-level <level>',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency (env: FS_LOG_LEVEL)',\n },\n { flags: '--http-host <host>', desc: 'HTTP server bind address (env: FS_HTTP_HOST)' },\n {\n flags: '--api-key <key>',\n desc: 'Require this API key on HTTP requests; prefer FS_API_KEY (argv is world-readable)',\n },\n {\n flags: '--allow-sensitive',\n desc: 'Allow access to sensitive system paths (env: FS_ALLOW_SENSITIVE)',\n },\n {\n flags: '--root-boundary <path>',\n desc: 'Require all allowed roots to fall under this path (env: FS_ROOT_BOUNDARY)',\n },\n {\n flags: '--max-file-size <bytes>',\n desc: 'Maximum file size for reads in bytes (env: FS_MAX_FILE_SIZE)',\n },\n { flags: '--walk-cwd', desc: 'Walk up from CWD to find a project root; implies --allow-cwd' },\n { flags: '--deny <pattern>', desc: 'Block paths matching this pattern; repeatable' },\n {\n flags: '--allow <pattern>',\n desc: 'Exempt a pattern from the built-in sensitive denylist; repeatable (env: FS_ALLOWLIST)',\n },\n {\n flags: '--allow-missing-roots',\n desc: 'Start even if configured allowed directories do not exist',\n },\n];\n\nconst ENV_HELP: HelpRow[] = [\n {\n flags: 'FS_LOG_LEVEL',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency',\n },\n { flags: 'FS_PORT', desc: 'Start HTTP transport on this port (unset = stdio)' },\n { flags: 'FS_HTTP_HOST', desc: 'HTTP bind address' },\n { flags: 'FS_API_KEY', desc: 'HTTP API key' },\n {\n flags: 'FS_TRUST_PROXY',\n desc: 'Express trust-proxy setting: hop count or expression (unset = do not trust X-Forwarded-*)',\n },\n {\n flags: 'FS_ALLOW_SENSITIVE',\n desc: 'Allow sensitive system paths (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_ROOT_BOUNDARY', desc: 'Path prefix all allowed roots must fall under' },\n { flags: 'FS_MAX_FILE_SIZE', desc: 'Maximum file size for reads in bytes' },\n {\n flags: 'FS_ALLOWED_DIRS',\n desc: 'Allowed dirs: colon-separated (Unix), semicolon-separated (Windows)',\n },\n {\n flags: 'FS_ALLOW_CWD_WALK',\n desc: 'Walk up from CWD to find a project root (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_DENYLIST', desc: 'Paths/patterns to block, comma-separated' },\n {\n flags: 'FS_ALLOWLIST',\n desc: 'Patterns exempted from the built-in sensitive denylist, comma-separated; --deny/FS_DENYLIST still win',\n },\n {\n flags: 'FS_ALLOW_MISSING_ROOTS',\n desc: 'Start even if configured allowed directories do not exist (\"true\" or \"1\" enables this)',\n },\n {\n flags: 'FS_ALLOWED_HOSTS',\n desc: 'Comma-separated Host header values to accept (HTTP transport)',\n },\n { flags: 'FS_ALLOWED_ORIGINS', desc: 'Comma-separated origin hostnames for CORS' },\n {\n flags: 'FS_ALLOW_UNRESTRICTED_HOSTS',\n desc: 'Bind a wildcard host with no Host validation, accepts the risk (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_PUBLIC_URL', desc: 'Resource identifier URL for RFC 9728 discovery' },\n {\n flags: 'FS_RATE_LIMIT_RPM',\n desc: 'Per-client-IP requests/min (default 120 authenticated, 6000 keyless loopback; 1–100000)',\n },\n {\n flags: 'FS_MAX_WATCHERS',\n desc: 'Max concurrent file watchers (default 256, 1–4096)',\n },\n { flags: 'NO_COLOR', desc: 'Any value disables ANSI color output' },\n {\n flags: 'FS_REQUEST_STATE_KEY',\n desc: 'HMAC key for input_required state; optional outside fleet mode, shared and >=32 bytes in fleet mode',\n },\n];\n\nconst EXAMPLES_HELP = [\n '$ filesystem-mcp /path/to/allowed/dir',\n '$ filesystem-mcp --allow-cwd',\n '$ filesystem-mcp /project/src /project/tests --allow-cwd',\n '$ filesystem-mcp --port 3000 /path/to/allowed/dir',\n '$ filesystem-mcp --read-only /data/readonly',\n '$ filesystem-mcp --print-config /project',\n];\n\nexport function printHelpAndExit(): never {\n const COL = 27;\n // Pad first, colour after: `styleText` honours NO_COLOR, and padding the\n // plain text keeps the columns aligned whether or not colour is on.\n const row = (color: 'green' | 'cyan', name: string, desc: string): string =>\n ` ${styleText(color, name.padEnd(COL))}${desc}`;\n const dim = (t: string): string => styleText('dim', t);\n const section = (t: string): string => styleText(['cyan', 'bold'], t);\n\n const lines = [\n '',\n `${styleText('bold', 'Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,\n '',\n dim('Pass one or more directories to set the allowed access roots.'),\n '',\n section('Options:'),\n ...OPTIONS_HELP.map(({ flags, desc }) => row('green', flags, desc)),\n '',\n `${section('Environment variables:')} ${dim('(flags take precedence when both are set)')}`,\n ...ENV_HELP.map(({ flags, desc }) => row('cyan', flags, desc)),\n '',\n section('Examples:'),\n ...EXAMPLES_HELP.map((ex) => ` ${dim(ex)}`),\n '',\n ];\n\n process.stdout.write(lines.join('\\n'));\n process.exit(0);\n}\n\nexport function printVersionAndExit(): never {\n process.stdout.write(`${styleText('cyan', SERVER_VERSION)}\\n`);\n process.exit(0);\n}\n"]}
package/dist/cli.d.ts CHANGED
@@ -7,7 +7,6 @@ export declare function parseArgs(): Promise<{
7
7
  port: number | undefined;
8
8
  readOnly: boolean;
9
9
  printConfig: boolean;
10
- json: boolean;
11
10
  httpHost: string | undefined;
12
11
  apiKey: string | undefined;
13
12
  }>;
@@ -15,7 +14,6 @@ export declare function runPrintConfig(options: {
15
14
  allowedDirs: string[];
16
15
  allowCwd: boolean;
17
16
  readOnly: boolean;
18
- json: boolean;
19
17
  /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */
20
18
  port?: number;
21
19
  httpHost?: string;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAsBA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAqGD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC,CA0ED;AAMD,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAqBA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAyFD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC,CA2ED;AAMD,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB"}
package/dist/cli.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { parseArgs as utilParseArgs } from 'node:util';
3
- import { printHelpAndExit, printVersionAndExit } from './cli-help.js';
4
- import { cli } from './core/config.js';
5
- import { formatUnknownErrorMessage } from './core/errors.js';
6
- import { cliFmt, padEndVisible } from './core/fmt.js';
7
- import { getReservedDeviceNameForPath, isWindowsDriveRelativePath, normalizePath, } from './core/path-utils.js';
8
- import { PathGuard } from './core/path.js';
9
- import { IS_WINDOWS, parseTrueEnvFlag } from './core/primitives.js';
10
- import { getMaxTextFileSize } from './core/util.js';
11
- import { registeredTools } from './tools/index.js';
3
+ import { printHelpAndExit, printVersionAndExit } from "./cli-help.js";
4
+ import { cli } from "./core/config.js";
5
+ import { formatUnknownErrorMessage } from "./core/errors.js";
6
+ import { getReservedDeviceNameForPath, isWindowsDriveRelativePath, normalizePath, parseTrueEnvFlag, } from "./core/path-utils.js";
7
+ import { PathGuard } from "./core/path.js";
8
+ import { getMaxTextFileSize } from "./core/util.js";
9
+ import { registeredTools } from "./tools/index.js";
12
10
  // ════════════════════════════════════════════════════════════
13
11
  // Path & Config Utilities — pure functions and error types
14
12
  // ════════════════════════════════════════════════════════════
@@ -48,29 +46,20 @@ async function validateDirectoryPath(inputPath, allowMissing = false) {
48
46
  throw new Error(`${inputPath} is not a directory`);
49
47
  return normalized;
50
48
  }
49
+ // No dedupe here: PathGuard's `normalizeAllowedDirectories` already Set-dedupes
50
+ // the normalized set, and containment checks are case-insensitive where the
51
+ // filesystem is.
51
52
  async function normalizeAndValidateDirs(paths, allowMissing = false) {
52
53
  const normalized = [];
53
54
  for (const p of paths) {
54
55
  normalized.push(await validateDirectoryPath(p, allowMissing));
55
56
  }
56
- return deduplicateAllowedDirectories(normalized);
57
+ return normalized;
57
58
  }
58
59
  function normalizeCliExitMessage(error) {
59
60
  const rawMessage = formatUnknownErrorMessage(error);
60
61
  return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;
61
62
  }
62
- function deduplicateAllowedDirectories(dirs) {
63
- const seen = new Set();
64
- const deduplicated = [];
65
- for (const dir of dirs) {
66
- const key = IS_WINDOWS ? dir.toLowerCase() : dir;
67
- if (seen.has(key))
68
- continue;
69
- seen.add(key);
70
- deduplicated.push(dir);
71
- }
72
- return deduplicated;
73
- }
74
63
  function parsePortOption(raw) {
75
64
  // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.
76
65
  if (raw === undefined || raw === '')
@@ -85,9 +74,7 @@ const CLI_PARSER_CONFIG = {
85
74
  options: {
86
75
  'allow-cwd': { type: 'boolean', default: false },
87
76
  'read-only': { type: 'boolean', default: false },
88
- safe: { type: 'boolean', default: false },
89
77
  'print-config': { type: 'boolean', default: false },
90
- json: { type: 'boolean', default: false },
91
78
  port: { type: 'string' },
92
79
  help: { type: 'boolean', short: 'h' },
93
80
  version: { type: 'boolean', short: 'v' },
@@ -99,6 +86,7 @@ const CLI_PARSER_CONFIG = {
99
86
  'max-file-size': { type: 'string' },
100
87
  'walk-cwd': { type: 'boolean', default: false },
101
88
  deny: { type: 'string', multiple: true },
89
+ allow: { type: 'string', multiple: true },
102
90
  'allow-missing-roots': { type: 'boolean', default: false },
103
91
  },
104
92
  strict: true,
@@ -136,17 +124,20 @@ export async function parseArgs() {
136
124
  cli.allowCwdWalk = true;
137
125
  if (v['allow-missing-roots'])
138
126
  cli.allowMissingRoots = true;
139
- if (v.deny !== undefined) {
140
- const denyPatterns = [...new Set(v.deny.map((entry) => entry.trim()).filter(Boolean))];
141
- if (denyPatterns.length > 0)
142
- cli.denyPatterns = denyPatterns;
143
- }
127
+ const patternList = (entries) => [
128
+ ...new Set((entries ?? []).map((entry) => entry.trim()).filter(Boolean)),
129
+ ];
130
+ const denyPatterns = patternList(v.deny);
131
+ if (denyPatterns.length > 0)
132
+ cli.denyPatterns = denyPatterns;
133
+ const allowPatterns = patternList(v.allow);
134
+ if (allowPatterns.length > 0)
135
+ cli.allowPatterns = allowPatterns;
144
136
  const allowCwd = v['allow-cwd'] ||
145
137
  v['walk-cwd'] ||
146
138
  parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');
147
- const readOnly = v['read-only'] || v.safe;
139
+ const readOnly = v['read-only'];
148
140
  const printConfig = v['print-config'];
149
- const json = v.json;
150
141
  const port = parsePortOption(v.port ?? process.env['FS_PORT']);
151
142
  const allowMissingRoots = v['allow-missing-roots'] ||
152
143
  parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');
@@ -166,7 +157,6 @@ export async function parseArgs() {
166
157
  port,
167
158
  readOnly,
168
159
  printConfig,
169
- json,
170
160
  httpHost,
171
161
  apiKey,
172
162
  };
@@ -179,10 +169,9 @@ export async function parseArgs() {
179
169
  }
180
170
  }
181
171
  // ════════════════════════════════════════════════════════════
182
- // Effective config reporting — prints the resolved server config
172
+ // Effective config reporting — prints the resolved server config as JSON
183
173
  // ════════════════════════════════════════════════════════════
184
174
  export async function runPrintConfig(options) {
185
- const write = (s) => process.stdout.write(s);
186
175
  const pathGuard = new PathGuard({
187
176
  allowCwd: options.allowCwd,
188
177
  cliAllowedDirs: options.allowedDirs,
@@ -202,17 +191,6 @@ export async function runPrintConfig(options) {
202
191
  apiKey: options.apiKey ? '***' : null,
203
192
  limits: { maxFileSizeBytes: getMaxTextFileSize() },
204
193
  };
205
- if (options.json) {
206
- write(JSON.stringify(config, null, 2));
207
- }
208
- else {
209
- const key = (k) => padEndVisible(cliFmt.cyan(k), 14);
210
- write(`${key('transport:')}${config.transport}\n`);
211
- write(`${key('readOnly:')}${cliFmt.bool(config.readOnly)}\n`);
212
- write(`${key('apiKey:')}${cliFmt.dim(config.apiKey ?? 'none')}\n`);
213
- write(`${key('allowedRoots:')}${config.allowedRoots.join(', ') || cliFmt.dim('(none)')}\n`);
214
- write(`${key('tools:')}${cliFmt.dim(config.tools.join(', '))}\n`);
215
- write(`${key('maxFileSize:')}${cliFmt.yellow(String(config.limits.maxFileSizeBytes))}\n`);
216
- }
194
+ process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
217
195
  }
218
196
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Stats } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { parseArgs as utilParseArgs } from 'node:util';\n\nimport { printHelpAndExit, printVersionAndExit } from './cli-help.js';\nimport { cli } from './core/config.js';\nimport { formatUnknownErrorMessage } from './core/errors.js';\nimport { cliFmt, padEndVisible } from './core/fmt.js';\nimport {\n getReservedDeviceNameForPath,\n isWindowsDriveRelativePath,\n normalizePath,\n} from './core/path-utils.js';\nimport { PathGuard } from './core/path.js';\nimport { IS_WINDOWS, parseTrueEnvFlag } from './core/primitives.js';\nimport { getMaxTextFileSize } from './core/util.js';\nimport { registeredTools } from './tools/index.js';\n\n// ════════════════════════════════════════════════════════════\n// Path & Config Utilities — pure functions and error types\n// ════════════════════════════════════════════════════════════\n\nexport class CliExitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliExitError';\n }\n}\n\nfunction validateCliPath(inputPath: string): void {\n if (inputPath.includes('\\0')) {\n throw new CliExitError('Path contains null bytes.');\n }\n\n if (isWindowsDriveRelativePath(inputPath)) {\n throw new CliExitError(\n 'Windows drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n );\n }\n\n const reserved = getReservedDeviceNameForPath(inputPath);\n if (reserved) {\n throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`);\n }\n}\n\nasync function validateDirectoryPath(inputPath: string, allowMissing = false): Promise<string> {\n const normalized = normalizePath(inputPath);\n\n let stats: Stats;\n try {\n stats = await stat(normalized);\n } catch (error) {\n if (allowMissing) return normalized;\n // Node's own message already names the syscall and the errno.\n throw new Error(`Cannot access directory ${inputPath}: ${formatUnknownErrorMessage(error)}`, {\n cause: error,\n });\n }\n\n if (!stats.isDirectory()) throw new Error(`${inputPath} is not a directory`);\n return normalized;\n}\n\nasync function normalizeAndValidateDirs(\n paths: readonly string[],\n allowMissing = false,\n): Promise<string[]> {\n const normalized: string[] = [];\n for (const p of paths) {\n normalized.push(await validateDirectoryPath(p, allowMissing));\n }\n return deduplicateAllowedDirectories(normalized);\n}\n\nfunction normalizeCliExitMessage(error: unknown): string {\n const rawMessage = formatUnknownErrorMessage(error);\n return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;\n}\n\nfunction deduplicateAllowedDirectories(dirs: readonly string[]): string[] {\n const seen = new Set<string>();\n const deduplicated: string[] = [];\n\n for (const dir of dirs) {\n const key = IS_WINDOWS ? dir.toLowerCase() : dir;\n if (seen.has(key)) continue;\n seen.add(key);\n deduplicated.push(dir);\n }\n\n return deduplicated;\n}\n\nfunction parsePortOption(raw: unknown): number | undefined {\n // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.\n if (raw === undefined || raw === '') return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliExitError(`Error: --port / FS_PORT must be an integer between 1 and 65535`);\n }\n return n;\n}\n\nconst CLI_PARSER_CONFIG = {\n options: {\n 'allow-cwd': { type: 'boolean', default: false },\n 'read-only': { type: 'boolean', default: false },\n safe: { type: 'boolean', default: false },\n 'print-config': { type: 'boolean', default: false },\n json: { type: 'boolean', default: false },\n port: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n 'log-level': { type: 'string' },\n 'http-host': { type: 'string' },\n 'api-key': { type: 'string' },\n 'allow-sensitive': { type: 'boolean', default: false },\n 'root-boundary': { type: 'string' },\n 'max-file-size': { type: 'string' },\n 'walk-cwd': { type: 'boolean', default: false },\n deny: { type: 'string', multiple: true },\n 'allow-missing-roots': { type: 'boolean', default: false },\n },\n strict: true,\n allowPositionals: true,\n} as const;\n\nexport async function parseArgs(): Promise<{\n allowedDirs: string[];\n allowCwd: boolean;\n port: number | undefined;\n readOnly: boolean;\n printConfig: boolean;\n json: boolean;\n httpHost: string | undefined;\n apiKey: string | undefined;\n}> {\n try {\n const parsed = utilParseArgs(CLI_PARSER_CONFIG);\n\n if (parsed.values.help) {\n printHelpAndExit();\n }\n\n if (parsed.values.version) {\n printVersionAndExit();\n }\n\n for (const positional of parsed.positionals) {\n validateCliPath(positional);\n }\n\n const v = parsed.values;\n // `--http-host` and `--api-key` are returned to the caller and handed to\n // the server as config. Every other flag lands in the CLI-override store\n // (core/config.ts) — the one owner of the flag-beats-env rule — which deep\n // core readers (path, sensitive, observability, util) consult before\n // falling back to the operator's environment. Nothing writes process.env.\n const httpHost = v['http-host'] ?? process.env['FS_HTTP_HOST'];\n const apiKey = v['api-key'] ?? process.env['FS_API_KEY'];\n if (v['log-level'] !== undefined) cli.logLevel = v['log-level'];\n if (v['max-file-size'] !== undefined) cli.maxFileSize = v['max-file-size'];\n if (v['root-boundary'] !== undefined) cli.rootBoundary = v['root-boundary'];\n if (v['allow-sensitive']) cli.allowSensitive = true;\n if (v['walk-cwd']) cli.allowCwdWalk = true;\n if (v['allow-missing-roots']) cli.allowMissingRoots = true;\n if (v.deny !== undefined) {\n const denyPatterns = [...new Set(v.deny.map((entry) => entry.trim()).filter(Boolean))];\n if (denyPatterns.length > 0) cli.denyPatterns = denyPatterns;\n }\n\n const allowCwd =\n v['allow-cwd'] ||\n v['walk-cwd'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n const readOnly = v['read-only'] || v.safe;\n const printConfig = v['print-config'];\n const json = v.json;\n const port = parsePortOption(v.port ?? process.env['FS_PORT']);\n const allowMissingRoots =\n v['allow-missing-roots'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n\n let allowedDirs: string[] = [];\n try {\n allowedDirs =\n parsed.positionals.length > 0\n ? await normalizeAndValidateDirs(parsed.positionals, allowMissingRoots)\n : [];\n } catch (error: unknown) {\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n\n return {\n allowedDirs,\n allowCwd,\n port,\n readOnly,\n printConfig,\n json,\n httpHost,\n apiKey,\n };\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n throw error;\n }\n\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n}\n\n// ════════════════════════════════════════════════════════════\n// Effective config reporting — prints the resolved server config\n// ════════════════════════════════════════════════════════════\n\nexport async function runPrintConfig(options: {\n allowedDirs: string[];\n allowCwd: boolean;\n readOnly: boolean;\n json: boolean;\n /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */\n port?: number;\n httpHost?: string;\n apiKey?: string;\n}): Promise<void> {\n const write = (s: string) => process.stdout.write(s);\n\n const pathGuard = new PathGuard({\n allowCwd: options.allowCwd,\n cliAllowedDirs: options.allowedDirs,\n });\n await pathGuard.recomputeAllowedDirectories();\n const allowedRoots = pathGuard.getAllowedDirectories();\n\n const tools = registeredTools(options.readOnly).map((t) => t.name);\n\n // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind\n // that `--port` would actually have started, not the stdio default.\n const config = {\n transport:\n options.port !== undefined\n ? `http://${options.httpHost ?? '127.0.0.1'}:${String(options.port)}`\n : 'stdio',\n readOnly: options.readOnly,\n allowedRoots,\n tools,\n apiKey: options.apiKey ? '***' : null,\n limits: { maxFileSizeBytes: getMaxTextFileSize() },\n };\n\n if (options.json) {\n write(JSON.stringify(config, null, 2));\n } else {\n const key = (k: string) => padEndVisible(cliFmt.cyan(k), 14);\n write(`${key('transport:')}${config.transport}\\n`);\n write(`${key('readOnly:')}${cliFmt.bool(config.readOnly)}\\n`);\n write(`${key('apiKey:')}${cliFmt.dim(config.apiKey ?? 'none')}\\n`);\n write(`${key('allowedRoots:')}${config.allowedRoots.join(', ') || cliFmt.dim('(none)')}\\n`);\n write(`${key('tools:')}${cliFmt.dim(config.tools.join(', '))}\\n`);\n write(`${key('maxFileSize:')}${cliFmt.yellow(String(config.limits.maxFileSizeBytes))}\\n`);\n }\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,EAC1B,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,+DAA+D;AAC/D,2DAA2D;AAC3D,+DAA+D;AAE/D,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,YAAY,CAAC,2BAA2B,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,YAAY,CACpB,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,4BAA4B,CAAC,SAAS,CAAC,CAAC;IACzD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,6CAA6C,QAAQ,GAAG,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,YAAY,GAAG,KAAK;IAC1E,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,KAAY,CAAC;IACjB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,YAAY;YAAE,OAAO,UAAU,CAAC;QACpC,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,KAAK,yBAAyB,CAAC,KAAK,CAAC,EAAE,EAAE;YAC3F,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,qBAAqB,CAAC,CAAC;IAC7E,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gFAAgF;AAChF,4EAA4E;AAC5E,iBAAiB;AACjB,KAAK,UAAU,wBAAwB,CACrC,KAAwB,EACxB,YAAY,GAAG,KAAK;IAEpB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,UAAU,CAAC,IAAI,CAAC,MAAM,qBAAqB,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,MAAM,UAAU,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,UAAU,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,6EAA6E;IAC7E,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;QAC/C,MAAM,IAAI,YAAY,CAAC,gEAAgE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,iBAAiB,GAAG;IACxB,OAAO,EAAE;QACP,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAChD,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACnD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;QACxC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC/B,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC7B,iBAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACtD,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACnC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACxC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;QACzC,qBAAqB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;KAC3D;IACD,MAAM,EAAE,IAAI;IACZ,gBAAgB,EAAE,IAAI;CACd,CAAC;AAEX,MAAM,CAAC,KAAK,UAAU,SAAS;IAS7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAEhD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACvB,gBAAgB,EAAE,CAAC;QACrB,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1B,mBAAmB,EAAE,CAAC;QACxB,CAAC;QAED,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5C,eAAe,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QACxB,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,qEAAqE;QACrE,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACzD,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,WAAW,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC3E,IAAI,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC,iBAAiB,CAAC;YAAE,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC;QACpD,IAAI,CAAC,CAAC,UAAU,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3C,IAAI,CAAC,CAAC,qBAAqB,CAAC;YAAE,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC3D,MAAM,WAAW,GAAG,CAAC,OAAsC,EAAqB,EAAE,CAAC;YACjF,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SACzE,CAAC;QACF,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,YAAY,GAAG,YAAY,CAAC;QAC7D,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,aAAa,GAAG,aAAa,CAAC;QAEhE,MAAM,QAAQ,GACZ,CAAC,CAAC,WAAW,CAAC;YACd,CAAC,CAAC,UAAU,CAAC;YACb,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;QAChC,MAAM,WAAW,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GACrB,CAAC,CAAC,qBAAqB,CAAC;YACxB,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAEpF,IAAI,WAAW,GAAa,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,WAAW;gBACT,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;oBAC3B,CAAC,CAAC,MAAM,wBAAwB,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,CAAC;oBACvE,CAAC,CAAC,EAAE,CAAC;QACX,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,IAAI,YAAY,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC;QACzD,CAAC;QAED,OAAO;YACL,WAAW;YACX,QAAQ;YACR,IAAI;YACJ,QAAQ;YACR,WAAW;YACX,QAAQ;YACR,MAAM;SACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,IAAI,YAAY,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,yEAAyE;AACzE,+DAA+D;AAE/D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAQpC;IACC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,WAAW;KACpC,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,2BAA2B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC;IAEvD,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnE,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,MAAM,GAAG;QACb,SAAS,EACP,OAAO,CAAC,IAAI,KAAK,SAAS;YACxB,CAAC,CAAC,UAAU,OAAO,CAAC,QAAQ,IAAI,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACrE,CAAC,CAAC,OAAO;QACb,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY;QACZ,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QACrC,MAAM,EAAE,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,EAAE;KACnD,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["import type { Stats } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { parseArgs as utilParseArgs } from 'node:util';\n\nimport { printHelpAndExit, printVersionAndExit } from './cli-help.ts';\nimport { cli } from './core/config.ts';\nimport { formatUnknownErrorMessage } from './core/errors.ts';\nimport {\n getReservedDeviceNameForPath,\n isWindowsDriveRelativePath,\n normalizePath,\n parseTrueEnvFlag,\n} from './core/path-utils.ts';\nimport { PathGuard } from './core/path.ts';\nimport { getMaxTextFileSize } from './core/util.ts';\nimport { registeredTools } from './tools/index.ts';\n\n// ════════════════════════════════════════════════════════════\n// Path & Config Utilities — pure functions and error types\n// ════════════════════════════════════════════════════════════\n\nexport class CliExitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CliExitError';\n }\n}\n\nfunction validateCliPath(inputPath: string): void {\n if (inputPath.includes('\\0')) {\n throw new CliExitError('Path contains null bytes.');\n }\n\n if (isWindowsDriveRelativePath(inputPath)) {\n throw new CliExitError(\n 'Windows drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n );\n }\n\n const reserved = getReservedDeviceNameForPath(inputPath);\n if (reserved) {\n throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`);\n }\n}\n\nasync function validateDirectoryPath(inputPath: string, allowMissing = false): Promise<string> {\n const normalized = normalizePath(inputPath);\n\n let stats: Stats;\n try {\n stats = await stat(normalized);\n } catch (error) {\n if (allowMissing) return normalized;\n // Node's own message already names the syscall and the errno.\n throw new Error(`Cannot access directory ${inputPath}: ${formatUnknownErrorMessage(error)}`, {\n cause: error,\n });\n }\n\n if (!stats.isDirectory()) throw new Error(`${inputPath} is not a directory`);\n return normalized;\n}\n\n// No dedupe here: PathGuard's `normalizeAllowedDirectories` already Set-dedupes\n// the normalized set, and containment checks are case-insensitive where the\n// filesystem is.\nasync function normalizeAndValidateDirs(\n paths: readonly string[],\n allowMissing = false,\n): Promise<string[]> {\n const normalized: string[] = [];\n for (const p of paths) {\n normalized.push(await validateDirectoryPath(p, allowMissing));\n }\n return normalized;\n}\n\nfunction normalizeCliExitMessage(error: unknown): string {\n const rawMessage = formatUnknownErrorMessage(error);\n return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;\n}\n\nfunction parsePortOption(raw: unknown): number | undefined {\n // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.\n if (raw === undefined || raw === '') return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new CliExitError(`Error: --port / FS_PORT must be an integer between 1 and 65535`);\n }\n return n;\n}\n\nconst CLI_PARSER_CONFIG = {\n options: {\n 'allow-cwd': { type: 'boolean', default: false },\n 'read-only': { type: 'boolean', default: false },\n 'print-config': { type: 'boolean', default: false },\n port: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n 'log-level': { type: 'string' },\n 'http-host': { type: 'string' },\n 'api-key': { type: 'string' },\n 'allow-sensitive': { type: 'boolean', default: false },\n 'root-boundary': { type: 'string' },\n 'max-file-size': { type: 'string' },\n 'walk-cwd': { type: 'boolean', default: false },\n deny: { type: 'string', multiple: true },\n allow: { type: 'string', multiple: true },\n 'allow-missing-roots': { type: 'boolean', default: false },\n },\n strict: true,\n allowPositionals: true,\n} as const;\n\nexport async function parseArgs(): Promise<{\n allowedDirs: string[];\n allowCwd: boolean;\n port: number | undefined;\n readOnly: boolean;\n printConfig: boolean;\n httpHost: string | undefined;\n apiKey: string | undefined;\n}> {\n try {\n const parsed = utilParseArgs(CLI_PARSER_CONFIG);\n\n if (parsed.values.help) {\n printHelpAndExit();\n }\n\n if (parsed.values.version) {\n printVersionAndExit();\n }\n\n for (const positional of parsed.positionals) {\n validateCliPath(positional);\n }\n\n const v = parsed.values;\n // `--http-host` and `--api-key` are returned to the caller and handed to\n // the server as config. Every other flag lands in the CLI-override store\n // (core/config.ts) — the one owner of the flag-beats-env rule — which deep\n // core readers (path, sensitive, observability, util) consult before\n // falling back to the operator's environment. Nothing writes process.env.\n const httpHost = v['http-host'] ?? process.env['FS_HTTP_HOST'];\n const apiKey = v['api-key'] ?? process.env['FS_API_KEY'];\n if (v['log-level'] !== undefined) cli.logLevel = v['log-level'];\n if (v['max-file-size'] !== undefined) cli.maxFileSize = v['max-file-size'];\n if (v['root-boundary'] !== undefined) cli.rootBoundary = v['root-boundary'];\n if (v['allow-sensitive']) cli.allowSensitive = true;\n if (v['walk-cwd']) cli.allowCwdWalk = true;\n if (v['allow-missing-roots']) cli.allowMissingRoots = true;\n const patternList = (entries: readonly string[] | undefined): readonly string[] => [\n ...new Set((entries ?? []).map((entry) => entry.trim()).filter(Boolean)),\n ];\n const denyPatterns = patternList(v.deny);\n if (denyPatterns.length > 0) cli.denyPatterns = denyPatterns;\n const allowPatterns = patternList(v.allow);\n if (allowPatterns.length > 0) cli.allowPatterns = allowPatterns;\n\n const allowCwd =\n v['allow-cwd'] ||\n v['walk-cwd'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n const readOnly = v['read-only'];\n const printConfig = v['print-config'];\n const port = parsePortOption(v.port ?? process.env['FS_PORT']);\n const allowMissingRoots =\n v['allow-missing-roots'] ||\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n\n let allowedDirs: string[] = [];\n try {\n allowedDirs =\n parsed.positionals.length > 0\n ? await normalizeAndValidateDirs(parsed.positionals, allowMissingRoots)\n : [];\n } catch (error: unknown) {\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n\n return {\n allowedDirs,\n allowCwd,\n port,\n readOnly,\n printConfig,\n httpHost,\n apiKey,\n };\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n throw error;\n }\n\n throw new CliExitError(normalizeCliExitMessage(error));\n }\n}\n\n// ════════════════════════════════════════════════════════════\n// Effective config reporting — prints the resolved server config as JSON\n// ════════════════════════════════════════════════════════════\n\nexport async function runPrintConfig(options: {\n allowedDirs: string[];\n allowCwd: boolean;\n readOnly: boolean;\n /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */\n port?: number;\n httpHost?: string;\n apiKey?: string;\n}): Promise<void> {\n const pathGuard = new PathGuard({\n allowCwd: options.allowCwd,\n cliAllowedDirs: options.allowedDirs,\n });\n await pathGuard.recomputeAllowedDirectories();\n const allowedRoots = pathGuard.getAllowedDirectories();\n\n const tools = registeredTools(options.readOnly).map((t) => t.name);\n\n // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind\n // that `--port` would actually have started, not the stdio default.\n const config = {\n transport:\n options.port !== undefined\n ? `http://${options.httpHost ?? '127.0.0.1'}:${String(options.port)}`\n : 'stdio',\n readOnly: options.readOnly,\n allowedRoots,\n tools,\n apiKey: options.apiKey ? '***' : null,\n limits: { maxFileSizeBytes: getMaxTextFileSize() },\n };\n\n process.stdout.write(`${JSON.stringify(config, null, 2)}\\n`);\n}\n"]}
@@ -2,8 +2,8 @@ import * as z from 'zod/v4';
2
2
  export type StoppedReason = 'maxResults' | 'maxFiles' | 'timeout';
3
3
  export declare const StoppedReasonSchema: z.ZodOptional<z.ZodEnum<{
4
4
  maxResults: "maxResults";
5
- timeout: "timeout";
6
5
  maxFiles: "maxFiles";
6
+ timeout: "timeout";
7
7
  }>>;
8
8
  /**
9
9
  * The subset `search_text` and `find_files` can actually emit. Their scans call
@@ -15,19 +15,6 @@ export declare const SearchStoppedReasonSchema: z.ZodOptional<z.ZodEnum<{
15
15
  maxResults: "maxResults";
16
16
  timeout: "timeout";
17
17
  }>>;
18
- /**
19
- * Accumulates why an enumeration stopped early. maxResults wins over maxFiles
20
- * wins over timeout (the most specific cap is the definite cause even if the
21
- * abort also fired on the same iteration). Call `resolve()` once at the end.
22
- */
23
- export declare class StopReasonTracker {
24
- #private;
25
- hitMaxResults(): void;
26
- hitMaxFiles(): void;
27
- hitAbort(): void;
28
- get truncated(): boolean;
29
- resolve(): StoppedReason | undefined;
30
- }
31
18
  interface ParallelResult<R> {
32
19
  results: {
33
20
  index: number;
@@ -46,8 +33,9 @@ export declare function processInParallel<T, R>(items: readonly T[], processor:
46
33
  *
47
34
  * `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`
48
35
  * lets the caller stop on its own accumulating result count (maxResults). Both
49
- * are checked before dispatch, so the returned tracker names exactly one
50
- * reason — the loop breaks on the first that fires.
36
+ * are checked before dispatch, so the returned reason is exactly one — the
37
+ * loop breaks on the first that fires. `undefined` means every entry was
38
+ * dispatched.
51
39
  */
52
40
  export declare function processEntriesConcurrently(entries: AsyncIterable<{
53
41
  path: string;
@@ -61,7 +49,7 @@ export declare function processEntriesConcurrently(entries: AsyncIterable<{
61
49
  onEntry: () => void;
62
50
  onError?: (entryPath: string, err: unknown) => void;
63
51
  runEntry: (entryPath: string) => Promise<void>;
64
- }): Promise<StopReasonTracker>;
52
+ }): Promise<StoppedReason | undefined>;
65
53
  export declare function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T>;
66
54
  export {};
67
55
  //# sourceMappingURL=concurrency.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAK5B,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;GAA2D,CAAC;AAE5F;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;GAA+C,CAAC;AAEtF;;;;GAIG;AACH,qBAAa,iBAAiB;;IAG5B,aAAa,IAAI,IAAI;IAIrB,WAAW,IAAI,IAAI;IAInB,QAAQ,IAAI,IAAI;IAIhB,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,OAAO,IAAI,aAAa,GAAG,SAAS;CAGrC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC;IACvC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAC1C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,WAAW,GAAE,MAA6B,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAkD5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrE,OAAO,EAAE;IACP,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,GACA,OAAO,CAAC,iBAAiB,CAAC,CA0D5B;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBlF"}
1
+ {"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAK5B,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;GAA2D,CAAC;AAE5F;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;GAA+C,CAAC;AAEtF,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC;IACvC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAC1C,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,WAAW,GAAE,MAA6B,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAgE5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACrE,OAAO,EAAE;IACP,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,GACA,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CA0DpC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAmBlF"}