@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,213 @@
1
+ import { createHash } from "node:crypto";
2
+ import { constants, type BigIntStats } from "node:fs";
3
+ import { lstat, open, type FileHandle } from "node:fs/promises";
4
+ import { isAbsolute } from "node:path";
5
+
6
+ import { z } from "zod";
7
+
8
+ import { createBoundedJsonValueSnapshot, deepFreezeJson } from "../../../src/code/json-snapshot";
9
+ import { spatialOutputDuration } from "../../../src/spatial-scene/time";
10
+ import { SPATIAL_COMPOSITOR_LIMITS, SpatialCompositorTimingVerificationV1Schema, SpatialCompositorVideoProfileSchema, type SpatialCompositorVideoProfile } from "../contracts/spatial-compositor";
11
+ import { assertSpatialCompositorCadence } from "../core/spatial-compositor";
12
+ import type { ProcessRunner } from "./io";
13
+
14
+ const unsigned = z.string().regex(/^(?:0|[1-9][0-9]{0,24})$/u);
15
+ const fraction = z.string().regex(/^[1-9][0-9]{0,12}\/[1-9][0-9]{0,12}$/u);
16
+ const StreamSchema = z.object({
17
+ codec_type: z.enum(["video", "audio"]), codec_name: z.string().max(80),
18
+ time_base: fraction, start_pts: z.number().int().safe(), duration_ts: z.number().int().safe().positive(),
19
+ avg_frame_rate: z.string().max(40).optional(), r_frame_rate: z.string().max(40).optional(),
20
+ nb_frames: unsigned.optional(), nb_read_frames: unsigned.optional(), sample_rate: unsigned.optional(),
21
+ width: z.number().int().positive().max(16_384).optional(), height: z.number().int().positive().max(16_384).optional(),
22
+ pix_fmt: z.string().max(80).optional(),
23
+ });
24
+ const ProbeSchema = z.object({
25
+ streams: z.array(StreamSchema).length(1),
26
+ frames: z.array(z.object({ best_effort_timestamp: z.number().int().safe().min(0) })).max(SPATIAL_COMPOSITOR_LIMITS.frames).optional(),
27
+ });
28
+ function ratio(value: string): readonly [bigint, bigint] {
29
+ if (!fraction.safeParse(value).success) throw new Error("Native stream contains an invalid positive rational.");
30
+ const parts = value.split("/");
31
+ return [BigInt(parts[0]!), BigInt(parts[1]!)];
32
+ }
33
+ function equalRatio(value: string | undefined, n: bigint, d: bigint): boolean {
34
+ if (value === undefined) return false;
35
+ const [pn, pd] = ratio(value);
36
+ return pn * d === n * pd;
37
+ }
38
+
39
+ function videoProfile(input: unknown): SpatialCompositorVideoProfile {
40
+ return SpatialCompositorVideoProfileSchema.parse(createBoundedJsonValueSnapshot(input, 4096, "expected compositor video profile", { maximumDepth: 4, maximumValues: 20 }).value);
41
+ }
42
+ function probeValue(input: unknown, maximumBytes: number) {
43
+ return createBoundedJsonValueSnapshot(input, maximumBytes, "compositor probe", { maximumDepth: 8, maximumValues: SPATIAL_COMPOSITOR_LIMITS.frames * 2 + 100 }).value;
44
+ }
45
+
46
+ /** Exact output geometry and integer timing oracle; decimal display timestamps are never authoritative. */
47
+ export function verifySpatialCompositorProbe(input: { readonly cadence: unknown; readonly expectedVideo: SpatialCompositorVideoProfile; readonly video: unknown; readonly audio: unknown }) {
48
+ const binding = assertSpatialCompositorCadence(input.cadence);
49
+ const expectedVideo = videoProfile(input.expectedVideo);
50
+ const video = ProbeSchema.parse(probeValue(input.video, SPATIAL_COMPOSITOR_LIMITS.probeBytes)), audio = ProbeSchema.parse(probeValue(input.audio, 64 * 1024));
51
+ const stream = video.streams[0]!, sound = audio.streams[0]!;
52
+ if (stream.width !== expectedVideo.pixelWidth || stream.height !== expectedVideo.pixelHeight || stream.pix_fmt !== expectedVideo.pixelFormat) {
53
+ throw new Error("Encoded compositor video dimensions or pixel format differ from the exact output profile.");
54
+ }
55
+ const measuredVideo = SpatialCompositorVideoProfileSchema.parse({ pixelWidth: stream.width, pixelHeight: stream.height, pixelFormat: stream.pix_fmt });
56
+ const rate = binding.cadence.frameRate, n = BigInt(rate.numerator), d = BigInt(rate.denominator);
57
+ const count = BigInt(binding.cadence.frameCount);
58
+ const [tn, td] = ratio(stream.time_base);
59
+ const stepNumerator = d * td, stepDenominator = n * tn;
60
+ if (stream.codec_type !== "video" || stream.codec_name !== "h264"
61
+ || stream.start_pts !== 0 || !equalRatio(stream.avg_frame_rate, n, d) || !equalRatio(stream.r_frame_rate, n, d)
62
+ || stream.nb_frames === undefined || BigInt(stream.nb_frames) !== count
63
+ || stream.nb_read_frames === undefined || BigInt(stream.nb_read_frames) !== count
64
+ || stepNumerator % stepDenominator !== 0n
65
+ || video.frames === undefined || video.frames.length !== binding.cadence.frameCount) throw new Error("Encoded compositor video does not match the exact rational cadence and frame count.");
66
+ const step = stepNumerator / stepDenominator;
67
+ for (let index = 0; index < video.frames.length; index++) {
68
+ if (BigInt(video.frames[index]!.best_effort_timestamp) !== BigInt(index) * step) throw new Error(`Encoded compositor video has an incorrect presentation timestamp at frame ${index}.`);
69
+ }
70
+ if (BigInt(stream.duration_ts) !== count * step) throw new Error("Encoded compositor duration does not match the half-open sample count.");
71
+ // AAC padding is represented by the MP4 edit list: its stream duration must
72
+ // retain the complete authored audio timeline to within one 48-kHz sample.
73
+ const audioDelta = BigInt(sound.duration_ts) * 1_000_000n - BigInt(binding.cadence.durationUs) * 48_000n;
74
+ if (sound.codec_type !== "audio" || sound.codec_name !== "aac" || sound.sample_rate !== "48000"
75
+ || sound.time_base !== "1/48000" || sound.start_pts !== 0 || audioDelta < -1_000_000n || audioDelta > 1_000_000n) throw new Error("Encoded compositor audio was truncated or changed from the authored duration.");
76
+ return deepFreezeJson(SpatialCompositorTimingVerificationV1Schema.parse({
77
+ kind: "slopcamera.spatial-compositor-timing-verification", schemaVersion: 1, cadenceSha256: binding.cadenceSha256,
78
+ profile: "mp4-h264-cfr-integer-pts-v1", video: measuredVideo, frameRate: rate, frameCount: binding.cadence.frameCount,
79
+ timeBase: { numerator: String(tn), denominator: String(td) }, firstPts: "0", lastPts: String((count - 1n) * step),
80
+ stepPts: String(step), durationTs: String(stream.duration_ts), authoredDurationUs: binding.cadence.durationUs,
81
+ encodedVideoDurationUs: spatialOutputDuration(binding.cadence.durationUs, rate),
82
+ audio: { sampleRate: 48_000, timeBase: "1/48000", durationTs: String(sound.duration_ts), durationToleranceSamples: 1 },
83
+ }));
84
+ }
85
+
86
+ const SNAPSHOT_FIELDS = ["dev", "ino", "mode", "nlink", "uid", "gid", "rdev", "size", "blksize", "blocks", "mtimeNs", "ctimeNs", "birthtimeNs"] as const;
87
+ function changedFields(expected: BigIntStats, actual: BigIntStats): readonly string[] {
88
+ return SNAPSHOT_FIELDS.filter(field => expected[field] !== actual[field]);
89
+ }
90
+ function assertSnapshot(expected: BigIntStats, actual: BigIntStats, phase: string, allowCtime: boolean): boolean {
91
+ const changes = changedFields(expected, actual);
92
+ if (!actual.isFile() || actual.isSymbolicLink() || changes.some(field => !allowCtime || field !== "ctimeNs")) {
93
+ throw new Error(`Compositor output changed ${phase} (fields: ${changes.join(", ") || "file type"}).`);
94
+ }
95
+ return changes.length > 0;
96
+ }
97
+ function aggregateFailure(label: string, failures: readonly unknown[]): AggregateError {
98
+ const reasons = failures.map(reason => {
99
+ try { return (reason instanceof Error ? reason.message : String(reason)).slice(0, 1800); }
100
+ catch { return "Unreadable native rejection."; }
101
+ });
102
+ return new AggregateError(failures, `${label}: ${reasons.join("; ").slice(0, 3800)}`);
103
+ }
104
+ async function withPinnedFile<T>(path: string, work: (descriptor: FileHandle) => Promise<T>): Promise<T> {
105
+ const descriptor = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
106
+ const result = await Promise.resolve().then(async () => await work(descriptor)).then(
107
+ value => ({ status: "fulfilled" as const, value }),
108
+ (reason: unknown) => ({ status: "rejected" as const, reason }),
109
+ );
110
+ try { await descriptor.close(); }
111
+ catch (cause) {
112
+ if (result.status === "rejected") throw aggregateFailure("Compositor verification and descriptor cleanup failed", [result.reason, cause]);
113
+ throw cause;
114
+ }
115
+ if (result.status === "rejected") throw result.reason;
116
+ return result.value;
117
+ }
118
+
119
+ /** Pinned byte identity and at most one conditional, read-only probe revalidation. */
120
+ export async function verifySpatialCompositorOutput(input: {
121
+ readonly cadence: unknown;
122
+ readonly expectedVideo: SpatialCompositorVideoProfile;
123
+ readonly outputPath: string;
124
+ readonly maximumBytes: number;
125
+ readonly ffprobe: string;
126
+ readonly runner: ProcessRunner;
127
+ readonly signal: AbortSignal;
128
+ }) {
129
+ const cadence = assertSpatialCompositorCadence(input.cadence);
130
+ const expectedVideo = videoProfile(input.expectedVideo);
131
+ if (!isAbsolute(input.outputPath) || !isAbsolute(input.ffprobe)
132
+ || !Number.isSafeInteger(input.maximumBytes) || input.maximumBytes <= 0 || input.maximumBytes > SPATIAL_COMPOSITOR_LIMITS.outputBytes) throw new Error("Compositor verification requires bounded host-owned absolute paths.");
133
+ const deadline = Date.now() + SPATIAL_COMPOSITOR_LIMITS.timeoutMs;
134
+ const checkControl = () => {
135
+ input.signal.throwIfAborted();
136
+ if (Date.now() >= deadline) throw new Error("Compositor timing verification exceeded its bounded deadline.");
137
+ };
138
+ checkControl();
139
+ const expected = await lstat(input.outputPath, { bigint: true });
140
+ if (!expected.isFile() || expected.isSymbolicLink() || expected.size <= 0n || expected.size > BigInt(input.maximumBytes)) throw new Error("Compositor output is not a bounded physical file.");
141
+ return await withPinnedFile(input.outputPath, async identityDescriptor => {
142
+ const buffer = Buffer.allocUnsafe(256 * 1024);
143
+ const hashIdentity = async (snapshot: BigIntStats, allowCtime: boolean) => {
144
+ checkControl();
145
+ const before = await identityDescriptor.stat({ bigint: true });
146
+ let metadataChanged = assertSnapshot(snapshot, before, "before byte verification", allowCtime);
147
+ const hash = createHash("sha256");
148
+ let bytes = 0;
149
+ const expectedBytes = Number(expected.size);
150
+ while (true) {
151
+ checkControl();
152
+ const read = await identityDescriptor.read(buffer, 0, Math.min(buffer.byteLength, expectedBytes - bytes + 1), bytes);
153
+ if (read.bytesRead === 0) break;
154
+ bytes += read.bytesRead;
155
+ if (bytes > expectedBytes) throw new Error("Compositor output changed during byte verification (fields: size).");
156
+ hash.update(buffer.subarray(0, read.bytesRead));
157
+ }
158
+ const after = await identityDescriptor.stat({ bigint: true });
159
+ const retained = await lstat(input.outputPath, { bigint: true });
160
+ metadataChanged = assertSnapshot(before, after, "during byte verification", allowCtime) || metadataChanged;
161
+ metadataChanged = assertSnapshot(after, retained, "at byte-verification path readback", allowCtime) || metadataChanged;
162
+ if (bytes !== expectedBytes) throw new Error("Compositor output changed during byte verification (fields: size).");
163
+ checkControl();
164
+ return { metadataChanged, sha256: hash.digest("hex"), snapshot: retained };
165
+ };
166
+ let baseline = await hashIdentity(expected, true);
167
+ const originalDigest = baseline.sha256;
168
+ if (baseline.metadataChanged) {
169
+ baseline = await hashIdentity(baseline.snapshot, false);
170
+ if (baseline.sha256 !== originalDigest) throw new Error("Compositor output bytes changed while establishing timing-verification identity.");
171
+ }
172
+ const probe = async (kind: "video" | "audio", snapshot: BigIntStats, allowCtime: boolean) => await withPinnedFile(input.outputPath, async descriptor => {
173
+ checkControl();
174
+ const before = await descriptor.stat({ bigint: true });
175
+ let metadataChanged = assertSnapshot(snapshot, before, `before ${kind} timing verification`, allowCtime);
176
+ const entries = kind === "video"
177
+ ? "stream=codec_type,codec_name,width,height,pix_fmt,time_base,start_pts,duration_ts,avg_frame_rate,r_frame_rate,nb_frames,nb_read_frames:frame=best_effort_timestamp:frame_side_data="
178
+ : "stream=codec_type,codec_name,time_base,start_pts,duration_ts,sample_rate";
179
+ const result = await input.runner.run([input.ffprobe,
180
+ "-v", "error", "-protocol_whitelist", "file", "-format_whitelist", "mov",
181
+ "-enable_drefs", "0", "-use_absolute_path", "0", "-select_streams", kind === "video" ? "v:0" : "a:0",
182
+ ...(kind === "video" ? ["-count_frames", "-show_frames"] : []),
183
+ "-show_streams", "-show_entries", entries, "-of", "json", "/dev/fd/3",
184
+ ], { inheritedFileDescriptors: [descriptor.fd], abortSignal: input.signal, timeoutMs: Math.max(1, deadline - Date.now()),
185
+ maxOutputBytes: kind === "video" ? SPATIAL_COMPOSITOR_LIMITS.probeBytes : 64 * 1024, stdin: "ignore" });
186
+ if (result.exitCode !== 0) throw new Error(`Compositor ${kind} timing verification failed: ${result.stderr.slice(-4096)}`);
187
+ checkControl();
188
+ const after = await descriptor.stat({ bigint: true });
189
+ metadataChanged = assertSnapshot(before, after, `during ${kind} timing verification`, allowCtime) || metadataChanged;
190
+ if (Buffer.byteLength(result.stdout) >= (kind === "video" ? SPATIAL_COMPOSITOR_LIMITS.probeBytes : 64 * 1024)) throw new Error("Compositor timing probe exceeded the bounded response limit.");
191
+ return { metadataChanged, value: JSON.parse(result.stdout) as unknown };
192
+ });
193
+ for (const attempt of [1, 2] as const) {
194
+ // A failed sibling must settle before descriptors or the workspace close.
195
+ const results = await Promise.allSettled([probe("video", baseline.snapshot, attempt === 1), probe("audio", baseline.snapshot, attempt === 1)]);
196
+ const failed = results.filter(result => result.status === "rejected");
197
+ if (failed.length > 0) throw aggregateFailure("Compositor native verification failed", failed.map(result => result.reason));
198
+ const video = (results[0] as PromiseFulfilledResult<Awaited<ReturnType<typeof probe>>>).value;
199
+ const audio = (results[1] as PromiseFulfilledResult<Awaited<ReturnType<typeof probe>>>).value;
200
+ // Native, schema, or semantic failures never authorize another probe.
201
+ const verified = verifySpatialCompositorProbe({ cadence, expectedVideo, video: video.value, audio: audio.value });
202
+ const after = await hashIdentity(baseline.snapshot, attempt === 1);
203
+ if (after.sha256 !== originalDigest) throw new Error("Compositor output bytes changed during timing verification.");
204
+ if (!video.metadataChanged && !audio.metadataChanged && !after.metadataChanged) return verified;
205
+ // Equal endpoint hashes do not prove what a probe saw during a mutable
206
+ // interval. Discard those results and certify one fresh pair only after
207
+ // an identical digest has been re-established in a stable snapshot.
208
+ baseline = await hashIdentity(after.snapshot, false);
209
+ if (baseline.sha256 !== originalDigest) throw new Error("Compositor output bytes changed before conditional timing revalidation.");
210
+ }
211
+ throw new Error("Compositor output did not remain stable during timing revalidation.");
212
+ });
213
+ }
@@ -0,0 +1,90 @@
1
+ import { basename, dirname, join, resolve } from "node:path";
2
+ import { lstat, realpath } from "node:fs/promises";
3
+ import { z } from "zod";
4
+ import type { ApplicationContext } from "../application/context";
5
+ import { createApplicationOperationRegistry } from "../application/default-registry";
6
+ import { bindProjectRenderInputV4 } from "../application/operations/render/project";
7
+ import { withSpatialProjectLease } from "../application/spatial-project-lease";
8
+ import { prepareSpatialProjectRender, SpatialProjectRenderPreparationInputSchema } from "../application/spatial-project-render-preparation";
9
+ import { spatialProjectStorePorts } from "../application/spatial-project-authority";
10
+ import { readSpatialProjectAuthority } from "../application/spatial-project-store";
11
+ import { ProjectRenderOutputRequestSchema, ProjectRenderSyncPolicySchema, ProjectRenderTargetSchema } from "../contracts/project-render";
12
+ import { SPATIAL_PROJECT_LIMITS } from "../contracts/spatial-project";
13
+ import type { SpatialProjectCommand } from "./args";
14
+ import { CliError } from "./errors";
15
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
16
+ import { bindSpatialCliExecutionProfile, publishSpatialSource, readSpatialJson } from "./spatial-scene-service";
17
+
18
+ const preparationDeliverySchema = z.strictObject({
19
+ output: ProjectRenderOutputRequestSchema,
20
+ syncPolicy: ProjectRenderSyncPolicySchema,
21
+ tier: z.enum(["preview", "final"]),
22
+ }).superRefine((value, context) => {
23
+ if (value.output.maximumBytes > 256 * 1024 * 1024) context.addIssue({ code: "custom", message: "Spatial project delivery permits at most 256 MiB of output." });
24
+ });
25
+
26
+ export async function executeSpatialProjectCommand(application: ApplicationContext, command: SpatialProjectCommand): Promise<unknown> {
27
+ const request = command.action === "snapshot" ? {} : await readSpatialJson(resolve(application.paths.repositoryRoot, command.input), SPATIAL_PROJECT_LIMITS.documentBytes);
28
+ if (typeof request !== "object" || request === null || Array.isArray(request)) throw new CliError("invalid-data", "A spatial project request must be a JSON object.");
29
+ if ("project" in request && request.project !== command.project) throw new CliError("conflict", "Project argument differs from the request project.");
30
+ if (command.action === "prepare-render") {
31
+ const { delivery: unparsedDelivery, ...preparation } = request as Record<string, unknown>;
32
+ const delivery = preparationDeliverySchema.parse(unparsedDelivery);
33
+ const input = SpatialProjectRenderPreparationInputSchema.parse({ ...preparation, project: command.project,
34
+ profile: bindSpatialCliExecutionProfile(preparation.profile, command.executionProfile) });
35
+ const target = ProjectRenderTargetSchema.parse({
36
+ canvas: { kind: "custom", pixelWidth: input.profile.pixelWidth, pixelHeight: input.profile.pixelHeight, frameRate: input.profile.frameRate.numerator / input.profile.frameRate.denominator },
37
+ tier: delivery.tier,
38
+ });
39
+ const requestedOutputPath = resolve(application.paths.repositoryRoot, command.output);
40
+ const preparedParent = await realpath(dirname(requestedOutputPath));
41
+ const preparedParentIdentity = await lstat(preparedParent);
42
+ const outputPath = join(preparedParent, basename(requestedOutputPath));
43
+ try {
44
+ await lstat(outputPath);
45
+ throw new CliError("conflict", "Prepared render output already exists. Choose a new path before rendering.");
46
+ } catch (error) {
47
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
48
+ }
49
+ const prepared = await withSpatialProjectLease(application, command.project, async leasedApplication => {
50
+ const ports = await spatialProjectStorePorts(leasedApplication, command.project);
51
+ const snapshot = await readSpatialProjectAuthority(ports);
52
+ if (snapshot.version !== 2 || input.expected.version !== 2 || snapshot.basis.sha256 !== input.expected.sha256) throw new CliError("conflict", "Scene delivery requires the exact current V2 project basis.");
53
+ if (delivery.syncPolicy === "require-verified" && snapshot.contents.legacy.project.placements.some(placement => placement.enabled && placement.sync.provenance.kind === "unverified")) throw new CliError("conflict", "Scene delivery includes unverified placement synchronization. Resolve it before migration or explicitly allow unverified synchronization.");
54
+ const projectDirectory = leasedApplication.spatialProjectCustody!.projectDirectory;
55
+ const finalOutput = join(projectDirectory, delivery.output.path);
56
+ if (finalOutput === outputPath) throw new CliError("conflict", "Prepared request and final video require distinct output paths.");
57
+ const finalParent = await ensurePhysicalPrivateDirectoryWithin(projectDirectory, dirname(delivery.output.path));
58
+ if (finalParent !== dirname(finalOutput)) throw new CliError("unsafe-path", "Scene delivery output parent changed.");
59
+ try {
60
+ await lstat(finalOutput);
61
+ throw new CliError("conflict", "Scene delivery output already exists. Choose a new path before rendering.");
62
+ } catch (error) {
63
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
64
+ }
65
+ await ports.custody.assertHeld();
66
+ return await prepareSpatialProjectRender({ application: leasedApplication, abortSignal: new AbortController().signal }, input);
67
+ });
68
+ try {
69
+ const render = await bindProjectRenderInputV4(application, { plan: prepared.plan, spatial: prepared.spatial, output: delivery.output, syncPolicy: delivery.syncPolicy, target });
70
+ await publishSpatialSource(outputPath, render, async () => {
71
+ const current = await lstat(preparedParent);
72
+ if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== preparedParentIdentity.dev || current.ino !== preparedParentIdentity.ino
73
+ || await realpath(preparedParent) !== preparedParent) throw new CliError("conflict", "Prepared render output parent changed during rendering.");
74
+ await application.hostResourceLease?.assertOwned();
75
+ });
76
+ } catch (error) {
77
+ throw new CliError("conflict", `Scene preparation completed, but the prepared delivery file could not be published: ${error instanceof Error ? error.message : String(error)}`, { prepared, preparedRender: outputPath });
78
+ }
79
+ return { sourceBasis: prepared.sourceBasis, projectionSha256: prepared.projectionSha256, plan: prepared.plan, preparedRender: outputPath, workflow: "directed-scene" };
80
+ }
81
+ const registry = createApplicationOperationRegistry();
82
+ const result = await withSpatialProjectLease(application, command.project, async leasedApplication => await registry.execute({
83
+ application: leasedApplication, abortSignal: new AbortController().signal,
84
+ }, { kind: `spatial.project.${command.action}`, version: 1, input: { ...request, project: command.project } }), undefined, command.action === "migrate" ? "mutation" : "read");
85
+ if (command.action !== "snapshot") {
86
+ const disposition = result.output as { readonly kind?: string; readonly message?: string };
87
+ if (disposition.kind !== "completed") throw new CliError(disposition.kind === "ambiguous" ? "ambiguous" : "conflict", disposition.message ?? "Spatial publication did not complete.", { spatialPublication: result.output });
88
+ }
89
+ return result.output;
90
+ }
@@ -0,0 +1,105 @@
1
+ import { constants } from "node:fs";
2
+ import { open, realpath } from "node:fs/promises";
3
+ import { basename, dirname, relative, resolve } from "node:path";
4
+ import { canonicalJson } from "../../../src/code/canonical-json";
5
+ import { createSpatialSceneStarter } from "../../../src/spatial-scene/authoring";
6
+ import { SPATIAL_SCENE_LIMITS } from "../../../src/spatial-scene/contracts";
7
+ import { parseSpatialScene, spatialSceneSha256 } from "../../../src/spatial-scene/index";
8
+ import { sampleSpatialCameraTrack } from "../../../src/spatial-scene/camera-track";
9
+ import type { ApplicationContext } from "../application/context";
10
+ import { createApplicationOperationRegistry } from "../application/default-registry";
11
+ import { planSpatialRender } from "../application/spatial-render";
12
+ import { createNodeBundleFileSystem } from "../core/storage";
13
+ import type { SpatialCliExecutionProfile, SpatialSceneCommand } from "./args";
14
+ import { CliError } from "./errors";
15
+
16
+ /** Capture an explicit local regular file once, without following a leaf symlink. */
17
+ export async function readSpatialJson(path: string, maximumBytes: number = SPATIAL_SCENE_LIMITS.sourceBytes): Promise<unknown> {
18
+ if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1 || maximumBytes > 32 * 1024 * 1024) throw new CliError("invalid-data", "Invalid spatial JSON byte limit.");
19
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
20
+ try {
21
+ const before = await handle.stat({ bigint: true });
22
+ if (!before.isFile() || before.size > BigInt(maximumBytes)) throw new CliError("invalid-data", "Scene source must be a bounded regular JSON file.");
23
+ const bytes = new Uint8Array(Number(before.size));
24
+ let offset = 0;
25
+ while (offset < bytes.byteLength) {
26
+ const read = await handle.read(bytes, offset, Math.min(256 * 1024, bytes.byteLength - offset), offset);
27
+ if (read.bytesRead === 0) throw new CliError("conflict", "Scene source ended while it was being read.");
28
+ offset += read.bytesRead;
29
+ }
30
+ const after = await handle.stat({ bigint: true });
31
+ if (bytes.byteLength > maximumBytes || after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
32
+ throw new CliError("conflict", "Scene source changed while it was being read.");
33
+ }
34
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) as unknown;
35
+ } finally { await handle.close(); }
36
+ }
37
+
38
+ export async function publishSpatialSource(path: string, value: unknown, beforePublication?: () => Promise<void>): Promise<void> {
39
+ const directory = await realpath(dirname(resolve(path)));
40
+ const fileSystem = createNodeBundleFileSystem(directory);
41
+ const written = await fileSystem.writeTextNoReplace?.(basename(path), `${canonicalJson(value)}\n`, beforePublication);
42
+ if (written !== "created") throw new CliError("conflict", "Scene output already exists. Choose a new path to retain both revisions.");
43
+ }
44
+
45
+ /** An explicit CLI profile may fill an omitted request field, never replace one. */
46
+ export function bindSpatialCliExecutionProfile(request: unknown, executionProfile: SpatialCliExecutionProfile | undefined): unknown {
47
+ if (executionProfile === undefined) return request;
48
+ if (typeof request !== "object" || request === null || Array.isArray(request)) throw new CliError("invalid-data", "A spatial render request must be a JSON object.");
49
+ if ("executionProfile" in request && request.executionProfile !== executionProfile) throw new CliError("conflict", "--profile differs from the execution profile retained in the request.");
50
+ return { ...request, executionProfile };
51
+ }
52
+
53
+ function assertCameraTrackActive(signal: AbortSignal | undefined): void {
54
+ if (signal?.aborted === true) throw new CliError("cancelled", "Camera track export was cancelled.");
55
+ }
56
+
57
+ export async function executeSpatialSceneCommand(application: ApplicationContext, command: SpatialSceneCommand, signal?: AbortSignal): Promise<unknown> {
58
+ if (command.action === "camera-track") assertCameraTrackActive(signal);
59
+ const sourcePath = resolve(application.paths.repositoryRoot, command.path);
60
+ if (command.action === "init") {
61
+ const scene = createSpatialSceneStarter();
62
+ await publishSpatialSource(sourcePath, scene);
63
+ return { path: sourcePath, sceneSha256: spatialSceneSha256(scene) };
64
+ }
65
+ const scene = parseSpatialScene(await readSpatialJson(sourcePath));
66
+ if (command.action === "camera-track") {
67
+ const fence = async () => {
68
+ assertCameraTrackActive(signal);
69
+ await application.hostResourceLease?.assertOwned();
70
+ assertCameraTrackActive(signal);
71
+ };
72
+ assertCameraTrackActive(signal);
73
+ const request = await readSpatialJson(resolve(application.paths.repositoryRoot, command.request));
74
+ assertCameraTrackActive(signal);
75
+ const track = sampleSpatialCameraTrack(scene, request);
76
+ const output = resolve(application.paths.repositoryRoot, command.output);
77
+ await publishSpatialSource(output, track, fence);
78
+ await fence();
79
+ return { path: output, sceneSha256: track.sceneSha256, cameraId: track.cameraId, clock: track.clock, executed: false };
80
+ }
81
+ if (command.action === "plan" || command.action === "render") {
82
+ const request = bindSpatialCliExecutionProfile(await readSpatialJson(resolve(application.paths.repositoryRoot, command.request)), command.executionProfile);
83
+ if (command.action === "plan") return planSpatialRender(scene, request);
84
+ const result = await createApplicationOperationRegistry().execute({ application, abortSignal: new AbortController().signal }, {
85
+ kind: "scene.render", version: 1, input: {
86
+ source: { path: relative(application.paths.repositoryRoot, sourcePath) }, request,
87
+ ...(command.assets === undefined ? {} : { assets: await readSpatialJson(resolve(application.paths.repositoryRoot, command.assets)) }),
88
+ },
89
+ });
90
+ return result.output;
91
+ }
92
+ const registry = createApplicationOperationRegistry();
93
+ const input = command.action === "patch"
94
+ ? { scene, patch: await readSpatialJson(resolve(application.paths.repositoryRoot, command.patch)) }
95
+ : command.action === "evaluate" ? { scene, cameraId: command.camera, timeUs: command.timeUs } : { scene };
96
+ const result = await registry.execute({ application, abortSignal: new AbortController().signal }, {
97
+ kind: command.action === "patch" ? "scene.patch" : command.action === "evaluate" ? "scene.evaluate" : "scene.inspect",
98
+ version: 1, input,
99
+ });
100
+ if (command.action === "patch") {
101
+ const output = result.output as { readonly scene: unknown };
102
+ await publishSpatialSource(resolve(application.paths.repositoryRoot, command.output), output.scene);
103
+ }
104
+ return result.output;
105
+ }
@@ -0,0 +1,39 @@
1
+ import { join, relative, resolve, sep } from "node:path";
2
+ import type { ApplicationContext } from "../application/context";
3
+ import { importSavedSpatialWorld, SavedSpatialWorldImportInputSchema, SpatialWorldImportError } from "../application/spatial-world-import";
4
+ import type { SpatialWorldCommand } from "./args";
5
+ import { CliError } from "./errors";
6
+ import { ensurePhysicalPrivateDirectoryWithin } from "./paths";
7
+ import { readSpatialJson } from "./spatial-scene-service";
8
+
9
+ export async function executeSpatialWorldCommand(
10
+ application: ApplicationContext,
11
+ command: SpatialWorldCommand,
12
+ adapter: Readonly<{ signal?: AbortSignal }>,
13
+ ): Promise<unknown> {
14
+ const repositoryRoot = application.paths.repositoryRoot;
15
+ try {
16
+ if (adapter.signal?.aborted) throw new CliError("cancelled", "World import cancelled before reading source assets.");
17
+ const input = SavedSpatialWorldImportInputSchema.safeParse(await readSpatialJson(resolve(repositoryRoot, command.input)));
18
+ if (!input.success) throw new CliError("invalid-data", "The saved world import input is invalid.");
19
+ const outputRoot = resolve(repositoryRoot, command.outputRoot);
20
+ const generatedRoot = join(repositoryRoot, "artifacts", "slopcamera", "generated");
21
+ const outputRelative = relative(generatedRoot, outputRoot);
22
+ if (outputRelative === "" || outputRelative.startsWith(`..${sep}`) || outputRelative === ".." || resolve(generatedRoot, outputRelative) !== outputRoot) {
23
+ throw new CliError("unsafe-path", "World imports require a dedicated directory below artifacts/slopcamera/generated.");
24
+ }
25
+ const destinationRoot = await ensurePhysicalPrivateDirectoryWithin(repositoryRoot, relative(repositoryRoot, outputRoot));
26
+ return await importSavedSpatialWorld({
27
+ sourceRoot: resolve(repositoryRoot, command.sourceRoot), destinationRoot, input: input.data,
28
+ signal: adapter.signal ?? new AbortController().signal,
29
+ beforePublication: async () => { await application.hostResourceLease?.assertOwned(); },
30
+ });
31
+ } catch (error) {
32
+ if (error instanceof SpatialWorldImportError) throw new CliError(adapter.signal?.aborted ? "cancelled" : "conflict", error.message, {
33
+ published: error.published, attempted: error.attempted, outputRoot: command.outputRoot,
34
+ });
35
+ if (adapter.signal?.aborted) throw new CliError("cancelled", "World import cancelled; retain its source and any publication evidence.");
36
+ if (error instanceof RangeError) throw new CliError("invalid-data", error.message);
37
+ throw error;
38
+ }
39
+ }