@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,17 @@
1
+ const gatewayCredentialNames = new Set([
2
+ "AI_GATEWAY_API_KEY",
3
+ "VERCEL_OIDC_TOKEN",
4
+ ])
5
+
6
+ /** Copy an environment for a non-Gateway child without delegating authority. */
7
+ export function nonGatewayChildEnvironment(
8
+ source: Readonly<Record<string, string | undefined>> = process.env,
9
+ ): Record<string, string | undefined> {
10
+ const environment = { ...source }
11
+ for (const name of Object.keys(environment)) {
12
+ if (gatewayCredentialNames.has(name.toLocaleUpperCase("en-US"))) {
13
+ delete environment[name]
14
+ }
15
+ }
16
+ return environment
17
+ }
package/src/render.ts ADDED
@@ -0,0 +1,437 @@
1
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference -- packed source types must include Bun asset modules
2
+ /// <reference path="./assets.d.ts" />
3
+
4
+ import { readFile } from "node:fs/promises"
5
+ import { extname, isAbsolute } from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+ import { Resvg } from "@resvg/resvg-js"
8
+ import nebulaSansBoldOtfAsset from "./assets/fonts/nebula-sans/NebulaSans-Bold.otf" with { type: "file" }
9
+ import nebulaSansBoldAsset from "./assets/fonts/nebula-sans/NebulaSans-Bold.woff2" with { type: "file" }
10
+ import nebulaSansBookOtfAsset from "./assets/fonts/nebula-sans/NebulaSans-Book.otf" with { type: "file" }
11
+ import nebulaSansBookAsset from "./assets/fonts/nebula-sans/NebulaSans-Book.woff2" with { type: "file" }
12
+ import { sanitizeIcon } from "./icons.js"
13
+ import { layoutBoxContent, wrapDiagramText } from "./label-layout.js"
14
+ import { resolveTheme } from "./theme.js"
15
+ import type {
16
+ Anchor,
17
+ BoxShape,
18
+ ColorMode,
19
+ DiagramConfig,
20
+ DiagramEdge,
21
+ DiagramShape,
22
+ DiagramSpec,
23
+ FontConfig,
24
+ IconDefinition,
25
+ RenderedDiagram,
26
+ TextShape,
27
+ ThemeColors,
28
+ } from "./types.js"
29
+
30
+ interface FontFamilies {
31
+ readonly default: string
32
+ readonly mono: string
33
+ }
34
+
35
+ const bundledAssetPath = (asset: string): string =>
36
+ isAbsolute(asset) ? asset : fileURLToPath(new URL(asset, import.meta.url))
37
+
38
+ const defaultFont: FontConfig = Object.freeze({
39
+ family: "Nebula Sans",
40
+ files: Object.freeze([
41
+ Object.freeze({
42
+ embed: true,
43
+ path: bundledAssetPath(nebulaSansBookAsset),
44
+ style: "normal" as const,
45
+ weight: 400,
46
+ }),
47
+ Object.freeze({
48
+ embed: true,
49
+ path: bundledAssetPath(nebulaSansBoldAsset),
50
+ style: "normal" as const,
51
+ weight: 700,
52
+ }),
53
+ Object.freeze({
54
+ embed: false,
55
+ path: bundledAssetPath(nebulaSansBookOtfAsset),
56
+ style: "normal" as const,
57
+ weight: 400,
58
+ }),
59
+ Object.freeze({
60
+ embed: false,
61
+ path: bundledAssetPath(nebulaSansBoldOtfAsset),
62
+ style: "normal" as const,
63
+ weight: 700,
64
+ }),
65
+ ]),
66
+ })
67
+
68
+ const escapeXml = (value: string): string =>
69
+ value
70
+ .replaceAll("&", "&amp;")
71
+ .replaceAll("<", "&lt;")
72
+ .replaceAll(">", "&gt;")
73
+ .replaceAll('"', "&quot;")
74
+ .replaceAll("'", "&apos;")
75
+
76
+ function fontMime(filePath: string): string {
77
+ switch (extname(filePath).toLowerCase()) {
78
+ case ".otf":
79
+ return "font/otf"
80
+ case ".woff":
81
+ return "font/woff"
82
+ case ".woff2":
83
+ return "font/woff2"
84
+ default:
85
+ return "font/ttf"
86
+ }
87
+ }
88
+
89
+ async function fontCss(font: FontConfig): Promise<string> {
90
+ if (font.files === undefined) return ""
91
+ const faces = await Promise.all(
92
+ font.files
93
+ .filter((file) => file.embed === true)
94
+ .map(async (file) => {
95
+ const encoded = (await readFile(file.path)).toString("base64")
96
+ return `@font-face{font-family:"${escapeXml(font.family)}";src:url(data:${fontMime(file.path)};base64,${encoded});font-weight:${file.weight ?? 400};font-style:${file.style ?? "normal"};}`
97
+ }),
98
+ )
99
+ return faces.join("")
100
+ }
101
+
102
+ function textSvg(options: {
103
+ readonly text: string
104
+ readonly x: number
105
+ readonly y: number
106
+ readonly width: number
107
+ readonly fontSize: number
108
+ readonly weight: number
109
+ readonly align: "start" | "middle" | "end"
110
+ readonly color: string
111
+ readonly opacity: number
112
+ readonly family: string
113
+ readonly lineHeight?: number
114
+ readonly lines?: readonly string[]
115
+ readonly centerLineBoxes?: boolean
116
+ }): string {
117
+ const lines = options.lines ?? wrapDiagramText(options.text, options.width, options.fontSize)
118
+ const lineHeight = options.lineHeight ?? options.fontSize * 1.25
119
+ const anchor = options.align
120
+ const x =
121
+ anchor === "middle"
122
+ ? options.x + options.width / 2
123
+ : anchor === "end"
124
+ ? options.x + options.width
125
+ : options.x
126
+ const dominantBaseline = options.centerLineBoxes ? "central" : "hanging"
127
+ return `<text x="${x}" y="${options.y}" text-anchor="${anchor}" dominant-baseline="${dominantBaseline}" fill="${options.color}" opacity="${options.opacity}" font-family="${escapeXml(options.family)}" font-size="${options.fontSize}" font-weight="${options.weight}">${lines
128
+ .map((line, index) =>
129
+ options.centerLineBoxes
130
+ ? `<tspan x="${x}" y="${options.y + (index + 0.5) * lineHeight}">${escapeXml(line)}</tspan>`
131
+ : `<tspan x="${x}" dy="${index === 0 ? 0 : lineHeight}">${escapeXml(line)}</tspan>`,
132
+ )
133
+ .join("")}</text>`
134
+ }
135
+
136
+ function iconSvg(options: {
137
+ readonly icon: IconDefinition
138
+ readonly x: number
139
+ readonly y: number
140
+ readonly size: number
141
+ readonly color: string
142
+ readonly opacity: number
143
+ }): string {
144
+ const icon = sanitizeIcon(options.icon)
145
+ const parts = icon.viewBox.trim().split(/\s+/).map(Number)
146
+ const width = (parts[2] ?? 24) - (parts[0] ?? 0)
147
+ const height = (parts[3] ?? 24) - (parts[1] ?? 0)
148
+ const scale = options.size / Math.max(width, height)
149
+ return `<g color="${options.color}" opacity="${options.opacity}" transform="translate(${options.x} ${options.y}) scale(${scale}) translate(${-Number(parts[0] ?? 0)} ${-Number(parts[1] ?? 0)})">${icon.body}</g>`
150
+ }
151
+
152
+ function boxSvg(
153
+ shape: BoxShape,
154
+ theme: ThemeColors,
155
+ families: FontFamilies,
156
+ icons: Readonly<Record<string, IconDefinition>>,
157
+ ): string {
158
+ const tone = theme.tones[shape.tone ?? "neutral"]
159
+ const strokeWidth = shape.strokeWidth ?? 2
160
+ const opacity = shape.opacity ?? 1
161
+ const radius = shape.type === "ellipse" ? 0 : Math.min(shape.radius ?? 22, shape.height / 2)
162
+ const geometry =
163
+ shape.type === "ellipse"
164
+ ? `<ellipse cx="${shape.x + shape.width / 2}" cy="${shape.y + shape.height / 2}" rx="${shape.width / 2}" ry="${shape.height / 2}" fill="${shape.fill === false ? "none" : tone.fill}" stroke="${tone.stroke}" stroke-width="${strokeWidth}"/>`
165
+ : `<rect x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}" rx="${radius}" fill="${shape.fill === false ? "none" : tone.fill}" stroke="${tone.stroke}" stroke-width="${strokeWidth}"/>`
166
+ const icon = shape.icon === undefined ? undefined : icons[shape.icon]
167
+ if (shape.icon !== undefined && icon === undefined) {
168
+ throw new Error(`Unknown icon "${shape.icon}" on shape ${shape.id}`)
169
+ }
170
+ const iconSize = Math.min(shape.iconSize ?? 52, shape.height * 0.45, shape.width * 0.32)
171
+ const content = layoutBoxContent(shape, icon === undefined ? undefined : iconSize)
172
+ const iconMarkup =
173
+ icon === undefined || content.icon === undefined
174
+ ? ""
175
+ : iconSvg({
176
+ icon,
177
+ x: content.icon.x,
178
+ y: content.icon.y,
179
+ size: content.icon.size,
180
+ color: tone.text,
181
+ opacity: 1,
182
+ })
183
+ const labelMarkup = content.rows
184
+ .map((row) =>
185
+ textSvg({
186
+ text: row.text,
187
+ x: shape.x + 16,
188
+ y: row.y,
189
+ width: shape.width - 32,
190
+ fontSize: row.fontSize,
191
+ weight: row.weight,
192
+ align: "middle",
193
+ color: tone.text,
194
+ opacity: 1,
195
+ family: families[row.fontFamily],
196
+ lineHeight: row.lineHeight,
197
+ lines: row.lines,
198
+ centerLineBoxes: true,
199
+ }),
200
+ )
201
+ .join("")
202
+ return `<g data-shape-id="${escapeXml(shape.id)}" opacity="${opacity}">${geometry}${iconMarkup}${labelMarkup}</g>`
203
+ }
204
+
205
+ function pointForAnchor(
206
+ shape: BoxShape,
207
+ anchor: Anchor | undefined,
208
+ toward: { readonly x: number; readonly y: number },
209
+ position: number | undefined,
210
+ ): { readonly x: number; readonly y: number; readonly normalized: { readonly x: number; readonly y: number } } {
211
+ const center = { x: shape.x + shape.width / 2, y: shape.y + shape.height / 2 }
212
+ let resolved = anchor ?? "auto"
213
+ if (resolved === "auto") {
214
+ const dx = toward.x - center.x
215
+ const dy = toward.y - center.y
216
+ resolved =
217
+ Math.abs(dx / shape.width) >= Math.abs(dy / shape.height)
218
+ ? dx >= 0
219
+ ? "right"
220
+ : "left"
221
+ : dy >= 0
222
+ ? "bottom"
223
+ : "top"
224
+ }
225
+ const resolvedPosition = position ?? 0.5
226
+ if (shape.type === "ellipse") {
227
+ const projected = (resolvedPosition - 0.5) * 2
228
+ const radial = Math.sqrt(Math.max(0, 1 - projected * projected)) / 2
229
+ const normalized =
230
+ resolved === "top"
231
+ ? { x: resolvedPosition, y: 0.5 - radial }
232
+ : resolved === "bottom"
233
+ ? { x: resolvedPosition, y: 0.5 + radial }
234
+ : resolved === "left"
235
+ ? { x: 0.5 - radial, y: resolvedPosition }
236
+ : { x: 0.5 + radial, y: resolvedPosition }
237
+ return {
238
+ x: shape.x + shape.width * normalized.x,
239
+ y: shape.y + shape.height * normalized.y,
240
+ normalized,
241
+ }
242
+ }
243
+ switch (resolved) {
244
+ case "top":
245
+ return {
246
+ x: shape.x + shape.width * resolvedPosition,
247
+ y: shape.y,
248
+ normalized: { x: resolvedPosition, y: 0 },
249
+ }
250
+ case "right":
251
+ return {
252
+ x: shape.x + shape.width,
253
+ y: shape.y + shape.height * resolvedPosition,
254
+ normalized: { x: 1, y: resolvedPosition },
255
+ }
256
+ case "bottom":
257
+ return {
258
+ x: shape.x + shape.width * resolvedPosition,
259
+ y: shape.y + shape.height,
260
+ normalized: { x: resolvedPosition, y: 1 },
261
+ }
262
+ case "left":
263
+ return {
264
+ x: shape.x,
265
+ y: shape.y + shape.height * resolvedPosition,
266
+ normalized: { x: 0, y: resolvedPosition },
267
+ }
268
+ }
269
+ }
270
+
271
+ export interface ResolvedEdge {
272
+ readonly edge: DiagramEdge
273
+ readonly from: BoxShape
274
+ readonly to: BoxShape
275
+ readonly start: ReturnType<typeof pointForAnchor>
276
+ readonly end: ReturnType<typeof pointForAnchor>
277
+ readonly control: { readonly x: number; readonly y: number }
278
+ }
279
+
280
+ export function resolveEdge(spec: DiagramSpec, edge: DiagramEdge): ResolvedEdge {
281
+ const boxes = new Map(
282
+ spec.shapes
283
+ .filter((shape): shape is BoxShape => shape.type === "rect" || shape.type === "ellipse")
284
+ .map((shape) => [shape.id, shape]),
285
+ )
286
+ const from = boxes.get(edge.from)
287
+ const to = boxes.get(edge.to)
288
+ if (from === undefined || to === undefined) {
289
+ throw new Error(`Edge ${edge.id} references a missing box`)
290
+ }
291
+ const fromCenter = { x: from.x + from.width / 2, y: from.y + from.height / 2 }
292
+ const toCenter = { x: to.x + to.width / 2, y: to.y + to.height / 2 }
293
+ const start = pointForAnchor(from, edge.start, toCenter, edge.startPosition)
294
+ const end = pointForAnchor(to, edge.end, fromCenter, edge.endPosition)
295
+ const dx = end.x - start.x
296
+ const dy = end.y - start.y
297
+ const length = Math.hypot(dx, dy) || 1
298
+ const bend = edge.bend ?? 0
299
+ return {
300
+ edge,
301
+ from,
302
+ to,
303
+ start,
304
+ end,
305
+ control: {
306
+ x: (start.x + end.x) / 2 + (-dy / length) * bend,
307
+ y: (start.y + end.y) / 2 + (dx / length) * bend,
308
+ },
309
+ }
310
+ }
311
+
312
+ export function resolveEdgeLabel(resolved: ResolvedEdge): {
313
+ readonly x: number
314
+ readonly y: number
315
+ } {
316
+ const { edge, start, end, control } = resolved
317
+ const t = edge.labelPosition ?? 0.5
318
+ const oneMinusT = 1 - t
319
+ const point = {
320
+ x: oneMinusT * oneMinusT * start.x + 2 * oneMinusT * t * control.x + t * t * end.x,
321
+ y: oneMinusT * oneMinusT * start.y + 2 * oneMinusT * t * control.y + t * t * end.y,
322
+ }
323
+ const tangent = {
324
+ x: 2 * oneMinusT * (control.x - start.x) + 2 * t * (end.x - control.x),
325
+ y: 2 * oneMinusT * (control.y - start.y) + 2 * t * (end.y - control.y),
326
+ }
327
+ const length = Math.hypot(tangent.x, tangent.y) || 1
328
+ const offset = edge.labelOffset ?? -14
329
+ return {
330
+ x: point.x + (-tangent.y / length) * offset,
331
+ y: point.y + (tangent.x / length) * offset,
332
+ }
333
+ }
334
+
335
+ function edgeSvg(resolved: ResolvedEdge, theme: ThemeColors, families: FontFamilies): string {
336
+ const { edge, start, end, control } = resolved
337
+ const toneName = edge.tone ?? "neutral"
338
+ const tone = theme.tones[toneName]
339
+ const marker =
340
+ edge.arrowhead === "none"
341
+ ? ""
342
+ : edge.arrowhead === "triangle"
343
+ ? `url(#arrow-triangle-${toneName})`
344
+ : `url(#arrow-open-${toneName})`
345
+ const path = `M ${start.x} ${start.y} Q ${control.x} ${control.y} ${end.x} ${end.y}`
346
+ const labelPoint = resolveEdgeLabel(resolved)
347
+ const label =
348
+ edge.label === undefined
349
+ ? ""
350
+ : `<text x="${labelPoint.x}" y="${labelPoint.y}" text-anchor="middle" dominant-baseline="central" fill="${tone.text}" stroke="${theme.background}" stroke-width="6" paint-order="stroke" font-family="${escapeXml(families[edge.labelFontFamily ?? "default"])}" font-size="${edge.labelFontSize ?? 18}" font-weight="${edge.labelWeight ?? 600}">${escapeXml(edge.label)}</text>`
351
+ return `<g data-edge-id="${escapeXml(edge.id)}"><path d="${path}" fill="none" stroke="${tone.stroke}" stroke-width="3" stroke-linecap="round" marker-end="${marker}"/>${label}</g>`
352
+ }
353
+
354
+ function shapeSvg(
355
+ shape: DiagramShape,
356
+ theme: ThemeColors,
357
+ families: FontFamilies,
358
+ icons: Readonly<Record<string, IconDefinition>>,
359
+ ): string {
360
+ if (shape.type === "rect" || shape.type === "ellipse") {
361
+ return boxSvg(shape, theme, families, icons)
362
+ }
363
+ if (shape.type === "line") {
364
+ return `<line data-shape-id="${escapeXml(shape.id)}" x1="${shape.x}" y1="${shape.y}" x2="${shape.x2}" y2="${shape.y2}" stroke="${theme.tones[shape.tone ?? "neutral"].stroke}" stroke-width="${shape.strokeWidth ?? 3}" stroke-linecap="round" opacity="${shape.opacity ?? 1}"/>`
365
+ }
366
+ const text = shape as TextShape
367
+ return textSvg({
368
+ text: text.text,
369
+ x: text.x,
370
+ y: text.y,
371
+ width: text.width ?? Math.max(text.text.length * (text.fontSize ?? 24) * 0.58, 12),
372
+ fontSize: text.fontSize ?? 24,
373
+ weight: text.weight ?? 500,
374
+ align: text.align ?? "start",
375
+ color: theme.tones[text.tone ?? "neutral"].text,
376
+ opacity: text.opacity ?? 1,
377
+ family: families[text.fontFamily ?? "default"],
378
+ })
379
+ }
380
+
381
+ export async function renderSvg(
382
+ spec: DiagramSpec,
383
+ mode: ColorMode,
384
+ config: DiagramConfig,
385
+ ): Promise<RenderedDiagram> {
386
+ const theme = resolveTheme(mode, config)
387
+ const font = config.font ?? defaultFont
388
+ const families: FontFamilies = {
389
+ default: font.family,
390
+ mono:
391
+ font.monoFamily ??
392
+ "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace",
393
+ }
394
+ const icons = config.icons ?? {}
395
+ const embeddedFonts = await fontCss(font)
396
+ const markerDefinitions = Object.entries(theme.tones)
397
+ .map(
398
+ ([toneName, tone]) =>
399
+ `<marker id="arrow-open-${toneName}" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M2 2 10 6 2 10" fill="none" stroke="${tone.stroke}" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></marker><marker id="arrow-triangle-${toneName}" markerWidth="11" markerHeight="11" refX="9" refY="5.5" orient="auto" markerUnits="strokeWidth"><path d="M1 1 10 5.5 1 10z" fill="${tone.stroke}"/></marker>`,
400
+ )
401
+ .join("")
402
+ const edgeMarkup = (spec.edges ?? [])
403
+ .map((edge) => edgeSvg(resolveEdge(spec, edge), theme, families))
404
+ .join("")
405
+ const shapeMarkup = spec.shapes
406
+ .map((shape) => shapeSvg(shape, theme, families, icons))
407
+ .join("")
408
+ const svg = [
409
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${spec.canvas.width}" height="${spec.canvas.height}" viewBox="0 0 ${spec.canvas.width} ${spec.canvas.height}" role="img" aria-labelledby="diagram-title" color-scheme="${mode}">`,
410
+ `<title id="diagram-title">${escapeXml(spec.name)}</title>`,
411
+ `<style>${embeddedFonts}text{font-kerning:normal;text-rendering:geometricPrecision}</style>`,
412
+ `<rect width="100%" height="100%" fill="${theme.background}"/>`,
413
+ `<defs>${markerDefinitions}</defs>`,
414
+ edgeMarkup,
415
+ shapeMarkup,
416
+ "</svg>",
417
+ ].join("")
418
+ return { mode, svg, width: spec.canvas.width, height: spec.canvas.height }
419
+ }
420
+
421
+ export function renderPng(
422
+ rendered: RenderedDiagram,
423
+ config: DiagramConfig,
424
+ scale = 2,
425
+ ): Uint8Array {
426
+ const font = config.font ?? defaultFont
427
+ const fontFiles = font.files?.map((file) => file.path) ?? []
428
+ const renderer = new Resvg(rendered.svg, {
429
+ fitTo: { mode: "zoom", value: scale },
430
+ font: {
431
+ loadSystemFonts: true,
432
+ ...(fontFiles.length === 0 ? {} : { fontFiles }),
433
+ defaultFontFamily: font.family,
434
+ },
435
+ })
436
+ return renderer.render().asPng()
437
+ }
@@ -0,0 +1,49 @@
1
+ import { cp, mkdir, rm } from "node:fs/promises"
2
+ import { homedir } from "node:os"
3
+ import { dirname, join, resolve } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { pathExists } from "./fs.js"
6
+
7
+ export type SkillTarget = "codex" | "claude" | "agents"
8
+ export type SkillScope = "user" | "project"
9
+
10
+ export function bundledSkillPath(): string {
11
+ return resolve(dirname(fileURLToPath(import.meta.url)), "../skills/slopcamera")
12
+ }
13
+
14
+ function targetRoot(target: SkillTarget, scope: SkillScope, projectDirectory: string): string {
15
+ const directory =
16
+ target === "codex" ? ".codex" : target === "claude" ? ".claude" : ".agents"
17
+ return scope === "user" ? join(homedir(), directory, "skills") : join(projectDirectory, directory, "skills")
18
+ }
19
+
20
+ export async function installSkill(options: {
21
+ readonly target: SkillTarget
22
+ readonly scope: SkillScope
23
+ readonly projectDirectory?: string
24
+ readonly force?: boolean
25
+ }): Promise<string> {
26
+ const source = bundledSkillPath()
27
+ if (!(await pathExists(source))) throw new Error(`Bundled skill is missing: ${source}`)
28
+ const root = targetRoot(
29
+ options.target,
30
+ options.scope,
31
+ resolve(options.projectDirectory ?? process.cwd()),
32
+ )
33
+ const legacy = join(root, "diagram")
34
+ if (await pathExists(legacy)) {
35
+ throw new Error(
36
+ `Legacy diagram skill found at ${legacy}. Remove or move that directory, then rerun "slopcamera skill install --target ${options.target} --scope ${options.scope}". Slopcamera will not install both skills side by side.`,
37
+ )
38
+ }
39
+ const destination = join(root, "slopcamera")
40
+ if (await pathExists(destination)) {
41
+ if (!options.force) {
42
+ throw new Error(`Skill already exists at ${destination}; pass --force to replace it`)
43
+ }
44
+ await rm(destination, { recursive: true, force: true })
45
+ }
46
+ await mkdir(dirname(destination), { recursive: true })
47
+ await cp(source, destination, { recursive: true, errorOnExist: true })
48
+ return destination
49
+ }
@@ -0,0 +1,27 @@
1
+ import { parseSpatialScene } from "./identity.js"
2
+ import type { SpatialSceneV1 } from "./contracts.js"
3
+
4
+ /** A complete editable source with stable part names and a calibrated camera. */
5
+ export function createSpatialSceneStarter(): SpatialSceneV1 {
6
+ const transform = { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [1, 1, 1] }
7
+ const common = { parentId: null, placement: { kind: "world" }, origin: { kind: "authored" }, visible: true }
8
+ return parseSpatialScene({
9
+ kind: "slopcamera.spatial-scene", schemaVersion: 1, sceneId: "scene_starter",
10
+ coordinates: "right-handed-y-up-meters", durationUs: 4_000_000,
11
+ entities: [
12
+ { ...common, entityId: "entity_product", name: "Product", kind: "mesh", transform,
13
+ geometry: { kind: "box", size: [1.4, 1.4, 1.4] }, material: { kind: "standard", color: "#3674ee", opacity: 1, roughness: 0.35, metalness: 0.1 } },
14
+ { ...common, entityId: "entity_pedestal", name: "Pedestal", kind: "mesh", transform: { ...transform, position: [0, -0.9, 0] },
15
+ geometry: { kind: "cylinder", radius: 1.15, height: 0.35 }, material: { kind: "standard", color: "#cbd5e1", opacity: 1, roughness: 0.7, metalness: 0 } },
16
+ { ...common, entityId: "entity_fill", name: "Fill", kind: "light", transform,
17
+ light: "ambient", color: "#ffffff", intensity: 2 },
18
+ { ...common, entityId: "entity_key", name: "Key", kind: "light", transform: { ...transform, position: [3, 4, 5] },
19
+ light: "directional", color: "#ffffff", intensity: 3 },
20
+ ],
21
+ cameras: [{ cameraId: "camera_hero", name: "Hero", pose: { position: [0, 0.3, 5], rotation: [0, 0, 0, 1] },
22
+ projection: { kind: "perspective", width: 960, height: 540, fx: 650, fy: 650, cx: 480, cy: 270, near: 0.1, far: 100 } }],
23
+ animations: [{ channelId: "channel_turn", targetId: "entity_product", property: "rotation", interpolation: "slerp",
24
+ keys: [{ timeUs: 0, value: [0, 0, 0, 1] }, { timeUs: 2_000_000, value: [0, 0.7071067811865475, 0, 0.7071067811865476] }, { timeUs: 4_000_000, value: [0, 1, 0, 0] }] }],
25
+ assets: [], generators: [], overrides: [],
26
+ })
27
+ }
@@ -0,0 +1,83 @@
1
+ import { z } from "zod"
2
+ import { deepFreezeJson } from "../code/json-snapshot.js"
3
+ import { SpatialCameraIdSchema, SpatialCameraSchema, SpatialDigestSchema, SpatialFrameRateSchema, SpatialTimeUsSchema } from "./contracts.js"
4
+ import { evaluateSpatialScene } from "./evaluate.js"
5
+ import { parseSpatialScene, parseSpatialValue, SpatialSceneError, spatialValueSha256 } from "./identity.js"
6
+ import { reduceSpatialFrameRate, spatialFrameSample } from "./time.js"
7
+
8
+ /** Export in bounded shot-sized chunks; source, sampling and projection stay explicit. */
9
+ export const SPATIAL_CAMERA_TRACK_MAX_FRAMES = 2_048
10
+ const clockSchema = z.strictObject({
11
+ startUs: SpatialTimeUsSchema,
12
+ frameRate: SpatialFrameRateSchema,
13
+ frameCount: z.number().int().min(1).max(SPATIAL_CAMERA_TRACK_MAX_FRAMES),
14
+ })
15
+ const rationalSchema = z.strictObject({
16
+ numerator: z.string().regex(/^(0|[1-9][0-9]{0,19})$/u),
17
+ denominator: z.string().regex(/^[1-9][0-9]{0,6}$/u),
18
+ })
19
+ export const SpatialCameraTrackSchema = z.strictObject({
20
+ kind: z.literal("slopcamera.spatial-camera-track"), schemaVersion: z.literal(1),
21
+ sceneSha256: SpatialDigestSchema, cameraId: SpatialCameraIdSchema, clock: clockSchema,
22
+ samples: z.array(z.strictObject({
23
+ frameIndex: z.number().int().min(0).max(SPATIAL_CAMERA_TRACK_MAX_FRAMES - 1),
24
+ timeUs: SpatialTimeUsSchema, exactTimeUs: rationalSchema, camera: SpatialCameraSchema,
25
+ })).min(1).max(SPATIAL_CAMERA_TRACK_MAX_FRAMES),
26
+ })
27
+ type DeepReadonly<T> = T extends object ? { readonly [Key in keyof T]: DeepReadonly<T[Key]> } : T
28
+ export type SpatialCameraTrack = DeepReadonly<z.infer<typeof SpatialCameraTrackSchema>>
29
+ const optionsSchema = clockSchema.extend({ cameraId: SpatialCameraIdSchema })
30
+ export type SampleSpatialCameraTrackOptions = z.infer<typeof optionsSchema>
31
+
32
+ function absoluteSample(frameIndex: number, clock: z.infer<typeof clockSchema>) {
33
+ const sample = spatialFrameSample(frameIndex, 3_600_000_000, clock.frameRate)
34
+ const denominator = BigInt(sample.exactTimeUs.denominator)
35
+ return { frameIndex, timeUs: clock.startUs + sample.timeUs, exactTimeUs: {
36
+ numerator: String(BigInt(clock.startUs) * denominator + BigInt(sample.exactTimeUs.numerator)),
37
+ denominator: String(denominator),
38
+ } }
39
+ }
40
+
41
+ /** Inert admission also rejects reordered frames, clock drift and mixed cameras. */
42
+ export function parseSpatialCameraTrack(input: unknown): SpatialCameraTrack {
43
+ const track = parseSpatialValue(SpatialCameraTrackSchema, input, "camera track")
44
+ const rate = reduceSpatialFrameRate(track.clock.frameRate)
45
+ if (rate.numerator !== track.clock.frameRate.numerator || rate.denominator !== track.clock.frameRate.denominator
46
+ || track.samples.length !== track.clock.frameCount) throw new SpatialSceneError("invalid-data", "Camera track clock or coverage is not canonical.")
47
+ const first = track.samples[0]!.camera.projection
48
+ for (const [index, sample] of track.samples.entries()) {
49
+ const expected = absoluteSample(index, track.clock)
50
+ if (sample.frameIndex !== index || sample.timeUs !== expected.timeUs
51
+ || sample.exactTimeUs.numerator !== expected.exactTimeUs.numerator
52
+ || sample.exactTimeUs.denominator !== expected.exactTimeUs.denominator
53
+ || sample.camera.cameraId !== track.cameraId
54
+ || sample.camera.projection.width !== first.width || sample.camera.projection.height !== first.height) {
55
+ throw new SpatialSceneError("invalid-data", "Camera track must preserve exact clock, camera identity, order and image dimensions.")
56
+ }
57
+ }
58
+ return deepFreezeJson(track)
59
+ }
60
+
61
+ /** Pure camera exchange. No assets are decoded and no native source is executed. */
62
+ export function sampleSpatialCameraTrack(sceneInput: unknown, optionsInput: unknown): SpatialCameraTrack {
63
+ const scene = parseSpatialScene(sceneInput)
64
+ const { cameraId, ...inputClock } = parseSpatialValue(optionsSchema, optionsInput, "camera track options")
65
+ const clock = { ...inputClock, frameRate: reduceSpatialFrameRate(inputClock.frameRate) }
66
+ const last = absoluteSample(clock.frameCount - 1, clock)
67
+ if (BigInt(last.exactTimeUs.numerator) >= BigInt(scene.durationUs) * BigInt(last.exactTimeUs.denominator)) {
68
+ throw new SpatialSceneError("invalid-data", "Camera samples exceed the half-open scene duration.")
69
+ }
70
+ const camera = scene.cameras.find(item => item.cameraId === cameraId)
71
+ if (!camera) throw new SpatialSceneError("not-found", `Unknown camera ${cameraId}.`)
72
+ // Camera channels have no entity/asset dependencies. Validate the original
73
+ // scene once, then evaluate only this closed subset at each absolute time.
74
+ const cameraScene = { ...scene, cameras: [camera], entities: [], assets: [], generators: [], overrides: [],
75
+ animations: scene.animations.filter(channel => channel.targetId === cameraId) }
76
+ return parseSpatialCameraTrack({ kind: "slopcamera.spatial-camera-track", schemaVersion: 1,
77
+ sceneSha256: spatialValueSha256(scene), cameraId, clock,
78
+ samples: Array.from({ length: clock.frameCount }, (_, frameIndex) => {
79
+ const sample = absoluteSample(frameIndex, clock)
80
+ return { ...sample, camera: evaluateSpatialScene(cameraScene, { cameraId, timeUs: sample.timeUs }).camera }
81
+ }),
82
+ })
83
+ }