@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,210 @@
|
|
|
1
|
+
import { Cause, Context, Effect, Exit, Fiber, Ref } from "effect";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
operationFinally,
|
|
5
|
+
operationResource,
|
|
6
|
+
operationValidation,
|
|
7
|
+
type OperationEffectFailure,
|
|
8
|
+
} from "../application/operation-effects";
|
|
9
|
+
import { CliError, errorMessage } from "./errors";
|
|
10
|
+
import type { ProcessRunner, RunResult } from "./io";
|
|
11
|
+
|
|
12
|
+
export interface AtomicRenderOutput {
|
|
13
|
+
readonly bytes: number;
|
|
14
|
+
readonly sha256: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AtomicRenderRequest {
|
|
18
|
+
readonly abortSignal?: AbortSignal;
|
|
19
|
+
readonly argv: readonly [string, ...string[]];
|
|
20
|
+
readonly failureLabel: string;
|
|
21
|
+
readonly finalOutputPath: string;
|
|
22
|
+
readonly maximumOutputBytes: number;
|
|
23
|
+
readonly requireFreshOutput?: boolean;
|
|
24
|
+
readonly runner: ProcessRunner;
|
|
25
|
+
readonly stagingDirectory?: string;
|
|
26
|
+
readonly timeoutMs?: number;
|
|
27
|
+
/** Trusted adapter fence, after staging and immediately before native publication. */
|
|
28
|
+
readonly beforeNativePublish?: (stagedOutputPath: string) => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AtomicRenderStaging {
|
|
32
|
+
readonly argv: readonly [string, ...string[]];
|
|
33
|
+
readonly path: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AtomicRenderProcess {
|
|
37
|
+
/** Repeated observation joins one retained native Promise; it never dispatches again. */
|
|
38
|
+
readonly result: Effect.Effect<RunResult, OperationEffectFailure>;
|
|
39
|
+
abort(reason?: unknown): Effect.Effect<void, OperationEffectFailure>;
|
|
40
|
+
detach(): Effect.Effect<void, OperationEffectFailure>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AtomicRenderPlatformService {
|
|
44
|
+
stage(request: AtomicRenderRequest, companionPath?: string): Effect.Effect<AtomicRenderStaging, OperationEffectFailure>;
|
|
45
|
+
start(staging: AtomicRenderStaging, request: AtomicRenderRequest): Effect.Effect<AtomicRenderProcess, OperationEffectFailure>;
|
|
46
|
+
outputSize(staging: AtomicRenderStaging): Effect.Effect<number | null, OperationEffectFailure>;
|
|
47
|
+
verify(staging: AtomicRenderStaging, request: AtomicRenderRequest): Effect.Effect<AtomicRenderOutput, OperationEffectFailure>;
|
|
48
|
+
publish(staging: AtomicRenderStaging, request: AtomicRenderRequest): Effect.Effect<void, OperationEffectFailure>;
|
|
49
|
+
invalidateCompanion(path: string): Effect.Effect<void, OperationEffectFailure>;
|
|
50
|
+
cleanup(staging: AtomicRenderStaging): Effect.Effect<void, OperationEffectFailure>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class AtomicRenderPlatform extends Context.Tag("@slopcamera/local/AtomicRenderPlatform")<
|
|
54
|
+
AtomicRenderPlatform, AtomicRenderPlatformService
|
|
55
|
+
>() { }
|
|
56
|
+
|
|
57
|
+
export interface AtomicRenderPublication<Prepared, R> {
|
|
58
|
+
prepare(output: AtomicRenderOutput, stagedOutputPath: string): Effect.Effect<Prepared, OperationEffectFailure, R>;
|
|
59
|
+
readonly companion?: {
|
|
60
|
+
readonly finalPath: string;
|
|
61
|
+
publish(prepared: Prepared, output: AtomicRenderOutput): Effect.Effect<void, OperationEffectFailure, R>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const OUTPUT_GROWTH_POLL_MILLISECONDS = 10;
|
|
66
|
+
|
|
67
|
+
function monitorOutput(
|
|
68
|
+
platform: AtomicRenderPlatformService,
|
|
69
|
+
staging: AtomicRenderStaging,
|
|
70
|
+
request: AtomicRenderRequest,
|
|
71
|
+
process: AtomicRenderProcess,
|
|
72
|
+
outcome: Ref.Ref<Exit.Exit<void, OperationEffectFailure>>,
|
|
73
|
+
): Effect.Effect<void> {
|
|
74
|
+
const poll = Effect.uninterruptible(Effect.gen(function*() {
|
|
75
|
+
const size = yield* Effect.exit(platform.outputSize(staging));
|
|
76
|
+
let failure: OperationEffectFailure;
|
|
77
|
+
let abortReason: unknown;
|
|
78
|
+
if (Exit.isFailure(size)) {
|
|
79
|
+
const nativeFailure = Cause.failureOption(size.cause);
|
|
80
|
+
const cause = nativeFailure._tag === "Some" ? nativeFailure.value.cause : Cause.squash(size.cause);
|
|
81
|
+
failure = {
|
|
82
|
+
_tag: "OperationBoundaryFailure",
|
|
83
|
+
phase: "media",
|
|
84
|
+
cause: new CliError("unavailable", `Renderer output could not be monitored safely: ${errorMessage(cause)}`),
|
|
85
|
+
priorCause: size.cause,
|
|
86
|
+
};
|
|
87
|
+
abortReason = cause;
|
|
88
|
+
} else if (size.value !== null && size.value > request.maximumOutputBytes) {
|
|
89
|
+
failure = {
|
|
90
|
+
_tag: "OperationBoundaryFailure",
|
|
91
|
+
phase: "media",
|
|
92
|
+
cause: new CliError(
|
|
93
|
+
"invalid-data",
|
|
94
|
+
`Renderer output exceeded its configured ${String(request.maximumOutputBytes)}-byte limit while encoding (${String(size.value)} bytes observed).`,
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
abortReason = new CliError("invalid-data", "Renderer output exceeded its configured byte limit while encoding.");
|
|
98
|
+
} else {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const stopped = yield* Effect.exit(operationFinally(Effect.fail(failure), process.abort(abortReason)));
|
|
102
|
+
yield* Ref.set(outcome, stopped);
|
|
103
|
+
return true;
|
|
104
|
+
}));
|
|
105
|
+
return Effect.catchAllCause(Effect.gen(function*() {
|
|
106
|
+
while (!(yield* poll)) yield* Effect.sleep(OUTPUT_GROWTH_POLL_MILLISECONDS);
|
|
107
|
+
}), cause => Cause.isInterruptedOnly(cause) ? Effect.void : Effect.gen(function*() {
|
|
108
|
+
const failure: OperationEffectFailure = {
|
|
109
|
+
_tag: "OperationBoundaryFailure", phase: "media", cause: Cause.squash(cause),
|
|
110
|
+
};
|
|
111
|
+
const stopped = yield* Effect.exit(operationFinally(Effect.fail(failure), process.abort(failure.cause)));
|
|
112
|
+
yield* Ref.set(outcome, stopped);
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runMonitored(
|
|
117
|
+
platform: AtomicRenderPlatformService,
|
|
118
|
+
staging: AtomicRenderStaging,
|
|
119
|
+
request: AtomicRenderRequest,
|
|
120
|
+
): Effect.Effect<RunResult, OperationEffectFailure> {
|
|
121
|
+
return operationResource(
|
|
122
|
+
Effect.gen(function*() {
|
|
123
|
+
const settled = yield* Ref.make(false);
|
|
124
|
+
const process = yield* platform.start(staging, request);
|
|
125
|
+
return { process, settled };
|
|
126
|
+
}),
|
|
127
|
+
({ process, settled }) => Effect.scoped(Effect.uninterruptibleMask(restore => Effect.gen(function*() {
|
|
128
|
+
const monitorOutcome = yield* Ref.make<Exit.Exit<void, OperationEffectFailure>>(Exit.void);
|
|
129
|
+
const monitor = yield* Effect.forkScoped(Effect.interruptible(
|
|
130
|
+
monitorOutput(platform, staging, request, process, monitorOutcome),
|
|
131
|
+
));
|
|
132
|
+
const observed = yield* Effect.exit(restore(process.result));
|
|
133
|
+
let rendered = observed;
|
|
134
|
+
if (Exit.isFailure(observed) && Cause.isInterrupted(observed.cause)) {
|
|
135
|
+
// Even a failed abort cannot skip the original native process join.
|
|
136
|
+
const abort = yield* Effect.exit(process.abort());
|
|
137
|
+
const completion = yield* Effect.exit(process.result);
|
|
138
|
+
yield* Ref.set(settled, true);
|
|
139
|
+
rendered = yield* Effect.exit(operationFinally(
|
|
140
|
+
observed,
|
|
141
|
+
operationFinally(Effect.asVoid(completion), abort),
|
|
142
|
+
));
|
|
143
|
+
} else {
|
|
144
|
+
yield* Ref.set(settled, true);
|
|
145
|
+
}
|
|
146
|
+
const monitorStopped = yield* Fiber.interrupt(monitor);
|
|
147
|
+
if (Exit.isFailure(monitorStopped) && !Cause.isInterruptedOnly(monitorStopped.cause)) {
|
|
148
|
+
yield* Ref.set(monitorOutcome, Exit.fail({
|
|
149
|
+
_tag: "OperationBoundaryFailure",
|
|
150
|
+
phase: "media",
|
|
151
|
+
cause: Cause.squash(monitorStopped.cause),
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
const monitored = yield* Ref.get(monitorOutcome);
|
|
155
|
+
return yield* operationFinally(rendered, monitored);
|
|
156
|
+
}))),
|
|
157
|
+
({ process, settled }) => operationFinally(
|
|
158
|
+
Effect.gen(function*() {
|
|
159
|
+
if (yield* Ref.get(settled)) return;
|
|
160
|
+
// Interruption may arrive after start but before the use body begins.
|
|
161
|
+
const abort = yield* Effect.exit(process.abort());
|
|
162
|
+
const completion = yield* Effect.exit(process.result);
|
|
163
|
+
yield* Ref.set(settled, true);
|
|
164
|
+
yield* operationFinally(Effect.asVoid(completion), abort);
|
|
165
|
+
}),
|
|
166
|
+
process.detach(),
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Native staged owner; publication carries its prepared value through one masked sequence. */
|
|
172
|
+
export function executeAtomicRenderEffect<Prepared, R>(
|
|
173
|
+
request: AtomicRenderRequest,
|
|
174
|
+
publication: AtomicRenderPublication<Prepared, R>,
|
|
175
|
+
): Effect.Effect<
|
|
176
|
+
{ readonly output: AtomicRenderOutput; readonly prepared: Prepared },
|
|
177
|
+
OperationEffectFailure,
|
|
178
|
+
R | AtomicRenderPlatform
|
|
179
|
+
> {
|
|
180
|
+
return Effect.gen(function*() {
|
|
181
|
+
const platform = yield* AtomicRenderPlatform;
|
|
182
|
+
return yield* operationResource(
|
|
183
|
+
platform.stage(request, publication.companion?.finalPath),
|
|
184
|
+
staging => Effect.gen(function*() {
|
|
185
|
+
const result = yield* runMonitored(platform, staging, request);
|
|
186
|
+
yield* operationValidation("media", () => {
|
|
187
|
+
if (result.exitCode !== 0) {
|
|
188
|
+
throw new CliError("subprocess", `${request.failureLabel}: ${result.stderr.trim().slice(-4_000) || `exit ${result.exitCode}`}`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const output = yield* platform.verify(staging, request);
|
|
192
|
+
// The final workflow fence is inside prepare. Mask before entering it,
|
|
193
|
+
// including the boundary between its successful return and precommit.
|
|
194
|
+
return yield* Effect.uninterruptible(Effect.gen(function*() {
|
|
195
|
+
const prepared = yield* publication.prepare(output, staging.path);
|
|
196
|
+
const companion = publication.companion;
|
|
197
|
+
if (companion !== undefined) yield* platform.invalidateCompanion(companion.finalPath);
|
|
198
|
+
yield* platform.publish(staging, request);
|
|
199
|
+
if (companion !== undefined) {
|
|
200
|
+
yield* Effect.catchAllCause(companion.publish(prepared, output), cause => operationFinally(
|
|
201
|
+
Effect.failCause(cause), platform.invalidateCompanion(companion.finalPath),
|
|
202
|
+
));
|
|
203
|
+
}
|
|
204
|
+
return { output, prepared };
|
|
205
|
+
}));
|
|
206
|
+
}),
|
|
207
|
+
staging => platform.cleanup(staging),
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants, linkSync, lstatSync, renameSync, type Stats } from "node:fs";
|
|
3
|
+
import { link, lstat, open, realpath, rename, rm, type FileHandle } from "node:fs/promises";
|
|
4
|
+
import { dirname, extname, join } from "node:path";
|
|
5
|
+
import { Effect, Exit, Layer } from "effect";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
operationBoundary,
|
|
9
|
+
operationResource,
|
|
10
|
+
operationValidation,
|
|
11
|
+
type OperationEffectFailure,
|
|
12
|
+
} from "../application/operation-effects";
|
|
13
|
+
import {
|
|
14
|
+
AtomicRenderPlatform,
|
|
15
|
+
type AtomicRenderOutput,
|
|
16
|
+
type AtomicRenderPlatformService,
|
|
17
|
+
type AtomicRenderRequest,
|
|
18
|
+
type AtomicRenderStaging,
|
|
19
|
+
} from "./atomic-render-effects";
|
|
20
|
+
import { CliError } from "./errors";
|
|
21
|
+
|
|
22
|
+
function missingFile(error: unknown): boolean {
|
|
23
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function stagePath(request: AtomicRenderRequest, companionPath?: string): Promise<AtomicRenderStaging> {
|
|
27
|
+
if (request.argv.at(-1) !== request.finalOutputPath) {
|
|
28
|
+
throw new CliError("internal", "Render invocation output does not match its requested final path.");
|
|
29
|
+
}
|
|
30
|
+
if (companionPath === request.finalOutputPath) {
|
|
31
|
+
throw new CliError("internal", "Render output and companion paths must be distinct.");
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isSafeInteger(request.maximumOutputBytes) || request.maximumOutputBytes < 1) {
|
|
34
|
+
throw new CliError("invalid-data", "Render output byte limit must be a positive safe integer.");
|
|
35
|
+
}
|
|
36
|
+
const extension = extname(request.finalOutputPath) || ".media";
|
|
37
|
+
let directory = dirname(request.finalOutputPath);
|
|
38
|
+
if (request.stagingDirectory !== undefined) {
|
|
39
|
+
const staging = await lstat(request.stagingDirectory);
|
|
40
|
+
if (staging.isSymbolicLink() || !staging.isDirectory() || (staging.mode & 0o077) !== 0) {
|
|
41
|
+
throw new CliError("unsafe-path", "Atomic render staging must be a private physical directory.");
|
|
42
|
+
}
|
|
43
|
+
directory = await realpath(request.stagingDirectory);
|
|
44
|
+
}
|
|
45
|
+
const path = join(directory, `.slopcamera-render-${randomUUID()}.tmp${extension}`);
|
|
46
|
+
const argv: [string, ...string[]] = [...request.argv];
|
|
47
|
+
const overwriteIndex = argv.indexOf("-y");
|
|
48
|
+
if (overwriteIndex < 0) {
|
|
49
|
+
throw new CliError("internal", "Atomic render invocation must contain FFmpeg's overwrite flag.");
|
|
50
|
+
}
|
|
51
|
+
// FFmpeg must exclusively create the absent private path, never follow a prepositioned link.
|
|
52
|
+
argv[overwriteIndex] = "-n";
|
|
53
|
+
argv[argv.length - 1] = path;
|
|
54
|
+
return { argv, path };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function syncDirectory(path: string): Effect.Effect<void, OperationEffectFailure> {
|
|
58
|
+
return operationResource(
|
|
59
|
+
operationBoundary("publication", () => open(path, constants.O_RDONLY)),
|
|
60
|
+
directory => Effect.uninterruptible(operationBoundary("publication", () => directory.sync())),
|
|
61
|
+
directory => operationBoundary("cleanup", () => directory.close()),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function hashOpenedOutput(
|
|
66
|
+
handle: FileHandle,
|
|
67
|
+
lexical: Awaited<ReturnType<typeof lstat>>,
|
|
68
|
+
): Promise<AtomicRenderOutput> {
|
|
69
|
+
const opened = await handle.stat();
|
|
70
|
+
if (!opened.isFile() || opened.dev !== lexical.dev || opened.ino !== lexical.ino
|
|
71
|
+
|| opened.size !== lexical.size || opened.nlink !== 1) {
|
|
72
|
+
throw new CliError("conflict", "Render output changed before it could be committed.");
|
|
73
|
+
}
|
|
74
|
+
await handle.chmod(0o600);
|
|
75
|
+
await handle.sync();
|
|
76
|
+
const beforeHash = await handle.stat();
|
|
77
|
+
if (beforeHash.size !== opened.size || beforeHash.dev !== opened.dev || beforeHash.ino !== opened.ino
|
|
78
|
+
|| beforeHash.nlink !== 1 || !Number.isSafeInteger(beforeHash.size)) {
|
|
79
|
+
throw new CliError("conflict", "Render output changed while it was being committed.");
|
|
80
|
+
}
|
|
81
|
+
const hash = createHash("sha256");
|
|
82
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
83
|
+
let bytes = 0;
|
|
84
|
+
while (bytes < beforeHash.size) {
|
|
85
|
+
const read = await handle.read(buffer, 0, Math.min(buffer.byteLength, beforeHash.size - bytes), bytes);
|
|
86
|
+
if (read.bytesRead === 0) break;
|
|
87
|
+
hash.update(buffer.subarray(0, read.bytesRead));
|
|
88
|
+
bytes += read.bytesRead;
|
|
89
|
+
}
|
|
90
|
+
const afterHash = await handle.stat();
|
|
91
|
+
if (bytes !== beforeHash.size || afterHash.size !== beforeHash.size || afterHash.dev !== beforeHash.dev
|
|
92
|
+
|| afterHash.ino !== beforeHash.ino || afterHash.nlink !== 1 || afterHash.mtimeMs !== beforeHash.mtimeMs
|
|
93
|
+
|| afterHash.ctimeMs !== beforeHash.ctimeMs) {
|
|
94
|
+
throw new CliError("conflict", "Render output changed while its integrity was being recorded.");
|
|
95
|
+
}
|
|
96
|
+
return { bytes, sha256: hash.digest("hex") };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const nativeAtomicRenderPlatform: AtomicRenderPlatformService = {
|
|
100
|
+
stage: (request, companionPath) => operationBoundary("workspace", () => stagePath(request, companionPath)),
|
|
101
|
+
start: (staging, request) => operationValidation("media", () => {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const signal = request.abortSignal;
|
|
104
|
+
const forward = (): void => { controller.abort(signal?.reason); };
|
|
105
|
+
signal?.addEventListener("abort", forward, { once: true });
|
|
106
|
+
if (signal?.aborted === true) forward();
|
|
107
|
+
try {
|
|
108
|
+
// Observe immediately, including primitive rejections. No second evaluation dispatches work.
|
|
109
|
+
const completion = request.runner.run(staging.argv, {
|
|
110
|
+
abortSignal: controller.signal,
|
|
111
|
+
maxOutputBytes: 1_000_000,
|
|
112
|
+
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
113
|
+
}).then(
|
|
114
|
+
result => Exit.succeed(result),
|
|
115
|
+
(cause: unknown) => Exit.fail<OperationEffectFailure>({
|
|
116
|
+
_tag: "OperationBoundaryFailure", phase: "media", cause,
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
return {
|
|
120
|
+
result: Effect.flatMap(Effect.promise(() => completion), exit => exit),
|
|
121
|
+
abort: (reason?: unknown) => operationValidation("cleanup", () => controller.abort(reason)),
|
|
122
|
+
detach: () => operationValidation("cleanup", () => signal?.removeEventListener("abort", forward)),
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
signal?.removeEventListener("abort", forward);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
outputSize: staging => operationBoundary("media", async () => {
|
|
130
|
+
try { return (await lstat(staging.path)).size; }
|
|
131
|
+
catch (error) { if (missingFile(error)) return null; throw error; }
|
|
132
|
+
}),
|
|
133
|
+
verify: (staging, request) => Effect.gen(function*() {
|
|
134
|
+
const lexical = yield* operationBoundary("media", () => lstat(staging.path));
|
|
135
|
+
yield* operationValidation("media", () => {
|
|
136
|
+
if (lexical.isSymbolicLink() || !lexical.isFile() || lexical.size <= 0 || lexical.nlink !== 1) {
|
|
137
|
+
throw new CliError("invalid-data", "Renderer produced an unsafe, empty, or multiply-linked output file.");
|
|
138
|
+
}
|
|
139
|
+
if (lexical.size > request.maximumOutputBytes) {
|
|
140
|
+
throw new CliError("invalid-data", "Renderer output exceeds its configured byte limit.");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return yield* operationResource(
|
|
144
|
+
operationBoundary("media", () => open(staging.path, constants.O_RDWR | constants.O_NOFOLLOW)),
|
|
145
|
+
// The foreign multi-step descriptor use must settle before close. A
|
|
146
|
+
// cancelled Promise wait does not stop native stat/sync/read continuations.
|
|
147
|
+
handle => Effect.uninterruptible(operationBoundary("media", () => hashOpenedOutput(handle, lexical))),
|
|
148
|
+
handle => operationBoundary("cleanup", () => handle.close()),
|
|
149
|
+
);
|
|
150
|
+
}),
|
|
151
|
+
publish: (staging, request) => Effect.gen(function*() {
|
|
152
|
+
yield* operationBoundary("publication", async () => {
|
|
153
|
+
if (request.beforeNativePublish !== undefined) {
|
|
154
|
+
const before = lstatSync(staging.path, { bigint: true });
|
|
155
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.size < 1n || before.size > BigInt(request.maximumOutputBytes)) throw new CliError("conflict", "Guarded render stage is unsafe.");
|
|
156
|
+
const parents = new Map<string, Stats>();
|
|
157
|
+
for (const path of [staging.path, request.finalOutputPath]) {
|
|
158
|
+
for (let directory = dirname(path);; directory = dirname(directory)) {
|
|
159
|
+
const snapshot = lstatSync(directory);
|
|
160
|
+
if (!snapshot.isDirectory() || snapshot.isSymbolicLink()) throw new CliError("unsafe-path", "Guarded render publication parent is unsafe.");
|
|
161
|
+
parents.set(directory, snapshot);
|
|
162
|
+
if (dirname(directory) === directory) break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
await request.beforeNativePublish(staging.path);
|
|
166
|
+
for (const [path, previous] of parents) {
|
|
167
|
+
const current = lstatSync(path);
|
|
168
|
+
if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== previous.dev || current.ino !== previous.ino) throw new CliError("conflict", "Render publication parent changed during its final custody check.");
|
|
169
|
+
}
|
|
170
|
+
const after = lstatSync(staging.path, { bigint: true });
|
|
171
|
+
if (!after.isFile() || after.isSymbolicLink() || after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size
|
|
172
|
+
|| after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs || after.nlink !== 1n || after.mode !== before.mode) throw new CliError("conflict", "Render stage changed during its final custody check.");
|
|
173
|
+
try {
|
|
174
|
+
if (request.requireFreshOutput === true) linkSync(staging.path, request.finalOutputPath);
|
|
175
|
+
else renameSync(staging.path, request.finalOutputPath);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") throw new CliError("conflict", `Render output was created concurrently: ${request.finalOutputPath}`);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
if (request.requireFreshOutput === true) await rm(staging.path);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (request.requireFreshOutput === true) {
|
|
184
|
+
try { await link(staging.path, request.finalOutputPath); }
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
|
|
187
|
+
throw new CliError("conflict", `Render output was created concurrently: ${request.finalOutputPath}`);
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
await rm(staging.path);
|
|
192
|
+
} else {
|
|
193
|
+
await rename(staging.path, request.finalOutputPath);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
yield* syncDirectory(dirname(request.finalOutputPath));
|
|
197
|
+
}),
|
|
198
|
+
invalidateCompanion: path => Effect.gen(function*() {
|
|
199
|
+
yield* operationBoundary("publication", () => rm(path, { force: true }));
|
|
200
|
+
yield* syncDirectory(dirname(path));
|
|
201
|
+
}),
|
|
202
|
+
cleanup: staging => operationBoundary("cleanup", () => rm(staging.path, { force: true })),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const AtomicRenderPlatformLive = Layer.succeed(AtomicRenderPlatform, nativeAtomicRenderPlatform);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { operationBoundary, runStandaloneOperation } from "../application/operation-effects";
|
|
3
|
+
import { executeAtomicRenderEffect, type AtomicRenderOutput } from "./atomic-render-effects";
|
|
4
|
+
import { AtomicRenderPlatformLive } from "./atomic-render-platform";
|
|
5
|
+
import type { ProcessRunner } from "./io";
|
|
6
|
+
|
|
7
|
+
export type { AtomicRenderOutput } from "./atomic-render-effects";
|
|
8
|
+
|
|
9
|
+
export async function executeAtomicRender(options: {
|
|
10
|
+
readonly abortSignal?: AbortSignal;
|
|
11
|
+
readonly argv: readonly [string, ...string[]];
|
|
12
|
+
/**
|
|
13
|
+
* Revalidates inputs after the subprocess exits but before generated bytes
|
|
14
|
+
* receive their durable public name.
|
|
15
|
+
*/
|
|
16
|
+
readonly beforePublish?: (output: AtomicRenderOutput) => Promise<void>;
|
|
17
|
+
readonly companion?: {
|
|
18
|
+
readonly finalPath: string;
|
|
19
|
+
publish(output: AtomicRenderOutput): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
readonly failureLabel: string;
|
|
22
|
+
readonly finalOutputPath: string;
|
|
23
|
+
readonly maximumOutputBytes: number;
|
|
24
|
+
/**
|
|
25
|
+
* Publishes with an atomic hard link so a destination created while the
|
|
26
|
+
* renderer runs is never replaced.
|
|
27
|
+
*/
|
|
28
|
+
readonly requireFreshOutput?: boolean;
|
|
29
|
+
readonly runner: ProcessRunner;
|
|
30
|
+
/**
|
|
31
|
+
* Optional host-owned private workspace for generated bytes. Workflow
|
|
32
|
+
* callers use this so FFmpeg never writes a temporary beside a public
|
|
33
|
+
* output; legacy CLI callers retain the final-output directory default.
|
|
34
|
+
*/
|
|
35
|
+
readonly stagingDirectory?: string;
|
|
36
|
+
readonly timeoutMs?: number;
|
|
37
|
+
}): Promise<AtomicRenderOutput> {
|
|
38
|
+
const companion = options.companion;
|
|
39
|
+
const result = await runStandaloneOperation(executeAtomicRenderEffect(options, {
|
|
40
|
+
prepare: output => operationBoundary("publication", () => options.beforePublish?.(output)),
|
|
41
|
+
...(companion === undefined ? {} : {
|
|
42
|
+
companion: {
|
|
43
|
+
finalPath: companion.finalPath,
|
|
44
|
+
publish: (_prepared: void, output: AtomicRenderOutput) =>
|
|
45
|
+
operationBoundary("publication", () => companion.publish(output)),
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
}).pipe(Effect.provide(AtomicRenderPlatformLive)));
|
|
49
|
+
return result.output;
|
|
50
|
+
}
|