@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,364 @@
1
+ import { z } from "zod";
2
+ import {
3
+ ProjectAssetRoleSchema, ProjectAssetV1Schema, ProjectMediaStreamSchema,
4
+ RepositoryRelativePathSchema, type ProjectAssetV1,
5
+ } from "../contracts";
6
+ import { CliError } from "./errors";
7
+ import type { ProcessRunner } from "./io";
8
+ import type { MediaIngestDurability } from "./media-ingest-platform";
9
+
10
+ export const MAX_MEDIA_BYTES = 4 * 1024 * 1024 * 1024 * 1024;
11
+ export const MAX_PROBE_BYTES = 4 * 1024 * 1024;
12
+ export const MAX_PROBE_TIMEOUT_MS = 2 * 60_000;
13
+ export const SELF_CONTAINED_MEDIA_INPUT_ARGUMENTS = [
14
+ "-protocol_whitelist",
15
+ "file",
16
+ "-format_whitelist",
17
+ [
18
+ "aac",
19
+ "ac3",
20
+ "aiff",
21
+ "alaw",
22
+ "amr",
23
+ "ape",
24
+ "apng",
25
+ "asf",
26
+ "au",
27
+ "av1",
28
+ "avi",
29
+ "bmp_pipe",
30
+ "caf",
31
+ "dv",
32
+ "eac3",
33
+ "flac",
34
+ "flv",
35
+ "g722",
36
+ "g726",
37
+ "gif",
38
+ "gsm",
39
+ "h261",
40
+ "h263",
41
+ "h264",
42
+ "hevc",
43
+ "ircam",
44
+ "ivf",
45
+ "jpeg_pipe",
46
+ "m4v",
47
+ "matroska",
48
+ "mov",
49
+ "mp3",
50
+ "mpeg",
51
+ "mpegts",
52
+ "mpegvideo",
53
+ "mulaw",
54
+ "mxf",
55
+ "nut",
56
+ "ogg",
57
+ "oma",
58
+ "opus",
59
+ "png_pipe",
60
+ "rawvideo",
61
+ "rm",
62
+ "vvc",
63
+ "w64",
64
+ "wav",
65
+ "webm",
66
+ "webp_pipe",
67
+ "tiff_pipe",
68
+ "yuv4mpegpipe",
69
+ ].join(","),
70
+ ] as const;
71
+
72
+ const ProbeStreamSchema = z.strictObject({
73
+ avg_frame_rate: z.string().optional(),
74
+ channels: z.number().int().positive().optional(),
75
+ codec_name: z.string().min(1),
76
+ codec_type: z.string().min(1).max(64),
77
+ duration: z.string().optional(),
78
+ disposition: z.strictObject({
79
+ attached_pic: z.union([z.boolean(), z.number().int().min(0).max(1)]).optional(),
80
+ still_image: z.union([z.boolean(), z.number().int().min(0).max(1)]).optional(),
81
+ timed_thumbnails: z.union([z.boolean(), z.number().int().min(0).max(1)]).optional(),
82
+ }).optional(),
83
+ height: z.number().int().positive().optional(),
84
+ index: z.number().int().nonnegative(),
85
+ r_frame_rate: z.string().optional(),
86
+ sample_rate: z.string().optional(),
87
+ start_time: z.string().optional(),
88
+ tags: z.strictObject({
89
+ DURATION: z.string().optional(),
90
+ duration: z.string().optional(),
91
+ }).optional(),
92
+ width: z.number().int().positive().optional(),
93
+ });
94
+
95
+ const ProbeOutputSchema = z.strictObject({
96
+ format: z.strictObject({
97
+ duration: z.string().optional(),
98
+ format_name: z.string().min(1),
99
+ start_time: z.string().optional(),
100
+ }),
101
+ programs: z.array(z.never()).max(0).optional(),
102
+ stream_groups: z.array(z.never()).max(0).optional(),
103
+ streams: z.array(ProbeStreamSchema).min(1).max(256),
104
+ });
105
+
106
+ export interface ProbedMediaStream extends z.infer<typeof ProbeStreamSchema> {
107
+ readonly assetRange: Readonly<{ readonly endUs: number; readonly startUs: number }>;
108
+ readonly fileRange: Readonly<{ readonly endUs: number; readonly startUs: number }>;
109
+ }
110
+
111
+ export interface ProbedMedia {
112
+ readonly container: string;
113
+ readonly durationUs: number;
114
+ readonly streams: readonly ProbedMediaStream[];
115
+ }
116
+
117
+ function decimalDurationToUs(value: string | undefined): number | null {
118
+ if (value === undefined || value.trim() === "") return null;
119
+ const seconds = Number(value);
120
+ const microseconds = Math.round(seconds * 1_000_000);
121
+ return Number.isFinite(seconds) && Number.isSafeInteger(microseconds) && microseconds > 0
122
+ ? microseconds
123
+ : null;
124
+ }
125
+
126
+ function decimalTimestampToUs(value: string | undefined): number | null {
127
+ if (value === undefined || value.trim() === "") return null;
128
+ const seconds = Number(value);
129
+ const microseconds = Math.round(seconds * 1_000_000);
130
+ return Number.isFinite(seconds) && Number.isSafeInteger(microseconds) ? microseconds : null;
131
+ }
132
+
133
+ function durationTagToUs(value: string | undefined): number | null {
134
+ if (value === undefined || value.trim() === "") return null;
135
+ const match = /^(?<hours>[0-9]+):(?<minutes>[0-5][0-9]):(?<seconds>[0-5][0-9])(?:\.(?<fraction>[0-9]{1,9}))?$/u.exec(value);
136
+ if (match?.groups === undefined) return null;
137
+ const wholeSeconds = Number(match.groups.hours) * 3_600
138
+ + Number(match.groups.minutes) * 60
139
+ + Number(match.groups.seconds);
140
+ const fraction = match.groups.fraction ?? "";
141
+ const microseconds = wholeSeconds * 1_000_000 + Math.round(Number(`0.${fraction}`) * 1_000_000);
142
+ return Number.isSafeInteger(microseconds) && microseconds > 0 ? microseconds : null;
143
+ }
144
+
145
+ function safeTimeSum(left: number, right: number, label: string): number {
146
+ const result = left + right;
147
+ if (!Number.isSafeInteger(result)) {
148
+ throw new CliError("invalid-data", `${label} exceeds the supported timestamp range.`);
149
+ }
150
+ return result;
151
+ }
152
+
153
+ function rational(value: string | undefined): number | null {
154
+ if (value === undefined) return null;
155
+ const match = /^(?<numerator>[0-9]+)\/(?<denominator>[0-9]+)?$/u.exec(value);
156
+ if (match?.groups?.numerator === undefined) return null;
157
+ const numerator = Number(match.groups.numerator);
158
+ const denominator = Number(match.groups.denominator ?? "1");
159
+ const result = numerator / denominator;
160
+ return Number.isFinite(result) && result > 0 ? result : null;
161
+ }
162
+
163
+ export function parseMediaProbe(input: string): ProbedMedia {
164
+ let json: unknown;
165
+ try {
166
+ json = JSON.parse(input) as unknown;
167
+ } catch {
168
+ throw new CliError("invalid-data", "FFprobe media output is not valid JSON.");
169
+ }
170
+ const parsed = ProbeOutputSchema.safeParse(json);
171
+ if (!parsed.success) {
172
+ throw new CliError("invalid-data", `FFprobe media output is invalid: ${parsed.error.issues[0]?.message ?? "unknown error"}`);
173
+ }
174
+ const playableStreams = parsed.data.streams.filter(
175
+ (stream): stream is typeof stream & { readonly codec_type: "audio" | "video" } => (
176
+ (stream.codec_type === "audio" || stream.codec_type === "video")
177
+ && (
178
+ stream.codec_type !== "video"
179
+ || (
180
+ stream.disposition?.attached_pic !== 1
181
+ && stream.disposition?.attached_pic !== true
182
+ && stream.disposition?.still_image !== 1
183
+ && stream.disposition?.still_image !== true
184
+ && stream.disposition?.timed_thumbnails !== 1
185
+ && stream.disposition?.timed_thumbnails !== true
186
+ )
187
+ )
188
+ ),
189
+ );
190
+ if (playableStreams.length === 0) {
191
+ throw new CliError("invalid-data", "Media contains no playable audio or video streams after attached pictures are excluded.");
192
+ }
193
+ if (playableStreams.length > 64) {
194
+ throw new CliError("invalid-data", "Media contains more than 64 playable audio/video streams.");
195
+ }
196
+ const formatDurationUs = decimalDurationToUs(parsed.data.format.duration);
197
+ if (
198
+ formatDurationUs === null
199
+ && playableStreams.every(stream => (
200
+ decimalDurationToUs(stream.duration) === null
201
+ && durationTagToUs(stream.tags?.DURATION ?? stream.tags?.duration) === null
202
+ ))
203
+ ) {
204
+ throw new CliError("invalid-data", "Media has no positive finite duration.");
205
+ }
206
+ const formatStartUs = decimalTimestampToUs(parsed.data.format.start_time);
207
+ const explicitStarts = playableStreams.flatMap(stream => {
208
+ const startUs = decimalTimestampToUs(stream.start_time);
209
+ return startUs === null ? [] : [startUs];
210
+ });
211
+ const clockOriginUs = Math.min(formatStartUs ?? Number.POSITIVE_INFINITY, ...explicitStarts);
212
+ const commonOriginUs = Number.isFinite(clockOriginUs) ? clockOriginUs : 0;
213
+ const formatEndUs = formatDurationUs === null
214
+ ? null
215
+ : safeTimeSum(formatStartUs ?? commonOriginUs, formatDurationUs, "Media format duration");
216
+ const streams: ProbedMediaStream[] = [];
217
+ for (const stream of playableStreams) {
218
+ if (stream.codec_type === "video") {
219
+ if (stream.width === undefined || stream.height === undefined) {
220
+ throw new CliError("invalid-data", `Video stream ${stream.index} omits pixel dimensions.`);
221
+ }
222
+ if ((rational(stream.avg_frame_rate) ?? rational(stream.r_frame_rate)) === null) {
223
+ throw new CliError("invalid-data", `Video stream ${stream.index} omits a positive frame rate.`);
224
+ }
225
+ } else {
226
+ if (stream.channels === undefined || !/^[1-9][0-9]*$/u.test(stream.sample_rate ?? "")) {
227
+ throw new CliError("invalid-data", `Audio stream ${stream.index} omits channel or sample-rate facts.`);
228
+ }
229
+ }
230
+ const nativeStartUs = decimalTimestampToUs(stream.start_time) ?? formatStartUs ?? commonOriginUs;
231
+ const startUs = nativeStartUs - commonOriginUs;
232
+ if (!Number.isSafeInteger(startUs) || startUs < 0) {
233
+ throw new CliError("invalid-data", `Stream ${stream.index} has an invalid start time.`);
234
+ }
235
+ const streamDurationUs = decimalDurationToUs(stream.duration)
236
+ ?? durationTagToUs(stream.tags?.DURATION ?? stream.tags?.duration)
237
+ ?? (formatEndUs === null ? null : formatEndUs - nativeStartUs);
238
+ if (streamDurationUs === null || !Number.isSafeInteger(streamDurationUs) || streamDurationUs <= 0) {
239
+ throw new CliError("invalid-data", `Stream ${stream.index} has no positive finite duration.`);
240
+ }
241
+ const endUs = safeTimeSum(startUs, streamDurationUs, `Stream ${stream.index} duration`);
242
+ streams.push({
243
+ ...stream,
244
+ assetRange: { endUs, startUs },
245
+ fileRange: { endUs, startUs },
246
+ });
247
+ }
248
+ const formatCoverageEndUs = formatEndUs === null ? 0 : formatEndUs - commonOriginUs;
249
+ const durationUs = Math.max(formatCoverageEndUs, ...streams.map(stream => stream.assetRange.endUs));
250
+ if (!Number.isSafeInteger(durationUs) || durationUs <= 0) {
251
+ throw new CliError("invalid-data", "Media has no positive finite duration.");
252
+ }
253
+ return {
254
+ container: parsed.data.format.format_name.split(",")[0]!,
255
+ durationUs,
256
+ streams,
257
+ };
258
+ }
259
+
260
+ function importedStreamRole(
261
+ assetRole: z.infer<typeof ProjectAssetRoleSchema>,
262
+ streamKind: "audio" | "video",
263
+ ): string {
264
+ if (streamKind === "video") {
265
+ if (assetRole === "camera") return "camera";
266
+ if (assetRole === "b-roll") return "b-roll";
267
+ if (assetRole === "screen") return "screen";
268
+ return "other";
269
+ }
270
+ if (
271
+ assetRole === "system-audio"
272
+ || assetRole === "microphone"
273
+ || assetRole === "portable-audio"
274
+ || assetRole === "music"
275
+ || assetRole === "dialogue"
276
+ ) return assetRole;
277
+ return "other";
278
+ }
279
+
280
+ export interface IngestProjectMediaOptions {
281
+ /** @internal Injectable durability boundary for focused fault and ordering tests. */
282
+ readonly durability?: MediaIngestDurability;
283
+ readonly ffprobe: string;
284
+ readonly now: Date;
285
+ readonly projectDirectory: string;
286
+ readonly repositoryRoot: string;
287
+ readonly role: z.input<typeof ProjectAssetRoleSchema>;
288
+ readonly runner: ProcessRunner;
289
+ readonly sourcePath: string;
290
+ }
291
+
292
+ export interface IngestedProjectMedia {
293
+ readonly absolutePath: string;
294
+ readonly asset: ProjectAssetV1;
295
+ readonly created: boolean;
296
+ }
297
+
298
+ export interface StagedMediaImport {
299
+ readonly absolutePath: string;
300
+ readonly bytes: number;
301
+ readonly sha256: string;
302
+ readonly temporaryPath: string;
303
+ readonly importsDirectory: string;
304
+ readonly identity: Readonly<{ dev: number; ino: number }>;
305
+ }
306
+
307
+ export function importedAsset(
308
+ options: IngestProjectMediaOptions,
309
+ role: z.infer<typeof ProjectAssetRoleSchema>,
310
+ imported: StagedMediaImport,
311
+ probe: ProbedMedia,
312
+ path: z.infer<typeof RepositoryRelativePathSchema>,
313
+ label: string,
314
+ ): ProjectAssetV1 {
315
+ const assetId = `asset_${imported.sha256.slice(0, 24)}`;
316
+ const streams = probe.streams.map(stream => {
317
+ const base = {
318
+ label: `${stream.codec_type} stream ${stream.index}`,
319
+ segments: [{
320
+ assetRange: stream.assetRange,
321
+ bytes: imported.bytes,
322
+ codec: stream.codec_name,
323
+ container: probe.container,
324
+ fileRange: stream.fileRange,
325
+ path,
326
+ sha256: imported.sha256,
327
+ streamIndex: stream.index,
328
+ }],
329
+ streamId: `stream_${imported.sha256.slice(0, 20)}_${stream.index}`,
330
+ };
331
+ if (stream.codec_type === "video") {
332
+ return ProjectMediaStreamSchema.parse({
333
+ ...base,
334
+ frameRate: rational(stream.avg_frame_rate) ?? rational(stream.r_frame_rate)!,
335
+ kind: "video",
336
+ pixelHeight: stream.height,
337
+ pixelWidth: stream.width,
338
+ role: importedStreamRole(role, "video"),
339
+ });
340
+ }
341
+ return ProjectMediaStreamSchema.parse({
342
+ ...base,
343
+ channels: stream.channels,
344
+ kind: "audio",
345
+ role: importedStreamRole(role, "audio"),
346
+ sampleRateHz: Number(stream.sample_rate),
347
+ });
348
+ });
349
+ const asset = ProjectAssetV1Schema.parse({
350
+ assetId,
351
+ createdAt: options.now.toISOString(),
352
+ durationUs: probe.durationUs,
353
+ label: label,
354
+ role,
355
+ source: {
356
+ importedAt: options.now.toISOString(),
357
+ kind: "imported",
358
+ originalName: label,
359
+ sourceSha256: imported.sha256,
360
+ },
361
+ streams,
362
+ });
363
+ return asset;
364
+ }
@@ -0,0 +1,227 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { constants, createReadStream, type Stats } from "node:fs";
3
+ import { type FileHandle, lstat, link, mkdir, open, realpath, rm } from "node:fs/promises";
4
+ import { basename, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { pipeline } from "node:stream/promises";
6
+ import { Transform, Writable } from "node:stream";
7
+ import { Cause, Context, Effect, Layer } from "effect";
8
+ import {
9
+ operationBoundary, operationResource, operationValidation, type OperationEffectFailure,
10
+ } from "../application/operation-effects";
11
+ import { RepositoryRelativePathSchema } from "../contracts";
12
+ import { CliError } from "./errors";
13
+ import type { ProcessRunner, RunResult } from "./io";
14
+ import {
15
+ MAX_MEDIA_BYTES, MAX_PROBE_BYTES, MAX_PROBE_TIMEOUT_MS,
16
+ SELF_CONTAINED_MEDIA_INPUT_ARGUMENTS,
17
+ } from "./media-ingest-model";
18
+ import { ensurePrivateDirectory } from "./paths";
19
+ import { verifyPhysicalProjectMedia } from "./project-media-integrity";
20
+
21
+ export interface MediaIngestDurability {
22
+ syncDirectory(path: string): Promise<void>;
23
+ syncFile(handle: FileHandle, path: string): Promise<void>;
24
+ }
25
+
26
+ function isWithin(root: string, candidate: string): boolean {
27
+ const pathFromRoot = relative(root, candidate);
28
+ return pathFromRoot === "" || (!pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot));
29
+ }
30
+
31
+ type Native<A> = Effect.Effect<A, OperationEffectFailure>;
32
+ export interface MediaIngestPlatformService {
33
+ realpath(path: string): Native<string>;
34
+ sourcePath(path: string): Native<string>;
35
+ importsPath(root: string): Native<string>;
36
+ temporaryPath(imports: string, name: string): Native<string>;
37
+ destinationPath(imports: string, sha256: string): Native<string>;
38
+ assertWithin(root: string, candidate: string, message: string): Native<void>;
39
+ assetLocation(root: string, destination: string, source: string): Native<Readonly<{
40
+ path: ReturnType<typeof RepositoryRelativePathSchema.parse>; label: string;
41
+ }>>;
42
+ lstat(path: string): Native<Stats>;
43
+ ensurePrivateDirectory(path: string): Native<void>;
44
+ mkdir(path: string): Native<void>;
45
+ uniqueName(): Native<string>;
46
+ openSource(path: string): Native<FileHandle>;
47
+ openTemporary(path: string): Native<FileHandle>;
48
+ stat(handle: FileHandle): Native<Stats>;
49
+ close(handle: FileHandle): Native<void>;
50
+ copy(source: FileHandle, staged: FileHandle): Native<Readonly<{ bytes: number; sha256: string }>>;
51
+ syncFile(handle: FileHandle, path: string): Native<void>;
52
+ syncDirectory(path: string): Native<void>;
53
+ remove(path: string): Native<void>;
54
+ link(source: string, destination: string): Native<void>;
55
+ verify(path: string, expected: Readonly<{ bytes: number; sha256: string }>, label: string): Native<void>;
56
+ probe(ffprobe: string, runner: ProcessRunner, path: string): Native<RunResult>;
57
+ }
58
+
59
+ export class MediaIngestPlatform extends Context.Tag("@slopcamera/local/MediaIngestPlatform")<
60
+ MediaIngestPlatform, MediaIngestPlatformService
61
+ >() { }
62
+
63
+ /** Each admitted foreign call retains custody until its actual settlement. */
64
+ function native<A>(execute: () => A | Promise<A>): Native<A> {
65
+ return Effect.uninterruptible(operationBoundary("media", execute));
66
+ }
67
+
68
+ interface NativeStreamFailure {
69
+ readonly cause: unknown;
70
+ readonly signal: Error;
71
+ }
72
+
73
+ type CopyObservation =
74
+ | Readonly<{ success: true; bytes: number; sha256: string }>
75
+ | Readonly<{ success: false; cause: unknown; prior: readonly NativeStreamFailure[] }>;
76
+
77
+ function fileHandleReadStream(handle: FileHandle, recordFailure: (cause: unknown) => Error) {
78
+ let pending = Promise.resolve();
79
+ // The built-in stream owns backpressure, but borrows the numeric descriptor.
80
+ // FileHandle.createReadStream adds a second close/ref owner on pinned Bun.
81
+ // With an fd supplied, the empty path is never opened.
82
+ const stream = createReadStream("", {
83
+ fd: handle.fd, autoClose: false, highWaterMark: 65_536,
84
+ fs: {
85
+ read(_fd: number, buffer: Buffer, offset: number, length: number, position: number | null, callback: (error: Error | null, bytesRead: number, buffer: Buffer) => void) {
86
+ pending = handle.read(buffer, offset, length, position).then(
87
+ result => callback(null, result.bytesRead, result.buffer),
88
+ (cause: unknown) => callback(recordFailure(cause), 0, buffer),
89
+ );
90
+ },
91
+ close(_fd: number, callback: (error: Error | null) => void) { callback(null); },
92
+ },
93
+ });
94
+ return { stream, settle: async () => { await pending; } };
95
+ }
96
+
97
+ function fileHandleWriteStream(handle: FileHandle, recordFailure: (cause: unknown) => Error): {
98
+ readonly stream: Writable;
99
+ readonly settle: () => Promise<void>;
100
+ } {
101
+ let position = 0;
102
+ let pending = Promise.resolve();
103
+ const stream = new Writable({
104
+ write(chunk: Buffer, _encoding, callback) {
105
+ const writeAll = async (): Promise<void> => {
106
+ let offset = 0;
107
+ while (offset < chunk.byteLength) {
108
+ const result = await handle.write(chunk, offset, chunk.byteLength - offset, position + offset);
109
+ if (result.bytesWritten <= 0) {
110
+ throw new Error("Media import made no progress while writing its staged file.");
111
+ }
112
+ offset += result.bytesWritten;
113
+ }
114
+ position += chunk.byteLength;
115
+ };
116
+ pending = writeAll().then(() => callback(), (cause: unknown) => callback(recordFailure(cause)));
117
+ },
118
+ });
119
+ return { stream, settle: async () => { await pending; } };
120
+ }
121
+
122
+ async function copyMedia(source: FileHandle, staged: FileHandle): Promise<CopyObservation> {
123
+ const hash = createHash("sha256");
124
+ let bytes = 0;
125
+ const failures: NativeStreamFailure[] = [];
126
+ const recordFailure = (cause: unknown): Error => {
127
+ // Callback streams interpret falsey errors as success. Keep rejection
128
+ // presence separately from the real Error used only as a stream signal.
129
+ const signal = cause instanceof Error ? cause : new Error("Native media I/O failed.");
130
+ failures.push({ cause, signal });
131
+ return signal;
132
+ };
133
+ const reader = fileHandleReadStream(source, recordFailure);
134
+ const writer = fileHandleWriteStream(staged, recordFailure);
135
+ const result = await pipeline(
136
+ reader.stream,
137
+ new Transform({
138
+ transform(chunk: Buffer, _encoding, callback) {
139
+ bytes += chunk.byteLength;
140
+ if (bytes > MAX_MEDIA_BYTES) return callback(new Error("Media source exceeded its size bound while reading."));
141
+ hash.update(chunk);
142
+ callback(null, chunk);
143
+ },
144
+ }),
145
+ writer.stream,
146
+ ).then(() => ({ success: true as const }), (cause: unknown) => ({ success: false as const, cause }));
147
+ // Destruction stops new stream admission, not already-issued native I/O.
148
+ // Join both before the operation owner closes descriptors or removes staging.
149
+ await Promise.all([reader.settle(), writer.settle()]);
150
+ if (!result.success) {
151
+ const selected = failures.find(failure => result.cause === failure.signal);
152
+ return {
153
+ success: false, cause: selected === undefined ? result.cause : selected.cause,
154
+ prior: failures.filter(failure => failure !== selected),
155
+ };
156
+ }
157
+ const [first, ...prior] = failures;
158
+ if (first !== undefined) return { success: false, cause: first.cause, prior };
159
+ return { success: true, bytes, sha256: hash.digest("hex") };
160
+ }
161
+
162
+ export function createMediaIngestPlatform(durability?: MediaIngestDurability): MediaIngestPlatformService {
163
+ return {
164
+ realpath: path => native(() => realpath(path)),
165
+ sourcePath: path => operationValidation("media", () => resolve(path)),
166
+ importsPath: root => operationValidation("media", () => join(root, "imports")),
167
+ temporaryPath: (imports, name) => operationValidation("media", () => join(imports, name)),
168
+ destinationPath: (imports, sha256) => operationValidation("media", () => join(imports, `${sha256}.media`)),
169
+ assertWithin: (root, candidate, message) => operationValidation("media", () => {
170
+ if (!isWithin(root, candidate)) throw new CliError("unsafe-path", message);
171
+ }),
172
+ assetLocation: (root, destination, source) => operationValidation("media", () => {
173
+ const repositoryPath = relative(root, destination);
174
+ if (!isWithin(root, destination) || isAbsolute(repositoryPath)) {
175
+ throw new CliError("unsafe-path", "Imported media destination is outside the repository.");
176
+ }
177
+ return { path: RepositoryRelativePathSchema.parse(repositoryPath), label: basename(source) };
178
+ }),
179
+ lstat: path => native(() => lstat(path)),
180
+ ensurePrivateDirectory: path => native(() => ensurePrivateDirectory(path)),
181
+ mkdir: path => native(() => mkdir(path, { mode: 0o700 })),
182
+ uniqueName: () => operationValidation("media", () => `.import-${randomUUID()}.tmp`),
183
+ openSource: path => native(() => open(path, constants.O_RDONLY | constants.O_NOFOLLOW)),
184
+ openTemporary: path => native(() => open(path, constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_WRONLY, 0o600)),
185
+ stat: handle => native(() => handle.stat()),
186
+ close: handle => native(() => handle.close()),
187
+ copy: (source, staged) => native(() => copyMedia(source, staged)).pipe(Effect.flatMap(result => result.success
188
+ ? Effect.succeed({ bytes: result.bytes, sha256: result.sha256 })
189
+ : Effect.fail({
190
+ _tag: "OperationBoundaryFailure" as const,
191
+ phase: "media" as const,
192
+ cause: result.cause,
193
+ ...(result.prior.length === 0 ? {} : { priorCause: result.prior.reduce<Cause.Cause<OperationEffectFailure>>(
194
+ (prior, failure) => Cause.sequential(prior, Cause.fail({
195
+ _tag: "OperationBoundaryFailure", phase: "media", cause: failure.cause,
196
+ })), Cause.empty,
197
+ ) }),
198
+ }))),
199
+ syncFile: (handle, path) => native(() => durability === undefined ? handle.sync() : durability.syncFile(handle, path)),
200
+ syncDirectory: path => durability === undefined
201
+ ? operationResource(
202
+ native(() => open(path, constants.O_RDONLY | constants.O_NOFOLLOW)),
203
+ directory => Effect.gen(function*() {
204
+ const details = yield* native(() => directory.stat());
205
+ yield* operationValidation("media", () => {
206
+ if (!details.isDirectory()) throw new CliError("unsafe-path", `Media durability path is not a physical directory: ${path}`);
207
+ });
208
+ yield* native(() => directory.sync());
209
+ }),
210
+ directory => native(() => directory.close()),
211
+ )
212
+ : native(() => durability.syncDirectory(path)),
213
+ remove: path => native(() => rm(path, { force: true })),
214
+ link: (source, destination) => native(() => link(source, destination)),
215
+ verify: (path, expected, label) => native(() => verifyPhysicalProjectMedia(path, expected, label)),
216
+ probe: (ffprobe, runner, path) => native(() => runner.run([
217
+ ffprobe,
218
+ "-v", "error",
219
+ ...SELF_CONTAINED_MEDIA_INPUT_ARGUMENTS,
220
+ "-show_entries", "format=duration,format_name,start_time:stream=index,codec_type,codec_name,width,height,avg_frame_rate,r_frame_rate,sample_rate,channels,start_time,duration:stream_disposition=attached_pic,still_image,timed_thumbnails:stream_tags=duration",
221
+ "-of", "json", path,
222
+ ], { maxOutputBytes: MAX_PROBE_BYTES, timeoutMs: MAX_PROBE_TIMEOUT_MS })),
223
+ };
224
+ }
225
+
226
+ export const MediaIngestPlatformLive = (durability?: MediaIngestDurability): Layer.Layer<MediaIngestPlatform> =>
227
+ Layer.succeed(MediaIngestPlatform, createMediaIngestPlatform(durability));