@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
package/src/parse.ts ADDED
@@ -0,0 +1,861 @@
1
+ import {
2
+ diagramVersion,
3
+ type Anchor,
4
+ type BoxLabelRow,
5
+ type BoxShape,
6
+ type DiagramEdge,
7
+ type DiagramFontFamily,
8
+ type DiagramSource,
9
+ type DiagramShape,
10
+ type DiagramSpec,
11
+ type LineShape,
12
+ type StackAlign,
13
+ type StackDiagramEdge,
14
+ type StackDiagramSource,
15
+ type StackDirection,
16
+ type StackLayout,
17
+ type StackShape,
18
+ type TextShape,
19
+ type TextWeight,
20
+ type Tone,
21
+ } from "./types.js"
22
+ import { resolveDiagramSource } from "./layout.js"
23
+
24
+ const tones = new Set<Tone>([
25
+ "neutral",
26
+ "blue",
27
+ "orange",
28
+ "green",
29
+ "red",
30
+ "purple",
31
+ "yellow",
32
+ ])
33
+ const anchors = new Set<Anchor>(["auto", "top", "right", "bottom", "left"])
34
+ const fontFamilies = new Set<DiagramFontFamily>(["default", "mono"])
35
+ const stackDirections = new Set<StackDirection>(["horizontal", "vertical"])
36
+ const stackAlignments = new Set<StackAlign>(["start", "center", "end"])
37
+ const idPattern = /^[A-Za-z0-9][A-Za-z0-9_-]*$/
38
+ const namePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
39
+
40
+ export class DiagramValidationError extends Error {
41
+ readonly issues: readonly string[]
42
+
43
+ constructor(issues: readonly string[]) {
44
+ super(`Invalid diagram specification:\n${issues.map((issue) => `- ${issue}`).join("\n")}`)
45
+ this.name = "DiagramValidationError"
46
+ this.issues = issues
47
+ }
48
+ }
49
+
50
+ function isRecord(value: unknown): value is Record<string, unknown> {
51
+ return typeof value === "object" && value !== null && !Array.isArray(value)
52
+ }
53
+
54
+ function readString(
55
+ record: Record<string, unknown>,
56
+ key: string,
57
+ at: string,
58
+ issues: string[],
59
+ ): string | undefined {
60
+ const value = record[key]
61
+ if (typeof value !== "string") {
62
+ issues.push(`${at}.${key} must be a string`)
63
+ return undefined
64
+ }
65
+ return value
66
+ }
67
+
68
+ function readOptionalString(
69
+ record: Record<string, unknown>,
70
+ key: string,
71
+ at: string,
72
+ issues: string[],
73
+ ): string | undefined {
74
+ const value = record[key]
75
+ if (value === undefined) return undefined
76
+ if (typeof value !== "string") {
77
+ issues.push(`${at}.${key} must be a string when present`)
78
+ return undefined
79
+ }
80
+ return value
81
+ }
82
+
83
+ function readNumber(
84
+ record: Record<string, unknown>,
85
+ key: string,
86
+ at: string,
87
+ issues: string[],
88
+ options: { readonly positive?: boolean; readonly nonNegative?: boolean } = {},
89
+ ): number | undefined {
90
+ const value = record[key]
91
+ if (typeof value !== "number" || !Number.isFinite(value)) {
92
+ issues.push(`${at}.${key} must be a finite number`)
93
+ return undefined
94
+ }
95
+ if (options.positive && value <= 0) {
96
+ issues.push(`${at}.${key} must be greater than zero`)
97
+ return undefined
98
+ }
99
+ if (options.nonNegative && value < 0) {
100
+ issues.push(`${at}.${key} must be zero or greater`)
101
+ return undefined
102
+ }
103
+ return value
104
+ }
105
+
106
+ function readOptionalNumber(
107
+ record: Record<string, unknown>,
108
+ key: string,
109
+ at: string,
110
+ issues: string[],
111
+ options: { readonly positive?: boolean; readonly nonNegative?: boolean } = {},
112
+ ): number | undefined {
113
+ if (record[key] === undefined) return undefined
114
+ return readNumber(record, key, at, issues, options)
115
+ }
116
+
117
+ function readOptionalTone(
118
+ record: Record<string, unknown>,
119
+ key: string,
120
+ at: string,
121
+ issues: string[],
122
+ ): Tone | undefined {
123
+ const value = record[key]
124
+ if (value === undefined) return undefined
125
+ if (typeof value !== "string" || !tones.has(value as Tone)) {
126
+ issues.push(`${at}.${key} must be one of ${[...tones].join(", ")}`)
127
+ return undefined
128
+ }
129
+ return value as Tone
130
+ }
131
+
132
+ function readOptionalFontFamily(
133
+ record: Record<string, unknown>,
134
+ key: string,
135
+ at: string,
136
+ issues: string[],
137
+ ): DiagramFontFamily | undefined {
138
+ const value = record[key]
139
+ if (value === undefined) return undefined
140
+ if (typeof value !== "string" || !fontFamilies.has(value as DiagramFontFamily)) {
141
+ issues.push(`${at}.${key} must be default or mono`)
142
+ return undefined
143
+ }
144
+ return value as DiagramFontFamily
145
+ }
146
+
147
+ function readOptionalWeight(
148
+ record: Record<string, unknown>,
149
+ key: string,
150
+ at: string,
151
+ issues: string[],
152
+ ): TextWeight | undefined {
153
+ const value = record[key]
154
+ if (value === undefined) return undefined
155
+ if (typeof value !== "number" || ![400, 500, 600, 700].includes(value)) {
156
+ issues.push(`${at}.${key} must be 400, 500, 600, or 700`)
157
+ return undefined
158
+ }
159
+ return value as TextWeight
160
+ }
161
+
162
+ function parseLabelRows(
163
+ value: unknown,
164
+ at: string,
165
+ issues: string[],
166
+ ): readonly [BoxLabelRow, ...BoxLabelRow[]] | undefined {
167
+ if (value === undefined) return undefined
168
+ if (!Array.isArray(value)) {
169
+ issues.push(`${at}.labelRows must be an array when present`)
170
+ return undefined
171
+ }
172
+ if (value.length < 1 || value.length > 4) {
173
+ issues.push(`${at}.labelRows must contain between 1 and 4 rows`)
174
+ }
175
+ const rows: BoxLabelRow[] = []
176
+ for (const [index, candidate] of value.entries()) {
177
+ const rowAt = `${at}.labelRows[${index}]`
178
+ if (!isRecord(candidate)) {
179
+ issues.push(`${rowAt} must be an object`)
180
+ continue
181
+ }
182
+ validateKnownKeys(
183
+ candidate,
184
+ new Set(["text", "fontSize", "fontFamily", "weight"]),
185
+ rowAt,
186
+ issues,
187
+ )
188
+ const text = readString(candidate, "text", rowAt, issues)
189
+ const fontSize = readOptionalNumber(candidate, "fontSize", rowAt, issues, {
190
+ positive: true,
191
+ })
192
+ const fontFamily = readOptionalFontFamily(candidate, "fontFamily", rowAt, issues)
193
+ const weight = readOptionalWeight(candidate, "weight", rowAt, issues)
194
+ if (text !== undefined) {
195
+ rows.push({
196
+ text,
197
+ ...(fontSize === undefined ? {} : { fontSize }),
198
+ ...(fontFamily === undefined ? {} : { fontFamily }),
199
+ ...(weight === undefined ? {} : { weight }),
200
+ })
201
+ }
202
+ }
203
+ return rows.length === 0 ? undefined : (rows as [BoxLabelRow, ...BoxLabelRow[]])
204
+ }
205
+
206
+ function validateKnownKeys(
207
+ record: Record<string, unknown>,
208
+ allowed: ReadonlySet<string>,
209
+ at: string,
210
+ issues: string[],
211
+ ): void {
212
+ for (const key of Object.keys(record)) {
213
+ if (!allowed.has(key)) issues.push(`${at}.${key} is not supported`)
214
+ }
215
+ }
216
+
217
+ function parseBoxLabelContent(
218
+ value: Record<string, unknown>,
219
+ at: string,
220
+ issues: string[],
221
+ ): Readonly<Record<string, unknown>> {
222
+ const label = readOptionalString(value, "label", at, issues)
223
+ const labelFontSize = readOptionalNumber(value, "labelFontSize", at, issues, {
224
+ positive: true,
225
+ })
226
+ const labelFontFamily = readOptionalFontFamily(value, "labelFontFamily", at, issues)
227
+ const labelWeight = readOptionalWeight(value, "labelWeight", at, issues)
228
+ const labelRows = parseLabelRows(value.labelRows, at, issues)
229
+ const labelRowGap = readOptionalNumber(value, "labelRowGap", at, issues, {
230
+ nonNegative: true,
231
+ })
232
+ const hasLegacyStyle =
233
+ value.labelFontSize !== undefined ||
234
+ value.labelFontFamily !== undefined ||
235
+ value.labelWeight !== undefined
236
+ if (value.label === undefined && hasLegacyStyle) {
237
+ issues.push(`${at} cannot style a label that is not present`)
238
+ }
239
+ if (value.labelRows !== undefined && value.label !== undefined) {
240
+ issues.push(`${at}.label and ${at}.labelRows are mutually exclusive`)
241
+ }
242
+ if (value.labelRows !== undefined && hasLegacyStyle) {
243
+ issues.push(`${at}.labelRows cannot be combined with legacy label styling`)
244
+ }
245
+ if (value.labelRows === undefined && value.labelRowGap !== undefined) {
246
+ issues.push(`${at}.labelRowGap requires labelRows`)
247
+ }
248
+ if (labelRows !== undefined) {
249
+ return {
250
+ labelRows,
251
+ ...(labelRowGap === undefined ? {} : { labelRowGap }),
252
+ }
253
+ }
254
+ if (label !== undefined) {
255
+ return {
256
+ label,
257
+ ...(labelFontSize === undefined ? {} : { labelFontSize }),
258
+ ...(labelFontFamily === undefined ? {} : { labelFontFamily }),
259
+ ...(labelWeight === undefined ? {} : { labelWeight }),
260
+ }
261
+ }
262
+ return {}
263
+ }
264
+
265
+ function parseBase(
266
+ record: Record<string, unknown>,
267
+ at: string,
268
+ issues: string[],
269
+ ): {
270
+ readonly id?: string
271
+ readonly x?: number
272
+ readonly y?: number
273
+ readonly tone?: Tone
274
+ readonly opacity?: number
275
+ } {
276
+ const id = readString(record, "id", at, issues)
277
+ if (id !== undefined && !idPattern.test(id)) {
278
+ issues.push(`${at}.id must contain only letters, numbers, underscores, or hyphens`)
279
+ }
280
+ const opacity = readOptionalNumber(record, "opacity", at, issues, { nonNegative: true })
281
+ if (opacity !== undefined && opacity > 1) issues.push(`${at}.opacity must not exceed 1`)
282
+ const x = readNumber(record, "x", at, issues)
283
+ const y = readNumber(record, "y", at, issues)
284
+ const tone = readOptionalTone(record, "tone", at, issues)
285
+ return {
286
+ ...(id === undefined ? {} : { id }),
287
+ ...(x === undefined ? {} : { x }),
288
+ ...(y === undefined ? {} : { y }),
289
+ ...(tone === undefined ? {} : { tone }),
290
+ ...(opacity === undefined ? {} : { opacity }),
291
+ }
292
+ }
293
+
294
+ function parseShape(value: unknown, index: number, issues: string[]): DiagramShape | null {
295
+ const at = `shapes[${index}]`
296
+ if (!isRecord(value)) {
297
+ issues.push(`${at} must be an object`)
298
+ return null
299
+ }
300
+ const type = readString(value, "type", at, issues)
301
+ const base = parseBase(value, at, issues)
302
+ if (base.id === undefined || base.x === undefined || base.y === undefined || type === undefined) {
303
+ return null
304
+ }
305
+ const requiredBase = {
306
+ id: base.id,
307
+ x: base.x,
308
+ y: base.y,
309
+ ...(base.tone === undefined ? {} : { tone: base.tone }),
310
+ ...(base.opacity === undefined ? {} : { opacity: base.opacity }),
311
+ }
312
+
313
+ if (type === "rect" || type === "ellipse") {
314
+ validateKnownKeys(
315
+ value,
316
+ new Set([
317
+ "id",
318
+ "type",
319
+ "x",
320
+ "y",
321
+ "tone",
322
+ "opacity",
323
+ "width",
324
+ "height",
325
+ "radius",
326
+ "label",
327
+ "labelFontSize",
328
+ "labelFontFamily",
329
+ "labelWeight",
330
+ "labelRows",
331
+ "labelRowGap",
332
+ "icon",
333
+ "iconSize",
334
+ "strokeWidth",
335
+ "fill",
336
+ ]),
337
+ at,
338
+ issues,
339
+ )
340
+ const width = readNumber(value, "width", at, issues, { positive: true })
341
+ const height = readNumber(value, "height", at, issues, { positive: true })
342
+ const labelContent = parseBoxLabelContent(value, at, issues)
343
+ const fill = value.fill
344
+ if (fill !== undefined && typeof fill !== "boolean") issues.push(`${at}.fill must be a boolean`)
345
+ if (width === undefined || height === undefined) return null
346
+ return {
347
+ ...requiredBase,
348
+ type,
349
+ width,
350
+ height,
351
+ ...(readOptionalNumber(value, "radius", at, issues, { nonNegative: true }) === undefined
352
+ ? {}
353
+ : { radius: value.radius as number }),
354
+ ...labelContent,
355
+ ...(readOptionalString(value, "icon", at, issues) === undefined
356
+ ? {}
357
+ : { icon: value.icon as string }),
358
+ ...(readOptionalNumber(value, "iconSize", at, issues, { positive: true }) === undefined
359
+ ? {}
360
+ : { iconSize: value.iconSize as number }),
361
+ ...(readOptionalNumber(value, "strokeWidth", at, issues, { nonNegative: true }) === undefined
362
+ ? {}
363
+ : { strokeWidth: value.strokeWidth as number }),
364
+ ...(typeof fill === "boolean" ? { fill } : {}),
365
+ } as BoxShape
366
+ }
367
+
368
+ if (type === "text") {
369
+ validateKnownKeys(
370
+ value,
371
+ new Set([
372
+ "id",
373
+ "type",
374
+ "x",
375
+ "y",
376
+ "tone",
377
+ "opacity",
378
+ "text",
379
+ "width",
380
+ "fontSize",
381
+ "fontFamily",
382
+ "weight",
383
+ "align",
384
+ ]),
385
+ at,
386
+ issues,
387
+ )
388
+ const text = readString(value, "text", at, issues)
389
+ const fontFamily = readOptionalFontFamily(value, "fontFamily", at, issues)
390
+ const weight = readOptionalWeight(value, "weight", at, issues)
391
+ const align = value.align
392
+ if (align !== undefined && !["start", "middle", "end"].includes(align as string)) {
393
+ issues.push(`${at}.align must be start, middle, or end`)
394
+ }
395
+ if (text === undefined) return null
396
+ return {
397
+ ...requiredBase,
398
+ type,
399
+ text,
400
+ ...(readOptionalNumber(value, "width", at, issues, { positive: true }) === undefined
401
+ ? {}
402
+ : { width: value.width as number }),
403
+ ...(readOptionalNumber(value, "fontSize", at, issues, { positive: true }) === undefined
404
+ ? {}
405
+ : { fontSize: value.fontSize as number }),
406
+ ...(fontFamily === undefined ? {} : { fontFamily }),
407
+ ...(weight === undefined ? {} : { weight }),
408
+ ...(align === undefined ? {} : { align: align as "start" | "middle" | "end" }),
409
+ } satisfies TextShape
410
+ }
411
+
412
+ if (type === "line") {
413
+ validateKnownKeys(
414
+ value,
415
+ new Set(["id", "type", "x", "y", "tone", "opacity", "x2", "y2", "strokeWidth"]),
416
+ at,
417
+ issues,
418
+ )
419
+ const x2 = readNumber(value, "x2", at, issues)
420
+ const y2 = readNumber(value, "y2", at, issues)
421
+ if (x2 === undefined || y2 === undefined) return null
422
+ return {
423
+ ...requiredBase,
424
+ type,
425
+ x2,
426
+ y2,
427
+ ...(readOptionalNumber(value, "strokeWidth", at, issues, { positive: true }) === undefined
428
+ ? {}
429
+ : { strokeWidth: value.strokeWidth as number }),
430
+ } satisfies LineShape
431
+ }
432
+
433
+ issues.push(`${at}.type must be rect, ellipse, text, or line`)
434
+ return null
435
+ }
436
+
437
+ function parseStackShape(value: unknown, index: number, issues: string[]): StackShape | null {
438
+ const at = `shapes[${index}]`
439
+ if (!isRecord(value)) {
440
+ issues.push(`${at} must be an object`)
441
+ return null
442
+ }
443
+ validateKnownKeys(
444
+ value,
445
+ new Set([
446
+ "id",
447
+ "type",
448
+ "tone",
449
+ "opacity",
450
+ "width",
451
+ "height",
452
+ "radius",
453
+ "label",
454
+ "labelFontSize",
455
+ "labelFontFamily",
456
+ "labelWeight",
457
+ "labelRows",
458
+ "labelRowGap",
459
+ "icon",
460
+ "iconSize",
461
+ "strokeWidth",
462
+ "fill",
463
+ ]),
464
+ at,
465
+ issues,
466
+ )
467
+ const id = readString(value, "id", at, issues)
468
+ if (id !== undefined && !idPattern.test(id)) {
469
+ issues.push(`${at}.id must contain only letters, numbers, underscores, or hyphens`)
470
+ }
471
+ const type = readString(value, "type", at, issues)
472
+ if (type !== undefined && type !== "rect" && type !== "ellipse") {
473
+ issues.push(`${at}.type must be rect or ellipse in a stack layout`)
474
+ }
475
+ const width = readNumber(value, "width", at, issues, { positive: true })
476
+ const height = readNumber(value, "height", at, issues, { positive: true })
477
+ const labelContent = parseBoxLabelContent(value, at, issues)
478
+ const tone = readOptionalTone(value, "tone", at, issues)
479
+ const opacity = readOptionalNumber(value, "opacity", at, issues, { nonNegative: true })
480
+ if (opacity !== undefined && opacity > 1) issues.push(`${at}.opacity must not exceed 1`)
481
+ const radius = readOptionalNumber(value, "radius", at, issues, { nonNegative: true })
482
+ const icon = readOptionalString(value, "icon", at, issues)
483
+ const iconSize = readOptionalNumber(value, "iconSize", at, issues, { positive: true })
484
+ const strokeWidth = readOptionalNumber(value, "strokeWidth", at, issues, {
485
+ nonNegative: true,
486
+ })
487
+ const fill = value.fill
488
+ if (fill !== undefined && typeof fill !== "boolean") issues.push(`${at}.fill must be a boolean`)
489
+ if (
490
+ id === undefined ||
491
+ (type !== "rect" && type !== "ellipse") ||
492
+ width === undefined ||
493
+ height === undefined
494
+ ) {
495
+ return null
496
+ }
497
+ return {
498
+ id,
499
+ type,
500
+ width,
501
+ height,
502
+ ...(tone === undefined ? {} : { tone }),
503
+ ...(opacity === undefined ? {} : { opacity }),
504
+ ...(radius === undefined ? {} : { radius }),
505
+ ...labelContent,
506
+ ...(icon === undefined ? {} : { icon }),
507
+ ...(iconSize === undefined ? {} : { iconSize }),
508
+ ...(strokeWidth === undefined ? {} : { strokeWidth }),
509
+ ...(typeof fill === "boolean" ? { fill } : {}),
510
+ } as StackShape
511
+ }
512
+
513
+ function parseEdge(value: unknown, index: number, issues: string[]): DiagramEdge | null {
514
+ const at = `edges[${index}]`
515
+ if (!isRecord(value)) {
516
+ issues.push(`${at} must be an object`)
517
+ return null
518
+ }
519
+ validateKnownKeys(
520
+ value,
521
+ new Set([
522
+ "id",
523
+ "from",
524
+ "to",
525
+ "label",
526
+ "tone",
527
+ "start",
528
+ "end",
529
+ "bend",
530
+ "arrowhead",
531
+ "startPosition",
532
+ "endPosition",
533
+ "labelFontSize",
534
+ "labelFontFamily",
535
+ "labelWeight",
536
+ "labelPosition",
537
+ "labelOffset",
538
+ ]),
539
+ at,
540
+ issues,
541
+ )
542
+ const id = readString(value, "id", at, issues)
543
+ const from = readString(value, "from", at, issues)
544
+ const to = readString(value, "to", at, issues)
545
+ if (id !== undefined && !idPattern.test(id)) issues.push(`${at}.id has unsupported characters`)
546
+ const start = value.start
547
+ const end = value.end
548
+ if (start !== undefined && (typeof start !== "string" || !anchors.has(start as Anchor))) {
549
+ issues.push(`${at}.start must be auto, top, right, bottom, or left`)
550
+ }
551
+ if (end !== undefined && (typeof end !== "string" || !anchors.has(end as Anchor))) {
552
+ issues.push(`${at}.end must be auto, top, right, bottom, or left`)
553
+ }
554
+ const startPosition = readOptionalNumber(value, "startPosition", at, issues, {
555
+ nonNegative: true,
556
+ })
557
+ if (startPosition !== undefined && startPosition > 1) {
558
+ issues.push(`${at}.startPosition must not exceed 1`)
559
+ }
560
+ const endPosition = readOptionalNumber(value, "endPosition", at, issues, {
561
+ nonNegative: true,
562
+ })
563
+ if (endPosition !== undefined && endPosition > 1) {
564
+ issues.push(`${at}.endPosition must not exceed 1`)
565
+ }
566
+ const arrowhead = value.arrowhead
567
+ if (
568
+ arrowhead !== undefined &&
569
+ !["arrow", "triangle", "none"].includes(arrowhead as string)
570
+ ) {
571
+ issues.push(`${at}.arrowhead must be arrow, triangle, or none`)
572
+ }
573
+ const label = readOptionalString(value, "label", at, issues)
574
+ const labelFontSize = readOptionalNumber(value, "labelFontSize", at, issues, {
575
+ positive: true,
576
+ })
577
+ const labelFontFamily = readOptionalFontFamily(value, "labelFontFamily", at, issues)
578
+ const labelWeight = readOptionalWeight(value, "labelWeight", at, issues)
579
+ const labelPosition = readOptionalNumber(value, "labelPosition", at, issues, {
580
+ nonNegative: true,
581
+ })
582
+ if (labelPosition !== undefined && labelPosition > 1) {
583
+ issues.push(`${at}.labelPosition must not exceed 1`)
584
+ }
585
+ const labelOffset = readOptionalNumber(value, "labelOffset", at, issues)
586
+ if (
587
+ value.label === undefined &&
588
+ [
589
+ value.labelFontSize,
590
+ value.labelFontFamily,
591
+ value.labelWeight,
592
+ value.labelPosition,
593
+ value.labelOffset,
594
+ ].some((candidate) => candidate !== undefined)
595
+ ) {
596
+ issues.push(`${at} cannot style or position a label that is not present`)
597
+ }
598
+ if (id === undefined || from === undefined || to === undefined) return null
599
+ return {
600
+ id,
601
+ from,
602
+ to,
603
+ ...(label === undefined
604
+ ? {}
605
+ : {
606
+ label,
607
+ ...(labelFontSize === undefined ? {} : { labelFontSize }),
608
+ ...(labelFontFamily === undefined ? {} : { labelFontFamily }),
609
+ ...(labelWeight === undefined ? {} : { labelWeight }),
610
+ ...(labelPosition === undefined ? {} : { labelPosition }),
611
+ ...(labelOffset === undefined ? {} : { labelOffset }),
612
+ }),
613
+ ...(readOptionalTone(value, "tone", at, issues) === undefined
614
+ ? {}
615
+ : { tone: value.tone as Tone }),
616
+ ...(start === undefined ? {} : { start: start as Anchor }),
617
+ ...(end === undefined ? {} : { end: end as Anchor }),
618
+ ...(startPosition === undefined ? {} : { startPosition }),
619
+ ...(endPosition === undefined ? {} : { endPosition }),
620
+ ...(readOptionalNumber(value, "bend", at, issues) === undefined
621
+ ? {}
622
+ : { bend: value.bend as number }),
623
+ ...(arrowhead === undefined
624
+ ? {}
625
+ : { arrowhead: arrowhead as "arrow" | "triangle" | "none" }),
626
+ } as DiagramEdge
627
+ }
628
+
629
+ function parseStackLayout(value: unknown, issues: string[]): StackLayout | null {
630
+ if (!isRecord(value)) {
631
+ issues.push("layout must be an object")
632
+ return null
633
+ }
634
+ validateKnownKeys(value, new Set(["type", "direction", "gap", "align"]), "layout", issues)
635
+ if (value.type !== "stack") issues.push("layout.type must be stack")
636
+ const direction = value.direction
637
+ if (
638
+ typeof direction !== "string" ||
639
+ !stackDirections.has(direction as StackDirection)
640
+ ) {
641
+ issues.push("layout.direction must be horizontal or vertical")
642
+ }
643
+ const gap = readOptionalNumber(value, "gap", "layout", issues, { nonNegative: true })
644
+ const align = value.align
645
+ if (
646
+ align !== undefined &&
647
+ (typeof align !== "string" || !stackAlignments.has(align as StackAlign))
648
+ ) {
649
+ issues.push("layout.align must be start, center, or end")
650
+ }
651
+ if (
652
+ value.type !== "stack" ||
653
+ typeof direction !== "string" ||
654
+ !stackDirections.has(direction as StackDirection)
655
+ ) {
656
+ return null
657
+ }
658
+ return {
659
+ type: "stack",
660
+ direction: direction as StackDirection,
661
+ ...(gap === undefined ? {} : { gap }),
662
+ ...(align === undefined || !stackAlignments.has(align as StackAlign)
663
+ ? {}
664
+ : { align: align as StackAlign }),
665
+ }
666
+ }
667
+
668
+ export function parseDiagramSource(value: unknown): DiagramSource {
669
+ const issues: string[] = []
670
+ if (!isRecord(value)) throw new DiagramValidationError(["root must be an object"])
671
+ const isStackSource = "layout" in value
672
+ validateKnownKeys(
673
+ value,
674
+ new Set([
675
+ "$schema",
676
+ "version",
677
+ "name",
678
+ "canvas",
679
+ "shapes",
680
+ "edges",
681
+ ...(isStackSource ? ["layout"] : []),
682
+ ]),
683
+ "root",
684
+ issues,
685
+ )
686
+
687
+ if (value.version !== diagramVersion) issues.push(`version must be ${diagramVersion}`)
688
+ const name = readString(value, "name", "root", issues)
689
+ if (name !== undefined && !namePattern.test(name)) {
690
+ issues.push("name must be lowercase kebab-case")
691
+ }
692
+
693
+ const canvasValue = value.canvas
694
+ let canvas: DiagramSpec["canvas"] | null = null
695
+ if (!isRecord(canvasValue)) {
696
+ issues.push("canvas must be an object")
697
+ } else {
698
+ validateKnownKeys(canvasValue, new Set(["width", "height", "padding"]), "canvas", issues)
699
+ const width = readNumber(canvasValue, "width", "canvas", issues, { positive: true })
700
+ const height = readNumber(canvasValue, "height", "canvas", issues, { positive: true })
701
+ const padding = readOptionalNumber(canvasValue, "padding", "canvas", issues, {
702
+ nonNegative: true,
703
+ })
704
+ if (width !== undefined && height !== undefined) {
705
+ canvas = { width, height, ...(padding === undefined ? {} : { padding }) }
706
+ }
707
+ }
708
+
709
+ const layout = isStackSource ? parseStackLayout(value.layout, issues) : null
710
+ const shapesValue = value.shapes
711
+ const positionedShapes: DiagramShape[] = []
712
+ const stackShapes: StackShape[] = []
713
+ if (!Array.isArray(shapesValue)) {
714
+ issues.push("shapes must be an array")
715
+ } else {
716
+ if (isStackSource && (shapesValue.length < 1 || shapesValue.length > 9)) {
717
+ issues.push("stack layouts must contain between 1 and 9 shapes")
718
+ }
719
+ for (const [index, shape] of shapesValue.entries()) {
720
+ if (isStackSource) {
721
+ const parsed = parseStackShape(shape, index, issues)
722
+ if (parsed !== null) stackShapes.push(parsed)
723
+ } else {
724
+ const parsed = parseShape(shape, index, issues)
725
+ if (parsed !== null) positionedShapes.push(parsed)
726
+ }
727
+ }
728
+ }
729
+
730
+ const edgesValue = value.edges
731
+ const edges: DiagramEdge[] = []
732
+ if (edgesValue !== undefined) {
733
+ if (!Array.isArray(edgesValue)) {
734
+ issues.push("edges must be an array when present")
735
+ } else {
736
+ for (const [index, edge] of edgesValue.entries()) {
737
+ const parsed = parseEdge(edge, index, issues)
738
+ if (parsed !== null) edges.push(parsed)
739
+ }
740
+ }
741
+ }
742
+
743
+ const shapes = isStackSource ? stackShapes : positionedShapes
744
+ const allIds = new Set<string>()
745
+ for (const [kind, records] of [
746
+ ["shape", shapes],
747
+ ["edge", edges],
748
+ ] as const) {
749
+ for (const record of records) {
750
+ if (allIds.has(record.id)) issues.push(`${kind} id ${record.id} is duplicated`)
751
+ allIds.add(record.id)
752
+ }
753
+ }
754
+ const connectableIds = new Set(
755
+ shapes
756
+ .filter(
757
+ (shape): shape is BoxShape | StackShape =>
758
+ shape.type === "rect" || shape.type === "ellipse",
759
+ )
760
+ .map((shape) => shape.id),
761
+ )
762
+ for (const edge of edges) {
763
+ if (!connectableIds.has(edge.from)) issues.push(`edge ${edge.id} has unknown or non-connectable from id ${edge.from}`)
764
+ if (!connectableIds.has(edge.to)) issues.push(`edge ${edge.id} has unknown or non-connectable to id ${edge.to}`)
765
+ if (edge.from === edge.to) issues.push(`edge ${edge.id} cannot connect a shape to itself`)
766
+ }
767
+
768
+ if (isStackSource) {
769
+ const indexes = new Map(stackShapes.map((shape, index) => [shape.id, index]))
770
+ const connectedPairs = new Set<string>()
771
+ for (const edge of edges) {
772
+ const fromIndex = indexes.get(edge.from)
773
+ const toIndex = indexes.get(edge.to)
774
+ if (
775
+ fromIndex !== undefined &&
776
+ toIndex !== undefined &&
777
+ Math.abs(fromIndex - toIndex) !== 1
778
+ ) {
779
+ issues.push(`edge ${edge.id} must connect adjacent stack shapes`)
780
+ }
781
+ if (fromIndex !== undefined && toIndex !== undefined && fromIndex !== toIndex) {
782
+ const pair = [fromIndex, toIndex].sort((left, right) => left - right).join(":")
783
+ if (connectedPairs.has(pair)) {
784
+ issues.push(`edge ${edge.id} duplicates a connection between the same stack shapes`)
785
+ }
786
+ connectedPairs.add(pair)
787
+ }
788
+ if (edge.start !== undefined && edge.start !== "auto") {
789
+ issues.push(`edge ${edge.id}.start must be auto or omitted in a stack layout`)
790
+ }
791
+ if (edge.end !== undefined && edge.end !== "auto") {
792
+ issues.push(`edge ${edge.id}.end must be auto or omitted in a stack layout`)
793
+ }
794
+ if (edge.bend !== undefined && edge.bend !== 0) {
795
+ issues.push(`edge ${edge.id}.bend must be 0 or omitted in a stack layout`)
796
+ }
797
+ if (edge.startPosition !== undefined) {
798
+ issues.push(`edge ${edge.id}.startPosition is not supported in a stack layout`)
799
+ }
800
+ if (edge.endPosition !== undefined) {
801
+ issues.push(`edge ${edge.id}.endPosition is not supported in a stack layout`)
802
+ }
803
+ }
804
+ }
805
+
806
+ if (
807
+ issues.length > 0 ||
808
+ name === undefined ||
809
+ canvas === null ||
810
+ (isStackSource && layout === null)
811
+ ) {
812
+ throw new DiagramValidationError(issues)
813
+ }
814
+ const common = {
815
+ ...("$schema" in value && typeof value.$schema === "string" ? { $schema: value.$schema } : {}),
816
+ version: diagramVersion,
817
+ name,
818
+ canvas,
819
+ } as const
820
+ if (isStackSource) {
821
+ const stackEdges: readonly StackDiagramEdge[] = edges.map((edge) => ({
822
+ id: edge.id,
823
+ from: edge.from,
824
+ to: edge.to,
825
+ ...(edge.label === undefined ? {} : { label: edge.label }),
826
+ ...(edge.tone === undefined ? {} : { tone: edge.tone }),
827
+ ...(edge.start === "auto" ? { start: edge.start } : {}),
828
+ ...(edge.end === "auto" ? { end: edge.end } : {}),
829
+ ...(edge.bend === 0 ? { bend: edge.bend } : {}),
830
+ ...(edge.arrowhead === undefined ? {} : { arrowhead: edge.arrowhead }),
831
+ ...(edge.label === undefined
832
+ ? {}
833
+ : {
834
+ ...(edge.labelFontSize === undefined ? {} : { labelFontSize: edge.labelFontSize }),
835
+ ...(edge.labelFontFamily === undefined
836
+ ? {}
837
+ : { labelFontFamily: edge.labelFontFamily }),
838
+ ...(edge.labelWeight === undefined ? {} : { labelWeight: edge.labelWeight }),
839
+ ...(edge.labelPosition === undefined
840
+ ? {}
841
+ : { labelPosition: edge.labelPosition }),
842
+ ...(edge.labelOffset === undefined ? {} : { labelOffset: edge.labelOffset }),
843
+ }),
844
+ })) as readonly StackDiagramEdge[]
845
+ return {
846
+ ...common,
847
+ layout: layout as StackLayout,
848
+ shapes: stackShapes,
849
+ ...(edgesValue === undefined ? {} : { edges: stackEdges }),
850
+ } satisfies StackDiagramSource
851
+ }
852
+ return {
853
+ ...common,
854
+ shapes: positionedShapes,
855
+ ...(edgesValue === undefined ? {} : { edges }),
856
+ }
857
+ }
858
+
859
+ export function parseDiagramSpec(value: unknown): DiagramSpec {
860
+ return resolveDiagramSource(parseDiagramSource(value))
861
+ }