@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,963 @@
1
+ import { rename, rm, writeFile } from "node:fs/promises"
2
+ import { dirname, join } from "node:path"
3
+ import { SlopcameraCloudError } from "../cloud-errors.js"
4
+ import {
5
+ generateSlopcameraImageFile,
6
+ type SlopcameraGenerateDependencies,
7
+ } from "../generate.js"
8
+ import { builtInIcons } from "../icons.js"
9
+ import { lintDiagram } from "../lint.js"
10
+ import {
11
+ SlopcameraOperationError,
12
+ slopcameraOperationCodes,
13
+ parseSlopcameraOperationInput,
14
+ searchSlopcameraOperations,
15
+ withSlopcameraOperationHostAdmission,
16
+ type CheckSlopcameraOperationInput,
17
+ type GenerateSlopcameraOperationInput,
18
+ type SlopcameraOperationCode,
19
+ type RenderSlopcameraOperationInput,
20
+ type VectorizeSlopcameraOperationInput,
21
+ } from "../operations.js"
22
+ import {
23
+ createDefaultHostResourceCoordinator,
24
+ type HostResourceCoordinator,
25
+ type HostResourceLease,
26
+ } from "../host-resources.js"
27
+ import { DiagramValidationError, parseDiagramSpec } from "../parse.js"
28
+ import { renderPng, renderSvg } from "../render.js"
29
+ import { serializeTldr } from "../tldr.js"
30
+ import type {
31
+ DiagramConfig,
32
+ DiagramSpec,
33
+ LintFinding,
34
+ RenderArtifacts,
35
+ } from "../types.js"
36
+ import {
37
+ vectorizeHardLimits,
38
+ vectorizeImage,
39
+ VectorizeError,
40
+ } from "../vectorize/index.js"
41
+ import {
42
+ WorkspaceBoundary,
43
+ WorkspaceBoundaryError,
44
+ type WorkspaceSource,
45
+ } from "./boundary.js"
46
+ import type {
47
+ McpToolDefinition,
48
+ McpToolResult,
49
+ } from "./types.js"
50
+
51
+ export const mcpMaximumScale = 4
52
+ export const mcpMaximumRenderedPixels = 16_777_216
53
+ export const mcpMaximumShapes = 64
54
+ export const mcpMaximumEdges = 128
55
+ export const mcpMaximumReturnedFindings = 40
56
+
57
+ const defaultScale = 2
58
+ const maximumShapeIdsPerFinding = 12
59
+ const builtInConfig: DiagramConfig = Object.freeze({ icons: builtInIcons })
60
+
61
+ const findingSchema = {
62
+ type: "object",
63
+ additionalProperties: false,
64
+ required: ["code", "message", "shapeIds"],
65
+ properties: {
66
+ code: { type: "string" },
67
+ message: { type: "string" },
68
+ shapeIds: { type: "array", items: { type: "string" } },
69
+ },
70
+ } as const
71
+
72
+ function deepFreeze<T>(value: T): T {
73
+ if (typeof value !== "object" || value === null || Object.isFrozen(value)) {
74
+ return value
75
+ }
76
+ for (const nested of Object.values(value)) deepFreeze(nested)
77
+ return Object.freeze(value)
78
+ }
79
+
80
+ export const slopcameraMcpTools: readonly McpToolDefinition[] = deepFreeze([
81
+ {
82
+ name: "check_diagram",
83
+ title: "Check diagram",
84
+ description:
85
+ "Parse and lint one root-relative Slopcamera diagram source without changing files. Uses only built-in icons and themes.",
86
+ inputSchema: {
87
+ type: "object",
88
+ additionalProperties: false,
89
+ required: ["path"],
90
+ properties: {
91
+ path: {
92
+ type: "string",
93
+ description: "Root-relative path to a diagram JSON source (1 MiB maximum).",
94
+ },
95
+ },
96
+ },
97
+ outputSchema: {
98
+ type: "object",
99
+ additionalProperties: false,
100
+ required: ["ok", "source", "findings", "summary"],
101
+ properties: {
102
+ ok: { const: true },
103
+ source: { type: "string" },
104
+ findings: { type: "array", items: findingSchema },
105
+ summary: {
106
+ type: "object",
107
+ additionalProperties: false,
108
+ required: [
109
+ "shapeCount",
110
+ "edgeCount",
111
+ "findingCount",
112
+ "returnedFindingCount",
113
+ "findingsTruncated",
114
+ ],
115
+ properties: {
116
+ shapeCount: { type: "integer", minimum: 0 },
117
+ edgeCount: { type: "integer", minimum: 0 },
118
+ findingCount: { type: "integer", minimum: 0 },
119
+ returnedFindingCount: { type: "integer", minimum: 0 },
120
+ findingsTruncated: { type: "boolean" },
121
+ },
122
+ },
123
+ },
124
+ },
125
+ annotations: {
126
+ title: "Check diagram",
127
+ readOnlyHint: true,
128
+ destructiveHint: false,
129
+ idempotentHint: true,
130
+ openWorldHint: false,
131
+ },
132
+ },
133
+ {
134
+ name: "render_diagram",
135
+ title: "Render diagram",
136
+ description:
137
+ "Render one root-relative Slopcamera diagram source with built-in icons and themes, overwriting its paired .tldr, light/dark SVG, and light/dark PNG artifacts.",
138
+ inputSchema: {
139
+ type: "object",
140
+ additionalProperties: false,
141
+ required: ["path"],
142
+ properties: {
143
+ path: {
144
+ type: "string",
145
+ description: "Root-relative path to a diagram JSON source (1 MiB maximum).",
146
+ },
147
+ out_dir: {
148
+ type: "string",
149
+ description:
150
+ "Optional root-relative output directory. Defaults to the source directory.",
151
+ },
152
+ scale: {
153
+ type: "number",
154
+ exclusiveMinimum: 0,
155
+ maximum: mcpMaximumScale,
156
+ default: defaultScale,
157
+ description: "PNG scale. The scaled canvas may contain at most 16,777,216 pixels.",
158
+ },
159
+ },
160
+ },
161
+ outputSchema: {
162
+ type: "object",
163
+ additionalProperties: false,
164
+ required: ["ok", "source", "scale", "findings", "artifacts", "summary"],
165
+ properties: {
166
+ ok: { const: true },
167
+ source: { type: "string" },
168
+ scale: { type: "number" },
169
+ findings: { type: "array", items: findingSchema },
170
+ artifacts: {
171
+ type: "object",
172
+ additionalProperties: false,
173
+ required: ["tldr", "lightSvg", "darkSvg", "lightPng", "darkPng"],
174
+ properties: {
175
+ tldr: { type: "string" },
176
+ lightSvg: { type: "string" },
177
+ darkSvg: { type: "string" },
178
+ lightPng: { type: "string" },
179
+ darkPng: { type: "string" },
180
+ },
181
+ },
182
+ summary: {
183
+ type: "object",
184
+ additionalProperties: false,
185
+ required: [
186
+ "shapeCount",
187
+ "edgeCount",
188
+ "findingCount",
189
+ "returnedFindingCount",
190
+ "findingsTruncated",
191
+ ],
192
+ properties: {
193
+ shapeCount: { type: "integer", minimum: 0 },
194
+ edgeCount: { type: "integer", minimum: 0 },
195
+ findingCount: { type: "integer", minimum: 0 },
196
+ returnedFindingCount: { type: "integer", minimum: 0 },
197
+ findingsTruncated: { type: "boolean" },
198
+ },
199
+ },
200
+ },
201
+ },
202
+ annotations: {
203
+ title: "Render diagram",
204
+ readOnlyHint: false,
205
+ destructiveHint: true,
206
+ idempotentHint: true,
207
+ openWorldHint: false,
208
+ },
209
+ },
210
+ {
211
+ name: "search_slopcamera",
212
+ title: "Search Slopcamera operations",
213
+ description:
214
+ "Search the fixed semantic Slopcamera operation registry by bounded text. This never executes code or changes files.",
215
+ inputSchema: {
216
+ type: "object",
217
+ additionalProperties: false,
218
+ properties: {
219
+ query: {
220
+ type: "string",
221
+ maxLength: 200,
222
+ description: "Optional terms matched against operation codes and descriptions.",
223
+ },
224
+ limit: {
225
+ type: "integer",
226
+ minimum: 1,
227
+ maximum: 20,
228
+ default: 4,
229
+ },
230
+ },
231
+ },
232
+ outputSchema: {
233
+ type: "object",
234
+ additionalProperties: false,
235
+ required: ["ok", "operations"],
236
+ properties: {
237
+ ok: { const: true },
238
+ operations: {
239
+ type: "array",
240
+ maxItems: 20,
241
+ items: {
242
+ type: "object",
243
+ required: [
244
+ "code",
245
+ "title",
246
+ "description",
247
+ "execution",
248
+ "authentication",
249
+ "inputSchema",
250
+ ],
251
+ },
252
+ },
253
+ },
254
+ },
255
+ annotations: {
256
+ title: "Search Slopcamera operations",
257
+ readOnlyHint: true,
258
+ destructiveHint: false,
259
+ idempotentHint: true,
260
+ openWorldHint: false,
261
+ },
262
+ },
263
+ {
264
+ name: "execute_slopcamera",
265
+ title: "Execute Slopcamera operation",
266
+ description:
267
+ "Execute one exact operation code with typed JSON input. Never accepts or evaluates source code. Local paths remain confined to the configured workspace root.",
268
+ inputSchema: {
269
+ type: "object",
270
+ additionalProperties: false,
271
+ required: ["operation", "input"],
272
+ properties: {
273
+ operation: {
274
+ type: "string",
275
+ enum: slopcameraOperationCodes,
276
+ },
277
+ input: {
278
+ type: "object",
279
+ description:
280
+ "Typed input matching the selected operation's registry schema.",
281
+ },
282
+ },
283
+ },
284
+ outputSchema: {
285
+ type: "object",
286
+ additionalProperties: false,
287
+ required: ["ok", "operation", "result"],
288
+ properties: {
289
+ ok: { const: true },
290
+ operation: { type: "string", enum: slopcameraOperationCodes },
291
+ result: { type: "object" },
292
+ },
293
+ },
294
+ annotations: {
295
+ title: "Execute Slopcamera operation",
296
+ readOnlyHint: false,
297
+ destructiveHint: true,
298
+ idempotentHint: false,
299
+ openWorldHint: true,
300
+ },
301
+ },
302
+ ])
303
+
304
+ class ToolFailure extends Error {
305
+ readonly code: string
306
+ readonly issues?: readonly string[]
307
+
308
+ constructor(code: string, message: string, issues?: readonly string[]) {
309
+ super(message)
310
+ this.name = "ToolFailure"
311
+ this.code = code
312
+ if (issues !== undefined) this.issues = issues
313
+ }
314
+ }
315
+
316
+ interface ParsedCheckArguments {
317
+ readonly path: string
318
+ }
319
+
320
+ interface ParsedRenderArguments extends ParsedCheckArguments {
321
+ readonly outDirectory?: string
322
+ readonly scale: number
323
+ }
324
+
325
+ interface ParsedSearchArguments {
326
+ readonly query: string
327
+ readonly limit: number
328
+ }
329
+
330
+ interface ParsedExecuteArguments {
331
+ readonly operation: SlopcameraOperationCode
332
+ readonly input: unknown
333
+ }
334
+
335
+ interface LoadedDiagram {
336
+ readonly source: WorkspaceSource
337
+ readonly spec: DiagramSpec
338
+ }
339
+
340
+ interface PublicArtifactPaths {
341
+ readonly tldr: string
342
+ readonly lightSvg: string
343
+ readonly darkSvg: string
344
+ readonly lightPng: string
345
+ readonly darkPng: string
346
+ }
347
+
348
+ function isRecord(value: unknown): value is Record<string, unknown> {
349
+ return typeof value === "object" && value !== null && !Array.isArray(value)
350
+ }
351
+
352
+ function safeFragment(value: string, maximumLength = 160): string {
353
+ return value
354
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
355
+ .replace(/\s+/g, " ")
356
+ .trim()
357
+ .slice(0, maximumLength)
358
+ }
359
+
360
+ function safeIssues(issues: readonly string[]): readonly string[] {
361
+ return issues.slice(0, 24).map((issue) => safeFragment(issue, 240))
362
+ }
363
+
364
+ function rejectUnknownKeys(
365
+ value: Record<string, unknown>,
366
+ allowed: ReadonlySet<string>,
367
+ ): void {
368
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key))
369
+ if (unknown.length > 0) {
370
+ throw new ToolFailure(
371
+ "INVALID_ARGUMENTS",
372
+ `Unsupported argument: ${safeFragment(unknown[0] ?? "unknown")}.`,
373
+ )
374
+ }
375
+ }
376
+
377
+ function parsePath(value: unknown): string {
378
+ if (typeof value !== "string" || value.length === 0) {
379
+ throw new ToolFailure(
380
+ "INVALID_ARGUMENTS",
381
+ "path must be a non-empty root-relative string.",
382
+ )
383
+ }
384
+ if (!value.toLowerCase().endsWith(".diagram.json")) {
385
+ throw new ToolFailure(
386
+ "INVALID_ARGUMENTS",
387
+ "path must end in .diagram.json.",
388
+ )
389
+ }
390
+ return value
391
+ }
392
+
393
+ function parseCheckArguments(value: unknown): ParsedCheckArguments {
394
+ if (!isRecord(value)) {
395
+ throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
396
+ }
397
+ rejectUnknownKeys(value, new Set(["path"]))
398
+ return { path: parsePath(value.path) }
399
+ }
400
+
401
+ function parseRenderArguments(value: unknown): ParsedRenderArguments {
402
+ if (!isRecord(value)) {
403
+ throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
404
+ }
405
+ rejectUnknownKeys(value, new Set(["path", "out_dir", "scale"]))
406
+ const outDirectory = value.out_dir
407
+ if (
408
+ outDirectory !== undefined &&
409
+ (typeof outDirectory !== "string" || outDirectory.length === 0)
410
+ ) {
411
+ throw new ToolFailure(
412
+ "INVALID_ARGUMENTS",
413
+ "out_dir must be a non-empty root-relative string when present.",
414
+ )
415
+ }
416
+ const scale = value.scale ?? defaultScale
417
+ if (
418
+ typeof scale !== "number" ||
419
+ !Number.isFinite(scale) ||
420
+ scale <= 0 ||
421
+ scale > mcpMaximumScale
422
+ ) {
423
+ throw new ToolFailure(
424
+ "RENDER_LIMIT",
425
+ `scale must be greater than zero and no more than ${mcpMaximumScale}.`,
426
+ )
427
+ }
428
+ return {
429
+ path: parsePath(value.path),
430
+ ...(outDirectory === undefined ? {} : { outDirectory }),
431
+ scale,
432
+ }
433
+ }
434
+
435
+ function parseSearchArguments(value: unknown): ParsedSearchArguments {
436
+ if (!isRecord(value)) {
437
+ throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
438
+ }
439
+ rejectUnknownKeys(value, new Set(["query", "limit"]))
440
+ const query = value.query ?? ""
441
+ const limit = value.limit ?? slopcameraOperationCodes.length
442
+ if (
443
+ typeof query !== "string" ||
444
+ query.length > 200 ||
445
+ /[\u0000-\u001f\u007f]/u.test(query) ||
446
+ !Number.isInteger(limit) ||
447
+ (limit as number) < 1 ||
448
+ (limit as number) > 20
449
+ ) {
450
+ throw new ToolFailure(
451
+ "INVALID_ARGUMENTS",
452
+ "query must be a bounded string and limit must be an integer from 1 through 20.",
453
+ )
454
+ }
455
+ return { query, limit: limit as number }
456
+ }
457
+
458
+ function parseExecuteArguments(value: unknown): ParsedExecuteArguments {
459
+ if (!isRecord(value)) {
460
+ throw new ToolFailure("INVALID_ARGUMENTS", "Tool arguments must be an object.")
461
+ }
462
+ rejectUnknownKeys(value, new Set(["operation", "input"]))
463
+ if (
464
+ typeof value.operation !== "string" ||
465
+ !slopcameraOperationCodes.includes(value.operation as SlopcameraOperationCode) ||
466
+ !isRecord(value.input)
467
+ ) {
468
+ throw new ToolFailure(
469
+ "INVALID_ARGUMENTS",
470
+ "operation must be an exact Slopcamera operation code and input must be an object.",
471
+ )
472
+ }
473
+ return {
474
+ operation: value.operation as SlopcameraOperationCode,
475
+ input: value.input,
476
+ }
477
+ }
478
+
479
+ function assertBuiltInIcons(spec: DiagramSpec): void {
480
+ for (const shape of spec.shapes) {
481
+ if (
482
+ (shape.type === "rect" || shape.type === "ellipse") &&
483
+ shape.icon !== undefined &&
484
+ !Object.hasOwn(builtInIcons, shape.icon)
485
+ ) {
486
+ throw new ToolFailure(
487
+ "UNKNOWN_ICON",
488
+ `Shape ${safeFragment(shape.id)} requests unavailable built-in icon ${safeFragment(shape.icon)}.`,
489
+ )
490
+ }
491
+ }
492
+ }
493
+
494
+ function assertComplexityLimits(spec: DiagramSpec): void {
495
+ const edgeCount = spec.edges?.length ?? 0
496
+ if (spec.shapes.length > mcpMaximumShapes || edgeCount > mcpMaximumEdges) {
497
+ throw new ToolFailure(
498
+ "COMPLEXITY_LIMIT",
499
+ `Diagram may contain at most ${mcpMaximumShapes} shapes and ${mcpMaximumEdges} edges in MCP mode.`,
500
+ )
501
+ }
502
+ }
503
+
504
+ function assertRawComplexityLimits(value: unknown): void {
505
+ if (!isRecord(value)) return
506
+ const shapeCount = Array.isArray(value.shapes) ? value.shapes.length : 0
507
+ const edgeCount = Array.isArray(value.edges) ? value.edges.length : 0
508
+ if (shapeCount > mcpMaximumShapes || edgeCount > mcpMaximumEdges) {
509
+ throw new ToolFailure(
510
+ "COMPLEXITY_LIMIT",
511
+ `Diagram may contain at most ${mcpMaximumShapes} shapes and ${mcpMaximumEdges} edges in MCP mode.`,
512
+ )
513
+ }
514
+ }
515
+
516
+ function assertRenderLimits(spec: DiagramSpec, scale: number): void {
517
+ const scaledWidth = spec.canvas.width * scale
518
+ const scaledHeight = spec.canvas.height * scale
519
+ const pixels = Math.ceil(scaledWidth) * Math.ceil(scaledHeight)
520
+ if (
521
+ !Number.isFinite(pixels) ||
522
+ scaledWidth < 1 ||
523
+ scaledHeight < 1 ||
524
+ pixels > mcpMaximumRenderedPixels
525
+ ) {
526
+ throw new ToolFailure(
527
+ "RENDER_LIMIT",
528
+ `Scaled canvas must be at least 1 pixel on each axis and no more than ${mcpMaximumRenderedPixels.toLocaleString("en-US")} pixels total.`,
529
+ )
530
+ }
531
+ }
532
+
533
+ function publicFinding(finding: LintFinding): LintFinding {
534
+ return {
535
+ code: safeFragment(finding.code, 64),
536
+ message: safeFragment(finding.message, 240),
537
+ shapeIds: finding.shapeIds
538
+ .slice(0, maximumShapeIdsPerFinding)
539
+ .map((shapeId) => safeFragment(shapeId, 120)),
540
+ }
541
+ }
542
+
543
+ function publicFindings(
544
+ findings: readonly LintFinding[],
545
+ ): readonly LintFinding[] {
546
+ return findings
547
+ .slice(0, mcpMaximumReturnedFindings)
548
+ .map(publicFinding)
549
+ }
550
+
551
+ function diagramSummary(
552
+ spec: DiagramSpec,
553
+ findingCount: number,
554
+ returnedFindingCount: number,
555
+ ): Readonly<Record<string, number | boolean>> {
556
+ return {
557
+ shapeCount: spec.shapes.length,
558
+ edgeCount: spec.edges?.length ?? 0,
559
+ findingCount,
560
+ returnedFindingCount,
561
+ findingsTruncated: returnedFindingCount < findingCount,
562
+ }
563
+ }
564
+
565
+ function successResult(
566
+ text: string,
567
+ structuredContent: Readonly<Record<string, unknown>>,
568
+ ): McpToolResult {
569
+ return {
570
+ content: [{ type: "text", text }],
571
+ structuredContent,
572
+ }
573
+ }
574
+
575
+ function failureResult(error: unknown): McpToolResult {
576
+ let code = "INTERNAL_ERROR"
577
+ let message = "The tool failed safely."
578
+ let issues: readonly string[] | undefined
579
+
580
+ if (error instanceof ToolFailure) {
581
+ code = error.code
582
+ message = safeFragment(error.message, 320)
583
+ issues = error.issues
584
+ } else if (error instanceof WorkspaceBoundaryError) {
585
+ code = error.code
586
+ message = safeFragment(error.message, 320)
587
+ } else if (error instanceof SlopcameraCloudError) {
588
+ code = error.code
589
+ message = safeFragment(
590
+ error.message.replace(/^\[[A-Z_]+\]\s*/u, ""),
591
+ 320,
592
+ )
593
+ } else if (error instanceof SlopcameraOperationError) {
594
+ code = error.code
595
+ message = safeFragment(
596
+ error.message.replace(/^\[[A-Z_]+\]\s*/u, ""),
597
+ 320,
598
+ )
599
+ } else if (error instanceof VectorizeError) {
600
+ code = `VECTORIZE_${error.code.toUpperCase()}`
601
+ message = "Local vectorization failed safely."
602
+ } else if (error instanceof DiagramValidationError) {
603
+ code = "INVALID_DIAGRAM"
604
+ message = "Diagram source did not pass validation."
605
+ issues = safeIssues(error.issues)
606
+ } else if (
607
+ typeof error === "object" &&
608
+ error !== null &&
609
+ "issues" in error &&
610
+ Array.isArray(error.issues) &&
611
+ error.issues.every((issue) => typeof issue === "string")
612
+ ) {
613
+ code = "INVALID_LAYOUT"
614
+ message = "Diagram layout could not be resolved."
615
+ issues = safeIssues(error.issues)
616
+ }
617
+
618
+ const issueText =
619
+ issues === undefined || issues.length === 0
620
+ ? ""
621
+ : `\n${issues.map((issue) => `- ${issue}`).join("\n")}`
622
+ return {
623
+ content: [{ type: "text", text: `[${code}] ${message}${issueText}` }],
624
+ isError: true,
625
+ }
626
+ }
627
+
628
+ function portableDirectory(filePath: string): string {
629
+ const separator = filePath.lastIndexOf("/")
630
+ return separator === -1 ? "." : filePath.slice(0, separator)
631
+ }
632
+
633
+ async function atomicOverwrite(
634
+ filePath: string,
635
+ data: string | Uint8Array,
636
+ ): Promise<void> {
637
+ const temporaryPath = join(
638
+ dirname(filePath),
639
+ `.${crypto.randomUUID()}.slopcamera-mcp.tmp`,
640
+ )
641
+ try {
642
+ await writeFile(temporaryPath, data, { flag: "wx" })
643
+ try {
644
+ await rename(temporaryPath, filePath)
645
+ } catch (error) {
646
+ const code =
647
+ typeof error === "object" &&
648
+ error !== null &&
649
+ "code" in error &&
650
+ typeof error.code === "string"
651
+ ? error.code
652
+ : undefined
653
+ if (code !== "EEXIST" && code !== "EPERM") throw error
654
+ // Windows does not consistently replace an existing destination with
655
+ // rename. The render queue prevents another MCP render from racing this
656
+ // narrow replacement fallback.
657
+ await rm(filePath, { force: true })
658
+ await rename(temporaryPath, filePath)
659
+ }
660
+ } finally {
661
+ await rm(temporaryPath, { force: true })
662
+ }
663
+ }
664
+
665
+ async function loadDiagram(
666
+ boundary: WorkspaceBoundary,
667
+ path: string,
668
+ ): Promise<LoadedDiagram> {
669
+ const source = await boundary.readSource(path)
670
+ let parsed: unknown
671
+ try {
672
+ parsed = JSON.parse(source.text)
673
+ } catch {
674
+ throw new ToolFailure("INVALID_JSON", "Diagram source is not valid JSON.")
675
+ }
676
+ assertRawComplexityLimits(parsed)
677
+ const spec = parseDiagramSpec(parsed)
678
+ assertComplexityLimits(spec)
679
+ assertBuiltInIcons(spec)
680
+ return { source, spec }
681
+ }
682
+
683
+ export class SlopcameraMcpToolRuntime {
684
+ readonly boundary: WorkspaceBoundary
685
+ readonly generateDependencies: SlopcameraGenerateDependencies
686
+ readonly hostResourceCoordinator: HostResourceCoordinator
687
+ private renderQueue: Promise<void> = Promise.resolve()
688
+
689
+ private constructor(
690
+ boundary: WorkspaceBoundary,
691
+ generateDependencies: SlopcameraGenerateDependencies,
692
+ hostResourceCoordinator: HostResourceCoordinator,
693
+ ) {
694
+ this.boundary = boundary
695
+ this.generateDependencies = generateDependencies
696
+ this.hostResourceCoordinator = hostResourceCoordinator
697
+ }
698
+
699
+ static async create(
700
+ rootDirectory: string,
701
+ generateDependencies: SlopcameraGenerateDependencies = {},
702
+ hostResourceCoordinator?: HostResourceCoordinator,
703
+ ): Promise<SlopcameraMcpToolRuntime> {
704
+ return new SlopcameraMcpToolRuntime(
705
+ await WorkspaceBoundary.create(rootDirectory),
706
+ generateDependencies,
707
+ hostResourceCoordinator ?? createDefaultHostResourceCoordinator(),
708
+ )
709
+ }
710
+
711
+ private async withHostAdmission<T>(
712
+ operation: SlopcameraOperationCode,
713
+ callback: (lease: HostResourceLease) => T | Promise<T>,
714
+ ): Promise<T> {
715
+ return await withSlopcameraOperationHostAdmission(operation, callback, {
716
+ hostResourceCoordinator: this.hostResourceCoordinator,
717
+ })
718
+ }
719
+
720
+ private enqueueRender<T>(operation: () => Promise<T>): Promise<T> {
721
+ const result = this.renderQueue.then(operation, operation)
722
+ this.renderQueue = result.then(
723
+ () => undefined,
724
+ () => undefined,
725
+ )
726
+ return result
727
+ }
728
+
729
+ async call(name: string, argumentsValue: unknown): Promise<McpToolResult> {
730
+ try {
731
+ if (name === "check_diagram") {
732
+ const options = parseCheckArguments(argumentsValue)
733
+ return await this.withHostAdmission(
734
+ "slopcamera.diagram.check",
735
+ async () => await this.check(options),
736
+ )
737
+ }
738
+ if (name === "render_diagram") {
739
+ const options = parseRenderArguments(argumentsValue)
740
+ return await this.enqueueRender(async () => (
741
+ await this.withHostAdmission(
742
+ "slopcamera.diagram.render",
743
+ async () => await this.render(options),
744
+ )
745
+ ))
746
+ }
747
+ if (name === "search_slopcamera") {
748
+ const options = parseSearchArguments(argumentsValue)
749
+ const operations = searchSlopcameraOperations(
750
+ options.query,
751
+ options.limit,
752
+ )
753
+ return successResult(
754
+ `Found ${operations.length} Slopcamera operation${operations.length === 1 ? "" : "s"}.`,
755
+ { ok: true, operations },
756
+ )
757
+ }
758
+ if (name === "execute_slopcamera") {
759
+ const options = parseExecuteArguments(argumentsValue)
760
+ return await this.execute(options)
761
+ }
762
+ throw new ToolFailure("UNKNOWN_TOOL", "Requested tool is not available.")
763
+ } catch (error) {
764
+ return failureResult(error)
765
+ }
766
+ }
767
+
768
+ private wrapSemanticResult(
769
+ operation: SlopcameraOperationCode,
770
+ result: McpToolResult,
771
+ ): McpToolResult {
772
+ if (result.isError === true) return result
773
+ return {
774
+ content: result.content,
775
+ structuredContent: {
776
+ ok: true,
777
+ operation,
778
+ result: result.structuredContent ?? {},
779
+ },
780
+ }
781
+ }
782
+
783
+ private async execute(
784
+ options: ParsedExecuteArguments,
785
+ ): Promise<McpToolResult> {
786
+ if (options.operation === "slopcamera.diagram.check") {
787
+ const input = parseSlopcameraOperationInput(
788
+ options.operation,
789
+ options.input,
790
+ ) as CheckSlopcameraOperationInput
791
+ return this.wrapSemanticResult(
792
+ options.operation,
793
+ await this.withHostAdmission(
794
+ options.operation,
795
+ async () => await this.check({ path: input.path }),
796
+ ),
797
+ )
798
+ }
799
+ if (options.operation === "slopcamera.diagram.render") {
800
+ const input = parseSlopcameraOperationInput(
801
+ options.operation,
802
+ options.input,
803
+ ) as RenderSlopcameraOperationInput
804
+ return this.enqueueRender(async () => await this.withHostAdmission(
805
+ options.operation,
806
+ async () => this.wrapSemanticResult(
807
+ options.operation,
808
+ await this.render({
809
+ path: input.path,
810
+ ...(input.outDirectory === undefined
811
+ ? {}
812
+ : { outDirectory: input.outDirectory }),
813
+ scale: input.scale ?? defaultScale,
814
+ }),
815
+ ),
816
+ ))
817
+ }
818
+ if (options.operation === "slopcamera.image.vectorize") {
819
+ const input = parseSlopcameraOperationInput(
820
+ options.operation,
821
+ options.input,
822
+ ) as VectorizeSlopcameraOperationInput
823
+ return this.enqueueRender(async () => await this.withHostAdmission(
824
+ options.operation,
825
+ async (lease) => {
826
+ const source = await this.boundary.resolveInputFile(
827
+ input.inputPath,
828
+ vectorizeHardLimits.maxInputBytes,
829
+ )
830
+ const output = await this.boundary.prepareOutputFile(input.outputPath)
831
+ const result = await vectorizeImage(source.absolutePath, {
832
+ outputPath: output.absolutePath,
833
+ ...(input.duotone === undefined ? {} : { duotone: input.duotone }),
834
+ ...(input.alphaCutoff === undefined
835
+ ? {}
836
+ : { alphaCutoff: input.alphaCutoff }),
837
+ ...(input.timeoutMs === undefined
838
+ ? {}
839
+ : { limits: { maxDurationMs: input.timeoutMs } }),
840
+ inheritedFileDescriptors: [lease.inheritedFileDescriptor],
841
+ })
842
+ return successResult(
843
+ `Executed ${options.operation}: ${output.relativePath}`,
844
+ {
845
+ ok: true,
846
+ operation: options.operation,
847
+ result: {
848
+ inputPath: source.relativePath,
849
+ outputPath: output.relativePath,
850
+ receipt: result.receipt,
851
+ },
852
+ },
853
+ )
854
+ },
855
+ ))
856
+ }
857
+ return await this.withHostAdmission(options.operation, async () => {
858
+ const input = parseSlopcameraOperationInput(
859
+ options.operation,
860
+ options.input,
861
+ ) as GenerateSlopcameraOperationInput
862
+ const output = await this.boundary.prepareOutputFile(input.outputPath)
863
+ const generated = await generateSlopcameraImageFile(
864
+ { ...input, outputPath: output.absolutePath },
865
+ this.generateDependencies,
866
+ )
867
+ return successResult(
868
+ `Executed ${options.operation}: ${output.relativePath} (request ${safeFragment(generated.requestId, 256)}).`,
869
+ {
870
+ ok: true,
871
+ operation: options.operation,
872
+ result: {
873
+ bytes: generated.bytes,
874
+ mediaType: generated.mediaType,
875
+ model: generated.model,
876
+ outputPath: output.relativePath,
877
+ provider: generated.provider,
878
+ requestId: generated.requestId,
879
+ sha256: generated.sha256,
880
+ warnings: generated.warnings,
881
+ },
882
+ },
883
+ )
884
+ })
885
+ }
886
+
887
+ private async check(options: ParsedCheckArguments): Promise<McpToolResult> {
888
+ const { source, spec } = await loadDiagram(this.boundary, options.path)
889
+ const allFindings = lintDiagram(spec)
890
+ const findings = publicFindings(allFindings)
891
+ const summary = diagramSummary(spec, allFindings.length, findings.length)
892
+ const text =
893
+ allFindings.length === 0
894
+ ? `Checked ${source.relativePath}: no findings.`
895
+ : `Checked ${source.relativePath}: ${allFindings.length} finding${allFindings.length === 1 ? "" : "s"}; ${findings.length} returned in structured content${findings.length < allFindings.length ? " (truncated)" : ""}.`
896
+ return successResult(text, {
897
+ ok: true,
898
+ source: source.relativePath,
899
+ findings,
900
+ summary,
901
+ })
902
+ }
903
+
904
+ private async render(options: ParsedRenderArguments): Promise<McpToolResult> {
905
+ const { source, spec } = await loadDiagram(this.boundary, options.path)
906
+ assertRenderLimits(spec, options.scale)
907
+ const outputDirectory = await this.boundary.prepareOutputDirectory(
908
+ options.outDirectory ?? portableDirectory(source.relativePath),
909
+ )
910
+
911
+ // Serialize before raster work so the existing generated-record budget is
912
+ // enforced before a render can consume substantial CPU or write anything.
913
+ const tldr = serializeTldr(spec, builtInConfig)
914
+ const [light, dark] = await Promise.all([
915
+ renderSvg(spec, "light", builtInConfig),
916
+ renderSvg(spec, "dark", builtInConfig),
917
+ ])
918
+ const lightPng = renderPng(light, builtInConfig, options.scale)
919
+ const darkPng = renderPng(dark, builtInConfig, options.scale)
920
+ const absoluteArtifacts = {
921
+ spec: source.absolutePath,
922
+ tldr: join(outputDirectory.absolutePath, `${spec.name}.tldr`),
923
+ lightSvg: join(outputDirectory.absolutePath, `${spec.name}.light.svg`),
924
+ darkSvg: join(outputDirectory.absolutePath, `${spec.name}.dark.svg`),
925
+ lightPng: join(outputDirectory.absolutePath, `${spec.name}.light.png`),
926
+ darkPng: join(outputDirectory.absolutePath, `${spec.name}.dark.png`),
927
+ } satisfies RenderArtifacts
928
+
929
+ await Promise.all([
930
+ atomicOverwrite(
931
+ absoluteArtifacts.tldr,
932
+ tldr,
933
+ ),
934
+ atomicOverwrite(absoluteArtifacts.lightSvg, light.svg),
935
+ atomicOverwrite(absoluteArtifacts.darkSvg, dark.svg),
936
+ atomicOverwrite(absoluteArtifacts.lightPng, lightPng),
937
+ atomicOverwrite(absoluteArtifacts.darkPng, darkPng),
938
+ ])
939
+
940
+ const artifacts: PublicArtifactPaths = {
941
+ tldr: this.boundary.toRelativePath(absoluteArtifacts.tldr),
942
+ lightSvg: this.boundary.toRelativePath(absoluteArtifacts.lightSvg),
943
+ darkSvg: this.boundary.toRelativePath(absoluteArtifacts.darkSvg),
944
+ lightPng: this.boundary.toRelativePath(absoluteArtifacts.lightPng),
945
+ darkPng: this.boundary.toRelativePath(absoluteArtifacts.darkPng),
946
+ }
947
+ const allFindings = lintDiagram(spec)
948
+ const findings = publicFindings(allFindings)
949
+ const summary = diagramSummary(spec, allFindings.length, findings.length)
950
+ const text = [
951
+ `Rendered ${source.relativePath} with built-in assets:`,
952
+ ...Object.values(artifacts).map((artifact) => `- ${artifact}`),
953
+ ].join("\n")
954
+ return successResult(text, {
955
+ ok: true,
956
+ source: source.relativePath,
957
+ scale: options.scale,
958
+ findings,
959
+ artifacts,
960
+ summary,
961
+ })
962
+ }
963
+ }