@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,747 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { constants, type Stats } from "node:fs";
3
+ import {
4
+ lstat,
5
+ link,
6
+ mkdir,
7
+ open,
8
+ realpath,
9
+ rm,
10
+ } from "node:fs/promises";
11
+ import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
12
+ import type { OverlayKind } from "./args";
13
+ import { EMOJI_GENERATION_COMMAND, type ResolvedEmojiAsset } from "./emoji-assets";
14
+ import { CliError } from "./errors";
15
+ import { ensurePrivateDirectory } from "./paths";
16
+
17
+ const MAX_OVERLAY_BYTES = 512 * 1024 * 1024;
18
+ const MAX_SVG_BYTES = 32 * 1024 * 1024;
19
+ const OVERLAY_COPY_CHUNK_BYTES = 1024 * 1024;
20
+ const OVERLAY_SIGNATURE_BYTES = 4_096;
21
+ const PUBLICATION_SETTLE_ATTEMPTS = 16;
22
+
23
+ type ImportedMediaType =
24
+ | "image/gif"
25
+ | "image/jpeg"
26
+ | "image/png"
27
+ | "image/svg+xml"
28
+ | "image/webp"
29
+ | "video/mp4"
30
+ | "video/quicktime"
31
+ | "video/webm";
32
+
33
+ export interface IngestedAsset {
34
+ readonly bytes: number;
35
+ readonly created: boolean;
36
+ readonly mediaType: ImportedMediaType;
37
+ readonly path: string;
38
+ readonly provenance:
39
+ | {
40
+ readonly kind: "imported";
41
+ readonly originalName: string;
42
+ readonly sourceSha256: string;
43
+ }
44
+ | {
45
+ readonly command: readonly string[];
46
+ readonly generator: string;
47
+ readonly generatorVersion: string;
48
+ readonly kind: "generated";
49
+ readonly sourceSha256: string;
50
+ };
51
+ readonly sha256: string;
52
+ }
53
+
54
+ export interface GeneratedVideoOverlayAssetInput {
55
+ readonly command: readonly string[];
56
+ readonly generator: string;
57
+ readonly generatorVersion: string;
58
+ readonly path: string;
59
+ readonly sourceSha256: string;
60
+ }
61
+
62
+ interface AssetFormat {
63
+ readonly extension: string;
64
+ readonly mediaType: ImportedMediaType;
65
+ }
66
+
67
+ const FORMAT_BY_EXTENSION: Readonly<Record<string, AssetFormat>> = {
68
+ ".gif": { extension: "gif", mediaType: "image/gif" },
69
+ ".jpeg": { extension: "jpg", mediaType: "image/jpeg" },
70
+ ".jpg": { extension: "jpg", mediaType: "image/jpeg" },
71
+ ".mov": { extension: "mov", mediaType: "video/quicktime" },
72
+ ".mp4": { extension: "mp4", mediaType: "video/mp4" },
73
+ ".png": { extension: "png", mediaType: "image/png" },
74
+ ".svg": { extension: "svg", mediaType: "image/svg+xml" },
75
+ ".webm": { extension: "webm", mediaType: "video/webm" },
76
+ ".webp": { extension: "webp", mediaType: "image/webp" },
77
+ };
78
+
79
+ const KIND_MEDIA_TYPES: Readonly<Record<Exclude<OverlayKind, "emoji">, readonly ImportedMediaType[]>> = {
80
+ gif: ["image/gif"],
81
+ image: ["image/jpeg", "image/png", "image/webp"],
82
+ svg: ["image/svg+xml"],
83
+ video: ["video/mp4", "video/quicktime", "video/webm"],
84
+ };
85
+
86
+ const GENERATED_MOV_FORMAT = {
87
+ extension: "mov",
88
+ mediaType: "video/quicktime",
89
+ } as const satisfies AssetFormat;
90
+
91
+ function isWithin(root: string, candidate: string): boolean {
92
+ const pathRelative = relative(root, candidate);
93
+ return pathRelative === "" || (!pathRelative.startsWith("..") && !isAbsolute(pathRelative));
94
+ }
95
+
96
+ function validateSvg(bytes: Uint8Array): boolean {
97
+ if (bytes.length > MAX_SVG_BYTES) return false;
98
+ let contents: string;
99
+ try {
100
+ contents = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
101
+ } catch {
102
+ return false;
103
+ }
104
+ const start = contents.trimStart();
105
+ if (!/^(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg(?:\s|>)/iu.test(start)) return false;
106
+ if (
107
+ /<!DOCTYPE|<!ENTITY|<\?xml-stylesheet|<\s*(?:script|foreignObject|iframe|object|embed)\b/iu.test(contents)
108
+ || /\s+on[a-z][a-z0-9_-]*\s*=/iu.test(contents)
109
+ || /\bjavascript\s*:/iu.test(contents)
110
+ || /@import\b|expression\s*\(/iu.test(contents)
111
+ ) {
112
+ return false;
113
+ }
114
+ for (const match of contents.matchAll(/\b(?:href|xlink:href|src)\s*=\s*(["'])(.*?)\1/giu)) {
115
+ const target = match[2]?.trim() ?? "";
116
+ if (!target.startsWith("#")) return false;
117
+ }
118
+ for (const match of contents.matchAll(/url\(\s*(["']?)(.*?)\1\s*\)/giu)) {
119
+ const target = match[2]?.trim() ?? "";
120
+ if (!target.startsWith("#")) return false;
121
+ }
122
+ return true;
123
+ }
124
+
125
+ export interface OverlayIntrinsicSize {
126
+ readonly height: number;
127
+ readonly width: number;
128
+ }
129
+
130
+ export interface OverlayChunkReader {
131
+ read(
132
+ buffer: Buffer,
133
+ offset: number,
134
+ length: number,
135
+ position: number,
136
+ ): Promise<{ readonly bytesRead: number }>;
137
+ }
138
+
139
+ export interface OverlayChunkWriter {
140
+ write(
141
+ buffer: Buffer,
142
+ offset: number,
143
+ length: number,
144
+ position: number,
145
+ ): Promise<{ readonly bytesWritten: number }>;
146
+ }
147
+
148
+ export interface CopiedOverlaySource {
149
+ readonly bytes: number;
150
+ readonly signature: Buffer;
151
+ readonly sha256: string;
152
+ }
153
+
154
+ export async function copyOverlaySourceChunks(
155
+ reader: OverlayChunkReader,
156
+ writer: OverlayChunkWriter,
157
+ expectedBytes: number,
158
+ ): Promise<CopiedOverlaySource> {
159
+ if (!Number.isSafeInteger(expectedBytes) || expectedBytes <= 0 || expectedBytes > MAX_OVERLAY_BYTES) {
160
+ throw new CliError("invalid-data", `Overlay source must contain 1 through ${MAX_OVERLAY_BYTES} bytes.`);
161
+ }
162
+ const buffer = Buffer.allocUnsafe(Math.min(OVERLAY_COPY_CHUNK_BYTES, expectedBytes));
163
+ const signature = Buffer.allocUnsafe(Math.min(OVERLAY_SIGNATURE_BYTES, expectedBytes));
164
+ const hash = createHash("sha256");
165
+ let position = 0;
166
+ let signatureBytes = 0;
167
+ while (position < expectedBytes) {
168
+ const requested = Math.min(buffer.byteLength, expectedBytes - position);
169
+ const result = await reader.read(buffer, 0, requested, position);
170
+ if (
171
+ !Number.isSafeInteger(result.bytesRead)
172
+ || result.bytesRead <= 0
173
+ || result.bytesRead > requested
174
+ ) {
175
+ throw new CliError("conflict", "Overlay source ended or changed while it was being staged.");
176
+ }
177
+ hash.update(buffer.subarray(0, result.bytesRead));
178
+ if (signatureBytes < signature.byteLength) {
179
+ const copied = Math.min(result.bytesRead, signature.byteLength - signatureBytes);
180
+ buffer.copy(signature, signatureBytes, 0, copied);
181
+ signatureBytes += copied;
182
+ }
183
+ let written = 0;
184
+ while (written < result.bytesRead) {
185
+ const write = await writer.write(
186
+ buffer,
187
+ written,
188
+ result.bytesRead - written,
189
+ position + written,
190
+ );
191
+ if (
192
+ !Number.isSafeInteger(write.bytesWritten)
193
+ || write.bytesWritten <= 0
194
+ || write.bytesWritten > result.bytesRead - written
195
+ ) {
196
+ throw new CliError("invalid-data", "Overlay source could not be staged completely.");
197
+ }
198
+ written += write.bytesWritten;
199
+ }
200
+ position += result.bytesRead;
201
+ }
202
+ return {
203
+ bytes: position,
204
+ signature: signature.subarray(0, signatureBytes),
205
+ sha256: hash.digest("hex"),
206
+ };
207
+ }
208
+
209
+ function svgAttribute(attributes: string, name: string): string | undefined {
210
+ const match = new RegExp(String.raw`(?:^|\s)${name}\s*=\s*(["'])(?<value>.*?)\1`, "iu").exec(attributes);
211
+ return match?.groups?.value?.trim();
212
+ }
213
+
214
+ function svgLength(value: string | undefined): number | null {
215
+ if (value === undefined || !/^[+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:e[+-]?[0-9]+)?(?:px)?$/iu.test(value)) {
216
+ return null;
217
+ }
218
+ const number = Number(value.replace(/px$/iu, ""));
219
+ return Number.isFinite(number) && number > 0 ? number : null;
220
+ }
221
+
222
+ export function parseSvgIntrinsicSize(bytes: Uint8Array): OverlayIntrinsicSize {
223
+ let contents: string;
224
+ try {
225
+ contents = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
226
+ } catch {
227
+ throw new CliError("invalid-data", "SVG overlay is not valid UTF-8.");
228
+ }
229
+ if (!validateSvg(bytes)) throw new CliError("invalid-data", "SVG overlay failed the safe SVG profile.");
230
+ const root = /^(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg\b(?<attributes>[^>]*)>/iu.exec(contents.trimStart());
231
+ if (root?.groups?.attributes === undefined) {
232
+ throw new CliError("invalid-data", "SVG overlay omits its root element.");
233
+ }
234
+ const width = svgLength(svgAttribute(root.groups.attributes, "width"));
235
+ const height = svgLength(svgAttribute(root.groups.attributes, "height"));
236
+ const viewBox = svgAttribute(root.groups.attributes, "viewBox")
237
+ ?.split(/[\s,]+/u)
238
+ .filter(Boolean)
239
+ .map(Number);
240
+ const viewBoxWidth = viewBox?.length === 4 && Number.isFinite(viewBox[2]) && viewBox[2]! > 0
241
+ ? viewBox[2]!
242
+ : null;
243
+ const viewBoxHeight = viewBox?.length === 4 && Number.isFinite(viewBox[3]) && viewBox[3]! > 0
244
+ ? viewBox[3]!
245
+ : null;
246
+ const resolvedWidth = Math.ceil(
247
+ width
248
+ ?? (height !== null && viewBoxWidth !== null && viewBoxHeight !== null
249
+ ? height * viewBoxWidth / viewBoxHeight
250
+ : viewBoxWidth ?? 0),
251
+ );
252
+ const resolvedHeight = Math.ceil(
253
+ height
254
+ ?? (width !== null && viewBoxWidth !== null && viewBoxHeight !== null
255
+ ? width * viewBoxHeight / viewBoxWidth
256
+ : viewBoxHeight ?? 0),
257
+ );
258
+ if (
259
+ resolvedWidth <= 0
260
+ || resolvedHeight <= 0
261
+ || resolvedWidth > 16_384
262
+ || resolvedHeight > 16_384
263
+ || resolvedWidth * resolvedHeight > 134_217_728
264
+ ) {
265
+ throw new CliError(
266
+ "invalid-data",
267
+ "SVG intrinsic dimensions must be declared and remain within 16384 pixels and 128 megapixels.",
268
+ );
269
+ }
270
+ return { height: resolvedHeight, width: resolvedWidth };
271
+ }
272
+
273
+ export async function inspectSvgIntrinsicSize(path: string): Promise<OverlayIntrinsicSize> {
274
+ const { bytes } = await readSourceFully(path, MAX_SVG_BYTES);
275
+ return parseSvgIntrinsicSize(bytes);
276
+ }
277
+
278
+ export async function inspectPngIntrinsicSize(path: string): Promise<OverlayIntrinsicSize> {
279
+ const { bytes } = await readSourcePrefix(path, 24, MAX_OVERLAY_BYTES);
280
+ if (
281
+ bytes.byteLength < 24
282
+ || ![0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
283
+ .every((value, index) => bytes[index] === value)
284
+ ) {
285
+ throw new CliError("invalid-data", "SVG derivative is not a complete PNG image.");
286
+ }
287
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
288
+ const width = view.getUint32(16, false);
289
+ const height = view.getUint32(20, false);
290
+ if (
291
+ width <= 0
292
+ || height <= 0
293
+ || width > 16_384
294
+ || height > 16_384
295
+ || width * height > 134_217_728
296
+ ) {
297
+ throw new CliError("invalid-data", "SVG derivative dimensions exceed the render safety limit.");
298
+ }
299
+ return { height, width };
300
+ }
301
+
302
+ function validateSignature(bytes: Uint8Array, mediaType: ImportedMediaType): void {
303
+ const startsWith = (...values: number[]): boolean => values.every((value, index) => bytes[index] === value);
304
+ const ascii = new TextDecoder().decode(bytes.slice(0, Math.min(bytes.length, 4_096))).trimStart();
305
+ const valid = mediaType === "image/png"
306
+ ? startsWith(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a)
307
+ : mediaType === "image/jpeg"
308
+ ? startsWith(0xff, 0xd8, 0xff)
309
+ : mediaType === "image/gif"
310
+ ? ascii.startsWith("GIF87a") || ascii.startsWith("GIF89a")
311
+ : mediaType === "image/webp"
312
+ ? ascii.startsWith("RIFF") && ascii.slice(8, 12) === "WEBP"
313
+ : mediaType === "image/svg+xml"
314
+ ? validateSvg(bytes)
315
+ : mediaType === "video/webm"
316
+ ? startsWith(0x1a, 0x45, 0xdf, 0xa3)
317
+ : mediaType === "video/mp4" || mediaType === "video/quicktime"
318
+ ? ascii.slice(4, 8) === "ftyp"
319
+ : false;
320
+ if (!valid) throw new CliError("invalid-data", `Overlay file bytes do not match ${mediaType}.`);
321
+ if (mediaType === "image/svg+xml") parseSvgIntrinsicSize(bytes);
322
+ }
323
+
324
+ async function safeAssetDirectory(bundleRoot: string): Promise<string> {
325
+ await ensurePrivateDirectory(bundleRoot);
326
+ const realBundleRoot = await realpath(bundleRoot);
327
+ const directory = join(realBundleRoot, "assets");
328
+ let created = false;
329
+ try {
330
+ const details = await lstat(directory);
331
+ if (details.isSymbolicLink()) {
332
+ throw new CliError("unsafe-path", `Bundle asset directory is a symlink: ${directory}`);
333
+ }
334
+ if (!details.isDirectory()) {
335
+ throw new CliError("unsafe-path", `Bundle asset directory is not a physical directory: ${directory}`);
336
+ }
337
+ } catch (error) {
338
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
339
+ try {
340
+ await mkdir(directory, { mode: 0o700, recursive: false });
341
+ created = true;
342
+ } catch (createError) {
343
+ if (
344
+ !(
345
+ createError instanceof Error
346
+ && "code" in createError
347
+ && createError.code === "EEXIST"
348
+ )
349
+ ) {
350
+ throw createError;
351
+ }
352
+ const raced = await lstat(directory);
353
+ if (raced.isSymbolicLink() || !raced.isDirectory()) {
354
+ throw new CliError(
355
+ "unsafe-path",
356
+ `Bundle asset directory is not a physical directory: ${directory}`,
357
+ );
358
+ }
359
+ }
360
+ }
361
+ const realDirectory = await realpath(directory);
362
+ if (!isWithin(realBundleRoot, realDirectory)) {
363
+ throw new CliError("unsafe-path", "Bundle asset directory resolves outside the recording bundle.");
364
+ }
365
+ if (created) await syncDirectory(realBundleRoot);
366
+ return realDirectory;
367
+ }
368
+
369
+ function samePhysicalFile(left: Stats, right: Stats): boolean {
370
+ return left.dev === right.dev
371
+ && left.ino === right.ino
372
+ && left.size === right.size
373
+ && left.mtimeMs === right.mtimeMs
374
+ && left.ctimeMs === right.ctimeMs;
375
+ }
376
+
377
+ async function physicalFileSnapshot(
378
+ path: string,
379
+ maximumBytes: number,
380
+ label = "Overlay source",
381
+ requireSingleLink = false,
382
+ ): Promise<{ readonly details: Stats; readonly realPath: string }> {
383
+ const lexicalPath = resolve(path);
384
+ let details;
385
+ try {
386
+ details = await lstat(lexicalPath);
387
+ } catch {
388
+ throw new CliError("not-found", `${label} does not exist: ${path}`);
389
+ }
390
+ if (details.isSymbolicLink()) {
391
+ throw new CliError("unsafe-path", `${label} may not be a symlink: ${path}`);
392
+ }
393
+ if (!details.isFile() || (requireSingleLink && details.nlink !== 1)) {
394
+ throw new CliError("invalid-data", `${label} is not a private physical regular file: ${path}`);
395
+ }
396
+ if (details.size <= 0 || details.size > maximumBytes) {
397
+ throw new CliError("invalid-data", `${label} must contain 1 through ${maximumBytes} bytes.`);
398
+ }
399
+ return { details, realPath: lexicalPath };
400
+ }
401
+
402
+ async function readSourceFully(
403
+ path: string,
404
+ maximumBytes: number,
405
+ ): Promise<{ readonly bytes: Buffer; readonly realPath: string }> {
406
+ const { details, realPath } = await physicalFileSnapshot(path, maximumBytes);
407
+ const handle = await open(realPath, constants.O_RDONLY | constants.O_NOFOLLOW);
408
+ try {
409
+ const before = await handle.stat();
410
+ if (!before.isFile() || !samePhysicalFile(details, before)) {
411
+ throw new CliError("conflict", "Overlay source changed before it was read.");
412
+ }
413
+ const bytes = await handle.readFile();
414
+ const after = await handle.stat();
415
+ if (bytes.length !== before.size || !samePhysicalFile(before, after)) {
416
+ throw new CliError("conflict", "Overlay source changed while it was being read.");
417
+ }
418
+ return { bytes, realPath };
419
+ } finally {
420
+ await handle.close();
421
+ }
422
+ }
423
+
424
+ async function readSourcePrefix(
425
+ path: string,
426
+ requestedBytes: number,
427
+ maximumBytes: number,
428
+ ): Promise<{ readonly bytes: Buffer; readonly realPath: string }> {
429
+ const { details, realPath } = await physicalFileSnapshot(path, maximumBytes);
430
+ const handle = await open(realPath, constants.O_RDONLY | constants.O_NOFOLLOW);
431
+ try {
432
+ const before = await handle.stat();
433
+ if (!before.isFile() || !samePhysicalFile(details, before)) {
434
+ throw new CliError("conflict", "Overlay source changed before it was inspected.");
435
+ }
436
+ const length = Math.min(requestedBytes, before.size);
437
+ const bytes = Buffer.allocUnsafe(length);
438
+ let position = 0;
439
+ while (position < length) {
440
+ const result = await handle.read(bytes, position, length - position, position);
441
+ if (result.bytesRead <= 0 || result.bytesRead > length - position) {
442
+ throw new CliError("conflict", "Overlay source ended while its signature was being inspected.");
443
+ }
444
+ position += result.bytesRead;
445
+ }
446
+ const after = await handle.stat();
447
+ if (!samePhysicalFile(before, after)) {
448
+ throw new CliError("conflict", "Overlay source changed while it was being inspected.");
449
+ }
450
+ return { bytes, realPath };
451
+ } finally {
452
+ await handle.close();
453
+ }
454
+ }
455
+
456
+ async function fingerprintPhysicalFile(
457
+ path: string,
458
+ maximumBytes: number,
459
+ label: string,
460
+ requireSingleLink = false,
461
+ ): Promise<CopiedOverlaySource> {
462
+ const { details, realPath } = await physicalFileSnapshot(path, maximumBytes, label, requireSingleLink);
463
+ const handle = await open(realPath, constants.O_RDONLY | constants.O_NOFOLLOW);
464
+ try {
465
+ const before = await handle.stat();
466
+ if (
467
+ !before.isFile()
468
+ || (requireSingleLink && before.nlink !== 1)
469
+ || !samePhysicalFile(details, before)
470
+ ) {
471
+ throw new CliError("conflict", `${label} changed before it was verified.`);
472
+ }
473
+ const fingerprint = await copyOverlaySourceChunks(handle, {
474
+ write: (_buffer, _offset, length) => Promise.resolve({ bytesWritten: length }),
475
+ }, before.size);
476
+ const after = await handle.stat();
477
+ if ((requireSingleLink && after.nlink !== 1) || !samePhysicalFile(before, after)) {
478
+ throw new CliError("conflict", `${label} changed while it was verified.`);
479
+ }
480
+ return fingerprint;
481
+ } finally {
482
+ await handle.close();
483
+ }
484
+ }
485
+
486
+ async function awaitSingleLink(path: string): Promise<void> {
487
+ for (let attempt = 0; attempt < PUBLICATION_SETTLE_ATTEMPTS; attempt += 1) {
488
+ if ((await lstat(path)).nlink === 1) return;
489
+ await new Promise(resolveDelay => setTimeout(resolveDelay, 0));
490
+ }
491
+ }
492
+
493
+ async function syncDirectory(path: string): Promise<void> {
494
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
495
+ try {
496
+ if (!(await handle.stat()).isDirectory()) {
497
+ throw new CliError("unsafe-path", `Asset durability path is not a physical directory: ${path}`);
498
+ }
499
+ await handle.sync();
500
+ } finally {
501
+ await handle.close();
502
+ }
503
+ }
504
+
505
+ async function publishStagedAsset(
506
+ directory: string,
507
+ temporary: string,
508
+ extension: string,
509
+ fingerprint: Pick<CopiedOverlaySource, "bytes" | "sha256">,
510
+ ): Promise<{ readonly created: boolean; readonly path: string }> {
511
+ const relativePath = `assets/${fingerprint.sha256}.${extension}`;
512
+ const destination = join(directory, `${fingerprint.sha256}.${extension}`);
513
+ if (!isWithin(directory, destination)) throw new CliError("unsafe-path", "Asset destination escaped its directory.");
514
+ let created = false;
515
+ try {
516
+ await link(temporary, destination);
517
+ created = true;
518
+ } catch (error) {
519
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
520
+ await awaitSingleLink(destination);
521
+ const existing = await fingerprintPhysicalFile(
522
+ destination,
523
+ MAX_OVERLAY_BYTES,
524
+ "Content-addressed overlay",
525
+ true,
526
+ );
527
+ if (existing.sha256 !== fingerprint.sha256 || existing.bytes !== fingerprint.bytes) {
528
+ throw new CliError("conflict", `Content-addressed overlay collision at ${relativePath}.`);
529
+ }
530
+ }
531
+ await rm(temporary);
532
+ await syncDirectory(directory);
533
+ return { created, path: relativePath };
534
+ }
535
+
536
+ async function materializeBytes(
537
+ bundleRoot: string,
538
+ bytes: Uint8Array,
539
+ extension: string,
540
+ sha256: string,
541
+ ): Promise<{ readonly created: boolean; readonly path: string }> {
542
+ const directory = await safeAssetDirectory(bundleRoot);
543
+ const temporary = join(directory, `.asset-${randomUUID()}.tmp`);
544
+ const handle = await open(
545
+ temporary,
546
+ constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_WRONLY,
547
+ 0o600,
548
+ );
549
+ try {
550
+ await handle.writeFile(bytes);
551
+ await handle.sync();
552
+ return await publishStagedAsset(directory, temporary, extension, { bytes: bytes.byteLength, sha256 });
553
+ } finally {
554
+ await handle.close();
555
+ await rm(temporary, { force: true });
556
+ }
557
+ }
558
+
559
+ async function materializeStreamedSource(
560
+ bundleRoot: string,
561
+ sourcePath: string,
562
+ format: AssetFormat,
563
+ expected?: {
564
+ readonly mismatchMessage: string;
565
+ readonly sha256: string;
566
+ },
567
+ ): Promise<CopiedOverlaySource & { readonly created: boolean; readonly path: string; readonly realPath: string }> {
568
+ const { details, realPath } = await physicalFileSnapshot(sourcePath, MAX_OVERLAY_BYTES);
569
+ const directory = await safeAssetDirectory(bundleRoot);
570
+ const temporary = join(directory, `.asset-${randomUUID()}.tmp`);
571
+ const source = await open(realPath, constants.O_RDONLY | constants.O_NOFOLLOW);
572
+ const staged = await open(
573
+ temporary,
574
+ constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_WRONLY,
575
+ 0o600,
576
+ ).catch(async (error: unknown) => {
577
+ await source.close();
578
+ throw error;
579
+ });
580
+ try {
581
+ const opened = await source.stat();
582
+ if (!opened.isFile() || !samePhysicalFile(details, opened)) {
583
+ throw new CliError("conflict", "Overlay source changed before it was staged.");
584
+ }
585
+ const copied = await copyOverlaySourceChunks(source, staged, opened.size);
586
+ const after = await source.stat();
587
+ if (copied.bytes !== opened.size || !samePhysicalFile(opened, after)) {
588
+ throw new CliError("conflict", "Overlay source changed while it was being staged.");
589
+ }
590
+ validateSignature(copied.signature, format.mediaType);
591
+ if (expected !== undefined && copied.sha256 !== expected.sha256) {
592
+ throw new CliError("invalid-data", expected.mismatchMessage);
593
+ }
594
+ const stagedDetails = await staged.stat();
595
+ if (!stagedDetails.isFile() || stagedDetails.size !== copied.bytes || stagedDetails.nlink !== 1) {
596
+ throw new CliError("conflict", "Staged overlay output changed before publication.");
597
+ }
598
+ await staged.sync();
599
+ const materialized = await publishStagedAsset(
600
+ directory,
601
+ temporary,
602
+ format.extension,
603
+ copied,
604
+ );
605
+ return { ...copied, ...materialized, realPath };
606
+ } finally {
607
+ await Promise.all([source.close(), staged.close()]);
608
+ await rm(temporary, { force: true });
609
+ }
610
+ }
611
+
612
+ export async function ingestOverlayAsset(
613
+ bundleRoot: string,
614
+ sourcePath: string,
615
+ kind: Exclude<OverlayKind, "emoji">,
616
+ ): Promise<IngestedAsset> {
617
+ const realPath = resolve(sourcePath);
618
+ const format = FORMAT_BY_EXTENSION[extname(realPath).toLocaleLowerCase()];
619
+ if (format === undefined || !KIND_MEDIA_TYPES[kind].includes(format.mediaType)) {
620
+ throw new CliError("invalid-data", `A ${kind} overlay does not accept ${extname(realPath) || "an extensionless file"}.`);
621
+ }
622
+ if (format.mediaType !== "image/svg+xml") {
623
+ const materialized = await materializeStreamedSource(bundleRoot, sourcePath, format);
624
+ return {
625
+ bytes: materialized.bytes,
626
+ created: materialized.created,
627
+ mediaType: format.mediaType,
628
+ path: materialized.path,
629
+ provenance: {
630
+ kind: "imported",
631
+ originalName: basename(materialized.realPath),
632
+ sourceSha256: materialized.sha256,
633
+ },
634
+ sha256: materialized.sha256,
635
+ };
636
+ }
637
+ const { bytes } = await readSourceFully(sourcePath, MAX_SVG_BYTES);
638
+ validateSignature(bytes, format.mediaType);
639
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
640
+ const materialized = await materializeBytes(bundleRoot, bytes, format.extension, sha256);
641
+ return {
642
+ bytes: bytes.length,
643
+ created: materialized.created,
644
+ mediaType: format.mediaType,
645
+ path: materialized.path,
646
+ provenance: {
647
+ kind: "imported",
648
+ originalName: basename(realPath),
649
+ sourceSha256: sha256,
650
+ },
651
+ sha256,
652
+ };
653
+ }
654
+
655
+ function validateGeneratedVideoOverlayInput(input: GeneratedVideoOverlayAssetInput): void {
656
+ if (input.generator.length === 0 || input.generator.length > 256) {
657
+ throw new CliError("invalid-data", "Generated overlay generator must contain 1 through 256 characters.");
658
+ }
659
+ if (input.generatorVersion.length === 0 || input.generatorVersion.length > 128) {
660
+ throw new CliError(
661
+ "invalid-data",
662
+ "Generated overlay generator version must contain 1 through 128 characters.",
663
+ );
664
+ }
665
+ if (input.command.length === 0 || input.command.some(part => part.length === 0)) {
666
+ throw new CliError("invalid-data", "Generated overlay command must contain at least one non-empty argument.");
667
+ }
668
+ if (!/^[a-f0-9]{64}$/u.test(input.sourceSha256)) {
669
+ throw new CliError("invalid-data", "Generated overlay source SHA-256 must be 64 lowercase hexadecimal characters.");
670
+ }
671
+ }
672
+
673
+ export async function ingestGeneratedVideoOverlayAsset(
674
+ bundleRoot: string,
675
+ input: GeneratedVideoOverlayAssetInput,
676
+ ): Promise<IngestedAsset> {
677
+ validateGeneratedVideoOverlayInput(input);
678
+ const sourcePath = input.path;
679
+ if (extname(resolve(sourcePath)).toLowerCase() !== ".mov") {
680
+ throw new CliError("invalid-data", "A generated video overlay must be a .mov file.");
681
+ }
682
+ const provenance = {
683
+ command: [...input.command],
684
+ generator: input.generator,
685
+ generatorVersion: input.generatorVersion,
686
+ kind: "generated" as const,
687
+ sourceSha256: input.sourceSha256,
688
+ };
689
+ const materialized = await materializeStreamedSource(
690
+ bundleRoot,
691
+ sourcePath,
692
+ GENERATED_MOV_FORMAT,
693
+ {
694
+ mismatchMessage: "Generated overlay source SHA-256 does not match the rendered MOV bytes.",
695
+ sha256: input.sourceSha256,
696
+ },
697
+ );
698
+ return {
699
+ bytes: materialized.bytes,
700
+ created: materialized.created,
701
+ mediaType: GENERATED_MOV_FORMAT.mediaType,
702
+ path: materialized.path,
703
+ provenance,
704
+ sha256: materialized.sha256,
705
+ };
706
+ }
707
+
708
+ export async function ingestEmojiAsset(
709
+ bundleRoot: string,
710
+ resolved: ResolvedEmojiAsset,
711
+ ): Promise<IngestedAsset> {
712
+ const mediaType = extname(resolved.path).toLocaleLowerCase() === ".png"
713
+ ? "image/png" as const
714
+ : "image/svg+xml" as const;
715
+ const extension = mediaType === "image/png" ? "png" : "svg";
716
+ const streamed = mediaType === "image/png"
717
+ ? await materializeStreamedSource(bundleRoot, resolved.path, { extension, mediaType }, {
718
+ mismatchMessage: `Resolved emoji asset ${resolved.id} changed before ingestion.`,
719
+ sha256: resolved.sha256,
720
+ })
721
+ : null;
722
+ const svg = streamed === null
723
+ ? await readSourceFully(resolved.path, MAX_SVG_BYTES)
724
+ : null;
725
+ if (svg !== null) validateSignature(svg.bytes, mediaType);
726
+ const actualSha256 = streamed?.sha256 ?? createHash("sha256").update(svg!.bytes).digest("hex");
727
+ if (actualSha256 !== resolved.sha256) {
728
+ throw new CliError("invalid-data", `Resolved emoji asset ${resolved.id} changed before ingestion.`);
729
+ }
730
+ const materialized = streamed ?? await materializeBytes(bundleRoot, svg!.bytes, extension, actualSha256);
731
+ return {
732
+ bytes: streamed?.bytes ?? svg!.bytes.length,
733
+ created: materialized.created,
734
+ mediaType,
735
+ path: materialized.path,
736
+ provenance: {
737
+ command: resolved.provider === "apple-emoji-pack"
738
+ ? EMOJI_GENERATION_COMMAND.split(" ")
739
+ : ["bun", "run", "brand-icons:generate"],
740
+ generator: resolved.provider,
741
+ generatorVersion: "1",
742
+ kind: "generated",
743
+ sourceSha256: actualSha256,
744
+ },
745
+ sha256: actualSha256,
746
+ };
747
+ }