@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,723 @@
1
+ import { constants } from "node:fs";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import {
4
+ chmod,
5
+ lstat,
6
+ mkdir,
7
+ open,
8
+ rename,
9
+ rm,
10
+ } from "node:fs/promises";
11
+ import { basename, isAbsolute, join, relative, sep } from "node:path";
12
+
13
+ import { canonicalJson } from "../core/canonical-json";
14
+ import type {
15
+ GatewayJsonValue,
16
+ GatewayMediaKind,
17
+ } from "./gateway-media-catalog";
18
+ import { gatewayMediaBytesMatchType } from "./gateway-media-signature";
19
+
20
+ const MAXIMUM_GENERATED_FILE_BYTES = 512 * 1024 * 1024;
21
+ const MAXIMUM_GENERATED_TOTAL_BYTES = 1024 * 1024 * 1024;
22
+ const MAXIMUM_OUTPUTS = 32;
23
+ const MAXIMUM_RECEIPT_BYTES = 256 * 1024;
24
+
25
+ export interface GatewayGeneratedFile {
26
+ readonly mediaType: string;
27
+ readonly uint8Array: Uint8Array;
28
+ }
29
+
30
+ export interface GatewayMediaInputDigest {
31
+ readonly bytes: number;
32
+ readonly mediaType: string;
33
+ readonly role: string;
34
+ readonly sha256: string;
35
+ readonly source: "inline" | "url";
36
+ }
37
+
38
+ export interface GatewayMediaReceiptOutput {
39
+ readonly bytes: number;
40
+ readonly file: string;
41
+ readonly mediaType: string;
42
+ readonly sha256: string;
43
+ }
44
+
45
+ export interface GatewayMediaRoutingReceipt {
46
+ readonly attemptCount: number;
47
+ readonly attempts: readonly Readonly<{
48
+ readonly configuredTimeoutMs?: number;
49
+ readonly credentialType?: string;
50
+ readonly error?: string;
51
+ readonly model?: string;
52
+ readonly provider?: string;
53
+ readonly providerTimeout?: boolean;
54
+ readonly statusCode?: number;
55
+ readonly success?: boolean;
56
+ }>[];
57
+ readonly attemptsTruncated: boolean;
58
+ readonly clientMaxRetries: 0;
59
+ readonly gatewayProviderFailover: "may-attempt-multiple-providers";
60
+ readonly generationId?: string;
61
+ readonly providerCount: number;
62
+ readonly providers: readonly string[];
63
+ readonly providersTruncated: boolean;
64
+ }
65
+
66
+ export interface GatewayMediaReceipt {
67
+ readonly catalog: Readonly<{
68
+ snapshotId: string;
69
+ status: "fresh" | "stale";
70
+ }>;
71
+ readonly createdAt: string;
72
+ readonly inputs: readonly GatewayMediaInputDigest[];
73
+ readonly kind:
74
+ | "slopcamera.gateway-media-receipt"
75
+ | "studio.gateway-media-receipt";
76
+ readonly localValidation: Readonly<{
77
+ readonly decodeValidatedOutputs: number;
78
+ readonly failureSha256?: string;
79
+ readonly signatureOnlyOutputs: number;
80
+ readonly status: "decode-failed" | "decode-passed" | "signature-only";
81
+ }>;
82
+ readonly model: string;
83
+ readonly nextCommands: readonly string[];
84
+ readonly operation: "image.generate" | "speech.generate" | "transcription.create" | "video.generate";
85
+ readonly outputs: readonly GatewayMediaReceiptOutput[];
86
+ readonly request: Readonly<Record<string, GatewayJsonValue>>;
87
+ readonly routing: GatewayMediaRoutingReceipt;
88
+ readonly sampleFulfillment?: Readonly<{
89
+ readonly produced: number;
90
+ readonly requested: number;
91
+ readonly status: "complete" | "overproduced" | "partial";
92
+ }>;
93
+ readonly schemaVersion: 1;
94
+ readonly warnings: readonly string[];
95
+ }
96
+
97
+ export interface GatewayMediaArtifactBundle {
98
+ readonly directory: string;
99
+ readonly outputs: readonly Readonly<GatewayMediaReceiptOutput & { readonly path: string }>[];
100
+ readonly receipt: GatewayMediaReceipt;
101
+ readonly receiptPath: string;
102
+ }
103
+
104
+ export interface GatewayMediaArtifactStore {
105
+ commit(input: Readonly<{
106
+ catalog: GatewayMediaReceipt["catalog"];
107
+ createdAt: string;
108
+ files: readonly GatewayGeneratedFile[];
109
+ inputs: readonly GatewayMediaInputDigest[];
110
+ mediaKind: GatewayMediaKind;
111
+ model: string;
112
+ operation: GatewayMediaReceipt["operation"];
113
+ request: Readonly<Record<string, GatewayJsonValue>>;
114
+ routing: GatewayMediaRoutingReceipt;
115
+ signal: AbortSignal;
116
+ sampleFulfillment?: Readonly<{
117
+ readonly produced: number;
118
+ readonly requested: number;
119
+ }>;
120
+ warnings: readonly string[];
121
+ }>): Promise<GatewayMediaArtifactBundle>;
122
+ }
123
+
124
+ export type GatewayMediaArtifactErrorCode =
125
+ | "artifact-invalid"
126
+ | "artifact-unavailable"
127
+ | "output-too-large"
128
+ | "unsafe-output";
129
+
130
+ const ERROR_MESSAGES: Readonly<Record<GatewayMediaArtifactErrorCode, string>> = {
131
+ "artifact-invalid": "The generated Gateway media output is invalid.",
132
+ "artifact-unavailable": "The generated Gateway media output could not be saved.",
133
+ "output-too-large": "The generated Gateway media output exceeds its local size limit.",
134
+ "unsafe-output": "The Gateway media output directory is unsafe.",
135
+ };
136
+
137
+ export class GatewayMediaArtifactError extends Error {
138
+ readonly code: GatewayMediaArtifactErrorCode;
139
+
140
+ constructor(code: GatewayMediaArtifactErrorCode) {
141
+ super(ERROR_MESSAGES[code]);
142
+ this.code = code;
143
+ this.name = "GatewayMediaArtifactError";
144
+ }
145
+ }
146
+
147
+ function canonicalTimestamp(value: string): string {
148
+ const milliseconds = Date.parse(value);
149
+ if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== value) {
150
+ throw new GatewayMediaArtifactError("artifact-invalid");
151
+ }
152
+ return value;
153
+ }
154
+
155
+ function validateMediaType(value: string): string {
156
+ if (
157
+ value.length < 3
158
+ || value.length > 128
159
+ || !/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/iu.test(value)
160
+ ) {
161
+ throw new GatewayMediaArtifactError("artifact-invalid");
162
+ }
163
+ return value.toLocaleLowerCase("en-US");
164
+ }
165
+
166
+ function extensionForMediaType(mediaType: string): string {
167
+ const extensions: Readonly<Record<string, string>> = {
168
+ "audio/aac": ".aac",
169
+ "audio/aiff": ".aiff",
170
+ "audio/alaw": ".alaw",
171
+ "audio/basic": ".au",
172
+ "audio/flac": ".flac",
173
+ "audio/l16": ".pcm",
174
+ "audio/m4a": ".m4a",
175
+ "audio/mp4": ".m4a",
176
+ "audio/mpeg": ".mp3",
177
+ "audio/mulaw": ".mulaw",
178
+ "audio/ogg": ".ogg",
179
+ "audio/opus": ".opus",
180
+ "audio/pcm": ".pcm",
181
+ "audio/wav": ".wav",
182
+ "audio/webm": ".weba",
183
+ "audio/x-wav": ".wav",
184
+ "application/json": ".json",
185
+ "application/x-subrip": ".srt",
186
+ "image/avif": ".avif",
187
+ "image/bmp": ".bmp",
188
+ "image/gif": ".gif",
189
+ "image/jpeg": ".jpg",
190
+ "image/png": ".png",
191
+ "image/svg+xml": ".svg",
192
+ "image/tiff": ".tiff",
193
+ "image/webp": ".webp",
194
+ "text/plain": ".txt",
195
+ "text/vtt": ".vtt",
196
+ "video/mp4": ".mp4",
197
+ "video/mpeg": ".mpeg",
198
+ "video/quicktime": ".mov",
199
+ "video/webm": ".webm",
200
+ "video/x-matroska": ".mkv",
201
+ "video/x-msvideo": ".avi",
202
+ };
203
+ return extensions[mediaType] ?? ".bin";
204
+ }
205
+
206
+ function boundedWarning(value: string): string {
207
+ const normalized = [...value].map((character) => {
208
+ const codePoint = character.codePointAt(0);
209
+ return codePoint !== undefined && (
210
+ codePoint <= 8
211
+ || codePoint === 11
212
+ || codePoint === 12
213
+ || (codePoint >= 14 && codePoint <= 31)
214
+ || codePoint === 127
215
+ )
216
+ ? " "
217
+ : character;
218
+ }).join("");
219
+ return normalized.slice(0, 2_000);
220
+ }
221
+
222
+ function validateDigest(value: GatewayMediaInputDigest): GatewayMediaInputDigest {
223
+ if (
224
+ !Number.isSafeInteger(value.bytes)
225
+ || value.bytes < 0
226
+ || !/^[a-f0-9]{64}$/u.test(value.sha256)
227
+ || value.role.length < 1
228
+ || value.role.length > 128
229
+ || (value.source !== "inline" && value.source !== "url")
230
+ || (value.source === "inline" && value.bytes < 1)
231
+ || (value.source === "url" && value.bytes !== 0)
232
+ ) {
233
+ throw new GatewayMediaArtifactError("artifact-invalid");
234
+ }
235
+ return {
236
+ bytes: value.bytes,
237
+ mediaType: validateMediaType(value.mediaType),
238
+ role: value.role,
239
+ sha256: value.sha256,
240
+ source: value.source,
241
+ };
242
+ }
243
+
244
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
245
+ return typeof value === "object" && value !== null && !Array.isArray(value);
246
+ }
247
+
248
+ function validateRouting(value: unknown): GatewayMediaRoutingReceipt {
249
+ const boundedString = (candidate: unknown): string | undefined => {
250
+ if (candidate === undefined) return undefined;
251
+ if (
252
+ typeof candidate !== "string"
253
+ || candidate.length < 1
254
+ || candidate.length > 256
255
+ ) {
256
+ throw new GatewayMediaArtifactError("artifact-invalid");
257
+ }
258
+ return boundedWarning(candidate);
259
+ };
260
+ if (!isRecord(value)) {
261
+ throw new GatewayMediaArtifactError("artifact-invalid");
262
+ }
263
+ const attemptsValue = value.attempts;
264
+ const providersValue = value.providers;
265
+ if (
266
+ !Array.isArray(attemptsValue)
267
+ || attemptsValue.length > 32
268
+ || !Number.isSafeInteger(value.attemptCount)
269
+ || (value.attemptCount as number) < attemptsValue.length
270
+ || (value.attemptCount as number) > 1_000_000
271
+ || typeof value.attemptsTruncated !== "boolean"
272
+ || value.attemptsTruncated
273
+ !== ((value.attemptCount as number) !== attemptsValue.length)
274
+ || value.clientMaxRetries !== 0
275
+ || value.gatewayProviderFailover !== "may-attempt-multiple-providers"
276
+ || !Array.isArray(providersValue)
277
+ || providersValue.length > 32
278
+ || !Number.isSafeInteger(value.providerCount)
279
+ || (value.providerCount as number) < providersValue.length
280
+ || (value.providerCount as number) > 1_000_000
281
+ || typeof value.providersTruncated !== "boolean"
282
+ || value.providersTruncated
283
+ !== ((value.providerCount as number) !== providersValue.length)
284
+ ) {
285
+ throw new GatewayMediaArtifactError("artifact-invalid");
286
+ }
287
+ const providers = providersValue.map((provider) => {
288
+ const parsed = boundedString(provider);
289
+ if (parsed === undefined) {
290
+ throw new GatewayMediaArtifactError("artifact-invalid");
291
+ }
292
+ return parsed;
293
+ });
294
+ const attempts = attemptsValue.map((candidate) => {
295
+ if (!isRecord(candidate)) {
296
+ throw new GatewayMediaArtifactError("artifact-invalid");
297
+ }
298
+ const configuredTimeoutMs = candidate.configuredTimeoutMs;
299
+ const credentialType = boundedString(candidate.credentialType);
300
+ const error = boundedString(candidate.error);
301
+ const model = boundedString(candidate.model);
302
+ const provider = boundedString(candidate.provider);
303
+ const statusCode = candidate.statusCode;
304
+ if (
305
+ (
306
+ configuredTimeoutMs !== undefined
307
+ && (
308
+ !Number.isSafeInteger(configuredTimeoutMs)
309
+ || (configuredTimeoutMs as number) < 0
310
+ )
311
+ )
312
+ || (
313
+ candidate.credentialType !== undefined
314
+ && credentialType === undefined
315
+ )
316
+ || (
317
+ candidate.error !== undefined
318
+ && (
319
+ error === undefined
320
+ || !/^sha256:[a-f0-9]{64}$/u.test(error)
321
+ )
322
+ )
323
+ || (candidate.model !== undefined && model === undefined)
324
+ || (candidate.provider !== undefined && provider === undefined)
325
+ || (
326
+ candidate.providerTimeout !== undefined
327
+ && typeof candidate.providerTimeout !== "boolean"
328
+ )
329
+ || (
330
+ statusCode !== undefined
331
+ && (
332
+ !Number.isSafeInteger(statusCode)
333
+ || (statusCode as number) < 0
334
+ || (statusCode as number) > 999
335
+ )
336
+ )
337
+ || (
338
+ candidate.success !== undefined
339
+ && typeof candidate.success !== "boolean"
340
+ )
341
+ ) {
342
+ throw new GatewayMediaArtifactError("artifact-invalid");
343
+ }
344
+ return {
345
+ ...(configuredTimeoutMs === undefined
346
+ ? {}
347
+ : { configuredTimeoutMs: configuredTimeoutMs as number }),
348
+ ...(credentialType === undefined ? {} : { credentialType }),
349
+ ...(error === undefined ? {} : { error }),
350
+ ...(model === undefined ? {} : { model }),
351
+ ...(provider === undefined ? {} : { provider }),
352
+ ...(candidate.providerTimeout === undefined
353
+ ? {}
354
+ : { providerTimeout: candidate.providerTimeout }),
355
+ ...(statusCode === undefined
356
+ ? {}
357
+ : { statusCode: statusCode as number }),
358
+ ...(candidate.success === undefined
359
+ ? {}
360
+ : { success: candidate.success }),
361
+ };
362
+ });
363
+ const generationId = boundedString(value.generationId);
364
+ if (value.generationId !== undefined && generationId === undefined) {
365
+ throw new GatewayMediaArtifactError("artifact-invalid");
366
+ }
367
+ return {
368
+ attemptCount: value.attemptCount as number,
369
+ attempts,
370
+ attemptsTruncated: value.attemptsTruncated,
371
+ clientMaxRetries: 0,
372
+ gatewayProviderFailover: "may-attempt-multiple-providers",
373
+ ...(generationId === undefined ? {} : { generationId }),
374
+ providerCount: value.providerCount as number,
375
+ providers: [...new Set(providers)],
376
+ providersTruncated: value.providersTruncated,
377
+ };
378
+ }
379
+
380
+ async function syncDirectory(path: string): Promise<void> {
381
+ const handle = await open(
382
+ path,
383
+ constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0),
384
+ );
385
+ try {
386
+ await handle.sync();
387
+ } finally {
388
+ await handle.close();
389
+ }
390
+ }
391
+
392
+ async function writePrivateFile(
393
+ path: string,
394
+ body: Uint8Array | string,
395
+ ): Promise<void> {
396
+ const handle = await open(
397
+ path,
398
+ constants.O_CREAT
399
+ | constants.O_EXCL
400
+ | constants.O_WRONLY
401
+ | (constants.O_NOFOLLOW ?? 0),
402
+ 0o600,
403
+ );
404
+ try {
405
+ await handle.writeFile(body);
406
+ await handle.sync();
407
+ } finally {
408
+ await handle.close();
409
+ }
410
+ }
411
+
412
+ function operationStem(operation: GatewayMediaReceipt["operation"]): string {
413
+ switch (operation) {
414
+ case "image.generate":
415
+ return "image";
416
+ case "speech.generate":
417
+ return "speech";
418
+ case "transcription.create":
419
+ return "transcript";
420
+ case "video.generate":
421
+ return "video";
422
+ }
423
+ }
424
+
425
+ function shellArgument(value: string): string {
426
+ return /^[A-Za-z0-9_./:@%+=,-]+$/u.test(value)
427
+ ? value
428
+ : `'${value.replaceAll("'", "'\"'\"'")}'`;
429
+ }
430
+
431
+ const NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES = new Set([
432
+ "audio/alaw",
433
+ "audio/basic",
434
+ "audio/l16",
435
+ "audio/mulaw",
436
+ "audio/pcm",
437
+ ]);
438
+
439
+ export function gatewayMediaTypeIsSelfDescribing(
440
+ mediaType: string,
441
+ ): boolean {
442
+ return /^(?:audio|image|video)\//u.test(mediaType)
443
+ && !NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES.has(mediaType);
444
+ }
445
+
446
+ function nextCommands(
447
+ operation: GatewayMediaReceipt["operation"],
448
+ finalDirectory: string,
449
+ outputs: readonly GatewayMediaReceiptOutput[],
450
+ repositoryRoot: string,
451
+ ): readonly string[] {
452
+ const role = operation === "speech.generate"
453
+ ? "dialogue"
454
+ : operation === "transcription.create"
455
+ ? undefined
456
+ : "b-roll";
457
+ return role === undefined
458
+ ? []
459
+ : outputs
460
+ .filter(output => (
461
+ operation !== "speech.generate"
462
+ || !NON_SELF_DESCRIBING_SPEECH_MEDIA_TYPES.has(output.mediaType)
463
+ ))
464
+ .map(output => (
465
+ `slopcamera project add <project> ${
466
+ shellArgument(
467
+ relative(
468
+ repositoryRoot,
469
+ join(finalDirectory, output.file),
470
+ ).split(sep).join("/"),
471
+ )
472
+ } --role ${role}`
473
+ ));
474
+ }
475
+
476
+ function runDirectoryName(
477
+ operation: GatewayMediaReceipt["operation"],
478
+ createdAt: string,
479
+ randomId: string,
480
+ ): string {
481
+ return `${createdAt.replace(/[-:.]/gu, "").replace("Z", "Z")}-${operationStem(operation)}-${randomId}`;
482
+ }
483
+
484
+ function validateFile(
485
+ file: GatewayGeneratedFile,
486
+ ): Readonly<GatewayGeneratedFile & { readonly mediaType: string }> {
487
+ if (!(file.uint8Array instanceof Uint8Array) || file.uint8Array.byteLength < 1) {
488
+ throw new GatewayMediaArtifactError("artifact-invalid");
489
+ }
490
+ if (file.uint8Array.byteLength > MAXIMUM_GENERATED_FILE_BYTES) {
491
+ throw new GatewayMediaArtifactError("output-too-large");
492
+ }
493
+ const mediaType = validateMediaType(file.mediaType);
494
+ if (
495
+ /^(?:audio|image|video)\//u.test(mediaType)
496
+ && !gatewayMediaBytesMatchType(file.uint8Array, mediaType)
497
+ ) {
498
+ throw new GatewayMediaArtifactError("artifact-invalid");
499
+ }
500
+ return {
501
+ mediaType,
502
+ uint8Array: file.uint8Array,
503
+ };
504
+ }
505
+
506
+ function sampleFulfillment(
507
+ value: Readonly<{ readonly produced: number; readonly requested: number }>,
508
+ ): NonNullable<GatewayMediaReceipt["sampleFulfillment"]> {
509
+ if (
510
+ !Number.isSafeInteger(value.requested)
511
+ || value.requested < 1
512
+ || value.requested > MAXIMUM_OUTPUTS
513
+ || !Number.isSafeInteger(value.produced)
514
+ || value.produced < 1
515
+ || value.produced > MAXIMUM_OUTPUTS
516
+ ) {
517
+ throw new GatewayMediaArtifactError("artifact-invalid");
518
+ }
519
+ return {
520
+ produced: value.produced,
521
+ requested: value.requested,
522
+ status: value.produced === value.requested
523
+ ? "complete"
524
+ : value.produced < value.requested
525
+ ? "partial"
526
+ : "overproduced",
527
+ };
528
+ }
529
+
530
+ export function createFileGatewayMediaArtifactStore(options: Readonly<{
531
+ beforePublication?: () => Promise<void>;
532
+ outputRoot: string;
533
+ randomId?: () => string;
534
+ repositoryRoot: string;
535
+ validateMediaFile?: (
536
+ file: Readonly<GatewayGeneratedFile & {
537
+ readonly path: string;
538
+ }>,
539
+ signal: AbortSignal,
540
+ ) => Promise<void>;
541
+ }>): GatewayMediaArtifactStore {
542
+ if (!isAbsolute(options.outputRoot) || !isAbsolute(options.repositoryRoot)) {
543
+ throw new GatewayMediaArtifactError("unsafe-output");
544
+ }
545
+ const outputRelative = relative(options.repositoryRoot, options.outputRoot);
546
+ if (
547
+ outputRelative.length === 0
548
+ || outputRelative === ".."
549
+ || outputRelative.startsWith(`..${sep}`)
550
+ || isAbsolute(outputRelative)
551
+ ) {
552
+ throw new GatewayMediaArtifactError("unsafe-output");
553
+ }
554
+ const randomId = options.randomId ?? (() => randomUUID().slice(0, 12));
555
+ return {
556
+ commit: async (input) => {
557
+ input.signal.throwIfAborted();
558
+ if (input.files.length < 1 || input.files.length > MAXIMUM_OUTPUTS) {
559
+ throw new GatewayMediaArtifactError("artifact-invalid");
560
+ }
561
+ const files = input.files.map(validateFile);
562
+ const totalBytes = files.reduce((sum, file) => sum + file.uint8Array.byteLength, 0);
563
+ if (totalBytes > MAXIMUM_GENERATED_TOTAL_BYTES) {
564
+ throw new GatewayMediaArtifactError("output-too-large");
565
+ }
566
+ const createdAt = canonicalTimestamp(input.createdAt);
567
+ const identifier = randomId();
568
+ if (!/^[a-z0-9_-]{4,64}$/iu.test(identifier)) {
569
+ throw new GatewayMediaArtifactError("artifact-invalid");
570
+ }
571
+ const directoryName = runDirectoryName(input.operation, createdAt, identifier);
572
+ const finalDirectory = join(options.outputRoot, directoryName);
573
+ const stagingDirectory = join(options.outputRoot, `.${directoryName}.tmp`);
574
+ let published = false;
575
+ const receiptOutputs: GatewayMediaReceiptOutput[] = [];
576
+ const stagedFiles: Readonly<GatewayGeneratedFile & {
577
+ readonly path: string;
578
+ }>[] = [];
579
+ try {
580
+ await mkdir(options.outputRoot, { mode: 0o700, recursive: true });
581
+ const rootDetails = await lstat(options.outputRoot);
582
+ if (rootDetails.isSymbolicLink() || !rootDetails.isDirectory()) {
583
+ throw new GatewayMediaArtifactError("unsafe-output");
584
+ }
585
+ await chmod(options.outputRoot, 0o700);
586
+ input.signal.throwIfAborted();
587
+ await mkdir(stagingDirectory, { mode: 0o700 });
588
+ for (const [index, file] of files.entries()) {
589
+ input.signal.throwIfAborted();
590
+ const fileName = `${operationStem(input.operation)}-${String(index + 1).padStart(2, "0")}${extensionForMediaType(file.mediaType)}`;
591
+ const outputPath = join(stagingDirectory, fileName);
592
+ await writePrivateFile(outputPath, file.uint8Array);
593
+ stagedFiles.push({
594
+ mediaType: file.mediaType,
595
+ path: outputPath,
596
+ uint8Array: file.uint8Array,
597
+ });
598
+ receiptOutputs.push({
599
+ bytes: file.uint8Array.byteLength,
600
+ file: fileName,
601
+ mediaType: file.mediaType,
602
+ sha256: createHash("sha256").update(file.uint8Array).digest("hex"),
603
+ });
604
+ input.signal.throwIfAborted();
605
+ }
606
+ const decodeCandidates = stagedFiles.filter(file => (
607
+ gatewayMediaTypeIsSelfDescribing(file.mediaType)
608
+ ));
609
+ let localValidation: GatewayMediaReceipt["localValidation"];
610
+ if (
611
+ decodeCandidates.length === 0
612
+ || options.validateMediaFile === undefined
613
+ ) {
614
+ localValidation = {
615
+ decodeValidatedOutputs: 0,
616
+ signatureOnlyOutputs: stagedFiles.length,
617
+ status: "signature-only",
618
+ };
619
+ } else {
620
+ try {
621
+ for (const file of decodeCandidates) {
622
+ input.signal.throwIfAborted();
623
+ await options.validateMediaFile(file, input.signal);
624
+ input.signal.throwIfAborted();
625
+ }
626
+ localValidation = {
627
+ decodeValidatedOutputs: decodeCandidates.length,
628
+ signatureOnlyOutputs:
629
+ stagedFiles.length - decodeCandidates.length,
630
+ status: "decode-passed",
631
+ };
632
+ } catch (error) {
633
+ input.signal.throwIfAborted();
634
+ const failureSource = error instanceof Error
635
+ ? `${error.name}\u0000${error.message}`
636
+ : typeof error === "string"
637
+ ? error
638
+ : typeof error;
639
+ localValidation = {
640
+ decodeValidatedOutputs: 0,
641
+ failureSha256: createHash("sha256")
642
+ .update(failureSource, "utf8")
643
+ .digest("hex"),
644
+ signatureOnlyOutputs:
645
+ stagedFiles.length - decodeCandidates.length,
646
+ status: "decode-failed",
647
+ };
648
+ }
649
+ }
650
+ input.signal.throwIfAborted();
651
+ const receipt: GatewayMediaReceipt = {
652
+ catalog: input.catalog,
653
+ createdAt,
654
+ inputs: input.inputs.map(validateDigest),
655
+ kind: "slopcamera.gateway-media-receipt",
656
+ localValidation,
657
+ model: input.model,
658
+ nextCommands: localValidation.status === "decode-passed"
659
+ ? nextCommands(
660
+ input.operation,
661
+ finalDirectory,
662
+ receiptOutputs,
663
+ options.repositoryRoot,
664
+ )
665
+ : [],
666
+ operation: input.operation,
667
+ outputs: receiptOutputs,
668
+ request: input.request,
669
+ routing: validateRouting(input.routing),
670
+ ...(input.sampleFulfillment === undefined
671
+ ? {}
672
+ : {
673
+ sampleFulfillment: sampleFulfillment(
674
+ input.sampleFulfillment,
675
+ ),
676
+ }),
677
+ schemaVersion: 1,
678
+ warnings: input.warnings.slice(0, 100).map(boundedWarning),
679
+ };
680
+ let receiptSource: string;
681
+ try {
682
+ receiptSource = `${canonicalJson(receipt)}\n`;
683
+ } catch {
684
+ throw new GatewayMediaArtifactError("artifact-invalid");
685
+ }
686
+ if (new TextEncoder().encode(receiptSource).byteLength > MAXIMUM_RECEIPT_BYTES) {
687
+ throw new GatewayMediaArtifactError("artifact-invalid");
688
+ }
689
+ await writePrivateFile(join(stagingDirectory, "receipt.json"), receiptSource);
690
+ await syncDirectory(stagingDirectory);
691
+ input.signal.throwIfAborted();
692
+ await options.beforePublication?.();
693
+ input.signal.throwIfAborted();
694
+ await rename(stagingDirectory, finalDirectory);
695
+ published = true;
696
+ await syncDirectory(options.outputRoot);
697
+ return {
698
+ directory: finalDirectory,
699
+ outputs: receiptOutputs.map(output => ({
700
+ ...output,
701
+ path: join(finalDirectory, output.file),
702
+ })),
703
+ receipt,
704
+ receiptPath: join(finalDirectory, "receipt.json"),
705
+ };
706
+ } catch (error) {
707
+ if (!published) input.signal.throwIfAborted();
708
+ if (error instanceof GatewayMediaArtifactError) throw error;
709
+ throw new GatewayMediaArtifactError("artifact-unavailable");
710
+ } finally {
711
+ await rm(stagingDirectory, { force: true, recursive: true }).catch(() => undefined);
712
+ }
713
+ },
714
+ };
715
+ }
716
+
717
+ export function displayGatewayArtifactFile(
718
+ bundle: GatewayMediaArtifactBundle,
719
+ file: string,
720
+ ): string {
721
+ const name = basename(file);
722
+ return bundle.outputs.find(output => output.file === name)?.path ?? file;
723
+ }