@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,970 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { constants, fstatSync, linkSync, lstatSync, readSync, renameSync, type BigIntStats, type Stats } from "node:fs";
3
+ import {
4
+ link,
5
+ lstat,
6
+ mkdir,
7
+ open,
8
+ realpath,
9
+ rename,
10
+ unlink,
11
+ type FileHandle,
12
+ } from "node:fs/promises";
13
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
+
15
+ import {
16
+ AudioAlignmentAnalysisV1Schema,
17
+ EditPlanV1Schema,
18
+ FaceAnalysisV1Schema,
19
+ MusicAnalysisV1Schema,
20
+ ProjectInactivityAnalysisV1Schema,
21
+ ProjectEditPlanV1Schema,
22
+ RecordingManifestV1Schema,
23
+ RepositoryRelativePathSchema,
24
+ SceneAnalysisV1Schema,
25
+ Sha256Schema,
26
+ SpeechAnalysisV1Schema,
27
+ VideoProjectV1Schema,
28
+ type AudioAlignmentAnalysisV1,
29
+ type EditPlanV1,
30
+ type FaceAnalysisV1,
31
+ type MusicAnalysisV1,
32
+ type ProjectInactivityAnalysisV1,
33
+ type ProjectEditPlanV1,
34
+ type RecordingManifestV1,
35
+ type SceneAnalysisV1,
36
+ type SpeechAnalysisV1,
37
+ type VideoProjectV1,
38
+ } from "../contracts";
39
+ import { canonicalJson, sha256Hex } from "./canonical-json";
40
+
41
+ export const RECORDING_MANIFEST_PATH = "manifest.json";
42
+ export const VIDEO_PROJECT_PATH = "project.json";
43
+ export const CURRENT_PROJECT_EDIT_PLAN_PATH = "edits/current.json";
44
+ const MAXIMUM_STRUCTURED_FILE_BYTES = 256 * 1024 * 1024;
45
+ const IMMUTABLE_COPY_TEMP_PREFIX = ".slopcamera-copy-";
46
+
47
+ export interface BundleFileSystem {
48
+ /**
49
+ * Inspect a physical regular file without following a leaf symlink.
50
+ * Implementations may omit binary support when they are used only for
51
+ * structured in-memory tests.
52
+ */
53
+ inspectFile?(path: string, maximumBytes?: number): Promise<BundleFileIntegrity>;
54
+ readText(path: string, maximumBytes?: number): Promise<string>;
55
+ writeTextAtomic(path: string, contents: string): Promise<void>;
56
+ /** Revalidate caller custody after staging and immediately before rename dispatch. */
57
+ writeTextAtomicGuarded?(path: string, contents: string, beforeReplace: () => Promise<void>): Promise<void>;
58
+ /**
59
+ * Install a new physical file without replacing an existing path.
60
+ *
61
+ * This is optional because lightweight read/write adapters do not need to
62
+ * support immutable publication. Callers that publish content-addressed
63
+ * artifacts must require it explicitly.
64
+ */
65
+ writeTextNoReplace?(
66
+ path: string,
67
+ contents: string,
68
+ beforePublication?: () => Promise<void>,
69
+ ): Promise<"created" | "exists">;
70
+ /**
71
+ * Copy one exact bundle file using atomic no-replace publication. The final
72
+ * path remains absent until the complete bytes are durable. Existing
73
+ * identical bytes are accepted for deterministic recovery; different bytes
74
+ * always fail.
75
+ */
76
+ copyFileNoReplace?(
77
+ sourcePath: string,
78
+ destinationPath: string,
79
+ expected: BundleFileIntegrity,
80
+ beforePublication?: () => Promise<void>,
81
+ ): Promise<"created" | "exists">;
82
+ }
83
+
84
+ export interface BundleFileIntegrity {
85
+ readonly bytes: number;
86
+ readonly sha256: string;
87
+ }
88
+
89
+ export type SlopcameraPersistenceDocument =
90
+ | AnalysisArtifact
91
+ | EditPlanV1
92
+ | ProjectEditPlanV1
93
+ | RecordingManifestV1
94
+ | VideoProjectV1;
95
+
96
+ /**
97
+ * Project a parsed mutable persistence document to the exact identity that a
98
+ * current Slopcamera writer will put on disk. Callers that authenticate immutable
99
+ * predecessor bytes must do that before invoking this projection.
100
+ */
101
+ export function canonicalSlopcameraPersistenceDocument<
102
+ Value extends SlopcameraPersistenceDocument,
103
+ >(value: Value): Value {
104
+ const kind = value.kind.replace(/^studio\./u, "slopcamera.");
105
+ if ("tool" in value && value.kind.endsWith(".recording-bundle")) {
106
+ if (kind === value.kind && value.tool.name === "slopcamera") return value;
107
+ return {
108
+ ...value,
109
+ kind,
110
+ tool: { ...value.tool, name: "slopcamera" },
111
+ } as Value;
112
+ }
113
+ return kind === value.kind ? value : { ...value, kind } as Value;
114
+ }
115
+
116
+ /** Publish canonical immutable text and verify the physical winner. */
117
+ export async function saveImmutableText(
118
+ fileSystem: BundleFileSystem,
119
+ path: string,
120
+ contents: string,
121
+ expectedSha256: string,
122
+ ): Promise<"created" | "exists"> {
123
+ RepositoryRelativePathSchema.parse(path);
124
+ const sha256 = Sha256Schema.parse(expectedSha256);
125
+ if (sha256Hex(contents) !== sha256) {
126
+ throw new Error(`Immutable text hash mismatch for ${path}.`);
127
+ }
128
+ if (fileSystem.writeTextNoReplace === undefined) {
129
+ throw new Error("Bundle file system does not support immutable no-replace publication.");
130
+ }
131
+ const disposition = await fileSystem.writeTextNoReplace(path, contents);
132
+ const published = await fileSystem.readText(path);
133
+ if (published !== contents || sha256Hex(published) !== sha256) {
134
+ throw new Error(
135
+ disposition === "exists"
136
+ ? `Immutable path already contains different bytes: ${path}`
137
+ : `Immutable publication failed read-back verification: ${path}`,
138
+ );
139
+ }
140
+ return disposition;
141
+ }
142
+
143
+ export function editPlanPath(planId: string): string {
144
+ return RepositoryRelativePathSchema.parse(`edits/${planId}.json`);
145
+ }
146
+
147
+ export function projectEditRevisionPath(artifactSha256: string): string {
148
+ const sha256 = Sha256Schema.parse(artifactSha256);
149
+ return RepositoryRelativePathSchema.parse(`edits/revisions/${sha256}.json`);
150
+ }
151
+
152
+ export async function loadRecordingManifest(
153
+ fileSystem: BundleFileSystem,
154
+ path = RECORDING_MANIFEST_PATH,
155
+ ): Promise<RecordingManifestV1> {
156
+ RepositoryRelativePathSchema.parse(path);
157
+ const input: unknown = JSON.parse(await fileSystem.readText(path));
158
+ return RecordingManifestV1Schema.parse(input);
159
+ }
160
+
161
+ export async function saveRecordingManifest(
162
+ fileSystem: BundleFileSystem,
163
+ manifest: RecordingManifestV1,
164
+ path = RECORDING_MANIFEST_PATH,
165
+ ): Promise<void> {
166
+ RepositoryRelativePathSchema.parse(path);
167
+ const parsed = RecordingManifestV1Schema.parse(canonicalSlopcameraPersistenceDocument(
168
+ RecordingManifestV1Schema.parse(manifest),
169
+ ));
170
+ await fileSystem.writeTextAtomic(path, `${canonicalJson(parsed)}\n`);
171
+ }
172
+
173
+ export async function loadEditPlan(fileSystem: BundleFileSystem, path: string): Promise<EditPlanV1> {
174
+ RepositoryRelativePathSchema.parse(path);
175
+ const input: unknown = JSON.parse(await fileSystem.readText(path));
176
+ return EditPlanV1Schema.parse(input);
177
+ }
178
+
179
+ export async function saveEditPlan(
180
+ fileSystem: BundleFileSystem,
181
+ plan: EditPlanV1,
182
+ path = editPlanPath(plan.planId),
183
+ ): Promise<void> {
184
+ RepositoryRelativePathSchema.parse(path);
185
+ const parsed = EditPlanV1Schema.parse(canonicalSlopcameraPersistenceDocument(
186
+ EditPlanV1Schema.parse(plan),
187
+ ));
188
+ await fileSystem.writeTextAtomic(path, `${canonicalJson(parsed)}\n`);
189
+ }
190
+
191
+ export async function loadVideoProject(
192
+ fileSystem: BundleFileSystem,
193
+ path = VIDEO_PROJECT_PATH,
194
+ ): Promise<VideoProjectV1> {
195
+ RepositoryRelativePathSchema.parse(path);
196
+ const input: unknown = JSON.parse(await fileSystem.readText(path));
197
+ return VideoProjectV1Schema.parse(input);
198
+ }
199
+
200
+ export async function saveVideoProject(
201
+ fileSystem: BundleFileSystem,
202
+ project: VideoProjectV1,
203
+ path = VIDEO_PROJECT_PATH,
204
+ ): Promise<void> {
205
+ RepositoryRelativePathSchema.parse(path);
206
+ const parsed = VideoProjectV1Schema.parse(canonicalSlopcameraPersistenceDocument(
207
+ VideoProjectV1Schema.parse(project),
208
+ ));
209
+ await fileSystem.writeTextAtomic(path, `${canonicalJson(parsed)}\n`);
210
+ }
211
+
212
+ export async function loadProjectEditPlan(
213
+ fileSystem: BundleFileSystem,
214
+ path = CURRENT_PROJECT_EDIT_PLAN_PATH,
215
+ ): Promise<ProjectEditPlanV1> {
216
+ RepositoryRelativePathSchema.parse(path);
217
+ const input: unknown = JSON.parse(await fileSystem.readText(path));
218
+ return ProjectEditPlanV1Schema.parse(input);
219
+ }
220
+
221
+ export async function saveProjectEditPlan(
222
+ fileSystem: BundleFileSystem,
223
+ plan: ProjectEditPlanV1,
224
+ path = CURRENT_PROJECT_EDIT_PLAN_PATH,
225
+ ): Promise<void> {
226
+ RepositoryRelativePathSchema.parse(path);
227
+ const parsed = ProjectEditPlanV1Schema.parse(canonicalSlopcameraPersistenceDocument(
228
+ ProjectEditPlanV1Schema.parse(plan),
229
+ ));
230
+ await fileSystem.writeTextAtomic(path, `${canonicalJson(parsed)}\n`);
231
+ }
232
+
233
+ /**
234
+ * Publish canonical revision bytes exactly once under their physical hash.
235
+ *
236
+ * A retry is accepted only when the already-published bytes are identical.
237
+ * The mutable current-plan pointer is intentionally outside this operation.
238
+ */
239
+ export async function saveProjectEditRevision(
240
+ fileSystem: BundleFileSystem,
241
+ contents: string,
242
+ expectedArtifactSha256: string,
243
+ ): Promise<string> {
244
+ const sha256 = Sha256Schema.parse(expectedArtifactSha256);
245
+ const actualSha256 = sha256Hex(contents);
246
+ if (actualSha256 !== sha256) {
247
+ throw new Error(
248
+ `Project edit revision artifact hash mismatch: expected ${sha256}, received ${actualSha256}.`,
249
+ );
250
+ }
251
+ if (fileSystem.writeTextNoReplace === undefined) {
252
+ throw new Error("Bundle file system does not support immutable no-replace publication.");
253
+ }
254
+ const path = projectEditRevisionPath(sha256);
255
+ const disposition = await fileSystem.writeTextNoReplace(path, contents);
256
+ const installed = await fileSystem.readText(path);
257
+ if (installed !== contents) {
258
+ throw new Error(
259
+ disposition === "exists"
260
+ ? `Project edit revision path already contains different bytes: ${path}`
261
+ : `Published project edit revision failed read-back verification: ${path}`,
262
+ );
263
+ }
264
+ return path;
265
+ }
266
+
267
+ export type AnalysisArtifact =
268
+ | AudioAlignmentAnalysisV1
269
+ | FaceAnalysisV1
270
+ | ProjectInactivityAnalysisV1
271
+ | MusicAnalysisV1
272
+ | SceneAnalysisV1
273
+ | SpeechAnalysisV1;
274
+
275
+ export async function loadAnalysisArtifact(
276
+ fileSystem: BundleFileSystem,
277
+ path: string,
278
+ ): Promise<AnalysisArtifact> {
279
+ RepositoryRelativePathSchema.parse(path);
280
+ const input: unknown = JSON.parse(await fileSystem.readText(path));
281
+ for (const schema of [
282
+ AudioAlignmentAnalysisV1Schema,
283
+ FaceAnalysisV1Schema,
284
+ ProjectInactivityAnalysisV1Schema,
285
+ MusicAnalysisV1Schema,
286
+ SceneAnalysisV1Schema,
287
+ SpeechAnalysisV1Schema,
288
+ ] as const) {
289
+ const parsed = schema.safeParse(input);
290
+ if (parsed.success) return parsed.data as AnalysisArtifact;
291
+ }
292
+ throw new Error(`Analysis artifact does not match a supported schema: ${path}`);
293
+ }
294
+
295
+ export async function saveAnalysisArtifact(
296
+ fileSystem: BundleFileSystem,
297
+ artifact: AnalysisArtifact,
298
+ path: string,
299
+ ): Promise<void> {
300
+ RepositoryRelativePathSchema.parse(path);
301
+ const schemas = [
302
+ AudioAlignmentAnalysisV1Schema,
303
+ FaceAnalysisV1Schema,
304
+ ProjectInactivityAnalysisV1Schema,
305
+ MusicAnalysisV1Schema,
306
+ SceneAnalysisV1Schema,
307
+ SpeechAnalysisV1Schema,
308
+ ] as const;
309
+ const parsed = schemas.map(schema => schema.safeParse(artifact)).find(result => result.success);
310
+ if (parsed === undefined || !parsed.success) {
311
+ throw new Error("Analysis artifact does not match a supported schema.");
312
+ }
313
+ await fileSystem.writeTextAtomic(
314
+ path,
315
+ `${canonicalJson(canonicalSlopcameraPersistenceDocument(parsed.data))}\n`,
316
+ );
317
+ }
318
+
319
+ function isWithin(root: string, candidate: string): boolean {
320
+ const pathFromRoot = relative(root, candidate);
321
+ return pathFromRoot === "" || (!pathFromRoot.startsWith(`..${sep}`) && pathFromRoot !== ".." && !isAbsolute(pathFromRoot));
322
+ }
323
+
324
+ function sameNonLinkSnapshot(before: Stats, after: Stats): boolean {
325
+ return after.isFile()
326
+ && after.dev === before.dev && after.ino === before.ino
327
+ && after.mode === before.mode && after.uid === before.uid && after.gid === before.gid
328
+ && after.rdev === before.rdev && after.size === before.size
329
+ && after.blksize === before.blksize && after.blocks === before.blocks
330
+ && after.mtimeMs === before.mtimeMs && after.birthtimeMs === before.birthtimeMs;
331
+ }
332
+
333
+ export function createNodeBundleFileSystem(
334
+ bundleRoot: string,
335
+ options: Readonly<{
336
+ readonly duringFileInspectionForTesting?: (input: Readonly<{
337
+ readonly attempt: 1 | 2;
338
+ readonly path: string;
339
+ }>) => Promise<void>;
340
+ readonly duringFileCopyForTesting?: (input: Readonly<{
341
+ readonly attempt: 1 | 2;
342
+ readonly path: string;
343
+ }>) => Promise<void>;
344
+ }> = {},
345
+ ): BundleFileSystem {
346
+ const lexicalRoot = resolve(bundleRoot);
347
+
348
+ async function physicalRoot(): Promise<string> {
349
+ await mkdir(lexicalRoot, { mode: 0o700, recursive: true });
350
+ const details = await lstat(lexicalRoot);
351
+ if (details.isSymbolicLink() || !details.isDirectory()) {
352
+ throw new Error(`Bundle root must be a physical directory: ${lexicalRoot}`);
353
+ }
354
+ return await realpath(lexicalRoot);
355
+ }
356
+
357
+ async function safePath(path: string, createParent: boolean): Promise<string> {
358
+ const relativePath = RepositoryRelativePathSchema.parse(path);
359
+ const root = await physicalRoot();
360
+ const candidate = resolve(root, relativePath);
361
+ if (!isWithin(root, candidate)) throw new Error(`Bundle path escapes its root: ${path}`);
362
+ const parentRelative = relative(root, dirname(candidate));
363
+ const parentParts = parentRelative === "" ? [] : parentRelative.split(sep);
364
+ let physicalParent = root;
365
+ for (const part of parentParts) {
366
+ const next = join(physicalParent, part);
367
+ try {
368
+ const details = await lstat(next);
369
+ if (details.isSymbolicLink() || !details.isDirectory()) {
370
+ throw new Error(`Bundle path requires physical directories: ${path}`);
371
+ }
372
+ } catch (error) {
373
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
374
+ if (!createParent) throw error;
375
+ try {
376
+ await mkdir(next, { mode: 0o700 });
377
+ } catch (mkdirError) {
378
+ // Concurrent immutable publishers may create the same private
379
+ // parent. Verify the winner below instead of treating that race as
380
+ // a publication failure.
381
+ if (!(
382
+ mkdirError instanceof Error
383
+ && "code" in mkdirError
384
+ && mkdirError.code === "EEXIST"
385
+ )) {
386
+ throw mkdirError;
387
+ }
388
+ }
389
+ const created = await lstat(next);
390
+ if (created.isSymbolicLink() || !created.isDirectory()) {
391
+ throw new Error(`Bundle directory creation was redirected: ${path}`);
392
+ }
393
+ }
394
+ physicalParent = next;
395
+ }
396
+ return join(physicalParent, basename(candidate));
397
+ }
398
+
399
+ async function readPhysicalText(path: string, maximumBytes = MAXIMUM_STRUCTURED_FILE_BYTES): Promise<string> {
400
+ if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 0 || maximumBytes > MAXIMUM_STRUCTURED_FILE_BYTES) throw new RangeError("Invalid structured file byte limit.");
401
+ const target = await safePath(path, false);
402
+ const lexical = await lstat(target);
403
+ if (
404
+ lexical.isSymbolicLink()
405
+ || !lexical.isFile()
406
+ || lexical.size > maximumBytes
407
+ ) {
408
+ throw new Error(`Bundle file must be a bounded physical regular file: ${path}`);
409
+ }
410
+ const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
411
+ try {
412
+ const before = await handle.stat();
413
+ if (
414
+ !before.isFile()
415
+ || before.dev !== lexical.dev
416
+ || before.ino !== lexical.ino
417
+ || before.size !== lexical.size
418
+ ) {
419
+ throw new Error(`Bundle file changed before it was read: ${path}`);
420
+ }
421
+ // Read at most the admitted size plus one byte. A concurrent append must
422
+ // not turn readFile into an unbounded allocation before the final stat.
423
+ const buffer = Buffer.alloc(before.size + 1);
424
+ let count = 0;
425
+ while (count < buffer.byteLength) {
426
+ const result = await handle.read(buffer, count, Math.min(256 * 1024, buffer.byteLength - count), count);
427
+ if (result.bytesRead === 0) break;
428
+ count += result.bytesRead;
429
+ }
430
+ const bytes = buffer.subarray(0, count);
431
+ const after = await handle.stat();
432
+ if (
433
+ bytes.byteLength !== before.size
434
+ || after.dev !== before.dev
435
+ || after.ino !== before.ino
436
+ || after.size !== before.size
437
+ || after.mtimeMs !== before.mtimeMs
438
+ || after.ctimeMs !== before.ctimeMs
439
+ ) {
440
+ throw new Error(`Bundle file changed while it was read: ${path}`);
441
+ }
442
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
443
+ } finally {
444
+ await handle.close();
445
+ }
446
+ }
447
+
448
+ async function inspectPhysicalFile(path: string, maximumBytes = Number.MAX_SAFE_INTEGER): Promise<BundleFileIntegrity> {
449
+ if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 0) throw new RangeError("Invalid physical file byte limit.");
450
+ const target = await safePath(path, false);
451
+ const lexical = await lstat(target);
452
+ if (lexical.isSymbolicLink() || !lexical.isFile() || lexical.size > maximumBytes) {
453
+ throw new Error(`Bundle file must be a physical regular file: ${path}`);
454
+ }
455
+ async function assertTargetStillNamesOpenedFile(
456
+ expected: Stats,
457
+ ): Promise<void> {
458
+ const current = await lstat(target).catch((error: unknown) => {
459
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
460
+ return null;
461
+ }
462
+ throw error;
463
+ });
464
+ if (
465
+ current === null
466
+ || current.isSymbolicLink()
467
+ || !current.isFile()
468
+ || !sameNonLinkSnapshot(expected, current)
469
+ || current.nlink !== expected.nlink
470
+ || current.ctimeMs !== expected.ctimeMs
471
+ ) {
472
+ throw new Error(`Bundle file changed while it was inspected: ${path}`);
473
+ }
474
+ }
475
+ const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
476
+ try {
477
+ const buffer = Buffer.allocUnsafe(256 * 1024);
478
+ let retrySnapshot: Stats | undefined;
479
+ let retryDigest: string | undefined;
480
+ for (const attempt of [1, 2] as const) {
481
+ const before = await handle.stat();
482
+ if (
483
+ !before.isFile()
484
+ || before.dev !== lexical.dev
485
+ || before.ino !== lexical.ino
486
+ || before.size !== lexical.size
487
+ || (retrySnapshot !== undefined && (
488
+ !sameNonLinkSnapshot(retrySnapshot, before)
489
+ || before.nlink !== retrySnapshot.nlink
490
+ || before.ctimeMs !== retrySnapshot.ctimeMs
491
+ ))
492
+ ) {
493
+ throw new Error(`Bundle file changed before it was inspected: ${path}`);
494
+ }
495
+ const digest = createHash("sha256");
496
+ let bytes = 0;
497
+ while (true) {
498
+ // Positional reads keep both bounded attempts pinned to byte zero on
499
+ // the same already-verified inode.
500
+ const result = await handle.read(
501
+ buffer,
502
+ 0,
503
+ Math.min(buffer.byteLength, maximumBytes - bytes + 1),
504
+ bytes,
505
+ );
506
+ if (result.bytesRead === 0) break;
507
+ digest.update(buffer.subarray(0, result.bytesRead));
508
+ bytes += result.bytesRead;
509
+ if (!Number.isSafeInteger(bytes) || bytes > maximumBytes) {
510
+ throw new Error(`Bundle file exceeds safe byte accounting: ${path}`);
511
+ }
512
+ }
513
+ await options.duringFileInspectionForTesting?.({ attempt, path });
514
+ const after = await handle.stat();
515
+ // Reading may update atime. A ctime transition invalidates this read;
516
+ // one fresh, stable pass must independently reproduce its exact bytes.
517
+ const nonLinkSnapshotStayedStable = bytes === before.size && sameNonLinkSnapshot(before, after);
518
+ const sha256 = digest.digest("hex");
519
+ if (retryDigest !== undefined && retryDigest !== sha256) {
520
+ throw new Error(`Bundle file changed while it was inspected: ${path}`);
521
+ }
522
+ if (
523
+ nonLinkSnapshotStayedStable
524
+ && after.nlink === before.nlink
525
+ && after.ctimeMs === before.ctimeMs
526
+ ) {
527
+ await assertTargetStillNamesOpenedFile(after);
528
+ return {
529
+ bytes,
530
+ sha256: Sha256Schema.parse(sha256),
531
+ };
532
+ }
533
+ const metadataRequiresFreshRead = (
534
+ attempt === 1
535
+ && nonLinkSnapshotStayedStable
536
+ && after.ctimeMs !== before.ctimeMs
537
+ && (after.nlink === before.nlink || (before.nlink >= 2 && after.nlink === before.nlink - 1))
538
+ );
539
+ if (!metadataRequiresFreshRead) {
540
+ throw new Error(`Bundle file changed while it was inspected: ${path}`);
541
+ }
542
+ await assertTargetStillNamesOpenedFile(after);
543
+ retrySnapshot = after;
544
+ retryDigest = sha256;
545
+ }
546
+ throw new Error(`Bundle file changed while it was inspected: ${path}`);
547
+ } finally {
548
+ await handle.close();
549
+ }
550
+ }
551
+
552
+ async function immutableLink(temporary: string, target: string): Promise<"created" | "exists"> {
553
+ try { await link(temporary, target); return "created"; }
554
+ catch (error) {
555
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") return "exists";
556
+ throw error;
557
+ }
558
+ }
559
+
560
+ async function guardedImmutableLink(temporary: string, target: string, expected: BundleFileIntegrity, beforePublication: () => Promise<void>): Promise<"created" | "exists"> {
561
+ const handle = await open(temporary, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
562
+ let staged: BigIntStats;
563
+ try {
564
+ staged = fstatSync(handle.fd, { bigint: true });
565
+ if (!staged.isFile() || staged.size !== BigInt(expected.bytes) || staged.nlink !== 1n || (staged.mode & 0o777n) !== 0o600n) throw new Error("Immutable stage is not a private exact-sized regular file.");
566
+ const digest = createHash("sha256"), buffer = Buffer.allocUnsafe(256 * 1024);
567
+ let bytes = 0;
568
+ while (bytes < expected.bytes) {
569
+ const count = readSync(handle.fd, buffer, 0, Math.min(buffer.length, expected.bytes - bytes), bytes);
570
+ if (count === 0) throw new Error("Immutable stage ended during verification.");
571
+ digest.update(buffer.subarray(0, count)); bytes += count;
572
+ }
573
+ const after = fstatSync(handle.fd, { bigint: true });
574
+ if (digest.digest("hex") !== expected.sha256 || after.size !== staged.size || after.mtimeNs !== staged.mtimeNs || after.ctimeNs !== staged.ctimeNs) throw new Error("Immutable stage changed during verification.");
575
+ } finally { await handle.close(); }
576
+ const parents = [];
577
+ for (let directory = dirname(target);; directory = dirname(directory)) {
578
+ const snapshot = lstatSync(directory, { bigint: true });
579
+ if (!snapshot.isDirectory() || snapshot.isSymbolicLink()) throw new Error("Immutable publication parent is unsafe.");
580
+ parents.push({ directory, snapshot });
581
+ if (dirname(directory) === directory) break;
582
+ }
583
+ await beforePublication();
584
+ for (const item of parents) {
585
+ const current = lstatSync(item.directory, { bigint: true });
586
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== item.snapshot.dev || current.ino !== item.snapshot.ino) throw new Error("Immutable publication parent changed during custody check.");
587
+ }
588
+ const current = lstatSync(temporary, { bigint: true });
589
+ if (!current.isFile() || current.isSymbolicLink() || current.dev !== staged.dev || current.ino !== staged.ino || current.size !== staged.size
590
+ || current.mtimeNs !== staged.mtimeNs || current.ctimeNs !== staged.ctimeNs || current.nlink !== 1n || current.mode !== staged.mode) throw new Error("Immutable stage changed during custody check.");
591
+ // Complete physical revalidation and no-replace dispatch share one synchronous turn.
592
+ try { linkSync(temporary, target); return "created"; }
593
+ catch (error) {
594
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") return "exists";
595
+ throw error;
596
+ }
597
+ }
598
+
599
+ return {
600
+ inspectFile: inspectPhysicalFile,
601
+ readText: readPhysicalText,
602
+ async writeTextAtomicGuarded(path, contents, beforeReplace) {
603
+ const target = await safePath(path, true);
604
+ const parent = await lstat(dirname(target));
605
+ const temporary = `${target}.tmp-${randomUUID()}`;
606
+ const handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
607
+ let identity: Stats | undefined;
608
+ let stagedSnapshot: BigIntStats;
609
+ try {
610
+ try {
611
+ identity = await handle.stat();
612
+ await handle.writeFile(contents, "utf8"); await handle.sync();
613
+ const expectedBytes = Buffer.from(contents, "utf8");
614
+ const actualBytes = Buffer.allocUnsafe(Math.min(expectedBytes.length, 256 * 1024));
615
+ let offset = 0;
616
+ while (offset < expectedBytes.length) {
617
+ const count = readSync(handle.fd, actualBytes, 0, Math.min(actualBytes.length, expectedBytes.length - offset), offset);
618
+ if (count === 0 || !actualBytes.subarray(0, count).equals(expectedBytes.subarray(offset, offset + count))) throw new Error("Guarded atomic publication stage differs from the requested bytes.");
619
+ offset += count;
620
+ }
621
+ stagedSnapshot = fstatSync(handle.fd, { bigint: true });
622
+ if (!stagedSnapshot.isFile() || stagedSnapshot.size !== BigInt(expectedBytes.length) || stagedSnapshot.nlink !== 1n) throw new Error("Guarded atomic publication stage metadata is invalid.");
623
+ }
624
+ finally { await handle.close(); }
625
+ const checked = await safePath(path, false);
626
+ const currentParent = await lstat(dirname(checked));
627
+ const staged = await lstat(temporary);
628
+ if (checked !== target || parent.dev !== currentParent.dev || parent.ino !== currentParent.ino
629
+ || !staged.isFile() || staged.isSymbolicLink() || staged.dev !== identity.dev || staged.ino !== identity.ino) {
630
+ throw new Error("Guarded atomic publication path changed during staging.");
631
+ }
632
+ const parents = [];
633
+ for (let directory = dirname(target);; directory = dirname(directory)) {
634
+ const snapshot = lstatSync(directory, { bigint: true });
635
+ if (!snapshot.isDirectory() || snapshot.isSymbolicLink()) throw new Error("Guarded atomic publication parent is unsafe.");
636
+ parents.push({ directory, snapshot });
637
+ if (dirname(directory) === directory) break;
638
+ }
639
+ await beforeReplace();
640
+ // The asynchronous custody check may yield to other filesystem users.
641
+ // Revalidate the complete physical path and staged content metadata,
642
+ // then dispatch synchronously without another JavaScript await gap.
643
+ for (const item of parents) {
644
+ const current = lstatSync(item.directory, { bigint: true });
645
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== item.snapshot.dev || current.ino !== item.snapshot.ino) throw new Error("Guarded atomic publication parent changed during custody check.");
646
+ }
647
+ const finalStage = lstatSync(temporary, { bigint: true });
648
+ if (!finalStage.isFile() || finalStage.isSymbolicLink() || finalStage.dev !== stagedSnapshot.dev || finalStage.ino !== stagedSnapshot.ino
649
+ || finalStage.size !== stagedSnapshot.size || finalStage.mtimeNs !== stagedSnapshot.mtimeNs || finalStage.ctimeNs !== stagedSnapshot.ctimeNs
650
+ || finalStage.nlink !== 1n || finalStage.mode !== stagedSnapshot.mode) throw new Error("Guarded atomic publication stage changed during custody check.");
651
+ renameSync(temporary, target);
652
+ const directoryHandle = await open(dirname(target), constants.O_RDONLY | constants.O_NOFOLLOW);
653
+ try { await directoryHandle.sync(); } finally { await directoryHandle.close(); }
654
+ } finally {
655
+ const staged = await lstat(temporary).catch((error: unknown) => {
656
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
657
+ throw error;
658
+ });
659
+ if (staged !== undefined && identity !== undefined && staged.dev === identity.dev && staged.ino === identity.ino && !staged.isSymbolicLink()) await unlink(temporary);
660
+ }
661
+ },
662
+ async writeTextAtomic(path, contents) {
663
+ const target = await safePath(path, true);
664
+ const temporary = `${target}.tmp-${randomUUID()}`;
665
+ const handle = await open(temporary, "wx", 0o600);
666
+ try {
667
+ await handle.writeFile(contents, "utf8");
668
+ await handle.sync();
669
+ } finally {
670
+ await handle.close();
671
+ }
672
+ await rename(temporary, target);
673
+ const directoryHandle = await open(dirname(target), "r");
674
+ try {
675
+ await directoryHandle.sync();
676
+ } finally {
677
+ await directoryHandle.close();
678
+ }
679
+ },
680
+ async writeTextNoReplace(path, contents, beforePublication) {
681
+ const target = await safePath(path, true);
682
+ const temporary = `${target}.tmp-${randomUUID()}`;
683
+ const handle = await open(temporary, "wx", 0o600);
684
+ let disposition: "created" | "exists" | undefined;
685
+ let cleanupError: Error | undefined;
686
+ try {
687
+ try {
688
+ await handle.writeFile(contents, "utf8");
689
+ await handle.sync();
690
+ } finally {
691
+ await handle.close();
692
+ }
693
+ if (beforePublication === undefined) disposition = await immutableLink(temporary, target);
694
+ else {
695
+ if (await safePath(path, false) !== target) throw new Error("Immutable publication path changed during staging.");
696
+ disposition = await guardedImmutableLink(temporary, target, { bytes: Buffer.byteLength(contents), sha256: sha256Hex(contents) }, beforePublication);
697
+ }
698
+ } finally {
699
+ try {
700
+ await unlink(temporary);
701
+ } catch (error) {
702
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
703
+ cleanupError = error instanceof Error
704
+ ? error
705
+ : new Error(String(error));
706
+ }
707
+ }
708
+ }
709
+ if (cleanupError !== undefined) throw cleanupError;
710
+ if (disposition === undefined) {
711
+ throw new Error(`Immutable publication did not resolve a disposition: ${path}`);
712
+ }
713
+ if (disposition === "created") {
714
+ const directoryHandle = await open(dirname(target), "r");
715
+ try {
716
+ await directoryHandle.sync();
717
+ } finally {
718
+ await directoryHandle.close();
719
+ }
720
+ }
721
+ return disposition;
722
+ },
723
+ async copyFileNoReplace(sourcePath, destinationPath, expectedInput, beforePublication) {
724
+ const expected = {
725
+ bytes: zSafeBytes(expectedInput.bytes),
726
+ sha256: Sha256Schema.parse(expectedInput.sha256),
727
+ };
728
+ if (sourcePath === destinationPath) {
729
+ throw new Error("Immutable bundle copy requires a distinct destination path.");
730
+ }
731
+ const source = await safePath(sourcePath, false);
732
+ const destination = await safePath(destinationPath, true);
733
+ const lexicalSource = await lstat(source);
734
+ if (lexicalSource.isSymbolicLink() || !lexicalSource.isFile()) {
735
+ throw new Error(`Immutable bundle copy source is not a physical file: ${sourcePath}`);
736
+ }
737
+ const sourceHandle = await open(
738
+ source,
739
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
740
+ );
741
+ const temporary = join(
742
+ dirname(destination),
743
+ `${IMMUTABLE_COPY_TEMP_PREFIX}${randomUUID()}.tmp`,
744
+ );
745
+ let temporaryHandle: FileHandle | undefined;
746
+ let ownsTemporary = false;
747
+ let createdIdentity:
748
+ | { readonly dev: number; readonly ino: number }
749
+ | undefined;
750
+ try {
751
+ const sourceBefore = await sourceHandle.stat();
752
+ if (
753
+ !sourceBefore.isFile()
754
+ || !sameNonLinkSnapshot(lexicalSource, sourceBefore)
755
+ || sourceBefore.nlink !== lexicalSource.nlink
756
+ || sourceBefore.size !== expected.bytes
757
+ ) {
758
+ throw new Error(`Immutable bundle copy source changed before opening: ${sourcePath}`);
759
+ }
760
+ let existingIntegrity: BundleFileIntegrity | undefined;
761
+ try { existingIntegrity = await inspectPhysicalFile(destinationPath, expected.bytes); }
762
+ catch (error) {
763
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
764
+ }
765
+ if (existingIntegrity !== undefined) {
766
+ if (
767
+ existingIntegrity.bytes !== expected.bytes
768
+ || existingIntegrity.sha256 !== expected.sha256
769
+ ) {
770
+ throw new Error(
771
+ `Immutable bundle copy destination contains different bytes: ${destinationPath}`,
772
+ );
773
+ }
774
+ await beforePublication?.();
775
+ return "exists";
776
+ }
777
+
778
+ // A killed copier may leave this private stage behind, but can never
779
+ // expose partial bytes under the immutable destination name.
780
+ temporaryHandle = await open(
781
+ temporary,
782
+ constants.O_CREAT
783
+ | constants.O_EXCL
784
+ | constants.O_WRONLY
785
+ | constants.O_NOFOLLOW,
786
+ 0o600,
787
+ );
788
+ ownsTemporary = true;
789
+ const temporaryBefore = await temporaryHandle.stat();
790
+ const digest = createHash("sha256");
791
+ const buffer = Buffer.allocUnsafe(256 * 1024);
792
+ let bytes = 0;
793
+ while (true) {
794
+ const result = await sourceHandle.read(
795
+ buffer,
796
+ 0,
797
+ Math.min(buffer.byteLength, expected.bytes - bytes + 1),
798
+ null,
799
+ );
800
+ if (result.bytesRead === 0) break;
801
+ if (bytes + result.bytesRead > expected.bytes) throw new Error(`Immutable bundle copy source grew beyond its exact byte limit: ${sourcePath}`);
802
+ digest.update(buffer.subarray(0, result.bytesRead));
803
+ let written = 0;
804
+ while (written < result.bytesRead) {
805
+ const write = await temporaryHandle.write(
806
+ buffer,
807
+ written,
808
+ result.bytesRead - written,
809
+ null,
810
+ );
811
+ if (write.bytesWritten < 1) {
812
+ throw new Error("Immutable bundle copy made no write progress.");
813
+ }
814
+ written += write.bytesWritten;
815
+ }
816
+ bytes += result.bytesRead;
817
+ if (!Number.isSafeInteger(bytes)) {
818
+ throw new Error(`Bundle file exceeds safe byte accounting: ${sourcePath}`);
819
+ }
820
+ }
821
+ await options.duringFileCopyForTesting?.({ attempt: 1, path: sourcePath });
822
+ const [sourceAfter, temporaryAfter] = await Promise.all([
823
+ sourceHandle.stat(),
824
+ temporaryHandle.stat(),
825
+ ]);
826
+ const copiedSha256 = Sha256Schema.parse(digest.digest("hex"));
827
+ if (
828
+ bytes !== expected.bytes
829
+ || copiedSha256 !== expected.sha256
830
+ || !sameNonLinkSnapshot(sourceBefore, sourceAfter)
831
+ || sourceAfter.nlink !== sourceBefore.nlink
832
+ || temporaryAfter.dev !== temporaryBefore.dev
833
+ || temporaryAfter.ino !== temporaryBefore.ino
834
+ || temporaryAfter.size !== bytes
835
+ ) {
836
+ throw new Error(`Immutable bundle copy source changed or failed verification: ${sourcePath}`);
837
+ }
838
+ let verifiedSource = sourceAfter;
839
+ if (sourceAfter.ctimeMs !== sourceBefore.ctimeMs) {
840
+ // A metadata-only transition invalidates the first source read. Do
841
+ // not recopy or ignore it: one stable complete pass on the same open
842
+ // inode must independently reproduce the exact expected bytes.
843
+ const retryBefore = await sourceHandle.stat();
844
+ if (!sameNonLinkSnapshot(sourceAfter, retryBefore)
845
+ || retryBefore.nlink !== sourceAfter.nlink
846
+ || retryBefore.ctimeMs !== sourceAfter.ctimeMs) {
847
+ throw new Error(`Immutable bundle copy source changed before verification reread: ${sourcePath}`);
848
+ }
849
+ const retryDigest = createHash("sha256");
850
+ let retryBytes = 0;
851
+ while (true) {
852
+ const result = await sourceHandle.read(buffer, 0, Math.min(buffer.byteLength, expected.bytes - retryBytes + 1), retryBytes);
853
+ if (result.bytesRead === 0) break;
854
+ if (retryBytes + result.bytesRead > expected.bytes) throw new Error(`Immutable bundle copy source grew during verification reread: ${sourcePath}`);
855
+ retryDigest.update(buffer.subarray(0, result.bytesRead));
856
+ retryBytes += result.bytesRead;
857
+ }
858
+ await options.duringFileCopyForTesting?.({ attempt: 2, path: sourcePath });
859
+ const retryAfter = await sourceHandle.stat();
860
+ if (retryBytes !== expected.bytes || retryDigest.digest("hex") !== expected.sha256
861
+ || !sameNonLinkSnapshot(retryBefore, retryAfter)
862
+ || retryAfter.nlink !== retryBefore.nlink
863
+ || retryAfter.ctimeMs !== retryBefore.ctimeMs) {
864
+ throw new Error(`Immutable bundle copy source changed or failed verification reread: ${sourcePath}`);
865
+ }
866
+ verifiedSource = retryAfter;
867
+ }
868
+ const currentSource = await lstat(source);
869
+ if (currentSource.isSymbolicLink() || !sameNonLinkSnapshot(verifiedSource, currentSource)
870
+ || currentSource.nlink !== verifiedSource.nlink || currentSource.ctimeMs !== verifiedSource.ctimeMs) {
871
+ throw new Error(`Immutable bundle copy source path changed after verification: ${sourcePath}`);
872
+ }
873
+ await temporaryHandle.sync();
874
+ await temporaryHandle.close();
875
+ temporaryHandle = undefined;
876
+ const staged = await lstat(temporary);
877
+ if (
878
+ staged.isSymbolicLink()
879
+ || !staged.isFile()
880
+ || staged.dev !== temporaryAfter.dev
881
+ || staged.ino !== temporaryAfter.ino
882
+ || staged.size !== temporaryAfter.size
883
+ ) {
884
+ throw new Error(`Immutable bundle copy staging path changed: ${destinationPath}`);
885
+ }
886
+
887
+ let disposition: "created" | "exists";
888
+ if (beforePublication === undefined) disposition = await immutableLink(temporary, destination);
889
+ else {
890
+ if (await safePath(destinationPath, false) !== destination) throw new Error("Immutable copy destination changed during staging.");
891
+ disposition = await guardedImmutableLink(temporary, destination, expected, beforePublication);
892
+ }
893
+ if (disposition === "created") {
894
+ createdIdentity = {
895
+ dev: temporaryAfter.dev,
896
+ ino: temporaryAfter.ino,
897
+ };
898
+ }
899
+ const destinationIntegrity = await inspectPhysicalFile(destinationPath, expected.bytes);
900
+ if (
901
+ destinationIntegrity.bytes !== expected.bytes
902
+ || destinationIntegrity.sha256 !== expected.sha256
903
+ ) {
904
+ throw new Error(
905
+ disposition === "exists"
906
+ ? `Immutable bundle copy destination contains different bytes: ${destinationPath}`
907
+ : `Immutable bundle copy failed read-back verification: ${destinationPath}`,
908
+ );
909
+ }
910
+ if (disposition === "created") {
911
+ await syncParentDirectory(destination);
912
+ }
913
+ return disposition;
914
+ } catch (error) {
915
+ if (createdIdentity !== undefined) {
916
+ const current = await lstat(destination).catch((lstatError: unknown) => {
917
+ if (
918
+ lstatError instanceof Error
919
+ && "code" in lstatError
920
+ && lstatError.code === "ENOENT"
921
+ ) return null;
922
+ throw lstatError;
923
+ });
924
+ if (
925
+ current !== null
926
+ && !current.isSymbolicLink()
927
+ && current.isFile()
928
+ && current.dev === createdIdentity.dev
929
+ && current.ino === createdIdentity.ino
930
+ ) {
931
+ await unlink(destination);
932
+ await syncParentDirectory(destination);
933
+ }
934
+ }
935
+ throw error;
936
+ } finally {
937
+ if (temporaryHandle !== undefined) {
938
+ await temporaryHandle.close().catch(() => undefined);
939
+ }
940
+ if (ownsTemporary) {
941
+ await unlink(temporary).catch((error: unknown) => {
942
+ if (
943
+ error instanceof Error
944
+ && "code" in error
945
+ && error.code === "ENOENT"
946
+ ) return;
947
+ throw error;
948
+ });
949
+ }
950
+ await sourceHandle.close();
951
+ }
952
+ },
953
+ };
954
+ }
955
+
956
+ function zSafeBytes(value: number): number {
957
+ if (!Number.isSafeInteger(value) || value < 0) {
958
+ throw new TypeError("File byte count must be a nonnegative safe integer.");
959
+ }
960
+ return value;
961
+ }
962
+
963
+ async function syncParentDirectory(path: string): Promise<void> {
964
+ const handle = await open(dirname(path), "r");
965
+ try {
966
+ await handle.sync();
967
+ } finally {
968
+ await handle.close();
969
+ }
970
+ }