@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
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { open, mkdir, realpath, stat } from "node:fs/promises"
|
|
2
|
+
import {
|
|
3
|
+
basename,
|
|
4
|
+
dirname,
|
|
5
|
+
isAbsolute,
|
|
6
|
+
join,
|
|
7
|
+
relative,
|
|
8
|
+
resolve,
|
|
9
|
+
win32,
|
|
10
|
+
} from "node:path"
|
|
11
|
+
|
|
12
|
+
export const mcpSourceByteLimit = 1024 * 1024
|
|
13
|
+
|
|
14
|
+
export class WorkspaceBoundaryError extends Error {
|
|
15
|
+
readonly code:
|
|
16
|
+
| "INVALID_PATH"
|
|
17
|
+
| "PATH_OUTSIDE_ROOT"
|
|
18
|
+
| "SOURCE_NOT_FOUND"
|
|
19
|
+
| "SOURCE_NOT_FILE"
|
|
20
|
+
| "SOURCE_TOO_LARGE"
|
|
21
|
+
| "SOURCE_ENCODING"
|
|
22
|
+
| "OUTPUT_NOT_DIRECTORY"
|
|
23
|
+
| "FILESYSTEM_ERROR"
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
code: WorkspaceBoundaryError["code"],
|
|
27
|
+
message: string,
|
|
28
|
+
) {
|
|
29
|
+
super(message)
|
|
30
|
+
this.name = "WorkspaceBoundaryError"
|
|
31
|
+
this.code = code
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface NormalizedRelativePath {
|
|
36
|
+
readonly native: string
|
|
37
|
+
readonly portable: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WorkspaceSource {
|
|
41
|
+
readonly absolutePath: string
|
|
42
|
+
readonly relativePath: string
|
|
43
|
+
readonly text: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WorkspaceDirectory {
|
|
47
|
+
readonly absolutePath: string
|
|
48
|
+
readonly relativePath: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WorkspaceFile {
|
|
52
|
+
readonly absolutePath: string
|
|
53
|
+
readonly relativePath: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function filesystemCode(error: unknown): string | undefined {
|
|
57
|
+
if (
|
|
58
|
+
typeof error === "object" &&
|
|
59
|
+
error !== null &&
|
|
60
|
+
"code" in error &&
|
|
61
|
+
typeof error.code === "string"
|
|
62
|
+
) {
|
|
63
|
+
return error.code
|
|
64
|
+
}
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeRelativePath(
|
|
69
|
+
value: string,
|
|
70
|
+
options: { readonly allowRoot: boolean },
|
|
71
|
+
): NormalizedRelativePath {
|
|
72
|
+
if (value.length === 0 || value.includes("\0")) {
|
|
73
|
+
throw new WorkspaceBoundaryError("INVALID_PATH", "Path must be a non-empty root-relative path.")
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
isAbsolute(value) ||
|
|
77
|
+
win32.isAbsolute(value) ||
|
|
78
|
+
/^[A-Za-z]:/.test(value)
|
|
79
|
+
) {
|
|
80
|
+
throw new WorkspaceBoundaryError("INVALID_PATH", "Absolute paths are not allowed.")
|
|
81
|
+
}
|
|
82
|
+
const segments = value
|
|
83
|
+
.split(/[\\/]/)
|
|
84
|
+
.filter((segment) => segment !== "" && segment !== ".")
|
|
85
|
+
if (segments.includes("..")) {
|
|
86
|
+
throw new WorkspaceBoundaryError("INVALID_PATH", "Parent-directory traversal is not allowed.")
|
|
87
|
+
}
|
|
88
|
+
if (segments.length === 0) {
|
|
89
|
+
if (!options.allowRoot) {
|
|
90
|
+
throw new WorkspaceBoundaryError("INVALID_PATH", "Path must identify a file below the root.")
|
|
91
|
+
}
|
|
92
|
+
return { native: ".", portable: "." }
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
native: segments.join("/"),
|
|
96
|
+
portable: segments.join("/"),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isConfined(rootDirectory: string, target: string): boolean {
|
|
101
|
+
const fromRoot = relative(rootDirectory, target)
|
|
102
|
+
return fromRoot === "" || (!fromRoot.startsWith("..") && !isAbsolute(fromRoot))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function readUtf8WithCap(filePath: string): Promise<string> {
|
|
106
|
+
let handle
|
|
107
|
+
try {
|
|
108
|
+
handle = await open(filePath, "r")
|
|
109
|
+
const metadata = await handle.stat()
|
|
110
|
+
if (!metadata.isFile()) {
|
|
111
|
+
throw new WorkspaceBoundaryError(
|
|
112
|
+
"SOURCE_NOT_FILE",
|
|
113
|
+
"Diagram source must be a regular file.",
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
if (metadata.size > mcpSourceByteLimit) {
|
|
117
|
+
throw new WorkspaceBoundaryError(
|
|
118
|
+
"SOURCE_TOO_LARGE",
|
|
119
|
+
`Diagram source exceeds the ${mcpSourceByteLimit}-byte limit.`,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const buffer = Buffer.allocUnsafe(mcpSourceByteLimit + 1)
|
|
124
|
+
let bytesRead = 0
|
|
125
|
+
while (bytesRead <= mcpSourceByteLimit) {
|
|
126
|
+
const next = await handle.read(
|
|
127
|
+
buffer,
|
|
128
|
+
bytesRead,
|
|
129
|
+
mcpSourceByteLimit + 1 - bytesRead,
|
|
130
|
+
null,
|
|
131
|
+
)
|
|
132
|
+
if (next.bytesRead === 0) break
|
|
133
|
+
bytesRead += next.bytesRead
|
|
134
|
+
}
|
|
135
|
+
if (bytesRead > mcpSourceByteLimit) {
|
|
136
|
+
throw new WorkspaceBoundaryError(
|
|
137
|
+
"SOURCE_TOO_LARGE",
|
|
138
|
+
`Diagram source exceeds the ${mcpSourceByteLimit}-byte limit.`,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(
|
|
143
|
+
buffer.subarray(0, bytesRead),
|
|
144
|
+
)
|
|
145
|
+
} catch {
|
|
146
|
+
throw new WorkspaceBoundaryError(
|
|
147
|
+
"SOURCE_ENCODING",
|
|
148
|
+
"Diagram source must contain valid UTF-8.",
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof WorkspaceBoundaryError) throw error
|
|
153
|
+
const code = filesystemCode(error)
|
|
154
|
+
if (code === "ENOENT") {
|
|
155
|
+
throw new WorkspaceBoundaryError("SOURCE_NOT_FOUND", "Diagram source does not exist.")
|
|
156
|
+
}
|
|
157
|
+
throw new WorkspaceBoundaryError("FILESYSTEM_ERROR", "Diagram source could not be read.")
|
|
158
|
+
} finally {
|
|
159
|
+
await handle?.close()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class WorkspaceBoundary {
|
|
164
|
+
readonly rootDirectory: string
|
|
165
|
+
|
|
166
|
+
private constructor(rootDirectory: string) {
|
|
167
|
+
this.rootDirectory = rootDirectory
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static async create(rootDirectory: string): Promise<WorkspaceBoundary> {
|
|
171
|
+
let resolvedRoot: string
|
|
172
|
+
try {
|
|
173
|
+
resolvedRoot = await realpath(resolve(rootDirectory))
|
|
174
|
+
if (!(await stat(resolvedRoot)).isDirectory()) {
|
|
175
|
+
throw new WorkspaceBoundaryError(
|
|
176
|
+
"OUTPUT_NOT_DIRECTORY",
|
|
177
|
+
"MCP root must be a directory.",
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error instanceof WorkspaceBoundaryError) throw error
|
|
182
|
+
throw new WorkspaceBoundaryError(
|
|
183
|
+
"FILESYSTEM_ERROR",
|
|
184
|
+
"MCP root could not be opened.",
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
return new WorkspaceBoundary(resolvedRoot)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private assertConfined(target: string): void {
|
|
191
|
+
if (!isConfined(this.rootDirectory, target)) {
|
|
192
|
+
throw new WorkspaceBoundaryError(
|
|
193
|
+
"PATH_OUTSIDE_ROOT",
|
|
194
|
+
"Path resolves outside the MCP root.",
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
toRelativePath(absolutePath: string): string {
|
|
200
|
+
this.assertConfined(absolutePath)
|
|
201
|
+
const fromRoot = relative(this.rootDirectory, absolutePath)
|
|
202
|
+
return fromRoot === "" ? "." : fromRoot.split("\\").join("/")
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async readSource(value: string): Promise<WorkspaceSource> {
|
|
206
|
+
const normalized = normalizeRelativePath(value, { allowRoot: false })
|
|
207
|
+
const lexicalPath = resolve(this.rootDirectory, normalized.native)
|
|
208
|
+
this.assertConfined(lexicalPath)
|
|
209
|
+
|
|
210
|
+
let canonicalPath: string
|
|
211
|
+
try {
|
|
212
|
+
canonicalPath = await realpath(lexicalPath)
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (filesystemCode(error) === "ENOENT") {
|
|
215
|
+
throw new WorkspaceBoundaryError(
|
|
216
|
+
"SOURCE_NOT_FOUND",
|
|
217
|
+
"Diagram source does not exist.",
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
throw new WorkspaceBoundaryError(
|
|
221
|
+
"FILESYSTEM_ERROR",
|
|
222
|
+
"Diagram source could not be resolved.",
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
this.assertConfined(canonicalPath)
|
|
226
|
+
return {
|
|
227
|
+
absolutePath: canonicalPath,
|
|
228
|
+
relativePath: this.toRelativePath(canonicalPath),
|
|
229
|
+
text: await readUtf8WithCap(canonicalPath),
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async resolveInputFile(
|
|
234
|
+
value: string,
|
|
235
|
+
maximumBytes: number,
|
|
236
|
+
): Promise<WorkspaceFile> {
|
|
237
|
+
const normalized = normalizeRelativePath(value, { allowRoot: false })
|
|
238
|
+
const lexicalPath = resolve(this.rootDirectory, normalized.native)
|
|
239
|
+
this.assertConfined(lexicalPath)
|
|
240
|
+
let canonicalPath: string
|
|
241
|
+
try {
|
|
242
|
+
canonicalPath = await realpath(lexicalPath)
|
|
243
|
+
this.assertConfined(canonicalPath)
|
|
244
|
+
const metadata = await stat(canonicalPath)
|
|
245
|
+
if (!metadata.isFile()) {
|
|
246
|
+
throw new WorkspaceBoundaryError(
|
|
247
|
+
"SOURCE_NOT_FILE",
|
|
248
|
+
"Input must be a regular file.",
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
!Number.isSafeInteger(maximumBytes) ||
|
|
253
|
+
maximumBytes < 1 ||
|
|
254
|
+
metadata.size > maximumBytes
|
|
255
|
+
) {
|
|
256
|
+
throw new WorkspaceBoundaryError(
|
|
257
|
+
"SOURCE_TOO_LARGE",
|
|
258
|
+
`Input exceeds the ${maximumBytes}-byte limit.`,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error instanceof WorkspaceBoundaryError) throw error
|
|
263
|
+
if (filesystemCode(error) === "ENOENT") {
|
|
264
|
+
throw new WorkspaceBoundaryError(
|
|
265
|
+
"SOURCE_NOT_FOUND",
|
|
266
|
+
"Input does not exist.",
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
throw new WorkspaceBoundaryError(
|
|
270
|
+
"FILESYSTEM_ERROR",
|
|
271
|
+
"Input could not be resolved.",
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
absolutePath: canonicalPath,
|
|
276
|
+
relativePath: this.toRelativePath(canonicalPath),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async prepareOutputFile(value: string): Promise<WorkspaceFile> {
|
|
281
|
+
const normalized = normalizeRelativePath(value, { allowRoot: false })
|
|
282
|
+
const fileName = basename(normalized.native)
|
|
283
|
+
if (fileName === "." || fileName === ".." || fileName.length === 0) {
|
|
284
|
+
throw new WorkspaceBoundaryError(
|
|
285
|
+
"INVALID_PATH",
|
|
286
|
+
"Output path must identify a file below the root.",
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
const directory = await this.prepareOutputDirectory(
|
|
290
|
+
dirname(normalized.native),
|
|
291
|
+
)
|
|
292
|
+
const absolutePath = join(directory.absolutePath, fileName)
|
|
293
|
+
this.assertConfined(absolutePath)
|
|
294
|
+
return {
|
|
295
|
+
absolutePath,
|
|
296
|
+
relativePath: this.toRelativePath(absolutePath),
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async prepareOutputDirectory(value: string): Promise<WorkspaceDirectory> {
|
|
301
|
+
const normalized = normalizeRelativePath(value, { allowRoot: true })
|
|
302
|
+
const lexicalPath = resolve(this.rootDirectory, normalized.native)
|
|
303
|
+
this.assertConfined(lexicalPath)
|
|
304
|
+
|
|
305
|
+
// Resolve the nearest existing ancestor before mkdir so a symlink in the
|
|
306
|
+
// requested path cannot redirect writes outside the server root.
|
|
307
|
+
let ancestor = lexicalPath
|
|
308
|
+
for (;;) {
|
|
309
|
+
try {
|
|
310
|
+
const canonicalAncestor = await realpath(ancestor)
|
|
311
|
+
this.assertConfined(canonicalAncestor)
|
|
312
|
+
break
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error instanceof WorkspaceBoundaryError) throw error
|
|
315
|
+
if (filesystemCode(error) !== "ENOENT") {
|
|
316
|
+
throw new WorkspaceBoundaryError(
|
|
317
|
+
"FILESYSTEM_ERROR",
|
|
318
|
+
"Output directory could not be resolved.",
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
const parent = dirname(ancestor)
|
|
322
|
+
if (parent === ancestor) {
|
|
323
|
+
throw new WorkspaceBoundaryError(
|
|
324
|
+
"PATH_OUTSIDE_ROOT",
|
|
325
|
+
"Output directory resolves outside the MCP root.",
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
ancestor = parent
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
await mkdir(lexicalPath, { recursive: true })
|
|
334
|
+
const canonicalPath = await realpath(lexicalPath)
|
|
335
|
+
this.assertConfined(canonicalPath)
|
|
336
|
+
if (!(await stat(canonicalPath)).isDirectory()) {
|
|
337
|
+
throw new WorkspaceBoundaryError(
|
|
338
|
+
"OUTPUT_NOT_DIRECTORY",
|
|
339
|
+
"Output path must be a directory.",
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
absolutePath: canonicalPath,
|
|
344
|
+
relativePath: this.toRelativePath(canonicalPath),
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
if (error instanceof WorkspaceBoundaryError) throw error
|
|
348
|
+
throw new WorkspaceBoundaryError(
|
|
349
|
+
"FILESYSTEM_ERROR",
|
|
350
|
+
"Output directory could not be created.",
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export {
|
|
2
|
+
slopcameraMcpProtocolVersion,
|
|
3
|
+
slopcameraMcpServerName,
|
|
4
|
+
runMcpServer,
|
|
5
|
+
} from "./server.js"
|
|
6
|
+
export {
|
|
7
|
+
slopcameraMcpTools,
|
|
8
|
+
SlopcameraMcpToolRuntime,
|
|
9
|
+
mcpMaximumEdges,
|
|
10
|
+
mcpMaximumRenderedPixels,
|
|
11
|
+
mcpMaximumReturnedFindings,
|
|
12
|
+
mcpMaximumScale,
|
|
13
|
+
mcpMaximumShapes,
|
|
14
|
+
} from "./tools.js"
|
|
15
|
+
export {
|
|
16
|
+
mcpSourceByteLimit,
|
|
17
|
+
WorkspaceBoundary,
|
|
18
|
+
WorkspaceBoundaryError,
|
|
19
|
+
type WorkspaceDirectory,
|
|
20
|
+
type WorkspaceFile,
|
|
21
|
+
type WorkspaceSource,
|
|
22
|
+
} from "./boundary.js"
|
|
23
|
+
export type {
|
|
24
|
+
JsonRpcFailure,
|
|
25
|
+
JsonRpcId,
|
|
26
|
+
JsonRpcResponseId,
|
|
27
|
+
JsonRpcResponse,
|
|
28
|
+
JsonRpcSuccess,
|
|
29
|
+
McpServerOptions,
|
|
30
|
+
McpTextContent,
|
|
31
|
+
McpToolDefinition,
|
|
32
|
+
McpToolResult,
|
|
33
|
+
} from "./types.js"
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
slopcameraMcpTools,
|
|
3
|
+
SlopcameraMcpToolRuntime,
|
|
4
|
+
} from "./tools.js"
|
|
5
|
+
import type {
|
|
6
|
+
JsonRpcId,
|
|
7
|
+
JsonRpcResponse,
|
|
8
|
+
JsonRpcResponseId,
|
|
9
|
+
McpServerOptions,
|
|
10
|
+
} from "./types.js"
|
|
11
|
+
import { SLOPCAMERA_VERSION } from "../version.js"
|
|
12
|
+
|
|
13
|
+
export const slopcameraMcpProtocolVersion = "2025-11-25"
|
|
14
|
+
export const slopcameraMcpServerName = "hraness-slopcamera"
|
|
15
|
+
|
|
16
|
+
const maximumMessageBytes = 1024 * 1024
|
|
17
|
+
|
|
18
|
+
interface RequestRecord {
|
|
19
|
+
readonly jsonrpc: "2.0"
|
|
20
|
+
readonly id?: JsonRpcId
|
|
21
|
+
readonly method: string
|
|
22
|
+
readonly params?: unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type LifecycleState = "new" | "initializing" | "ready"
|
|
26
|
+
|
|
27
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
28
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isJsonRpcId(value: unknown): value is JsonRpcId {
|
|
32
|
+
return (
|
|
33
|
+
typeof value === "string" ||
|
|
34
|
+
(typeof value === "number" && Number.isSafeInteger(value))
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isInitializeParams(value: unknown): value is Readonly<{
|
|
39
|
+
protocolVersion: string
|
|
40
|
+
capabilities: Readonly<Record<string, unknown>>
|
|
41
|
+
clientInfo: Readonly<{ name: string; version: string }>
|
|
42
|
+
}> {
|
|
43
|
+
return (
|
|
44
|
+
isRecord(value) &&
|
|
45
|
+
typeof value.protocolVersion === "string" &&
|
|
46
|
+
isRecord(value.capabilities) &&
|
|
47
|
+
isRecord(value.clientInfo) &&
|
|
48
|
+
typeof value.clientInfo.name === "string" &&
|
|
49
|
+
typeof value.clientInfo.version === "string"
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseRequest(value: unknown): RequestRecord {
|
|
54
|
+
if (
|
|
55
|
+
!isRecord(value) ||
|
|
56
|
+
value.jsonrpc !== "2.0" ||
|
|
57
|
+
typeof value.method !== "string" ||
|
|
58
|
+
value.method.length === 0 ||
|
|
59
|
+
("id" in value && !isJsonRpcId(value.id))
|
|
60
|
+
) {
|
|
61
|
+
throw new Error("invalid request")
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
jsonrpc: "2.0",
|
|
65
|
+
...("id" in value ? { id: value.id as JsonRpcId } : {}),
|
|
66
|
+
method: value.method,
|
|
67
|
+
...("params" in value ? { params: value.params } : {}),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function success(id: JsonRpcId, result: unknown): JsonRpcResponse {
|
|
72
|
+
return { jsonrpc: "2.0", id, result }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function failure(
|
|
76
|
+
id: JsonRpcResponseId,
|
|
77
|
+
code: number,
|
|
78
|
+
message: string,
|
|
79
|
+
): JsonRpcResponse {
|
|
80
|
+
return { jsonrpc: "2.0", id, error: { code, message } }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseToolCall(
|
|
84
|
+
params: unknown,
|
|
85
|
+
): { readonly name: string; readonly argumentsValue: unknown } {
|
|
86
|
+
if (
|
|
87
|
+
!isRecord(params) ||
|
|
88
|
+
typeof params.name !== "string" ||
|
|
89
|
+
(params.arguments !== undefined && !isRecord(params.arguments))
|
|
90
|
+
) {
|
|
91
|
+
throw new Error("invalid params")
|
|
92
|
+
}
|
|
93
|
+
const unknownKeys = Object.keys(params).filter(
|
|
94
|
+
(key) => key !== "name" && key !== "arguments",
|
|
95
|
+
)
|
|
96
|
+
if (unknownKeys.length > 0) throw new Error("invalid params")
|
|
97
|
+
return {
|
|
98
|
+
name: params.name,
|
|
99
|
+
argumentsValue: params.arguments ?? {},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class SlopcameraMcpSession {
|
|
104
|
+
readonly runtime: SlopcameraMcpToolRuntime
|
|
105
|
+
readonly serverVersion: string
|
|
106
|
+
state: LifecycleState = "new"
|
|
107
|
+
|
|
108
|
+
constructor(runtime: SlopcameraMcpToolRuntime, serverVersion: string) {
|
|
109
|
+
this.runtime = runtime
|
|
110
|
+
this.serverVersion = serverVersion
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async handle(value: unknown): Promise<JsonRpcResponse | null> {
|
|
114
|
+
let request: RequestRecord
|
|
115
|
+
try {
|
|
116
|
+
request = parseRequest(value)
|
|
117
|
+
} catch {
|
|
118
|
+
return failure(null, -32600, "Invalid Request")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const notification = request.id === undefined
|
|
122
|
+
if (request.method === "notifications/initialized") {
|
|
123
|
+
if (!notification) {
|
|
124
|
+
return failure(request.id!, -32600, "Invalid Request")
|
|
125
|
+
}
|
|
126
|
+
if (this.state === "initializing") this.state = "ready"
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
if (notification) return null
|
|
130
|
+
const id = request.id!
|
|
131
|
+
|
|
132
|
+
if (request.method === "initialize") {
|
|
133
|
+
if (this.state !== "new" || !isInitializeParams(request.params)) {
|
|
134
|
+
return failure(id, -32602, "Invalid initialize parameters")
|
|
135
|
+
}
|
|
136
|
+
this.state = "initializing"
|
|
137
|
+
return success(id, {
|
|
138
|
+
protocolVersion: slopcameraMcpProtocolVersion,
|
|
139
|
+
capabilities: {
|
|
140
|
+
tools: { listChanged: false },
|
|
141
|
+
},
|
|
142
|
+
serverInfo: {
|
|
143
|
+
name: slopcameraMcpServerName,
|
|
144
|
+
version: this.serverVersion,
|
|
145
|
+
},
|
|
146
|
+
instructions:
|
|
147
|
+
"Use check_diagram/render_diagram or search_slopcamera followed by execute_slopcamera with an exact registry code and typed JSON. Local paths are root-relative; source code is never accepted or evaluated.",
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.state !== "ready") {
|
|
152
|
+
return failure(id, -32002, "Server is not initialized")
|
|
153
|
+
}
|
|
154
|
+
if (request.method === "ping") return success(id, {})
|
|
155
|
+
if (request.method === "tools/list") {
|
|
156
|
+
if (
|
|
157
|
+
request.params !== undefined &&
|
|
158
|
+
(!isRecord(request.params) || Object.keys(request.params).length > 0)
|
|
159
|
+
) {
|
|
160
|
+
return failure(id, -32602, "Invalid tools/list parameters")
|
|
161
|
+
}
|
|
162
|
+
return success(id, { tools: slopcameraMcpTools })
|
|
163
|
+
}
|
|
164
|
+
if (request.method === "tools/call") {
|
|
165
|
+
try {
|
|
166
|
+
const toolCall = parseToolCall(request.params)
|
|
167
|
+
if (!slopcameraMcpTools.some((tool) => tool.name === toolCall.name)) {
|
|
168
|
+
return failure(id, -32602, "Unknown tool")
|
|
169
|
+
}
|
|
170
|
+
return success(
|
|
171
|
+
id,
|
|
172
|
+
await this.runtime.call(toolCall.name, toolCall.argumentsValue),
|
|
173
|
+
)
|
|
174
|
+
} catch {
|
|
175
|
+
return failure(id, -32602, "Invalid tools/call parameters")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return failure(id, -32601, "Method not found")
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function defaultWriteLine(line: string): Promise<void> {
|
|
183
|
+
await new Promise<void>((resolve, reject) => {
|
|
184
|
+
process.stdout.write(`${line}\n`, (error) => {
|
|
185
|
+
if (error === null || error === undefined) resolve()
|
|
186
|
+
else reject(error)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function defaultInput(): AsyncIterable<Uint8Array> {
|
|
192
|
+
return process.stdin
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function emitResponse(
|
|
196
|
+
writeLine: (line: string) => void | Promise<void>,
|
|
197
|
+
response: JsonRpcResponse,
|
|
198
|
+
): Promise<void> {
|
|
199
|
+
await writeLine(JSON.stringify(response))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function processLine(
|
|
203
|
+
line: Uint8Array,
|
|
204
|
+
session: SlopcameraMcpSession,
|
|
205
|
+
writeLine: (line: string) => void | Promise<void>,
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
if (line.byteLength === 0) return
|
|
208
|
+
let value: unknown
|
|
209
|
+
try {
|
|
210
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(line)
|
|
211
|
+
if (text.trim() === "") return
|
|
212
|
+
value = JSON.parse(text)
|
|
213
|
+
} catch {
|
|
214
|
+
await emitResponse(writeLine, failure(null, -32700, "Parse error"))
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
await emitResponse(writeLine, failure(null, -32600, "Invalid Request"))
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const response = await session.handle(value)
|
|
222
|
+
if (response !== null) await emitResponse(writeLine, response)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Run one newline-delimited JSON-RPC MCP session. Protocol output is the only
|
|
227
|
+
* stdout surface; callers that need logs must write them to stderr.
|
|
228
|
+
*/
|
|
229
|
+
export async function runMcpServer(
|
|
230
|
+
options: McpServerOptions = {},
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
const runtime = await SlopcameraMcpToolRuntime.create(
|
|
233
|
+
options.rootDirectory ?? process.cwd(),
|
|
234
|
+
options.generateDependencies,
|
|
235
|
+
)
|
|
236
|
+
const session = new SlopcameraMcpSession(
|
|
237
|
+
runtime,
|
|
238
|
+
options.serverVersion ?? SLOPCAMERA_VERSION,
|
|
239
|
+
)
|
|
240
|
+
const writeLine = options.writeLine ?? defaultWriteLine
|
|
241
|
+
let buffered = Buffer.alloc(0)
|
|
242
|
+
|
|
243
|
+
for await (const chunk of options.input ?? defaultInput()) {
|
|
244
|
+
const bytes =
|
|
245
|
+
typeof chunk === "string" ? Buffer.from(chunk, "utf8") : Buffer.from(chunk)
|
|
246
|
+
buffered = Buffer.concat([buffered, bytes])
|
|
247
|
+
if (buffered.byteLength > maximumMessageBytes && !buffered.includes(0x0a)) {
|
|
248
|
+
buffered = Buffer.alloc(0)
|
|
249
|
+
await emitResponse(writeLine, failure(null, -32700, "Parse error"))
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
for (;;) {
|
|
253
|
+
const newline = buffered.indexOf(0x0a)
|
|
254
|
+
if (newline === -1) break
|
|
255
|
+
let line = buffered.subarray(0, newline)
|
|
256
|
+
buffered = buffered.subarray(newline + 1)
|
|
257
|
+
if (line.at(-1) === 0x0d) line = line.subarray(0, -1)
|
|
258
|
+
if (line.byteLength > maximumMessageBytes) {
|
|
259
|
+
await emitResponse(writeLine, failure(null, -32700, "Parse error"))
|
|
260
|
+
} else {
|
|
261
|
+
await processLine(line, session, writeLine)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (buffered.byteLength > 0) {
|
|
266
|
+
if (buffered.byteLength > maximumMessageBytes) {
|
|
267
|
+
await emitResponse(writeLine, failure(null, -32700, "Parse error"))
|
|
268
|
+
} else {
|
|
269
|
+
await processLine(buffered, session, writeLine)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|