@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,177 @@
|
|
|
1
|
+
import { CliError } from "./errors";
|
|
2
|
+
|
|
3
|
+
export const MAX_ANIMATED_OVERLAY_VIDEO_LOOP_BYTES = 512 * 1024 * 1024;
|
|
4
|
+
export const MAX_ANIMATED_OVERLAY_AUDIO_LOOP_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const RGBA_BYTES_PER_PIXEL = 4;
|
|
6
|
+
const FLOAT32_STEREO_BYTES_PER_SAMPLE = 8;
|
|
7
|
+
const VIDEO_ROW_ALIGNMENT_BYTES = 64;
|
|
8
|
+
|
|
9
|
+
export interface AudibleSourceRangeOptions {
|
|
10
|
+
readonly audioDelayUs: number;
|
|
11
|
+
readonly audioEndUs: number;
|
|
12
|
+
readonly endBehavior: "freeze-end" | "hide" | "loop";
|
|
13
|
+
readonly requestedDurationUs: number;
|
|
14
|
+
readonly requestedStartUs: number;
|
|
15
|
+
readonly sourceWindowUs: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AudibleSourceRange {
|
|
19
|
+
readonly endUs: number;
|
|
20
|
+
readonly startUs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveAudibleSourceRange(options: AudibleSourceRangeOptions): AudibleSourceRange | null {
|
|
24
|
+
const {
|
|
25
|
+
audioDelayUs,
|
|
26
|
+
audioEndUs,
|
|
27
|
+
endBehavior,
|
|
28
|
+
requestedDurationUs,
|
|
29
|
+
requestedStartUs,
|
|
30
|
+
sourceWindowUs,
|
|
31
|
+
} = options;
|
|
32
|
+
const requestedEndUs = requestedStartUs + requestedDurationUs;
|
|
33
|
+
if (
|
|
34
|
+
!Number.isSafeInteger(audioDelayUs)
|
|
35
|
+
|| !Number.isSafeInteger(audioEndUs)
|
|
36
|
+
|| !Number.isSafeInteger(requestedDurationUs)
|
|
37
|
+
|| !Number.isSafeInteger(requestedStartUs)
|
|
38
|
+
|| !Number.isSafeInteger(requestedEndUs)
|
|
39
|
+
|| !Number.isSafeInteger(sourceWindowUs)
|
|
40
|
+
|| audioDelayUs < 0
|
|
41
|
+
|| audioEndUs < 0
|
|
42
|
+
|| requestedDurationUs <= 0
|
|
43
|
+
|| requestedStartUs < 0
|
|
44
|
+
|| sourceWindowUs <= 0
|
|
45
|
+
|| audioEndUs > sourceWindowUs
|
|
46
|
+
) {
|
|
47
|
+
throw new CliError("unsupported-plan", "Animated overlay audio has an unsupported playback window.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (audioEndUs <= audioDelayUs) return null;
|
|
51
|
+
if (endBehavior !== "loop") {
|
|
52
|
+
const startUs = Math.max(requestedStartUs, audioDelayUs);
|
|
53
|
+
const endUs = Math.min(requestedEndUs, audioEndUs);
|
|
54
|
+
return endUs > startUs ? { endUs, startUs } : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const positionInFirstCycleUs = requestedStartUs % sourceWindowUs;
|
|
58
|
+
const firstAudibleUs = positionInFirstCycleUs < audioDelayUs
|
|
59
|
+
? requestedStartUs + audioDelayUs - positionInFirstCycleUs
|
|
60
|
+
: positionInFirstCycleUs < audioEndUs
|
|
61
|
+
? requestedStartUs
|
|
62
|
+
: requestedStartUs + sourceWindowUs - positionInFirstCycleUs + audioDelayUs;
|
|
63
|
+
const endPositionUs = requestedEndUs % sourceWindowUs;
|
|
64
|
+
const lastAudibleUs = endPositionUs === 0
|
|
65
|
+
? requestedEndUs - sourceWindowUs + audioEndUs
|
|
66
|
+
: endPositionUs <= audioDelayUs
|
|
67
|
+
? requestedEndUs - endPositionUs - sourceWindowUs + audioEndUs
|
|
68
|
+
: endPositionUs <= audioEndUs
|
|
69
|
+
? requestedEndUs
|
|
70
|
+
: requestedEndUs - endPositionUs + audioEndUs;
|
|
71
|
+
return lastAudibleUs > firstAudibleUs
|
|
72
|
+
? { endUs: lastAudibleUs, startUs: firstAudibleUs }
|
|
73
|
+
: null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function assertVideoLoopBufferWithinLimit(options: {
|
|
77
|
+
readonly frameCount: number;
|
|
78
|
+
readonly label: string;
|
|
79
|
+
readonly pixelHeight: number;
|
|
80
|
+
readonly pixelWidth: number;
|
|
81
|
+
}): void {
|
|
82
|
+
const { frameCount, label, pixelHeight, pixelWidth } = options;
|
|
83
|
+
const rowBytes = Math.ceil(pixelWidth * RGBA_BYTES_PER_PIXEL / VIDEO_ROW_ALIGNMENT_BYTES)
|
|
84
|
+
* VIDEO_ROW_ALIGNMENT_BYTES;
|
|
85
|
+
const frameBytes = rowBytes * pixelHeight;
|
|
86
|
+
if (
|
|
87
|
+
!Number.isSafeInteger(frameCount)
|
|
88
|
+
|| frameCount <= 0
|
|
89
|
+
|| !Number.isSafeInteger(pixelHeight)
|
|
90
|
+
|| pixelHeight <= 0
|
|
91
|
+
|| !Number.isSafeInteger(pixelWidth)
|
|
92
|
+
|| pixelWidth <= 0
|
|
93
|
+
|| !Number.isSafeInteger(frameBytes)
|
|
94
|
+
|| frameBytes <= 0
|
|
95
|
+
|| frameCount > Math.floor(MAX_ANIMATED_OVERLAY_VIDEO_LOOP_BYTES / frameBytes)
|
|
96
|
+
) {
|
|
97
|
+
throw new CliError(
|
|
98
|
+
"unsupported-plan",
|
|
99
|
+
`${label} exceeds the ${MAX_ANIMATED_OVERLAY_VIDEO_LOOP_BYTES}-byte decoded video loop buffer bound.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function assertAudioLoopBufferWithinLimit(options: {
|
|
105
|
+
readonly label: string;
|
|
106
|
+
readonly sampleCount: number;
|
|
107
|
+
}): void {
|
|
108
|
+
const { label, sampleCount } = options;
|
|
109
|
+
if (
|
|
110
|
+
!Number.isSafeInteger(sampleCount)
|
|
111
|
+
|| sampleCount <= 0
|
|
112
|
+
|| sampleCount > Math.floor(
|
|
113
|
+
MAX_ANIMATED_OVERLAY_AUDIO_LOOP_BYTES / FLOAT32_STEREO_BYTES_PER_SAMPLE,
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
throw new CliError(
|
|
117
|
+
"unsupported-plan",
|
|
118
|
+
`${label} exceeds the ${MAX_ANIMATED_OVERLAY_AUDIO_LOOP_BYTES}-byte decoded audio loop buffer bound.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface AnimatedPlaybackWindowOptions {
|
|
124
|
+
readonly mediaDurationUs: number;
|
|
125
|
+
readonly outputDurationUs: number;
|
|
126
|
+
readonly playbackRate: number;
|
|
127
|
+
readonly sourceInUs: number;
|
|
128
|
+
readonly sourceOutUs: number | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface AnimatedPlaybackWindow {
|
|
132
|
+
readonly sourceInUs: number;
|
|
133
|
+
readonly sourceOutUs: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveAnimatedPlaybackWindow(
|
|
137
|
+
options: AnimatedPlaybackWindowOptions,
|
|
138
|
+
): AnimatedPlaybackWindow {
|
|
139
|
+
const {
|
|
140
|
+
mediaDurationUs,
|
|
141
|
+
outputDurationUs,
|
|
142
|
+
playbackRate,
|
|
143
|
+
sourceInUs,
|
|
144
|
+
sourceOutUs: explicitSourceOutUs,
|
|
145
|
+
} = options;
|
|
146
|
+
if (!Number.isSafeInteger(mediaDurationUs) || mediaDurationUs <= 0) {
|
|
147
|
+
throw new CliError("invalid-data", "Animated overlay media has no positive, safe duration.");
|
|
148
|
+
}
|
|
149
|
+
if (!Number.isSafeInteger(sourceInUs) || sourceInUs < 0 || sourceInUs >= mediaDurationUs) {
|
|
150
|
+
throw new CliError(
|
|
151
|
+
"usage",
|
|
152
|
+
`Animated overlay --source-in must be before the media duration (${mediaDurationUs}us).`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isSafeInteger(outputDurationUs) || outputDurationUs <= 0) {
|
|
156
|
+
throw new CliError("usage", "Animated overlay output duration must be positive.");
|
|
157
|
+
}
|
|
158
|
+
if (!Number.isFinite(playbackRate) || playbackRate <= 0 || playbackRate > 64) {
|
|
159
|
+
throw new CliError("usage", "Animated overlay --playback-rate must be greater than 0 and at most 64.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// An omitted source-out means "through the immutable asset", not "for the
|
|
163
|
+
// edit's current mapped duration". The renderer still trims to the output
|
|
164
|
+
// range, while a later speed edit can reveal more source media without
|
|
165
|
+
// silently turning a hide into an early hide/loop/freeze.
|
|
166
|
+
const sourceOutUs = explicitSourceOutUs ?? mediaDurationUs;
|
|
167
|
+
if (!Number.isSafeInteger(sourceOutUs) || sourceOutUs <= sourceInUs) {
|
|
168
|
+
throw new CliError("usage", "Animated overlay --source-out must be after --source-in.");
|
|
169
|
+
}
|
|
170
|
+
if (sourceOutUs > mediaDurationUs) {
|
|
171
|
+
throw new CliError(
|
|
172
|
+
"usage",
|
|
173
|
+
`Animated overlay --source-out exceeds the media duration (${mediaDurationUs}us).`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return { sourceInUs, sourceOutUs };
|
|
177
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { chmod, lstat, mkdir, realpath } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { CliError } from "./errors";
|
|
5
|
+
|
|
6
|
+
async function isDirectory(path: string): Promise<boolean> {
|
|
7
|
+
try {
|
|
8
|
+
return (await lstat(path)).isDirectory();
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function isFile(path: string): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
return (await lstat(path)).isFile();
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function discoverRepositoryRoot(start: string): Promise<string> {
|
|
23
|
+
let candidate = resolve(start);
|
|
24
|
+
while (true) {
|
|
25
|
+
if (
|
|
26
|
+
await isFile(join(candidate, "package.json"))
|
|
27
|
+
&& await isDirectory(join(candidate, "apps", "desktop"))
|
|
28
|
+
) {
|
|
29
|
+
return await realpath(candidate);
|
|
30
|
+
}
|
|
31
|
+
const parent = dirname(candidate);
|
|
32
|
+
if (parent === candidate) break;
|
|
33
|
+
candidate = parent;
|
|
34
|
+
}
|
|
35
|
+
throw new CliError(
|
|
36
|
+
"not-found",
|
|
37
|
+
`Could not find a Slopcamera checkout from ${resolve(start)} (expected package.json and apps/desktop).`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function defaultArtifactRoot(repositoryRoot: string): string {
|
|
42
|
+
return join(repositoryRoot, "artifacts", "slopcamera", "recordings");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function defaultProjectRoot(repositoryRoot: string): string {
|
|
46
|
+
return join(repositoryRoot, "artifacts", "slopcamera", "projects");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function defaultPrivateRoot(repositoryRoot: string): string {
|
|
50
|
+
return join(repositoryRoot, "artifacts", "slopcamera", "private");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Shared local state coordinates resource admission across checkouts. */
|
|
54
|
+
export function defaultCliStateRoot(
|
|
55
|
+
platform: NodeJS.Platform,
|
|
56
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
57
|
+
): string {
|
|
58
|
+
const userHome = homedir();
|
|
59
|
+
if (platform === "darwin") {
|
|
60
|
+
return join(userHome, "Library", "Application Support", "Slopcamera", "cli");
|
|
61
|
+
}
|
|
62
|
+
if (platform === "win32") {
|
|
63
|
+
const localAppData = env.LOCALAPPDATA;
|
|
64
|
+
return join(
|
|
65
|
+
localAppData !== undefined && isAbsolute(localAppData)
|
|
66
|
+
? localAppData
|
|
67
|
+
: join(userHome, "AppData", "Local"),
|
|
68
|
+
"Slopcamera",
|
|
69
|
+
"cli",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const stateHome = env.XDG_STATE_HOME;
|
|
73
|
+
return join(
|
|
74
|
+
stateHome !== undefined && isAbsolute(stateHome)
|
|
75
|
+
? stateHome
|
|
76
|
+
: join(userHome, ".local", "state"),
|
|
77
|
+
"slopcamera",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function displayPath(repositoryRoot: string, path: string): string {
|
|
82
|
+
const pathRelative = relative(repositoryRoot, path);
|
|
83
|
+
if (pathRelative === "") return ".";
|
|
84
|
+
if (!pathRelative.startsWith("..") && !isAbsolute(pathRelative)) return pathRelative;
|
|
85
|
+
return path;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function ensurePrivateDirectory(path: string): Promise<void> {
|
|
89
|
+
try {
|
|
90
|
+
const details = await lstat(path);
|
|
91
|
+
if (details.isSymbolicLink() || !details.isDirectory()) {
|
|
92
|
+
throw new CliError("unsafe-path", `Private directory must be a real directory, not a symlink or file: ${path}`);
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof CliError) throw error;
|
|
96
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
97
|
+
await mkdir(path, { mode: 0o700, recursive: true });
|
|
98
|
+
const details = await lstat(path);
|
|
99
|
+
if (details.isSymbolicLink() || !details.isDirectory()) {
|
|
100
|
+
throw new CliError("unsafe-path", `Private directory creation was redirected: ${path}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await chmod(path, 0o700);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Create a private directory by walking physical components below an existing physical root. */
|
|
107
|
+
export async function ensurePhysicalPrivateDirectoryWithin(
|
|
108
|
+
root: string,
|
|
109
|
+
requested: string,
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
if (requested.trim() === "" || isAbsolute(requested)) {
|
|
112
|
+
throw new CliError("unsafe-path", `Private directory must be relative to its root: ${requested}`);
|
|
113
|
+
}
|
|
114
|
+
const lexicalRoot = resolve(root);
|
|
115
|
+
const rootDetails = await lstat(lexicalRoot);
|
|
116
|
+
if (rootDetails.isSymbolicLink() || !rootDetails.isDirectory()) {
|
|
117
|
+
throw new CliError("unsafe-path", `Private directory root must be a physical directory: ${root}`);
|
|
118
|
+
}
|
|
119
|
+
const physicalRoot = await realpath(lexicalRoot);
|
|
120
|
+
const target = resolve(physicalRoot, requested);
|
|
121
|
+
if (!isWithin(physicalRoot, target) || target === physicalRoot) {
|
|
122
|
+
throw new CliError("unsafe-path", `Private directory escapes its root: ${requested}`);
|
|
123
|
+
}
|
|
124
|
+
let current = physicalRoot;
|
|
125
|
+
for (const part of relative(physicalRoot, target).split(sep)) {
|
|
126
|
+
current = join(current, part);
|
|
127
|
+
try {
|
|
128
|
+
const details = await lstat(current);
|
|
129
|
+
if (details.isSymbolicLink() || !details.isDirectory()) {
|
|
130
|
+
throw new CliError("unsafe-path", `Private directory requires physical components: ${requested}`);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof CliError) throw error;
|
|
134
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
135
|
+
try {
|
|
136
|
+
await mkdir(current, { mode: 0o700 });
|
|
137
|
+
} catch (mkdirError) {
|
|
138
|
+
if (!(mkdirError instanceof Error && "code" in mkdirError && mkdirError.code === "EEXIST")) {
|
|
139
|
+
throw mkdirError;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const created = await lstat(current);
|
|
143
|
+
if (created.isSymbolicLink() || !created.isDirectory()) {
|
|
144
|
+
throw new CliError("unsafe-path", `Private directory creation was redirected: ${requested}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isWithin(root: string, candidate: string): boolean {
|
|
152
|
+
const pathRelative = relative(root, candidate);
|
|
153
|
+
return pathRelative === "" || (!pathRelative.startsWith("..") && !isAbsolute(pathRelative));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function nearestExistingPath(path: string): Promise<string> {
|
|
157
|
+
let candidate = path;
|
|
158
|
+
while (true) {
|
|
159
|
+
try {
|
|
160
|
+
await lstat(candidate);
|
|
161
|
+
return candidate;
|
|
162
|
+
} catch {
|
|
163
|
+
const parent = dirname(candidate);
|
|
164
|
+
if (parent === candidate) return candidate;
|
|
165
|
+
candidate = parent;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function resolveSafePath(root: string, requested: string): Promise<string> {
|
|
171
|
+
if (requested.trim() === "") {
|
|
172
|
+
throw new CliError("usage", "Output path cannot be empty.");
|
|
173
|
+
}
|
|
174
|
+
const absoluteRoot = resolve(root);
|
|
175
|
+
const rootDetails = await lstat(absoluteRoot);
|
|
176
|
+
if (rootDetails.isSymbolicLink() || !rootDetails.isDirectory()) {
|
|
177
|
+
throw new CliError("unsafe-path", `Output root must be a physical directory: ${absoluteRoot}`);
|
|
178
|
+
}
|
|
179
|
+
const target = isAbsolute(requested) ? resolve(requested) : resolve(absoluteRoot, requested);
|
|
180
|
+
if (!isWithin(absoluteRoot, target)) {
|
|
181
|
+
throw new CliError("unsafe-path", `Output path escapes ${absoluteRoot}: ${requested}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const [realRoot, existing] = await Promise.all([realpath(absoluteRoot), nearestExistingPath(target)]);
|
|
185
|
+
const realExisting = await realpath(existing);
|
|
186
|
+
if (!isWithin(realRoot, realExisting)) {
|
|
187
|
+
throw new CliError("unsafe-path", `Output path crosses a symlink outside ${absoluteRoot}: ${requested}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const targetRealPath = await realpath(target);
|
|
192
|
+
if (!isWithin(realRoot, targetRealPath)) {
|
|
193
|
+
throw new CliError("unsafe-path", `Output path resolves outside ${absoluteRoot}: ${requested}`);
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof CliError) throw error;
|
|
197
|
+
// A missing leaf is safe because its nearest existing ancestor was checked.
|
|
198
|
+
}
|
|
199
|
+
return target;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface RepositoryPaths {
|
|
203
|
+
readonly artifactRoot: string;
|
|
204
|
+
readonly desktopRoot: string;
|
|
205
|
+
readonly privateRoot: string;
|
|
206
|
+
readonly projectRoot: string;
|
|
207
|
+
readonly repositoryRoot: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function resolveRepositoryPaths(
|
|
211
|
+
cwd: string,
|
|
212
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
213
|
+
installedFrom: string = import.meta.dir,
|
|
214
|
+
executable: string = process.execPath,
|
|
215
|
+
): Promise<RepositoryPaths> {
|
|
216
|
+
// A copied Bun executable embeds its source under a read-only virtual root.
|
|
217
|
+
// Optional native helpers may live beside that executable; state never does.
|
|
218
|
+
const desktopRoot = installedFrom.startsWith("/$bunfs/")
|
|
219
|
+
? dirname(await realpath(executable))
|
|
220
|
+
: join(await discoverRepositoryRoot(installedFrom), "apps", "desktop");
|
|
221
|
+
const repositoryRootInput = env.SLOPCAMERA_REPOSITORY_ROOT;
|
|
222
|
+
const requestedRoot = resolve(repositoryRootInput ?? cwd);
|
|
223
|
+
if (requestedRoot === "/$bunfs" || requestedRoot.startsWith("/$bunfs/")) {
|
|
224
|
+
throw new CliError("unsafe-path", "Slopcamera project state requires a physical caller workspace, not the embedded executable filesystem.");
|
|
225
|
+
}
|
|
226
|
+
if (!await isDirectory(requestedRoot)) {
|
|
227
|
+
throw new CliError("not-found", `Slopcamera project root is not a directory: ${requestedRoot}`);
|
|
228
|
+
}
|
|
229
|
+
const repositoryRoot = await realpath(requestedRoot);
|
|
230
|
+
const requiredArtifactRoot = defaultArtifactRoot(repositoryRoot);
|
|
231
|
+
const configuredArtifactRoot = env.SLOPCAMERA_ARTIFACT_ROOT;
|
|
232
|
+
if (
|
|
233
|
+
configuredArtifactRoot !== undefined
|
|
234
|
+
&& resolve(configuredArtifactRoot) !== resolve(requiredArtifactRoot)
|
|
235
|
+
) {
|
|
236
|
+
throw new CliError(
|
|
237
|
+
"unsafe-path",
|
|
238
|
+
`SLOPCAMERA_ARTIFACT_ROOT must remain ${requiredArtifactRoot}; external recording roots are forbidden.`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const artifactRoot = await ensurePhysicalPrivateDirectoryWithin(
|
|
242
|
+
repositoryRoot,
|
|
243
|
+
"artifacts/slopcamera/recordings",
|
|
244
|
+
);
|
|
245
|
+
const projectRoot = await ensurePhysicalPrivateDirectoryWithin(
|
|
246
|
+
repositoryRoot,
|
|
247
|
+
relative(repositoryRoot, defaultProjectRoot(repositoryRoot)),
|
|
248
|
+
);
|
|
249
|
+
const privateRoot = await ensurePhysicalPrivateDirectoryWithin(
|
|
250
|
+
repositoryRoot,
|
|
251
|
+
relative(repositoryRoot, defaultPrivateRoot(repositoryRoot)),
|
|
252
|
+
);
|
|
253
|
+
await Promise.all([
|
|
254
|
+
ensurePrivateDirectory(artifactRoot),
|
|
255
|
+
ensurePrivateDirectory(projectRoot),
|
|
256
|
+
ensurePrivateDirectory(privateRoot),
|
|
257
|
+
]);
|
|
258
|
+
return {
|
|
259
|
+
artifactRoot,
|
|
260
|
+
desktopRoot,
|
|
261
|
+
privateRoot,
|
|
262
|
+
projectRoot,
|
|
263
|
+
repositoryRoot,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { mkdir, open } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { canonicalJson } from "../core/canonical-json";
|
|
6
|
+
import {
|
|
7
|
+
HTML_OVERLAY_SCAFFOLD_KINDS,
|
|
8
|
+
HTML_OVERLAY_SCAFFOLD_PROFILES,
|
|
9
|
+
createHtmlOverlayScaffold,
|
|
10
|
+
getApprovedHtmlOverlayLibraryLock,
|
|
11
|
+
getHtmlOverlayScaffoldProfile,
|
|
12
|
+
type HtmlOverlayLibrarySpecifier,
|
|
13
|
+
type HtmlOverlayScaffoldKind,
|
|
14
|
+
} from "../html-overlay";
|
|
15
|
+
import { CliError } from "./errors";
|
|
16
|
+
|
|
17
|
+
const HEADLESS_SLOPCAMERA_CLI_MODULE = "@hraness/slopcamera/cli";
|
|
18
|
+
|
|
19
|
+
async function runHeadlessSlopcameraCli(argv: readonly string[]): Promise<void> {
|
|
20
|
+
// Keep this as a runtime package import so the headless CLI retains its own
|
|
21
|
+
// package-relative skill and asset resolution inside an installed bundle.
|
|
22
|
+
const module: unknown = await import(HEADLESS_SLOPCAMERA_CLI_MODULE);
|
|
23
|
+
if (
|
|
24
|
+
typeof module !== "object"
|
|
25
|
+
|| module === null
|
|
26
|
+
|| !("main" in module)
|
|
27
|
+
|| typeof module.main !== "function"
|
|
28
|
+
) {
|
|
29
|
+
throw new CliError("unavailable", "The portable Slopcamera CLI is unavailable.");
|
|
30
|
+
}
|
|
31
|
+
await module.main(argv);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PortableSurfaceDependencies {
|
|
35
|
+
readonly cwd?: () => string;
|
|
36
|
+
readonly log?: (value: string) => void;
|
|
37
|
+
readonly runHeadless?: (argv: readonly string[]) => Promise<void>;
|
|
38
|
+
readonly writeScaffold?: (path: string, html: string) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
function optionValue(argv: readonly string[], name: string): string | undefined {
|
|
41
|
+
const indexes = argv.flatMap((value, index) => value === name ? [index] : []);
|
|
42
|
+
if (indexes.length > 1) {
|
|
43
|
+
throw new CliError("usage", `${name} may be supplied at most once.`);
|
|
44
|
+
}
|
|
45
|
+
const index = indexes[0];
|
|
46
|
+
if (index === undefined) return undefined;
|
|
47
|
+
const value = argv[index + 1];
|
|
48
|
+
if (value === undefined || value.startsWith("--")) {
|
|
49
|
+
throw new CliError("usage", `${name} requires a value.`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `image generate --prompt ...` is the project/media spelling and stays on the
|
|
56
|
+
* desktop Gateway lane. The portable file command has an explicit --output
|
|
57
|
+
* and delegates to @hraness/slopcamera without duplicating its parser.
|
|
58
|
+
*/
|
|
59
|
+
export function canonicalizeUnifiedCliArgs(
|
|
60
|
+
argv: readonly string[],
|
|
61
|
+
): readonly string[] {
|
|
62
|
+
if (argv[0] !== "image" || argv[1] !== "generate") return argv;
|
|
63
|
+
const output = optionValue(argv, "--output");
|
|
64
|
+
const prompt = optionValue(argv, "--prompt");
|
|
65
|
+
if (output === undefined) {
|
|
66
|
+
return ["ai", "image", "generate", ...argv.slice(2)];
|
|
67
|
+
}
|
|
68
|
+
if (prompt === undefined) return argv;
|
|
69
|
+
const promptIndex = argv.indexOf("--prompt");
|
|
70
|
+
return [
|
|
71
|
+
"image",
|
|
72
|
+
"generate",
|
|
73
|
+
prompt,
|
|
74
|
+
...argv.slice(2, promptIndex),
|
|
75
|
+
...argv.slice(promptIndex + 2),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function scaffoldKind(input: string | undefined): HtmlOverlayScaffoldKind {
|
|
80
|
+
if (input !== undefined) {
|
|
81
|
+
try {
|
|
82
|
+
return getHtmlOverlayScaffoldProfile(input as HtmlOverlayScaffoldKind).kind;
|
|
83
|
+
} catch {
|
|
84
|
+
// Preserve one stable CLI usage error for every foreign scaffold kind.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new CliError(
|
|
88
|
+
"usage",
|
|
89
|
+
`HTML scaffold must be one of: ${HTML_OVERLAY_SCAFFOLD_KINDS.join(", ")}.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function catalogLibraries(
|
|
94
|
+
specifiers: readonly HtmlOverlayLibrarySpecifier[],
|
|
95
|
+
): readonly Readonly<{ specifier: string; version: string }>[] {
|
|
96
|
+
return specifiers.map(specifier => {
|
|
97
|
+
const lock = getApprovedHtmlOverlayLibraryLock(specifier);
|
|
98
|
+
return Object.freeze({
|
|
99
|
+
specifier: lock.specifier,
|
|
100
|
+
version: lock.version,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function htmlOverlayCatalogJson(): string {
|
|
106
|
+
return canonicalJson({
|
|
107
|
+
profiles: HTML_OVERLAY_SCAFFOLD_PROFILES.map(profile => ({
|
|
108
|
+
bestFor: profile.bestFor,
|
|
109
|
+
clockIntegration: profile.clockIntegration,
|
|
110
|
+
kind: profile.kind,
|
|
111
|
+
libraries: catalogLibraries(profile.libraries),
|
|
112
|
+
primaryJob: profile.primaryJob,
|
|
113
|
+
substrate: profile.substrate,
|
|
114
|
+
summary: profile.summary,
|
|
115
|
+
})),
|
|
116
|
+
schemaVersion: 1,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function htmlOverlayCatalogText(): string {
|
|
121
|
+
const rows = ["HTML overlay scaffold profiles:"];
|
|
122
|
+
for (const profile of HTML_OVERLAY_SCAFFOLD_PROFILES) {
|
|
123
|
+
const libraries = catalogLibraries(profile.libraries)
|
|
124
|
+
.map(library => `${library.specifier}@${library.version}`)
|
|
125
|
+
.join(", ") || "none";
|
|
126
|
+
rows.push(
|
|
127
|
+
`${profile.kind} job=${profile.primaryJob} substrate=${profile.substrate} libraries=${libraries}`,
|
|
128
|
+
` ${profile.summary} Best for: ${profile.bestFor}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return rows.join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function writeScaffoldWithoutReplacement(
|
|
135
|
+
path: string,
|
|
136
|
+
html: string,
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
await mkdir(dirname(path), { mode: 0o700, recursive: true });
|
|
139
|
+
let handle;
|
|
140
|
+
try {
|
|
141
|
+
handle = await open(
|
|
142
|
+
path,
|
|
143
|
+
constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_WRONLY,
|
|
144
|
+
0o600,
|
|
145
|
+
);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
148
|
+
throw new CliError("conflict", `Refusing to overwrite existing file: ${path}`);
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
await handle.writeFile(html, "utf8");
|
|
154
|
+
await handle.sync();
|
|
155
|
+
} finally {
|
|
156
|
+
await handle.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function runHtmlScaffold(
|
|
161
|
+
argv: readonly string[],
|
|
162
|
+
dependencies: PortableSurfaceDependencies,
|
|
163
|
+
): Promise<number> {
|
|
164
|
+
if (argv[1] !== "scaffold") {
|
|
165
|
+
throw new CliError("usage", "Use slopcamera html scaffold <kind> --output <file.html>.");
|
|
166
|
+
}
|
|
167
|
+
const kind = scaffoldKind(argv[2]);
|
|
168
|
+
const output = optionValue(argv, "--output");
|
|
169
|
+
if (output === undefined) {
|
|
170
|
+
throw new CliError("usage", "slopcamera html scaffold requires --output <file.html>.");
|
|
171
|
+
}
|
|
172
|
+
if (!output.toLowerCase().endsWith(".html")) {
|
|
173
|
+
throw new CliError("usage", "HTML scaffold output must end in .html.");
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
argv.length !== 5
|
|
177
|
+
|| argv[3] !== "--output"
|
|
178
|
+
) {
|
|
179
|
+
throw new CliError(
|
|
180
|
+
"usage",
|
|
181
|
+
"Use slopcamera html scaffold <kind> --output <file.html>.",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const outputPath = resolve((dependencies.cwd ?? process.cwd)(), output);
|
|
185
|
+
await (dependencies.writeScaffold ?? writeScaffoldWithoutReplacement)(
|
|
186
|
+
outputPath,
|
|
187
|
+
createHtmlOverlayScaffold(kind),
|
|
188
|
+
);
|
|
189
|
+
(dependencies.log ?? console.log)(`Created ${outputPath}`);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function runHtmlCatalog(
|
|
194
|
+
argv: readonly string[],
|
|
195
|
+
dependencies: PortableSurfaceDependencies,
|
|
196
|
+
): number {
|
|
197
|
+
const options = argv.slice(2);
|
|
198
|
+
if (
|
|
199
|
+
options.length > 1
|
|
200
|
+
|| (options.length === 1 && options[0] !== "--json")
|
|
201
|
+
) {
|
|
202
|
+
throw new CliError("usage", "Use slopcamera html catalog [--json].");
|
|
203
|
+
}
|
|
204
|
+
const output = options[0] === "--json"
|
|
205
|
+
? htmlOverlayCatalogJson()
|
|
206
|
+
: htmlOverlayCatalogText();
|
|
207
|
+
(dependencies.log ?? console.log)(output);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function runPortableSurface(
|
|
212
|
+
argvInput: readonly string[],
|
|
213
|
+
dependencies: PortableSurfaceDependencies = {},
|
|
214
|
+
): Promise<number | undefined> {
|
|
215
|
+
const argv = canonicalizeUnifiedCliArgs(argvInput);
|
|
216
|
+
if (argv[0] === "html") {
|
|
217
|
+
if (argv[1] === "catalog") return runHtmlCatalog(argv, dependencies);
|
|
218
|
+
return await runHtmlScaffold(argv, dependencies);
|
|
219
|
+
}
|
|
220
|
+
const delegatesToHeadless = argv[0] === "diagram"
|
|
221
|
+
|| argv[0] === "mcp"
|
|
222
|
+
|| argv[0] === "canvas"
|
|
223
|
+
|| argv[0] === "skill"
|
|
224
|
+
|| (
|
|
225
|
+
argv[0] === "code"
|
|
226
|
+
&& (argv[1] === "search" || argv[1] === "execute")
|
|
227
|
+
)
|
|
228
|
+
|| (
|
|
229
|
+
argv[0] === "image"
|
|
230
|
+
&& (argv[1] === "vectorize" || argv[1] === "generate")
|
|
231
|
+
);
|
|
232
|
+
if (!delegatesToHeadless) return undefined;
|
|
233
|
+
|
|
234
|
+
const previousExitCode = process.exitCode;
|
|
235
|
+
process.exitCode = undefined;
|
|
236
|
+
try {
|
|
237
|
+
await (dependencies.runHeadless ?? runHeadlessSlopcameraCli)(argv);
|
|
238
|
+
return process.exitCode ?? 0;
|
|
239
|
+
} finally {
|
|
240
|
+
process.exitCode = previousExitCode;
|
|
241
|
+
}
|
|
242
|
+
}
|