toolcraft 0.0.749 → 0.0.750

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 (374) hide show
  1. package/composition.json +23 -3
  2. package/dist/composition.json +23 -3
  3. package/dist/human-in-loop/approvals-commands.d.ts +1 -1
  4. package/dist/index.d.ts +1 -262
  5. package/dist/index.js +1 -573
  6. package/dist/runtime-platform.d.ts +2 -0
  7. package/dist/runtime-platform.js +2 -0
  8. package/dist/runtime-platform.workerd.d.ts +3 -0
  9. package/dist/runtime-platform.workerd.js +10 -0
  10. package/dist/runtime.d.ts +262 -0
  11. package/dist/runtime.js +576 -0
  12. package/node_modules/@noble/hashes/LICENSE +21 -0
  13. package/node_modules/@noble/hashes/README.md +478 -0
  14. package/node_modules/@noble/hashes/_blake.d.ts +20 -0
  15. package/node_modules/@noble/hashes/_blake.js +59 -0
  16. package/node_modules/@noble/hashes/_md.d.ts +95 -0
  17. package/node_modules/@noble/hashes/_md.js +209 -0
  18. package/node_modules/@noble/hashes/_u64.d.ts +36 -0
  19. package/node_modules/@noble/hashes/_u64.js +77 -0
  20. package/node_modules/@noble/hashes/argon2.d.ts +148 -0
  21. package/node_modules/@noble/hashes/argon2.js +577 -0
  22. package/node_modules/@noble/hashes/blake1.d.ts +162 -0
  23. package/node_modules/@noble/hashes/blake1.js +630 -0
  24. package/node_modules/@noble/hashes/blake2.d.ts +203 -0
  25. package/node_modules/@noble/hashes/blake2.js +573 -0
  26. package/node_modules/@noble/hashes/blake3.d.ts +68 -0
  27. package/node_modules/@noble/hashes/blake3.js +310 -0
  28. package/node_modules/@noble/hashes/eskdf.d.ts +91 -0
  29. package/node_modules/@noble/hashes/eskdf.js +225 -0
  30. package/node_modules/@noble/hashes/hkdf.d.ts +70 -0
  31. package/node_modules/@noble/hashes/hkdf.js +152 -0
  32. package/node_modules/@noble/hashes/hmac.d.ts +50 -0
  33. package/node_modules/@noble/hashes/hmac.js +93 -0
  34. package/node_modules/@noble/hashes/index.d.ts +1 -0
  35. package/node_modules/@noble/hashes/index.js +34 -0
  36. package/node_modules/@noble/hashes/legacy.d.ts +107 -0
  37. package/node_modules/@noble/hashes/legacy.js +348 -0
  38. package/node_modules/@noble/hashes/package.json +91 -0
  39. package/node_modules/@noble/hashes/pbkdf2.d.ts +66 -0
  40. package/node_modules/@noble/hashes/pbkdf2.js +176 -0
  41. package/node_modules/@noble/hashes/scrypt.d.ts +98 -0
  42. package/node_modules/@noble/hashes/scrypt.js +304 -0
  43. package/node_modules/@noble/hashes/sha2.d.ts +176 -0
  44. package/node_modules/@noble/hashes/sha2.js +446 -0
  45. package/node_modules/@noble/hashes/sha3-addons.d.ts +533 -0
  46. package/node_modules/@noble/hashes/sha3-addons.js +973 -0
  47. package/node_modules/@noble/hashes/sha3.d.ts +208 -0
  48. package/node_modules/@noble/hashes/sha3.js +475 -0
  49. package/node_modules/@noble/hashes/src/_blake.ts +67 -0
  50. package/node_modules/@noble/hashes/src/_md.ts +227 -0
  51. package/node_modules/@noble/hashes/src/_u64.ts +111 -0
  52. package/node_modules/@noble/hashes/src/argon2.ts +693 -0
  53. package/node_modules/@noble/hashes/src/blake1.ts +677 -0
  54. package/node_modules/@noble/hashes/src/blake2.ts +648 -0
  55. package/node_modules/@noble/hashes/src/blake3.ts +344 -0
  56. package/node_modules/@noble/hashes/src/eskdf.ts +265 -0
  57. package/node_modules/@noble/hashes/src/hkdf.ts +161 -0
  58. package/node_modules/@noble/hashes/src/hmac.ts +130 -0
  59. package/node_modules/@noble/hashes/src/index.ts +33 -0
  60. package/node_modules/@noble/hashes/src/legacy.ts +361 -0
  61. package/node_modules/@noble/hashes/src/pbkdf2.ts +219 -0
  62. package/node_modules/@noble/hashes/src/scrypt.ts +375 -0
  63. package/node_modules/@noble/hashes/src/sha2.ts +486 -0
  64. package/node_modules/@noble/hashes/src/sha3-addons.ts +1157 -0
  65. package/node_modules/@noble/hashes/src/sha3.ts +525 -0
  66. package/node_modules/@noble/hashes/src/utils.ts +978 -0
  67. package/node_modules/@noble/hashes/src/webcrypto.ts +283 -0
  68. package/node_modules/@noble/hashes/utils.d.ts +560 -0
  69. package/node_modules/@noble/hashes/utils.js +687 -0
  70. package/node_modules/@noble/hashes/webcrypto.d.ts +121 -0
  71. package/node_modules/@noble/hashes/webcrypto.js +228 -0
  72. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +23 -9
  73. package/node_modules/@poe-code/config-mutations/README.md +2 -0
  74. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +4 -5
  75. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +3 -4
  76. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +3 -3
  77. package/node_modules/@poe-code/config-mutations/dist/template/render.js +1 -1
  78. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +2 -2
  79. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +3 -6
  80. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +4 -2
  81. package/node_modules/@poe-code/process-runner/README.md +2 -1
  82. package/node_modules/@poe-code/process-runner/dist/transfer-filesystem.d.ts +1 -0
  83. package/node_modules/@poe-code/process-runner/dist/transfer-filesystem.js +1 -0
  84. package/node_modules/@poe-code/process-runner/dist/transfer-filesystem.workerd.d.ts +2 -0
  85. package/node_modules/@poe-code/process-runner/dist/transfer-filesystem.workerd.js +1 -0
  86. package/node_modules/@poe-code/process-runner/dist/types.d.ts +1 -1
  87. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +6 -5
  88. package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +67 -29
  89. package/node_modules/@poe-code/process-runner/package.json +13 -0
  90. package/node_modules/@poe-code/safe-fs/LICENSE +21 -0
  91. package/node_modules/@poe-code/safe-fs/PROVENANCE.json +428 -0
  92. package/node_modules/@poe-code/safe-fs/README.md +411 -0
  93. package/node_modules/@poe-code/safe-fs/dist/bridge/confinement.d.ts +3 -0
  94. package/node_modules/@poe-code/safe-fs/dist/bridge/confinement.js +171 -0
  95. package/node_modules/@poe-code/safe-fs/dist/bridge/filesystem.d.ts +93 -0
  96. package/node_modules/@poe-code/safe-fs/dist/bridge/filesystem.js +453 -0
  97. package/node_modules/@poe-code/safe-fs/dist/bridge/index.d.ts +7 -0
  98. package/node_modules/@poe-code/safe-fs/dist/bridge/index.js +19 -0
  99. package/node_modules/@poe-code/safe-fs/dist/bridge/stats.d.ts +4 -0
  100. package/node_modules/@poe-code/safe-fs/dist/bridge/stats.js +37 -0
  101. package/node_modules/@poe-code/safe-fs/dist/bridge/types.d.ts +81 -0
  102. package/node_modules/@poe-code/safe-fs/dist/bridge/types.js +1 -0
  103. package/node_modules/@poe-code/safe-fs/dist/bridge/values.d.ts +6 -0
  104. package/node_modules/@poe-code/safe-fs/dist/bridge/values.js +65 -0
  105. package/node_modules/@poe-code/safe-fs/dist/config/memory.d.ts +3 -0
  106. package/node_modules/@poe-code/safe-fs/dist/config/memory.js +7 -0
  107. package/node_modules/@poe-code/safe-fs/dist/config/real.d.ts +5 -0
  108. package/node_modules/@poe-code/safe-fs/dist/config/real.js +16 -0
  109. package/node_modules/@poe-code/safe-fs/dist/config/registry.d.ts +2 -0
  110. package/node_modules/@poe-code/safe-fs/dist/config/registry.js +15 -0
  111. package/node_modules/@poe-code/safe-fs/dist/config.d.ts +15 -0
  112. package/node_modules/@poe-code/safe-fs/dist/config.js +46 -0
  113. package/node_modules/@poe-code/safe-fs/dist/config.node.d.ts +2 -0
  114. package/node_modules/@poe-code/safe-fs/dist/config.node.js +11 -0
  115. package/node_modules/@poe-code/safe-fs/dist/config.portable.d.ts +3 -0
  116. package/node_modules/@poe-code/safe-fs/dist/config.portable.js +9 -0
  117. package/node_modules/@poe-code/safe-fs/dist/contracts/abort.d.ts +5 -0
  118. package/node_modules/@poe-code/safe-fs/dist/contracts/abort.js +34 -0
  119. package/node_modules/@poe-code/safe-fs/dist/contracts/cleanup.d.ts +1 -0
  120. package/node_modules/@poe-code/safe-fs/dist/contracts/cleanup.js +9 -0
  121. package/node_modules/@poe-code/safe-fs/dist/contracts/descriptor.d.ts +38 -0
  122. package/node_modules/@poe-code/safe-fs/dist/contracts/descriptor.js +1 -0
  123. package/node_modules/@poe-code/safe-fs/dist/contracts/errors.d.ts +53 -0
  124. package/node_modules/@poe-code/safe-fs/dist/contracts/errors.js +99 -0
  125. package/node_modules/@poe-code/safe-fs/dist/contracts/filesystem.d.ts +379 -0
  126. package/node_modules/@poe-code/safe-fs/dist/contracts/filesystem.js +1 -0
  127. package/node_modules/@poe-code/safe-fs/dist/contracts/index.d.ts +6 -0
  128. package/node_modules/@poe-code/safe-fs/dist/contracts/index.js +6 -0
  129. package/node_modules/@poe-code/safe-fs/dist/contracts/io.d.ts +12 -0
  130. package/node_modules/@poe-code/safe-fs/dist/contracts/io.js +321 -0
  131. package/node_modules/@poe-code/safe-fs/dist/contracts/object.d.ts +134 -0
  132. package/node_modules/@poe-code/safe-fs/dist/contracts/object.js +97 -0
  133. package/node_modules/@poe-code/safe-fs/dist/contracts/path.d.ts +3 -0
  134. package/node_modules/@poe-code/safe-fs/dist/contracts/path.js +2 -0
  135. package/node_modules/@poe-code/safe-fs/dist/contracts/portable-path.d.ts +21 -0
  136. package/node_modules/@poe-code/safe-fs/dist/contracts/portable-path.js +78 -0
  137. package/node_modules/@poe-code/safe-fs/dist/contracts/virtual-path.d.ts +15 -0
  138. package/node_modules/@poe-code/safe-fs/dist/contracts/virtual-path.js +162 -0
  139. package/node_modules/@poe-code/safe-fs/dist/core.d.ts +19 -0
  140. package/node_modules/@poe-code/safe-fs/dist/core.js +17 -0
  141. package/node_modules/@poe-code/safe-fs/dist/fs/capabilities.d.ts +10 -0
  142. package/node_modules/@poe-code/safe-fs/dist/fs/capabilities.js +216 -0
  143. package/node_modules/@poe-code/safe-fs/dist/fs/conditional-chmod.d.ts +7 -0
  144. package/node_modules/@poe-code/safe-fs/dist/fs/conditional-chmod.js +19 -0
  145. package/node_modules/@poe-code/safe-fs/dist/fs/descriptor.d.ts +21 -0
  146. package/node_modules/@poe-code/safe-fs/dist/fs/descriptor.js +279 -0
  147. package/node_modules/@poe-code/safe-fs/dist/fs/devices/index.d.ts +59 -0
  148. package/node_modules/@poe-code/safe-fs/dist/fs/devices/index.js +875 -0
  149. package/node_modules/@poe-code/safe-fs/dist/fs/devices/path.d.ts +7 -0
  150. package/node_modules/@poe-code/safe-fs/dist/fs/devices/path.js +250 -0
  151. package/node_modules/@poe-code/safe-fs/dist/fs/devices/stream.d.ts +6 -0
  152. package/node_modules/@poe-code/safe-fs/dist/fs/devices/stream.js +136 -0
  153. package/node_modules/@poe-code/safe-fs/dist/fs/directory-admission.d.ts +3 -0
  154. package/node_modules/@poe-code/safe-fs/dist/fs/directory-admission.js +14 -0
  155. package/node_modules/@poe-code/safe-fs/dist/fs/memory/atomic-view.d.ts +11 -0
  156. package/node_modules/@poe-code/safe-fs/dist/fs/memory/atomic-view.js +13 -0
  157. package/node_modules/@poe-code/safe-fs/dist/fs/memory/index.d.ts +186 -0
  158. package/node_modules/@poe-code/safe-fs/dist/fs/memory/index.js +3771 -0
  159. package/node_modules/@poe-code/safe-fs/dist/fs/memory/ledger.d.ts +26 -0
  160. package/node_modules/@poe-code/safe-fs/dist/fs/memory/ledger.js +96 -0
  161. package/node_modules/@poe-code/safe-fs/dist/fs/memory/limits.d.ts +13 -0
  162. package/node_modules/@poe-code/safe-fs/dist/fs/memory/limits.js +26 -0
  163. package/node_modules/@poe-code/safe-fs/dist/fs/memory/missing-target.d.ts +15 -0
  164. package/node_modules/@poe-code/safe-fs/dist/fs/memory/missing-target.js +144 -0
  165. package/node_modules/@poe-code/safe-fs/dist/fs/mount/comparison.d.ts +19 -0
  166. package/node_modules/@poe-code/safe-fs/dist/fs/mount/comparison.js +118 -0
  167. package/node_modules/@poe-code/safe-fs/dist/fs/mount/identity.d.ts +6 -0
  168. package/node_modules/@poe-code/safe-fs/dist/fs/mount/identity.js +29 -0
  169. package/node_modules/@poe-code/safe-fs/dist/fs/mount/index.d.ts +74 -0
  170. package/node_modules/@poe-code/safe-fs/dist/fs/mount/index.js +1291 -0
  171. package/node_modules/@poe-code/safe-fs/dist/fs/object-authority.d.ts +27 -0
  172. package/node_modules/@poe-code/safe-fs/dist/fs/object-authority.js +287 -0
  173. package/node_modules/@poe-code/safe-fs/dist/fs/object-publication/index.d.ts +45 -0
  174. package/node_modules/@poe-code/safe-fs/dist/fs/object-publication/index.js +531 -0
  175. package/node_modules/@poe-code/safe-fs/dist/fs/overlay/index.d.ts +87 -0
  176. package/node_modules/@poe-code/safe-fs/dist/fs/overlay/index.js +1185 -0
  177. package/node_modules/@poe-code/safe-fs/dist/fs/overlay/memory-publication.d.ts +42 -0
  178. package/node_modules/@poe-code/safe-fs/dist/fs/overlay/memory-publication.js +558 -0
  179. package/node_modules/@poe-code/safe-fs/dist/fs/path-namespace.d.ts +4 -0
  180. package/node_modules/@poe-code/safe-fs/dist/fs/path-namespace.js +63 -0
  181. package/node_modules/@poe-code/safe-fs/dist/fs/quota/index.d.ts +12 -0
  182. package/node_modules/@poe-code/safe-fs/dist/fs/quota/index.js +520 -0
  183. package/node_modules/@poe-code/safe-fs/dist/fs/readonly/index.d.ts +37 -0
  184. package/node_modules/@poe-code/safe-fs/dist/fs/readonly/index.js +208 -0
  185. package/node_modules/@poe-code/safe-fs/dist/fs/real/allocation.d.ts +1 -0
  186. package/node_modules/@poe-code/safe-fs/dist/fs/real/allocation.js +8 -0
  187. package/node_modules/@poe-code/safe-fs/dist/fs/real/index.d.ts +105 -0
  188. package/node_modules/@poe-code/safe-fs/dist/fs/real/index.js +1259 -0
  189. package/node_modules/@poe-code/safe-fs/dist/fs/s3/authority.d.ts +7 -0
  190. package/node_modules/@poe-code/safe-fs/dist/fs/s3/authority.js +38 -0
  191. package/node_modules/@poe-code/safe-fs/dist/fs/s3/filesystem.d.ts +130 -0
  192. package/node_modules/@poe-code/safe-fs/dist/fs/s3/filesystem.js +1137 -0
  193. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/bytes.d.ts +1 -0
  194. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/bytes.js +9 -0
  195. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/index.d.ts +2 -0
  196. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/index.js +1 -0
  197. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request-fetch.d.ts +15 -0
  198. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request-fetch.js +74 -0
  199. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request-node.d.ts +4 -0
  200. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request-node.js +111 -0
  201. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request.d.ts +17 -0
  202. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/request.js +89 -0
  203. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/signature.d.ts +20 -0
  204. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/signature.js +76 -0
  205. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/transport.d.ts +3 -0
  206. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/transport.js +478 -0
  207. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/types.d.ts +30 -0
  208. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/types.js +1 -0
  209. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/xml.d.ts +14 -0
  210. package/node_modules/@poe-code/safe-fs/dist/fs/s3/http/xml.js +192 -0
  211. package/node_modules/@poe-code/safe-fs/dist/fs/s3/index.d.ts +8 -0
  212. package/node_modules/@poe-code/safe-fs/dist/fs/s3/index.js +4 -0
  213. package/node_modules/@poe-code/safe-fs/dist/fs/s3/key-order.d.ts +2 -0
  214. package/node_modules/@poe-code/safe-fs/dist/fs/s3/key-order.js +11 -0
  215. package/node_modules/@poe-code/safe-fs/dist/fs/s3/manifest-admission.d.ts +8 -0
  216. package/node_modules/@poe-code/safe-fs/dist/fs/s3/manifest-admission.js +167 -0
  217. package/node_modules/@poe-code/safe-fs/dist/fs/s3/mock.d.ts +48 -0
  218. package/node_modules/@poe-code/safe-fs/dist/fs/s3/mock.js +249 -0
  219. package/node_modules/@poe-code/safe-fs/dist/fs/s3/namespace.d.ts +13 -0
  220. package/node_modules/@poe-code/safe-fs/dist/fs/s3/namespace.js +400 -0
  221. package/node_modules/@poe-code/safe-fs/dist/fs/s3/registry.d.ts +10 -0
  222. package/node_modules/@poe-code/safe-fs/dist/fs/s3/registry.js +73 -0
  223. package/node_modules/@poe-code/safe-fs/dist/fs/s3/transport.d.ts +103 -0
  224. package/node_modules/@poe-code/safe-fs/dist/fs/s3/transport.js +28 -0
  225. package/node_modules/@poe-code/safe-fs/dist/fs/scoped.d.ts +19 -0
  226. package/node_modules/@poe-code/safe-fs/dist/fs/scoped.js +663 -0
  227. package/node_modules/@poe-code/safe-fs/dist/fs/staging-ancestry.d.ts +8 -0
  228. package/node_modules/@poe-code/safe-fs/dist/fs/staging-ancestry.js +115 -0
  229. package/node_modules/@poe-code/safe-fs/dist/fs/staging-cleanup.d.ts +5 -0
  230. package/node_modules/@poe-code/safe-fs/dist/fs/staging-cleanup.js +63 -0
  231. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/index.d.ts +2 -0
  232. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/index.js +1 -0
  233. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/resource-id.d.ts +18 -0
  234. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/resource-id.js +109 -0
  235. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/webdav.d.ts +141 -0
  236. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/webdav.js +1377 -0
  237. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/xml.d.ts +15 -0
  238. package/node_modules/@poe-code/safe-fs/dist/fs/webdav/xml.js +59 -0
  239. package/node_modules/@poe-code/safe-fs/dist/index.d.ts +25 -0
  240. package/node_modules/@poe-code/safe-fs/dist/index.js +21 -0
  241. package/node_modules/@poe-code/safe-fs/dist/native/fs-seek/linux-x64-glibc.node +0 -0
  242. package/node_modules/@poe-code/safe-fs/dist/native/fs-seek/loader.d.ts +5 -0
  243. package/node_modules/@poe-code/safe-fs/dist/native/fs-seek/loader.mjs +120 -0
  244. package/node_modules/@poe-code/safe-fs/dist/native/fs-seek/manifest.json +46 -0
  245. package/node_modules/@poe-code/safe-fs/dist/node/filesystem.d.ts +19 -0
  246. package/node_modules/@poe-code/safe-fs/dist/node/filesystem.js +30 -0
  247. package/node_modules/@poe-code/safe-fs/dist/node/host.d.ts +3 -0
  248. package/node_modules/@poe-code/safe-fs/dist/node/host.js +78 -0
  249. package/node_modules/@poe-code/safe-fs/dist/node/index.d.ts +3 -0
  250. package/node_modules/@poe-code/safe-fs/dist/node/index.js +2 -0
  251. package/node_modules/@poe-code/safe-fs/dist/node/native-seek-unavailable.d.ts +1 -0
  252. package/node_modules/@poe-code/safe-fs/dist/node/native-seek-unavailable.js +5 -0
  253. package/node_modules/@poe-code/safe-fs/dist/node/native-seek.d.ts +3 -0
  254. package/node_modules/@poe-code/safe-fs/dist/node/native-seek.js +54 -0
  255. package/node_modules/@poe-code/safe-fs/dist/node/stats.d.ts +1 -0
  256. package/node_modules/@poe-code/safe-fs/dist/node/stats.js +1 -0
  257. package/node_modules/@poe-code/safe-fs/dist/node/values.d.ts +1 -0
  258. package/node_modules/@poe-code/safe-fs/dist/node/values.js +1 -0
  259. package/node_modules/@poe-code/safe-fs/dist/node-host.d.ts +1 -0
  260. package/node_modules/@poe-code/safe-fs/dist/node-host.js +1 -0
  261. package/node_modules/@poe-code/safe-fs/dist/node-unavailable.d.ts +1 -0
  262. package/node_modules/@poe-code/safe-fs/dist/node-unavailable.js +1 -0
  263. package/node_modules/@poe-code/safe-fs/dist/package.json +29 -0
  264. package/node_modules/@poe-code/safe-fs/dist/platform/browser-path.d.ts +1 -0
  265. package/node_modules/@poe-code/safe-fs/dist/platform/browser-path.js +1 -0
  266. package/node_modules/@poe-code/safe-fs/dist/platform/browser.d.ts +26 -0
  267. package/node_modules/@poe-code/safe-fs/dist/platform/browser.js +58 -0
  268. package/node_modules/@poe-code/safe-fs/dist/platform/node-path.d.ts +1 -0
  269. package/node_modules/@poe-code/safe-fs/dist/platform/node-path.js +1 -0
  270. package/node_modules/@poe-code/safe-fs/dist/platform/node.d.ts +26 -0
  271. package/node_modules/@poe-code/safe-fs/dist/platform/node.js +64 -0
  272. package/node_modules/@poe-code/safe-fs/dist/platform/transport-budget.d.ts +9 -0
  273. package/node_modules/@poe-code/safe-fs/dist/platform/transport-budget.js +17 -0
  274. package/node_modules/@poe-code/safe-fs/dist/python/emscripten.d.ts +15 -0
  275. package/node_modules/@poe-code/safe-fs/dist/python/emscripten.js +357 -0
  276. package/node_modules/@poe-code/safe-fs/dist/python/filesystem.d.ts +51 -0
  277. package/node_modules/@poe-code/safe-fs/dist/python/filesystem.js +243 -0
  278. package/node_modules/@poe-code/safe-fs/dist/python/flags.d.ts +3 -0
  279. package/node_modules/@poe-code/safe-fs/dist/python/flags.js +14 -0
  280. package/node_modules/@poe-code/safe-fs/dist/python/index.d.ts +9 -0
  281. package/node_modules/@poe-code/safe-fs/dist/python/index.js +5 -0
  282. package/node_modules/@poe-code/safe-fs/dist/python/native.d.ts +18 -0
  283. package/node_modules/@poe-code/safe-fs/dist/python/native.js +446 -0
  284. package/node_modules/@poe-code/safe-fs/dist/python/request.d.ts +2 -0
  285. package/node_modules/@poe-code/safe-fs/dist/python/request.js +61 -0
  286. package/node_modules/@poe-code/safe-fs/dist/python/stat.d.ts +8 -0
  287. package/node_modules/@poe-code/safe-fs/dist/python/stat.js +34 -0
  288. package/node_modules/@poe-code/safe-fs/dist/runtime-core.d.ts +27 -0
  289. package/node_modules/@poe-code/safe-fs/dist/runtime-core.js +57 -0
  290. package/node_modules/@poe-code/safe-fs/dist/testing/atomic-filesystem.d.ts +16 -0
  291. package/node_modules/@poe-code/safe-fs/dist/testing/atomic-filesystem.js +496 -0
  292. package/node_modules/@poe-code/safe-fs/dist/testing/object-io-control.d.ts +23 -0
  293. package/node_modules/@poe-code/safe-fs/dist/testing/object-io-control.js +39 -0
  294. package/node_modules/@poe-code/safe-fs/dist/testing/object-io-metrics.d.ts +31 -0
  295. package/node_modules/@poe-code/safe-fs/dist/testing/object-io-metrics.js +143 -0
  296. package/node_modules/@poe-code/safe-fs/dist/testing/object-publication.d.ts +19 -0
  297. package/node_modules/@poe-code/safe-fs/dist/testing/object-publication.js +209 -0
  298. package/node_modules/@poe-code/safe-fs/dist/xml.d.ts +1 -0
  299. package/node_modules/@poe-code/safe-fs/dist/xml.js +1 -0
  300. package/node_modules/@poe-code/safe-fs/package.json +189 -0
  301. package/node_modules/@poe-code/task-list/README.md +2 -0
  302. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +1 -1
  303. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +11 -4
  304. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +2 -2
  305. package/node_modules/@poe-code/task-list/dist/backends/utils.js +5 -7
  306. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +1 -1
  307. package/node_modules/@poe-code/task-list/dist/default-host.d.ts +2 -0
  308. package/node_modules/@poe-code/task-list/dist/default-host.js +2 -0
  309. package/node_modules/@poe-code/task-list/dist/default-host.workerd.d.ts +4 -0
  310. package/node_modules/@poe-code/task-list/dist/default-host.workerd.js +6 -0
  311. package/node_modules/@poe-code/task-list/dist/filesystem.d.ts +3 -0
  312. package/node_modules/@poe-code/task-list/dist/filesystem.js +32 -0
  313. package/node_modules/@poe-code/task-list/dist/move.js +3 -3
  314. package/node_modules/@poe-code/task-list/dist/open.js +2 -5
  315. package/node_modules/@poe-code/task-list/dist/paths.d.ts +12 -0
  316. package/node_modules/@poe-code/task-list/dist/paths.js +7 -0
  317. package/node_modules/@poe-code/task-list/dist/types.d.ts +6 -5
  318. package/node_modules/@poe-code/task-list/package.json +9 -0
  319. package/node_modules/@poe-code/terminal-text/LICENSE +21 -0
  320. package/node_modules/@poe-code/terminal-text/README.md +12 -0
  321. package/node_modules/@poe-code/terminal-text/dist/index.d.ts +2 -0
  322. package/node_modules/@poe-code/terminal-text/dist/index.js +20 -0
  323. package/node_modules/@poe-code/terminal-text/package.json +22 -0
  324. package/node_modules/@poe-code/xml-ast/LICENSE +21 -0
  325. package/node_modules/@poe-code/xml-ast/README.md +28 -0
  326. package/node_modules/@poe-code/xml-ast/dist/index.d.ts +47 -0
  327. package/node_modules/@poe-code/xml-ast/dist/index.js +635 -0
  328. package/node_modules/@poe-code/xml-ast/package.json +25 -0
  329. package/node_modules/auth-store/README.md +35 -0
  330. package/node_modules/auth-store/dist/index.browser.d.ts +4 -0
  331. package/node_modules/auth-store/dist/index.browser.js +2 -0
  332. package/node_modules/auth-store/dist/provider-store.d.ts +1 -1
  333. package/node_modules/auth-store/dist/safe-fs-secret-store.d.ts +21 -0
  334. package/node_modules/auth-store/dist/safe-fs-secret-store.js +119 -0
  335. package/node_modules/auth-store/dist/secret-store.d.ts +12 -0
  336. package/node_modules/auth-store/dist/secret-store.js +1 -0
  337. package/node_modules/auth-store/dist/transaction-lock.d.ts +2 -4
  338. package/node_modules/auth-store/dist/types.d.ts +2 -9
  339. package/node_modules/auth-store/package.json +16 -2
  340. package/node_modules/tiny-http-mcp-server/dist/composition.json +16 -1
  341. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +25 -0
  342. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/authorization-state.js +4 -4
  343. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +6 -40
  344. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/oauth-form.d.ts +10 -0
  345. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/oauth-form.js +20 -0
  346. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/pkce.js +4 -3
  347. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/register-client.d.ts +14 -0
  348. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/register-client.js +53 -0
  349. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resumable.d.ts +16 -2
  350. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resumable.js +29 -9
  351. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/revoke-token.d.ts +12 -0
  352. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/revoke-token.js +23 -0
  353. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -19
  354. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +1 -1
  355. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.browser.d.ts +6 -2
  356. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.browser.js +5 -1
  357. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +5 -2
  358. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +4 -1
  359. package/node_modules/tiny-http-mcp-server/package.json +4 -4
  360. package/node_modules/tiny-mcp-client/README.md +2 -0
  361. package/node_modules/tiny-mcp-client/dist/index.browser.d.ts +25 -23
  362. package/node_modules/tiny-mcp-client/dist/index.browser.js +116 -1
  363. package/node_modules/tiny-mcp-client/dist/index.d.ts +25 -23
  364. package/node_modules/tiny-mcp-client/dist/index.js +1034 -561
  365. package/node_modules/tiny-mcp-client/package.json +15 -0
  366. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  367. package/node_modules/toolcraft-design/dist/components/table.js +6 -0
  368. package/node_modules/toolcraft-design/dist/escape-terminal-text.d.ts +1 -2
  369. package/node_modules/toolcraft-design/dist/escape-terminal-text.js +1 -20
  370. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +2 -1
  371. package/node_modules/toolcraft-design/dist/terminal-markdown/parser.js +2 -1
  372. package/node_modules/toolcraft-schema/dist/json-schema/utils.js +3 -0
  373. package/node_modules/toolcraft-schema/package.json +1 -1
  374. package/package.json +32 -7
@@ -0,0 +1,411 @@
1
+ # safe-fs
2
+
3
+ An asynchronous filesystem interface for memory, rooted host directories, S3, and WebDAV, with mounts, copy-on-write overlays, and Node-style bridges. Portable filesystem types and POSIX helpers compile without Node types. The `posixPath` export from `@poe-code/safe-fs/core` provides `basename`, `dirname`, `extname`, `join`, `isAbsolute`, `normalize`, `resolve`, `relative`, `sep`, and `delimiter`; `resolve` and `relative` use `/` as their working directory in every runtime.
4
+
5
+ Under `workerd` and `browser` conditions, the root export includes portable filesystems, `S3FileSystem`, and `MockS3Client`. Supply an S3 transport to use object storage without Node globals.
6
+
7
+ ## Quickstart
8
+
9
+ Install the standalone package; it does not depend on SafeJS, Safe Bash, or the `poe-code` CLI. These examples use TypeScript in a Node ESM application.
10
+
11
+ ```sh
12
+ npm install @poe-platform/safe-fs
13
+ ```
14
+
15
+ ```ts
16
+ import { createMemoryFileSystem, createNodeFsBridge } from "@poe-platform/safe-fs";
17
+
18
+ const storage = createMemoryFileSystem();
19
+ await storage.mkdir("/work");
20
+ await storage.writeFile("/work/note.txt", new TextEncoder().encode("hello"));
21
+ console.log(new TextDecoder().decode(await storage.readFile("/work/note.txt")));
22
+
23
+ const fs = createNodeFsBridge(storage, { cwd: "/work" });
24
+ await fs.appendFile("note.txt", " world", "utf8");
25
+ console.log(await fs.readFile("note.txt", "utf8"));
26
+ ```
27
+
28
+ Output: `hello`, then `hello world`. Nothing touches the host filesystem. Raw adapters exchange `Uint8Array` values; the Node bridge adds strings, encodings, `Buffer` results, and stat predicates. Its `cwd` is the relative-path base and, by default, the confinement boundary. Set an explicit `root` to use a different boundary, for example `{ cwd: "/work", root: "/" }` to address a complete provider namespace. The bridge supports `unlink` when the provider offers atomic file removal; unsupported methods fail without host fallback. `createHostFileSystem()` provides trusted, unrestricted native host access when a rooted adapter is not desired.
29
+
30
+ Under the `workerd` export condition, `@poe-platform/safe-fs/fs/real` reads the
31
+ native request-local filesystem, including retained `/tmp` file bytes.
32
+ The restricted profile refuses generic descriptors, permission/timestamp
33
+ changes, exact creation modes, and owned staging; it promises neither atomic
34
+ rename nor inode/version identity from placeholder native metadata. Retained
35
+ reads remain available, with native seek reporting `ENOTSUP`. Ordinary browser
36
+ bundles cannot import this backend. Node keeps its POSIX host profile.
37
+
38
+ For host storage, use `await createRealFileSystem({ root: "/absolute/existing/directory" })` instead. The root must already exist; virtual `/` maps to that directory. ZIP creation and updates can use this adapter's private owned staging in an isolated host tree. Archive extraction requires atomic ancestry verification. MemoryFileSystem supports it, including a writable root composed with read-only mounts such as `/skills/.system`; the real adapter refuses it. Mount extraction delegates confinement to the root backend and keeps other mounts read-only in the extraction view. Extraction roots inside a separate mount are rejected because directory and metadata mutations cannot atomically guard ancestors on another backend. Staged file publication supports nested writable mounts when every ancestor provides authoritative validation. Its `trustedOwnedStaging` capability checks original entries before publication and cleanup, and preserves foreign staging children. It does not advertise atomic conditional mutations: keep external writers and other in-flight writes away from the tree during these operations. Read the safety boundary below before exposing it to untrusted code.
39
+
40
+ To create new outputs with Safe Bash's `dos2unix`, `unix2dos`, or compression commands, host adapters need atomic no-replace publication. Supply `createRealFileSystem({ root, renameNoReplace })` only when that callback binds a qualified native primitive such as Linux `renameat2` with `RENAME_NOREPLACE`. It receives resolved absolute host paths and an optional signal in its third argument. Existing destinations must reject `EEXIST` atomically; existence checks followed by rename and copy/delete are insufficient. Without this binding the adapter explicitly refuses those operations. See the [no-replace contract](src/contracts/filesystem.md#atomic-no-replace-rename).
41
+
42
+ ## Entry points and shared identity
43
+
44
+ | Import | Use |
45
+ | --- | --- |
46
+ | `@poe-platform/safe-fs` | Full Node API; selects the portable core under the `browser` and `workerd` conditions. |
47
+ | `@poe-platform/safe-fs/core` | Memory, mounts, overlays, read-only, WebDAV, portable bridges, types, and errors. Bundle with the `browser` condition for browsers and Workers. |
48
+ | `@poe-platform/safe-fs/node` | Host filesystem, Node bridge, S3, and configuration helpers; unavailable in browsers. |
49
+ | `@poe-platform/safe-fs/fs/*` | Individual adapters: `memory`, `readonly`, `mount`, `overlay`, `webdav`, `real`, `s3`, and `s3/http`. S3, its mock, and the signed `s3/http` transport work in browsers and Workers. |
50
+
51
+ ```ts
52
+ import { createMemoryFileSystem, FsError, type FileSystem } from "@poe-platform/safe-fs/core";
53
+
54
+ const fs: FileSystem = createMemoryFileSystem();
55
+ ```
56
+
57
+ Matching scoped SafeJS and Safe Bash releases use this package's types and runtime.
58
+ Existing `@poe-platform/safe-js/fs`, `/fs/core`, and `/fs/node` imports remain
59
+ re-exports, so `FsError` identity is shared; do not copy its implementation or the
60
+ `FileSystem` declaration. `FileSystem` is an interface, not a runtime class.
61
+
62
+ Custom filesystem views can use `registerEntryView(view, async path => ({ filesystem: backing, path }))`
63
+ from `/core` so `compareEntries` recognizes their backing entries in either direction.
64
+ The resolver can also return `readOnly: true`; each view can be registered once.
65
+
66
+ Legacy `poe-code/safe-fs` imports still use the CLI's bundled runtime. Keep an
67
+ application's factories, shell, and errors in one import family. At a host-owned
68
+ legacy adapter boundary, normalize foreign filesystem failures with `toFsError`
69
+ from the receiving runtime; it retains the code, operation paths, and original
70
+ cause. Preserve cancellation/control exceptions separately. Cross-family
71
+ `instanceof FsError` is not supported without normalization.
72
+
73
+ ## Supported operations
74
+
75
+ | Operation | Interface |
76
+ | --- | --- |
77
+ | Read and write bytes | `readFile`, `writeFile`, `appendFile` |
78
+ | Inspect entries | `stat`, `lstat`, `readdir`, `realpath`, `access` |
79
+ | Change the namespace | `mkdir`, `rm`, `rename`, `copyFile` |
80
+ | Remove an empty directory | Optional `rmdir`; never a recursive-delete fallback |
81
+ | Links and metadata | Optional `readlink`, `symlink`, `link`, `chmod`, `utimes`, `truncate` |
82
+ | Stream bytes | Optional `readStream`, `writeStream`, using async iterables of byte chunks |
83
+ | Retain an open file | Optional `open` with positioned I/O and synchronization; memory and real backends support `noFollow: true` to atomically refuse a final symlink |
84
+ | Publish immutable objects atomically | Optional `publishFileConditional` with opaque identity/version stats and authoritative compare-and-publish |
85
+ | Compare backing entries | Optional `compareEntry`, returning `same`, `distinct`, or `unknown` |
86
+
87
+ All required methods must exist, but a backend may reject an operation with `FsError`, such as `EROFS` for a write or `ENOTSUP` for unsupported semantics. Check optional methods and `capabilities` rather than assuming every backend behaves like a local disk. Errors expose `code` and may include `syscall`, `path`, `dest`, and `cause`.
88
+
89
+ `readBytes` supports frozen host abort signals. The exported
90
+ `listenForAbort(signal, listener)` returns a cleanup function for custom
91
+ operations; if frozen host bookkeeping prevents registration, callers must
92
+ check cancellation at operation boundaries, as `readBytes` does.
93
+
94
+ `createDeviceFileSystem(fs)` adds portable `/dev/null` whole-file, stream, and descriptor I/O. Descriptor reads return EOF and writes discard bytes; stat remains a zero-size character device and descriptor position remains zero. Truncating opens are accepted, exclusive creation fails with `EEXIST`, and descriptor resizing and synchronization are unsupported. Access modes, cancellation, and closed handles use the normal descriptor checks. With an authoritative object store, use `createDeviceFileSystem(withObjectFileDescriptors(fs, store))` so null-device I/O never acquires or publishes an object version; ordinary files retain conditional publication. The device wrapper must be outermost for this composition.
95
+
96
+ `PythonStatTranslator` and `createPythonRuntimeStatMapper` allocate stable guest device
97
+ identities without a default device-count budget. Their optional `maxDevices` accepts
98
+ `Infinity` or a positive safe integer. Explicit budgets remain enforced; device numbers
99
+ must still fit the disjoint application/runtime ranges of the uint32 Python stat ABI.
100
+
101
+ `withObjectFileDescriptors(fs, store)` supports large shell and Python descriptor
102
+ writes when the host supplies `store.createStaging`: private externally backed
103
+ pages keep working memory bounded without publishing the growing file after
104
+ every write. Conditional publication still occurs at sync/close, and retained
105
+ readers keep their old versions. Without that optional backend primitive, the
106
+ optional dirty-page budget limits unflushed output only when configured. Descriptor
107
+ resource limits accept `Infinity` and are unlimited when omitted; `chunkBytes`
108
+ remains a positive finite integer. See the
109
+ [object descriptor and spill contract](src/contracts/object-publication.md) for
110
+ backend methods, failure semantics and qualification; no provider storage is
111
+ configured automatically.
112
+
113
+ Immutable flat stores can supply `identityScope`, `opaqueIdentity` and an ABA-safe
114
+ `opaqueVersion` on file stats, then expose `atomicFilePublication: true` with
115
+ `publishFileConditional(path, source, { expected, parent, maxBytes, signal })`.
116
+ The host consumes bytes privately and atomically creates an absent binding
117
+ (`expected: null`) or replaces the observed generation. Failed producers,
118
+ uploads and stale writers leave the old file intact. This supports Safe Bash ZIP
119
+ creation and updates without inode numbers, staging directories or permission
120
+ APIs. Read the [conditional publication contract](src/contracts/conditional-publication.md)
121
+ before implementing the host operation; ordinary writes do not provide it.
122
+
123
+ For temporary staging that must be cleaned up after its parent moves, adapters
124
+ advertising `retainedStagingCleanup` accept `createStagedFile(..., { parent,
125
+ retainCleanup: true })`. Use the returned `staging.cleanup.remove()` and always
126
+ call `staging.cleanup.close()` in `finally`. Memory and its supported wrappers
127
+ retain only the owned staging entries; replacement entries remain protected.
128
+ Overlays backed by stock Memory filesystems support conditional file mutations,
129
+ directory preparation, and confined staging. Publication may omit `ancestors`
130
+ when the destination shares the staging parent; the overlay checks the ancestry
131
+ retained before staging began.
132
+ See the [staging contract](src/contracts/filesystem.md#atomic-owned-staging).
133
+
134
+ Capture `retainFileSystemCleanup(scoped, callback, { maxOperations })` before
135
+ cancellation to clean up through a bounded view after ordinary scoped operations
136
+ close. Cleanup preserves its owning scope and limits across separately bundled
137
+ command runtimes, including restrictions added by filesystem wrappers.
138
+ Omit `maxOperations` or pass `Infinity` for unlimited cleanup operations.
139
+
140
+ | Backend or wrapper | Use it for |
141
+ | --- | --- |
142
+ | `createMemoryFileSystem()` | Isolated, nonpersistent storage with links, permissions, timestamps, and streams; each path resolution admits at most 65,536 cumulative UTF-16 code units across the input and followed symlink targets, rejecting excess with `ENAMETOOLONG` before component allocation |
143
+ | `createRealFileSystem({ root })` | An existing host directory, with virtual paths rooted inside it; Node or qualified Workerd operations |
144
+ | `new S3FileSystem({ transport, bucket, … })` | Bucket/prefix storage through an explicitly supplied transport in Node, browsers, and Workers |
145
+ | `new WebDavFileSystem({ baseUrl, fetch, … })` | A WebDAV namespace through an explicitly supplied Fetch implementation |
146
+ | `createReadOnlyFileSystem(filesystem)` | Rejecting writes through one view of an existing filesystem |
147
+ | `createMountFileSystem({ root, mounts })` | Routing absolute virtual mount paths to different filesystems |
148
+ | `createOverlayFileSystem({ upper, lower })` | Reading through to a lower layer and writing changes to an upper layer |
149
+ | `withFileSystemQuota(filesystem, { maxBytes })` | Enforcing a cumulative logical-byte ceiling across every write path |
150
+
151
+ `createNodeFsBridge` offers a promises-shaped subset, including recursive `cp` and `mkdtemp`. A trusted `reserveReadFile` callback can reserve host resources and return a release function; cancellation retains the reservation until the backend read settles. The portable `createFsBridge` from `@poe-platform/safe-fs/bridge` instead requires a caller-supplied text codec and returns `Uint8Array` values.
152
+
153
+ ## Write an adapter
154
+
155
+ A backend implements the exported [`FileSystem` interface](src/contracts/filesystem.ts). It needs every non-optional method, a truthful `capabilities` object, byte-safe reads/writes, and appropriate errors. A configuration adapter adds `validateOptions` and a `create` factory returning that filesystem, synchronously or asynchronously.
156
+
157
+ This hypothetical `catalog` adapter presents a snapshot of named text documents as read-only files at `/`. It implements the full required interface without a host filesystem or network connection. Only the root directory and single-component filenames are supported; timestamps are synthetic and optional operations are absent.
158
+
159
+ ```ts
160
+ import {
161
+ ACCESS_MODES, FsError, createFileSystem,
162
+ createNodeFileSystemAdapterRegistry, readConfigRecord, validatePath,
163
+ type FileStat, type FileSystem, type FileSystemAdapterDescriptor,
164
+ type FsOptions,
165
+ } from "@poe-platform/safe-fs";
166
+
167
+ const catalogAdapter = {
168
+ validateOptions(options) {
169
+ const config = readConfigRecord(options, "catalog option", ["files"]);
170
+ const files = readConfigRecord(config.files, "catalog files");
171
+ for (const [name, text] of Object.entries(files)) {
172
+ if (!name || name === "." || name === ".." || name.includes("/")
173
+ || name.includes("\0") || typeof text !== "string") {
174
+ throw new TypeError("files must map single-component names to strings");
175
+ }
176
+ }
177
+ },
178
+ create(options): FileSystem {
179
+ const files = new Map(Object.entries(readConfigRecord(options.files, "catalog files"))
180
+ .map(([name, text]) => [name, new TextEncoder().encode(text as string)]));
181
+
182
+ function lookup(path: string, options?: FsOptions) {
183
+ if (options?.signal?.aborted) throw new FsError("ECANCELED", { path });
184
+ validatePath(path);
185
+ let name = "";
186
+ let bytes: Uint8Array | undefined;
187
+ for (const part of path.split("/")) {
188
+ if (bytes !== undefined) throw new FsError("ENOTDIR", { path });
189
+ if (part === "" || part === "." || part === "..") continue;
190
+ bytes = files.get(part);
191
+ if (bytes === undefined) throw new FsError("ENOENT", { path });
192
+ name = part;
193
+ }
194
+ return { path: `/${name}`, bytes };
195
+ }
196
+
197
+ async function inspect(path: string, options?: FsOptions): Promise<FileStat> {
198
+ const { bytes } = lookup(path, options);
199
+ return {
200
+ type: bytes === undefined ? "directory" : "file",
201
+ size: bytes?.byteLength ?? 0,
202
+ mode: bytes === undefined ? 0o40555 : 0o100444,
203
+ atimeMs: 0, mtimeMs: 0, ctimeMs: 0,
204
+ };
205
+ }
206
+
207
+ async function denyWrite(): Promise<never> {
208
+ throw new FsError("EROFS");
209
+ }
210
+
211
+ return {
212
+ capabilities: { readOnly: true },
213
+ async readFile(path, options) {
214
+ const { bytes } = lookup(path, options);
215
+ if (bytes === undefined) throw new FsError("EISDIR", { path });
216
+ const limit = options?.maxBytes ?? Number.MAX_SAFE_INTEGER;
217
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new FsError("EINVAL", { path });
218
+ if (bytes.byteLength > limit) throw new FsError("EFBIG", { path });
219
+ return bytes.slice();
220
+ },
221
+ stat: inspect,
222
+ lstat: inspect,
223
+ async readdir(path, options) {
224
+ if (lookup(path, options).bytes !== undefined) throw new FsError("ENOTDIR", { path });
225
+ return [...files.keys()].sort().map(name => ({ name, type: "file" as const }));
226
+ },
227
+ async realpath(path, options) {
228
+ const entry = lookup(path, options);
229
+ return entry.path;
230
+ },
231
+ async access(path, mode = ACCESS_MODES.F_OK, options) {
232
+ const entry = lookup(path, options);
233
+ if (!Number.isInteger(mode) || mode < 0 || mode > 7) throw new FsError("EINVAL", { path });
234
+ if (mode & ACCESS_MODES.W_OK) throw new FsError("EROFS", { path });
235
+ if ((mode & ACCESS_MODES.X_OK) && entry.bytes !== undefined) {
236
+ throw new FsError("EACCES", { path });
237
+ }
238
+ },
239
+ writeFile: denyWrite, appendFile: denyWrite, mkdir: denyWrite,
240
+ rm: denyWrite, rename: denyWrite, copyFile: denyWrite,
241
+ };
242
+ },
243
+ } satisfies FileSystemAdapterDescriptor;
244
+
245
+ const registry = createNodeFileSystemAdapterRegistry(new Map([["catalog", catalogAdapter]]));
246
+ const catalog = await createFileSystem({
247
+ type: "catalog",
248
+ options: { files: { "welcome.txt": "Hello from the catalog" } },
249
+ }, { registry });
250
+
251
+ console.log(new TextDecoder().decode(await catalog.readFile("/welcome.txt")));
252
+ ```
253
+
254
+ Output: `Hello from the catalog`. `readFile` returns a copy so callers cannot mutate the stored snapshot. Reads check cancellation and `maxBytes`; required mutations always reject with `EROFS`. `stat` and `lstat` share an implementation because this adapter has no symlinks.
255
+
256
+ `createFileSystem` validates options before calling the factory. For Workers, import `createPortableFileSystemAdapterRegistry` and `createFileSystem` from `@poe-code/safe-fs/core`; the portable registry includes `memory` and caller-supplied adapters, without ambient filesystem access. The Node registry includes `memory` and `real`, accepts new names, and rejects attempts to replace those defaults. For an explicit allowlist without either default, pass `new Map([["catalog", catalogAdapter]])` directly as `registry`. Names select registered factories; they do not load modules from config.
257
+
258
+ For a mutable backend, preserve exclusive-create behavior, return independent byte buffers, handle cancellation and size limits, and declare only capabilities you actually provide. Use `FsError` for filesystem failures. Do not invent entry identity from matching paths or metadata; omit `compareEntry` when identity is unknown.
259
+
260
+ ## Options
261
+
262
+ There are no package environment variables, implicit credentials, or automatic `.env` loading. Applications supply configuration, transports, and credentials explicitly. Factories accept the same options as their corresponding classes.
263
+
264
+ ### Local storage, composition, and bridges
265
+
266
+ | API | Options and defaults |
267
+ | --- | --- |
268
+ | `createFileSystem(config, { registry })` | Required `config.type`; `config.options` defaults to an empty record. `registry` is required. Built-in `memory` accepts optional file, retained-byte, metadata, total-byte, `maxPathBytes` and `maxPathComponents` quotas (omitted, `undefined`, or `Infinity` disables each quota); built-in `real` requires `root`. |
269
+ | `createNodeFileSystemAdapterRegistry(extensions?)` | Optional map of additional adapter descriptors; defaults to only `memory` and `real`. |
270
+ | Memory / read-only | Memory accepts independent optional `maxFileBytes`, `maxRetainedBytes`, `maxMetadataUnits` and `maxBytes` quotas, all unlimited by default. Read-only takes the backing filesystem, without an options object. |
271
+ | Real | Required `root`: existing absolute host directory; the constructor/factory also accepts the root string directly. |
272
+ | Mount | Required `root`: fallback filesystem. `mounts` defaults to `{}` and maps absolute virtual paths to filesystems. |
273
+ | Overlay | Required `upper` and `lower`; `maxBufferBytes` is unlimited unless configured; it and per-read `maxBytes` accept explicit `Infinity`. |
274
+ | Quota | `withFileSystemQuota` requires a nonnegative safe-integer `maxBytes`. It serializes mutations and counts files, symlinks, copies, hard links, truncation, and streaming writes. |
275
+ | Node bridge | `cwd` defaults to `/`, must be an absolute virtual path; optional lifetime `signal` and trusted `readFileMaxBytes` cap forwarded to backend reads before copy/decode. |
276
+ | Portable bridge | Same `cwd`, `signal` and `readFileMaxBytes`, plus required `codec` with `isEncoding`, `encode`, and `decode` functions. |
277
+ | Catalog example | Required `files`: a plain record of single-component filenames to text strings; no other options. |
278
+
279
+ ### Per-operation options
280
+
281
+ Every raw filesystem operation accepts an optional `signal`. Additional fields are:
282
+
283
+ | Operation | Options |
284
+ | --- | --- |
285
+ | `readFile` | `maxBytes`: nonnegative safe-integer byte cap; `Infinity` adds no per-read cap |
286
+ | `readdir` | `maxEntries`: nonnegative safe-integer entry cap; omission or `Infinity` preserves backend limits and ordering |
287
+ | `writeFile`, `writeStream` | `flag`: `w` (default, replace), `wx` (exclusive create), `a` (append), or `ax` (exclusive append/create); optional `mode` |
288
+ | `appendFile` | Optional `mode` |
289
+ | `mkdir` | `recursive` (default false); optional `mode` |
290
+ | `rm` | `recursive` and `force` (default false) |
291
+ | `copyFile` | `exclusive` (default false) |
292
+ | `readStream` | `start` (default 0), `endExclusive` (default end of file), `chunkSize` (built-in default 64 KiB) |
293
+
294
+ `validatePath(path, { maxPathBytes, maxPathComponents })` accepts optional UTF-8 byte and component quotas; both default to `Infinity`. Memory accepts the same constructor options and counts cumulative symlink expansion. Device operations and WebDAV stat, access, and writes accept these quotas under `pathLimits`. WebDAV accepts `maxTimestampPropertyBytes` for timestamp metadata, also unlimited by default.
295
+
296
+ `access` takes a separate mode bitmask from `ACCESS_MODES`. `chmod` takes a mode, `utimes` takes millisecond timestamps, and `truncate` takes a byte length (default 0). For conditional chmod, check `capabilitiesFor(path, { conditionalChmod: true })` (or `capabilities`) and require `conditionalChmod: true`; supply `parent`, `expected`, and complete root-to-parent `ancestors` together. An optional mutation-free `commitGuard` must return literal `true` synchronously. Memory validates at its metadata commit; mount and supported Memory overlays preserve wrapper ancestry. Real uses its existing externally isolated host-tree boundary and does not prevent races with other processes. Backend limits still apply. Node-shaped bridge methods translate their own options rather than accepting these raw option objects; see the [bridge signatures](src/bridge/filesystem.ts).
297
+
298
+ `collectBytes(source, { maxBytes, maxMemoryBytes, signal })` snapshots streamed chunks into one growing buffer. `maxBytes` and `maxMemoryBytes` accept explicit `Infinity`. `maxMemoryBytes` limits owned capacity, the current input's full backing buffer, and overlapping allocations during growth; exhaustion throws `EFBIG`. Collection limits are disabled by default on Node, browser, and Worker targets. Each call owns its budget; concurrent collections do not consume one another’s allowance. The returned view may retain geometric spare capacity. Configured budgets cover collection, not caller-retained results, transport buffering, archive decoding, strings, or the rest of the runtime; use streaming APIs and limit concurrent workloads for larger inputs.
299
+
300
+ <details>
301
+ <summary>S3 filesystem and HTTP transport options</summary>
302
+
303
+ `S3FileSystem` requires `transport` and `bucket`. A transport supplies `headObject`, `getObject`, `putObject`, `deleteObject`, `copyObject`, and `listObjectsV2`; see the [transport interface](src/fs/s3/transport.ts).
304
+
305
+ `createS3NamespaceFileSystem` resource limits (`maxBytes`, `maxEntries`,
306
+ `maxManifestBytes`, `maxAttempts`, and inherited descriptor limits) are unlimited
307
+ when omitted or set to `Infinity`; positive safe integers enable finite limits.
308
+ Namespace paths have no implicit length cap; configured manifest budgets still apply.
309
+
310
+ `createS3Transport(client, capabilities?)` wraps a compatible client with explicit `streamingRead`, `streamingWrite`, `conditionalPut`, `conditionalCopy`, and `conditionalDelete` flags (all absent by default). Direct transport requests accept an optional `abortSignal`.
311
+
312
+ For in-memory S3 simulations, `new MockS3Client({ buckets, pageSize?, now?, authorize? })` requires bucket names. `pageSize` defaults to 1,000 (range 1–1,000), `now` defaults to the current date/time, and optional `authorize(request)` can reject requests. It does not verify a real provider.
313
+
314
+ | Optional field | Default / meaning |
315
+ | --- | --- |
316
+ | `prefix` | Empty; canonical relative object-key prefix |
317
+ | `readOnly` | `false` |
318
+ | `allowNonAtomicRename` | `true`; rename copies then deletes, without rollback guarantees |
319
+ | `pageSize` | 1,000; range 1–1,000 |
320
+ | `maxReadBytes` | Unlimited unless configured |
321
+ | `maxStreamBytes` | Unlimited unless configured |
322
+ | `maxListEntries` | Unlimited unless configured |
323
+ | `removalLimits.maxRequests` | Unlimited unless configured; transport calls per `rm`, including lookup, listing, and deletes |
324
+ | `removalLimits.maxListEntries` | Unlimited unless configured; returned listing entries in aggregate per `rm`, including lookup |
325
+ | `removalLimits.maxDeleteObjects` | Unlimited unless configured; objects per `rm` |
326
+ | `compareEntry` | Optional trusted backing-identity callback |
327
+
328
+ Removal limits apply even when shell filesystem-call limits admit a recursive `rm` as one operation. Removal limits default to unlimited and accept a positive safe integer or `Infinity`. Read, stream, listing, and request limits also accept explicit `Infinity`. Traversal stops at the listing/request cap and rejects with `EFBIG`; all delete requests must fit the remaining request budget before the first mutation. Nonrecursive removal checks for children using pages of at most two entries. Configure finite `removalLimits` to bound the work in deployments that need them. These limits count adapter transport calls; retries inside a supplied transport need their own limit. Remote failures or concurrent writers can still cause partial deletion after preflight.
329
+
330
+ For larger trees, a trusted integration can process one bounded batch per request/job using its explicitly supplied transport. This example uses at most 17 transport calls and retains at most 16 summaries; repeat in a later job until `done`. The prefix must come from trusted deployment configuration, include the filesystem's configured prefix, and end in `/`. This deliberately bypasses filesystem collision checks and deletes directory markers as well as files; serialize it with writers when complete removal is required.
331
+
332
+ ```ts
333
+ async function removeBatch(transport, bucket, trustedDirectoryPrefix, abortSignal) {
334
+ const options = { abortSignal };
335
+ const page = await transport.listObjectsV2({
336
+ Bucket: bucket, Prefix: trustedDirectoryPrefix, MaxKeys: 16,
337
+ }, options);
338
+ const objects = page.Contents ?? [];
339
+ if (objects.length > 16 || page.CommonPrefixes?.length
340
+ || objects.some(object => !object.Key?.startsWith(trustedDirectoryPrefix))
341
+ || typeof page.IsTruncated !== "boolean") {
342
+ throw new Error("Invalid batch listing");
343
+ }
344
+ for (const object of objects) {
345
+ await transport.deleteObject({ Bucket: bucket, Key: object.Key }, options);
346
+ }
347
+ return { deleted: objects.length, done: !page.IsTruncated };
348
+ }
349
+ ```
350
+
351
+ Each batch lists from the beginning because previous keys have been deleted; it does not reuse continuation tokens across mutations. A job runner should cap the number of batches and schedule remaining work separately.
352
+
353
+ `createS3HttpTransport` requires `endpoint` (an origin without path or credentials), `region`, and `credentials`. Credentials contain `accessKeyId`, `secretAccessKey`, and optional `sessionToken`, or come from an async provider receiving `{ signal }`.
354
+
355
+ | Optional field | Default / meaning |
356
+ | --- | --- |
357
+ | `addressingStyle` | `path`; alternative `virtual-hosted` requires a DNS endpoint |
358
+ | `listUrlEncoding` | `percent`; alternative `form` |
359
+ | `allowInsecureHttp` | `false`; HTTPS required unless explicitly enabled |
360
+ | `maxPutBytes`, `maxGetBytes` | Unlimited when omitted or `Infinity`; positive safe integers set byte caps |
361
+ | `maxXmlBytes` | Unlimited when omitted or `Infinity`; positive safe integers set a byte cap |
362
+ | `requestTimeoutMs` | Unlimited unless configured |
363
+ | `enableCopy` | `true`; disabling uses a buffered GET/PUT fallback |
364
+ | `verifiedConditionalOperations` | Optional `put`, `copy`, `delete` booleans, each defaulting to false; enable only after verifying the server's semantics |
365
+ | `clock` | Current date/time function, used for signing |
366
+ | `request` | Optional Node HTTP request factory; Node defaults to HTTP(S), browsers and Workers use global fetch |
367
+
368
+ </details>
369
+
370
+ <details>
371
+ <summary>WebDAV options</summary>
372
+
373
+ `WebDavFileSystem` requires `baseUrl` and `fetch`.
374
+
375
+ | Optional field | Default / meaning |
376
+ | --- | --- |
377
+ | `headers` | Empty; explicit authentication/custom headers. Protocol-reserved headers are rejected; authorization and cookies require HTTPS. |
378
+ | `requestStreamSupport` | `native` for global Fetch, otherwise false; accepts `native` or a boolean declaration for the injected transport |
379
+ | `maxResponseBytes` | Unlimited unless configured; applies to decoded response bytes |
380
+ | `maxXmlBytes` | Unlimited unless configured; applies before metadata decoding/parsing |
381
+ | `xmlLimits` | Optional `maxNodes`, `maxContentNodes`, `maxDepth`, and `maxAttributes`; each accepts a positive safe integer or `Infinity`, and is unlimited unless configured |
382
+ | `maxEntries` | Unlimited unless configured |
383
+ | `timeoutMs` | Unlimited unless configured |
384
+ | `overwritePolicy` | `lock`; alternative `etag` uses conditional overwrites |
385
+ | `atomicEmptyDirectory` | Optional trusted binding with the canonical `namespaceUrl` and `removeEmptyDirectory` callback; required for strict empty-only `rmdir` |
386
+ | `compareEntry` | Optional trusted backing-identity callback on Node; unavailable under browser policy |
387
+
388
+ Known identity Content-Length responses use one result buffer; other responses grow storage up to the configured ceiling and return a view without a final copy. Growth can temporarily retain the old and new buffers (up to three times the response size), plus transport chunks. Hosts can configure per-response limits to budget for metadata parsing, text decoding, transport buffers, and concurrent reads. Use streaming reads for large files.
389
+
390
+ WebDAV `maxEntries`, `maxResponseBytes`, `maxXmlBytes`, and per-read `maxBytes` accept explicit `Infinity`. WebDAV metadata structure limits are disabled by default; configure `xmlLimits` independently of `maxEntries`. Text is bounded by `maxXmlBytes`. Exceeding a structural budget reports `EFBIG`; parsing yields cooperatively so caller cancellation and `timeoutMs` remain active.
391
+
392
+ See the [binding types](src/fs/webdav/webdav.ts) before implementing atomic directory removal. A recursive WebDAV DELETE does not satisfy that contract.
393
+
394
+ </details>
395
+
396
+ ## Safety boundary and limitations
397
+
398
+ - **Not an OS sandbox.** Rooted storage and bridge confinement check paths and symlinks, but checks and host operations can race concurrent changes. Use a separate OS isolation boundary for hostile workloads. A custom adapter or transport is trusted code with its own host authority.
399
+ - **A read-only view is not an immutable store.** Mount views serialize guest symlink, rename, and removal operations against active path operations. Finish or close read streams before awaiting these namespace changes. Other references can still change the backing filesystem; this coordination applies only to operations through the same mount view. Overlay reads require retained handles and stable object and ancestor identities; unsupported backends or changed read admission fail with `ENOTSUP`. Overlays with two stock MemoryFileSystem layers support race-safe patch publication, conditional file writes/removal, no-replace rename, and confined directory creation/removal and file writes. Compression file output, `mv -n`, `sed -i`, and ssconvert work with these layers. Upper-file writes retain file identity; lower-file writes copy up without changing lower bytes. Publication checks both layers, retains logical directory identity across copy-up, and leaves lower bytes intact; wrapped or customized layers remain unsupported. Overlays are not transactions; cancellation and cleanup do not guarantee rollback. Cross-mount rename can fail with `EXDEV`.
400
+ - **Remote storage is not a POSIX disk.** S3 and WebDAV do not provide hardlinks or symlinks. S3 rename is non-atomic and may leave partial copies/deletions; `S3RenameError` reports the phase and affected keys. Strong empty-only removal and conditional writes depend on backend support, not a prior listing.
401
+ - **The bridges are partial Node compatibility layers.** No synchronous/callback API, file handles, watchers, bigint stats, flush/retry support, or `cp` dereference/timestamp-preservation options. Missing optional operations fail rather than being approximated with destructive alternatives.
402
+ - **Browser support is filesystem-only.** The `browser` and `workerd` export conditions select the portable surface: memory, mounts, overlays, read-only, WebDAV, and the codec-based bridge. S3 is available through `fs/s3` with an explicit transport, including its portable mock and `fs/s3/http`. The HTTP transport uses fetch in browsers and Workers and requires server CORS support in browsers. Fetch rejects keys containing dot segments that the URL parser would normalize, and never follows redirects. Node request factories are only supported in Node. Real storage, the Node bridge, and the configuration registry are not browser exports. WebDAV still needs server CORS support; no OPFS or directory-handle adapter is included. This does not make the SafeJS runtime browser-compatible.
403
+ - **Portable temporary names require secure randomness.** Browser overlay staging and portable bridge `mkdtemp` require `crypto.randomUUID` or `crypto.getRandomValues`; without either, they fail with `ENOTSUP` rather than use `Math.random`.
404
+ - **Allocation and identity may be unknown.** Optional `FileStat.allocatedBytes` is provider-reported allocation, not logical length or reclaimable space. Do not infer identity from size, timestamps, or inode numbers across unrelated backends.
405
+
406
+ `MemoryFileSystem.confineExtraction(roots)` returns a view for archive extraction.
407
+ It retains each root directory and its ancestors, refuses symlink ancestry at
408
+ mutation commit, and keeps streamed writes attached to the opened file node.
409
+ Other adapters omit this operation unless they can enforce the same boundary.
410
+ Trusted host decorators must explicitly preserve confinement when forwarding it;
411
+ custom mutation hooks must preserve the atomic commit contract.
@@ -0,0 +1,3 @@
1
+ import type { FileSystem, FsOptions } from "../contracts/filesystem.js";
2
+ export declare function assertBridgePath(root: string, path: string): void;
3
+ export declare function checkedBridgePath(fs: FileSystem, root: string, path: string, options: FsOptions, followFinal: boolean): Promise<string>;
@@ -0,0 +1,171 @@
1
+ import { FsError, isFsError } from "../contracts/errors.js";
2
+ import { dirname } from "../contracts/virtual-path.js";
3
+ import { checkSignal } from "./values.js";
4
+ function denied(path) {
5
+ throw new FsError("EACCES", { syscall: "resolve", path, message: "path escapes the bridge cwd" });
6
+ }
7
+ function relativeComponents(root, path) {
8
+ if (typeof path !== "string" || !path.startsWith("/") || path.includes("\0"))
9
+ denied(path);
10
+ const boundary = root.split("/").filter(Boolean);
11
+ const components = path.split("/").filter(Boolean);
12
+ let index = 0;
13
+ for (const component of boundary) {
14
+ while (components[index] === ".")
15
+ index++;
16
+ if (components[index] !== component)
17
+ denied(path);
18
+ index++;
19
+ }
20
+ const relative = components.slice(index);
21
+ let depth = 0;
22
+ for (const component of relative) {
23
+ if (component === ".")
24
+ continue;
25
+ if (component === "..") {
26
+ if (depth === 0)
27
+ denied(path);
28
+ depth--;
29
+ }
30
+ else
31
+ depth++;
32
+ }
33
+ if (path.endsWith("/"))
34
+ relative.push(".");
35
+ return relative;
36
+ }
37
+ export function assertBridgePath(root, path) {
38
+ if (root !== "/")
39
+ relativeComponents(root, path);
40
+ }
41
+ async function assertCanonicalPath(fs, root, path, options) {
42
+ checkSignal(options.signal);
43
+ const canonical = await fs.realpath(path, options);
44
+ checkSignal(options.signal);
45
+ assertBridgePath(root, canonical);
46
+ if (canonical !== path)
47
+ denied(path);
48
+ }
49
+ async function assertCanonicalOperand(fs, root, path, expected, options) {
50
+ let operand = path;
51
+ let target = expected;
52
+ while (true) {
53
+ checkSignal(options.signal);
54
+ try {
55
+ const canonical = await fs.realpath(operand, options);
56
+ checkSignal(options.signal);
57
+ assertBridgePath(root, canonical);
58
+ if (canonical !== target)
59
+ denied(path);
60
+ return;
61
+ }
62
+ catch (error) {
63
+ checkSignal(options.signal);
64
+ if (!isFsError(error) || error.code !== "ENOENT")
65
+ throw error;
66
+ let missing = false;
67
+ try {
68
+ await fs.lstat(operand, options);
69
+ }
70
+ catch (inspectionError) {
71
+ checkSignal(options.signal);
72
+ if (!isFsError(inspectionError) || inspectionError.code !== "ENOENT")
73
+ throw inspectionError;
74
+ missing = true;
75
+ }
76
+ checkSignal(options.signal);
77
+ if (!missing || operand === root)
78
+ denied(path);
79
+ operand = dirname(operand);
80
+ target = dirname(target);
81
+ assertBridgePath(root, operand);
82
+ assertBridgePath(root, target);
83
+ }
84
+ }
85
+ }
86
+ export async function checkedBridgePath(fs, root, path, options, followFinal) {
87
+ if (root === "/")
88
+ return path;
89
+ let pending = relativeComponents(root, path);
90
+ let current = "";
91
+ for (const component of root.split("/").filter(Boolean)) {
92
+ current += `/${component}`;
93
+ checkSignal(options.signal);
94
+ try {
95
+ const stat = await fs.lstat(current, options);
96
+ checkSignal(options.signal);
97
+ if (stat.type === "symlink")
98
+ denied(current);
99
+ if (stat.type !== "directory")
100
+ throw new FsError("ENOTDIR", { syscall: "resolve", path: current });
101
+ }
102
+ catch (error) {
103
+ checkSignal(options.signal);
104
+ if (!isFsError(error) || error.code !== "ENOENT")
105
+ throw error;
106
+ if (current !== root)
107
+ denied(path);
108
+ if (pending.includes(".."))
109
+ throw error;
110
+ const parent = dirname(root);
111
+ await assertCanonicalPath(fs, parent, parent, options);
112
+ return path;
113
+ }
114
+ }
115
+ await assertCanonicalPath(fs, root, root, options);
116
+ let links = 0;
117
+ let finalLink = false;
118
+ let index = 0;
119
+ while (index < pending.length) {
120
+ const component = pending[index++];
121
+ checkSignal(options.signal);
122
+ if (component === ".")
123
+ continue;
124
+ if (component === "..") {
125
+ if (current === root)
126
+ denied(path);
127
+ current = dirname(current);
128
+ continue;
129
+ }
130
+ const candidate = `${current}/${component}`;
131
+ let stat;
132
+ try {
133
+ stat = await fs.lstat(candidate, options);
134
+ }
135
+ catch (error) {
136
+ checkSignal(options.signal);
137
+ if (!isFsError(error) || error.code !== "ENOENT")
138
+ throw error;
139
+ if (pending.slice(index).includes(".."))
140
+ throw error;
141
+ await assertCanonicalPath(fs, root, current, options);
142
+ const expected = [candidate, ...pending.slice(index)].join("/");
143
+ await assertCanonicalOperand(fs, root, path, expected, options);
144
+ return path;
145
+ }
146
+ checkSignal(options.signal);
147
+ if (stat.type === "symlink" && (followFinal || index < pending.length)) {
148
+ if (++links > 40)
149
+ throw new FsError("ELOOP", { syscall: "resolve", path });
150
+ if (typeof fs.readlink !== "function")
151
+ throw new FsError("ENOTSUP", { syscall: "readlink", path: candidate });
152
+ const target = await fs.readlink(candidate, options);
153
+ checkSignal(options.signal);
154
+ if (typeof target !== "string" || target.length === 0 || target.includes("\0"))
155
+ denied(candidate);
156
+ const absolute = target.startsWith("/") ? target : `${current}/${target}`;
157
+ pending = [...relativeComponents(root, absolute), ...pending.slice(index)];
158
+ index = 0;
159
+ current = root;
160
+ continue;
161
+ }
162
+ if (index < pending.length && stat.type !== "directory") {
163
+ throw new FsError("ENOTDIR", { syscall: "resolve", path: candidate });
164
+ }
165
+ current = candidate;
166
+ finalLink = stat.type === "symlink";
167
+ }
168
+ await assertCanonicalPath(fs, root, finalLink ? dirname(current) : current, options);
169
+ await assertCanonicalOperand(fs, root, finalLink ? dirname(path) : path, finalLink ? dirname(current) : current, options);
170
+ return path;
171
+ }