@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.
- package/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
package/src/mcp/tools.ts
ADDED
|
@@ -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
|
+
}
|