@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,530 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { constants, type Stats } from "node:fs";
|
|
4
|
+
import { link, lstat, open, opendir, realpath, unlink, type FileHandle } from "node:fs/promises";
|
|
5
|
+
import { hostname, uptime } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { CliError } from "./errors";
|
|
11
|
+
import { environmentWithoutGatewayCredentials } from "./io";
|
|
12
|
+
|
|
13
|
+
export const MUTATION_LOCK_FILE = ".slopcamera-mutation.lock";
|
|
14
|
+
export const MUTATION_LOCK_TEMP_PREFIX = `${MUTATION_LOCK_FILE}.acquire-`;
|
|
15
|
+
|
|
16
|
+
const MAX_OWNER_BYTES = 16_384;
|
|
17
|
+
const DEFAULT_STALE_AFTER_MS = 30_000;
|
|
18
|
+
const MAX_TEMP_FILES_PER_CLEANUP = 4;
|
|
19
|
+
const MAX_DIRECTORY_ENTRIES_PER_CLEANUP = 256;
|
|
20
|
+
const BOOT_TIME_SAFETY_MARGIN_MS = 5_000;
|
|
21
|
+
const MACOS_LSOF_PATH = "/usr/sbin/lsof";
|
|
22
|
+
|
|
23
|
+
type ArtifactOpenState = "closed" | "open" | "unknown";
|
|
24
|
+
|
|
25
|
+
const MutationLockOwnerSchema = z.object({
|
|
26
|
+
acquiredAt: z.string().datetime({ offset: true }),
|
|
27
|
+
command: z.string().min(1).max(512),
|
|
28
|
+
hostname: z.string().min(1).max(255),
|
|
29
|
+
pid: z.number().int().positive(),
|
|
30
|
+
schemaVersion: z.literal(1),
|
|
31
|
+
token: z.string().uuid(),
|
|
32
|
+
}).strict();
|
|
33
|
+
|
|
34
|
+
type MutationLockOwner = z.infer<typeof MutationLockOwnerSchema>;
|
|
35
|
+
|
|
36
|
+
interface LockSnapshot {
|
|
37
|
+
readonly birthtimeMs: number;
|
|
38
|
+
readonly blksize: number;
|
|
39
|
+
readonly blocks: number;
|
|
40
|
+
readonly ctimeMs: number;
|
|
41
|
+
readonly dev: number;
|
|
42
|
+
readonly ino: number;
|
|
43
|
+
readonly gid: number;
|
|
44
|
+
readonly mode: number;
|
|
45
|
+
readonly mtimeMs: number;
|
|
46
|
+
readonly nlink: number;
|
|
47
|
+
readonly rdev: number;
|
|
48
|
+
readonly size: number;
|
|
49
|
+
readonly uid: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ExistingLock =
|
|
53
|
+
| { readonly kind: "malformed"; readonly snapshot: LockSnapshot }
|
|
54
|
+
| { readonly kind: "missing" }
|
|
55
|
+
| { readonly kind: "unsafe" }
|
|
56
|
+
| { readonly kind: "valid"; readonly owner: MutationLockOwner; readonly snapshot: LockSnapshot };
|
|
57
|
+
|
|
58
|
+
export interface MutationLockOptions {
|
|
59
|
+
readonly command: string;
|
|
60
|
+
readonly legacyArtifactOpenState?: (path: string) => Promise<ArtifactOpenState>;
|
|
61
|
+
readonly label: string;
|
|
62
|
+
readonly now?: () => Date;
|
|
63
|
+
readonly processAlive?: (pid: number) => boolean;
|
|
64
|
+
readonly staleAfterMs?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function errno(error: unknown, code: string): boolean {
|
|
68
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function snapshotOf(value: Stats): LockSnapshot {
|
|
72
|
+
return {
|
|
73
|
+
birthtimeMs: value.birthtimeMs,
|
|
74
|
+
blksize: value.blksize,
|
|
75
|
+
blocks: value.blocks,
|
|
76
|
+
ctimeMs: value.ctimeMs,
|
|
77
|
+
dev: value.dev,
|
|
78
|
+
ino: value.ino,
|
|
79
|
+
gid: value.gid,
|
|
80
|
+
mode: value.mode,
|
|
81
|
+
mtimeMs: value.mtimeMs,
|
|
82
|
+
nlink: value.nlink,
|
|
83
|
+
rdev: value.rdev,
|
|
84
|
+
size: value.size,
|
|
85
|
+
uid: value.uid,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sameSnapshot(left: LockSnapshot, right: LockSnapshot): boolean {
|
|
90
|
+
return changedSnapshotFields(left, right).length === 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function changedSnapshotFields(left: LockSnapshot, right: LockSnapshot): readonly (keyof LockSnapshot)[] {
|
|
94
|
+
const fields = ["dev", "ino", "mode", "nlink", "size", "uid", "gid", "rdev", "blksize", "blocks", "mtimeMs", "ctimeMs", "birthtimeMs"] as const;
|
|
95
|
+
return fields.filter(field => left[field] !== right[field]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function assertOwnedSnapshot(expected: LockSnapshot, actual: LockSnapshot, phase: string, allowCtime: boolean): void {
|
|
99
|
+
const fields = changedSnapshotFields(expected, actual);
|
|
100
|
+
if (fields.some(field => !allowCtime || field !== "ctimeMs")) {
|
|
101
|
+
throw new CliError("conflict", `Project mutation custody changed ${phase} (fields: ${fields.join(", ")}).`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function verifyExactOwnedLock(handle: FileHandle, path: string, expected: LockSnapshot, ownerBytes: Buffer): Promise<LockSnapshot> {
|
|
106
|
+
if (ownerBytes.length < 1 || ownerBytes.length > MAX_OWNER_BYTES || expected.size !== ownerBytes.length) {
|
|
107
|
+
throw new CliError("conflict", "Project mutation custody has an invalid owner byte bound.");
|
|
108
|
+
}
|
|
109
|
+
const buffer = Buffer.alloc(ownerBytes.length + 1);
|
|
110
|
+
let previous: LockSnapshot | undefined;
|
|
111
|
+
for (const attempt of [1, 2] as const) {
|
|
112
|
+
const held = await handle.stat();
|
|
113
|
+
if (!ownedPrivateFile(held)) throw new CliError("conflict", "Project mutation custody changed before owner verification (fields: mode, uid or file type).");
|
|
114
|
+
const before = snapshotOf(held);
|
|
115
|
+
assertOwnedSnapshot(expected, before, "before owner verification", true);
|
|
116
|
+
if (previous !== undefined) assertOwnedSnapshot(previous, before, "between owner verification reads", false);
|
|
117
|
+
let bytes = 0;
|
|
118
|
+
while (bytes < buffer.length) {
|
|
119
|
+
const read = await handle.read(buffer, bytes, buffer.length - bytes, bytes);
|
|
120
|
+
if (read.bytesRead === 0) break;
|
|
121
|
+
bytes += read.bytesRead;
|
|
122
|
+
}
|
|
123
|
+
const after = snapshotOf(await handle.stat());
|
|
124
|
+
const current = await lstat(path);
|
|
125
|
+
if (!ownedPrivateFile(current)) throw new CliError("conflict", "Project mutation custody changed at owner path readback (fields: mode, uid or file type).");
|
|
126
|
+
const retained = snapshotOf(current);
|
|
127
|
+
assertOwnedSnapshot(before, after, "during owner verification", attempt === 1);
|
|
128
|
+
assertOwnedSnapshot(after, retained, "at owner path readback", attempt === 1);
|
|
129
|
+
if (bytes !== ownerBytes.length || !buffer.subarray(0, bytes).equals(ownerBytes)) {
|
|
130
|
+
throw new CliError("conflict", "Project mutation custody changed during owner verification (fields: owner bytes).");
|
|
131
|
+
}
|
|
132
|
+
if (sameSnapshot(before, after) && sameSnapshot(after, retained)) return retained;
|
|
133
|
+
previous = retained;
|
|
134
|
+
}
|
|
135
|
+
throw new CliError("conflict", "Project mutation custody remained unstable during owner verification.");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ownedPrivateFile(details: {
|
|
139
|
+
isFile(): boolean;
|
|
140
|
+
isSymbolicLink(): boolean;
|
|
141
|
+
readonly mode: number;
|
|
142
|
+
readonly uid: number;
|
|
143
|
+
}): boolean {
|
|
144
|
+
const expectedUid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
145
|
+
return details.isFile()
|
|
146
|
+
&& !details.isSymbolicLink()
|
|
147
|
+
&& (details.mode & 0o777) === 0o600
|
|
148
|
+
&& (expectedUid === undefined || details.uid === expectedUid);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function defaultArtifactOpenState(path: string): Promise<ArtifactOpenState> {
|
|
152
|
+
if (process.platform !== "darwin") return "unknown";
|
|
153
|
+
return await new Promise(resolve => {
|
|
154
|
+
execFile(
|
|
155
|
+
MACOS_LSOF_PATH,
|
|
156
|
+
["-F", "p", "--", path],
|
|
157
|
+
{
|
|
158
|
+
encoding: "utf8",
|
|
159
|
+
env: environmentWithoutGatewayCredentials(process.env),
|
|
160
|
+
maxBuffer: 32_768,
|
|
161
|
+
timeout: 1_000,
|
|
162
|
+
},
|
|
163
|
+
(error, stdout, stderr) => {
|
|
164
|
+
if (error === null) {
|
|
165
|
+
resolve(/^p[0-9]+$/mu.test(stdout) ? "open" : "unknown");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (error.code === 1 && stderr.trim() === "") {
|
|
169
|
+
resolve("closed");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
resolve("unknown");
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function defaultProcessAlive(pid: number): boolean {
|
|
179
|
+
try {
|
|
180
|
+
process.kill(pid, 0);
|
|
181
|
+
return true;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (errno(error, "ESRCH")) return false;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function physicalBundleDirectory(bundleDirectory: string): Promise<string> {
|
|
189
|
+
let details;
|
|
190
|
+
try {
|
|
191
|
+
details = await lstat(bundleDirectory);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (errno(error, "ENOENT")) {
|
|
194
|
+
throw new CliError("not-found", `Mutation target does not exist: ${bundleDirectory}`);
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
if (details.isSymbolicLink() || !details.isDirectory()) {
|
|
199
|
+
throw new CliError("unsafe-path", `Mutation target must be a physical directory: ${bundleDirectory}`);
|
|
200
|
+
}
|
|
201
|
+
return await realpath(bundleDirectory);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function readExistingLock(path: string): Promise<ExistingLock> {
|
|
205
|
+
let pathDetails;
|
|
206
|
+
try {
|
|
207
|
+
pathDetails = await lstat(path);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (errno(error, "ENOENT")) return { kind: "missing" };
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
if (!ownedPrivateFile(pathDetails) || pathDetails.size > MAX_OWNER_BYTES) return { kind: "unsafe" };
|
|
213
|
+
const pathSnapshot = snapshotOf(pathDetails);
|
|
214
|
+
if (pathDetails.size === 0) return { kind: "malformed", snapshot: pathSnapshot };
|
|
215
|
+
|
|
216
|
+
let handle: FileHandle | undefined;
|
|
217
|
+
try {
|
|
218
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
219
|
+
const before = snapshotOf(await handle.stat());
|
|
220
|
+
if (!sameSnapshot(pathSnapshot, before)) return { kind: "unsafe" };
|
|
221
|
+
const text = await handle.readFile({ encoding: "utf8" });
|
|
222
|
+
const after = snapshotOf(await handle.stat());
|
|
223
|
+
if (!sameSnapshot(before, after)) return { kind: "unsafe" };
|
|
224
|
+
let value: unknown;
|
|
225
|
+
try {
|
|
226
|
+
value = JSON.parse(text) as unknown;
|
|
227
|
+
} catch {
|
|
228
|
+
return { kind: "malformed", snapshot: after };
|
|
229
|
+
}
|
|
230
|
+
const parsed = MutationLockOwnerSchema.safeParse(value);
|
|
231
|
+
return parsed.success
|
|
232
|
+
? { kind: "valid", owner: parsed.data, snapshot: after }
|
|
233
|
+
: { kind: "malformed", snapshot: after };
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (errno(error, "ENOENT")) return { kind: "missing" };
|
|
236
|
+
if (errno(error, "ELOOP")) return { kind: "unsafe" };
|
|
237
|
+
throw error;
|
|
238
|
+
} finally {
|
|
239
|
+
await handle?.close();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function unlinkIfSame(path: string, expected: LockSnapshot): Promise<boolean> {
|
|
244
|
+
let current;
|
|
245
|
+
try {
|
|
246
|
+
current = await lstat(path);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (errno(error, "ENOENT")) return true;
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
if (current.isSymbolicLink() || !current.isFile()) return false;
|
|
252
|
+
if (!sameSnapshot(snapshotOf(current), expected)) return false;
|
|
253
|
+
try {
|
|
254
|
+
await unlink(path);
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (errno(error, "ENOENT")) return true;
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function provablyUnheldLegacyArtifact(
|
|
263
|
+
path: string,
|
|
264
|
+
snapshot: LockSnapshot,
|
|
265
|
+
options: MutationLockOptions,
|
|
266
|
+
now: Date,
|
|
267
|
+
): Promise<boolean> {
|
|
268
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
269
|
+
const ageMs = now.getTime() - Math.max(snapshot.ctimeMs, snapshot.mtimeMs);
|
|
270
|
+
if (ageMs < staleAfterMs) return false;
|
|
271
|
+
const estimatedBootTimeMs = Date.now() - uptime() * 1_000;
|
|
272
|
+
if (snapshot.ctimeMs < estimatedBootTimeMs - BOOT_TIME_SAFETY_MARGIN_MS) return true;
|
|
273
|
+
return await (options.legacyArtifactOpenState ?? defaultArtifactOpenState)(path) === "closed";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function reclaimConflictingLock(
|
|
277
|
+
path: string,
|
|
278
|
+
options: MutationLockOptions,
|
|
279
|
+
now: Date,
|
|
280
|
+
): Promise<{ readonly owner: MutationLockOwner | undefined; readonly retry: boolean }> {
|
|
281
|
+
const existing = await readExistingLock(path);
|
|
282
|
+
if (existing.kind === "missing") return { owner: undefined, retry: true };
|
|
283
|
+
if (existing.kind === "unsafe") return { owner: undefined, retry: false };
|
|
284
|
+
if (existing.kind === "malformed") {
|
|
285
|
+
if (
|
|
286
|
+
existing.snapshot.nlink !== 1
|
|
287
|
+
|| !await provablyUnheldLegacyArtifact(path, existing.snapshot, options, now)
|
|
288
|
+
) {
|
|
289
|
+
return { owner: undefined, retry: false };
|
|
290
|
+
}
|
|
291
|
+
return { owner: undefined, retry: await unlinkIfSame(path, existing.snapshot) };
|
|
292
|
+
}
|
|
293
|
+
const ageMs = now.getTime() - Date.parse(existing.owner.acquiredAt);
|
|
294
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
295
|
+
if (
|
|
296
|
+
ageMs < staleAfterMs
|
|
297
|
+
|| existing.owner.hostname !== hostname()
|
|
298
|
+
|| (options.processAlive ?? defaultProcessAlive)(existing.owner.pid)
|
|
299
|
+
) {
|
|
300
|
+
return { owner: existing.owner, retry: false };
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
owner: existing.owner,
|
|
304
|
+
retry: await unlinkIfSame(path, existing.snapshot),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function busyError(options: MutationLockOptions, owner: MutationLockOwner | undefined): CliError {
|
|
309
|
+
return new CliError(
|
|
310
|
+
"conflict",
|
|
311
|
+
`Another mutation is already in progress for ${options.label}.`,
|
|
312
|
+
{
|
|
313
|
+
...(owner === undefined ? {} : {
|
|
314
|
+
acquiredAt: owner.acquiredAt,
|
|
315
|
+
command: owner.command,
|
|
316
|
+
hostname: owner.hostname,
|
|
317
|
+
pid: owner.pid,
|
|
318
|
+
}),
|
|
319
|
+
target: options.label,
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function discardStagedOwner(path: string, handle: FileHandle): Promise<void> {
|
|
325
|
+
let snapshot: LockSnapshot | undefined;
|
|
326
|
+
try {
|
|
327
|
+
snapshot = snapshotOf(await handle.stat());
|
|
328
|
+
} catch {
|
|
329
|
+
// A unique acquisition temp that cannot be identified is safe to leave for
|
|
330
|
+
// bounded stale-temp cleanup; never unlink an unverified replacement.
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
await handle.close();
|
|
334
|
+
} finally {
|
|
335
|
+
if (snapshot !== undefined) await unlinkIfSame(path, snapshot).catch(() => false);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function stageOwner(
|
|
340
|
+
directory: string,
|
|
341
|
+
owner: MutationLockOwner,
|
|
342
|
+
): Promise<{ readonly handle: FileHandle; readonly path: string; readonly ownerBytes: Buffer }> {
|
|
343
|
+
const path = join(directory, `${MUTATION_LOCK_TEMP_PREFIX}${randomUUID()}.tmp`);
|
|
344
|
+
const ownerBytes = Buffer.from(`${JSON.stringify(owner)}\n`);
|
|
345
|
+
const handle = await open(
|
|
346
|
+
path,
|
|
347
|
+
constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_RDWR,
|
|
348
|
+
0o600,
|
|
349
|
+
);
|
|
350
|
+
try {
|
|
351
|
+
await handle.writeFile(ownerBytes);
|
|
352
|
+
await handle.sync();
|
|
353
|
+
return { handle, path, ownerBytes };
|
|
354
|
+
} catch (error) {
|
|
355
|
+
await discardStagedOwner(path, handle);
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function acquisitionTempName(name: string): boolean {
|
|
361
|
+
return name.startsWith(MUTATION_LOCK_TEMP_PREFIX)
|
|
362
|
+
&& name.endsWith(".tmp")
|
|
363
|
+
&& name.length <= MUTATION_LOCK_TEMP_PREFIX.length + 64;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function cleanupAbandonedAcquisitionTemps(
|
|
367
|
+
directory: string,
|
|
368
|
+
options: MutationLockOptions,
|
|
369
|
+
now: Date,
|
|
370
|
+
): Promise<void> {
|
|
371
|
+
let entries;
|
|
372
|
+
try {
|
|
373
|
+
entries = await opendir(directory);
|
|
374
|
+
} catch {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
let inspectedEntries = 0;
|
|
378
|
+
let matchingEntries = 0;
|
|
379
|
+
try {
|
|
380
|
+
for await (const entry of entries) {
|
|
381
|
+
inspectedEntries += 1;
|
|
382
|
+
if (inspectedEntries > MAX_DIRECTORY_ENTRIES_PER_CLEANUP) break;
|
|
383
|
+
if (!acquisitionTempName(entry.name)) continue;
|
|
384
|
+
matchingEntries += 1;
|
|
385
|
+
if (matchingEntries > MAX_TEMP_FILES_PER_CLEANUP) break;
|
|
386
|
+
const path = join(directory, entry.name);
|
|
387
|
+
let details;
|
|
388
|
+
try {
|
|
389
|
+
details = await lstat(path);
|
|
390
|
+
} catch {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (
|
|
394
|
+
!ownedPrivateFile(details)
|
|
395
|
+
|| details.nlink !== 1
|
|
396
|
+
|| details.size > MAX_OWNER_BYTES
|
|
397
|
+
) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const snapshot = snapshotOf(details);
|
|
401
|
+
if (await provablyUnheldLegacyArtifact(path, snapshot, options, now)) {
|
|
402
|
+
await unlinkIfSame(path, snapshot).catch(() => false);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
// Temp hygiene must never make an otherwise safe lock acquisition fail.
|
|
407
|
+
} finally {
|
|
408
|
+
try {
|
|
409
|
+
await entries.close();
|
|
410
|
+
} catch {
|
|
411
|
+
// Async directory iteration may already have closed the handle.
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function acquire(
|
|
417
|
+
bundleDirectory: string,
|
|
418
|
+
options: MutationLockOptions,
|
|
419
|
+
): Promise<{ readonly handle: FileHandle; readonly path: string; readonly snapshot: LockSnapshot; readonly ownerBytes: Buffer }> {
|
|
420
|
+
const directory = await physicalBundleDirectory(bundleDirectory);
|
|
421
|
+
const path = join(directory, MUTATION_LOCK_FILE);
|
|
422
|
+
const now = (options.now ?? (() => new Date()))();
|
|
423
|
+
const owner = MutationLockOwnerSchema.parse({
|
|
424
|
+
acquiredAt: now.toISOString(),
|
|
425
|
+
command: options.command,
|
|
426
|
+
hostname: hostname(),
|
|
427
|
+
pid: process.pid,
|
|
428
|
+
schemaVersion: 1,
|
|
429
|
+
token: randomUUID(),
|
|
430
|
+
});
|
|
431
|
+
await cleanupAbandonedAcquisitionTemps(directory, options, now);
|
|
432
|
+
|
|
433
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
434
|
+
const staged = await stageOwner(directory, owner);
|
|
435
|
+
try {
|
|
436
|
+
await link(staged.path, path);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
await discardStagedOwner(staged.path, staged.handle);
|
|
439
|
+
if (!errno(error, "EEXIST")) throw error;
|
|
440
|
+
const conflict = await reclaimConflictingLock(path, options, now);
|
|
441
|
+
if (conflict.retry && attempt < 2) continue;
|
|
442
|
+
throw busyError(options, conflict.owner);
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
await unlink(staged.path).catch(() => undefined);
|
|
446
|
+
const snapshot = snapshotOf(await staged.handle.stat());
|
|
447
|
+
const verified = await verifyExactOwnedLock(staged.handle, path, snapshot, staged.ownerBytes);
|
|
448
|
+
return { handle: staged.handle, path, snapshot: verified, ownerBytes: staged.ownerBytes };
|
|
449
|
+
} catch (error) {
|
|
450
|
+
await staged.handle.close().catch(() => undefined);
|
|
451
|
+
// A published owner that failed exact verification is not safe to unlink.
|
|
452
|
+
// Preserve it for the existing conservative stale-owner protocol.
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const existing = await readExistingLock(path);
|
|
457
|
+
throw busyError(options, existing.kind === "valid" ? existing.owner : undefined);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Native custody remains with this descriptor and exact published inode. */
|
|
461
|
+
export interface MutationLease {
|
|
462
|
+
assertOwned(): Promise<void>;
|
|
463
|
+
close(): Promise<void>;
|
|
464
|
+
unlinkIfOwned(): Promise<void>;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export async function acquireMutationLease(
|
|
468
|
+
bundleDirectory: string,
|
|
469
|
+
options: MutationLockOptions,
|
|
470
|
+
): Promise<MutationLease> {
|
|
471
|
+
const lease = await acquire(bundleDirectory, options);
|
|
472
|
+
let snapshot = lease.snapshot, closed = false;
|
|
473
|
+
let pending: Promise<void> = Promise.resolve();
|
|
474
|
+
const serialize = (work: () => Promise<void>): Promise<void> => {
|
|
475
|
+
const result = pending.then(work);
|
|
476
|
+
pending = result.then(() => undefined, () => undefined);
|
|
477
|
+
return result;
|
|
478
|
+
};
|
|
479
|
+
return {
|
|
480
|
+
assertOwned: async () => await serialize(async () => {
|
|
481
|
+
if (closed) throw new CliError("conflict", "Project mutation custody changed before publication (fields: closed descriptor).");
|
|
482
|
+
const current = await lstat(lease.path);
|
|
483
|
+
const held = await lease.handle.stat();
|
|
484
|
+
if (!ownedPrivateFile(current) || !ownedPrivateFile(held)) throw new CliError("conflict", "Project mutation custody changed before publication (fields: mode, uid or file type).");
|
|
485
|
+
assertOwnedSnapshot(snapshot, snapshotOf(current), "at publication path", true);
|
|
486
|
+
assertOwnedSnapshot(snapshot, snapshotOf(held), "at publication descriptor", true);
|
|
487
|
+
if (!sameSnapshot(snapshot, snapshotOf(current)) || !sameSnapshot(snapshot, snapshotOf(held))) {
|
|
488
|
+
snapshot = await verifyExactOwnedLock(lease.handle, lease.path, snapshot, lease.ownerBytes);
|
|
489
|
+
}
|
|
490
|
+
}),
|
|
491
|
+
close: async () => await serialize(async () => {
|
|
492
|
+
if (closed) return;
|
|
493
|
+
closed = true;
|
|
494
|
+
await lease.handle.close();
|
|
495
|
+
}),
|
|
496
|
+
unlinkIfOwned: async () => await serialize(async () => {
|
|
497
|
+
let handle: FileHandle | undefined;
|
|
498
|
+
let verified: LockSnapshot | undefined;
|
|
499
|
+
try {
|
|
500
|
+
// The caller closes custody before cleanup. Reopen only our exact
|
|
501
|
+
// original inode and byte-for-byte owner, never a replacement owner.
|
|
502
|
+
handle = await open(lease.path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
503
|
+
verified = await verifyExactOwnedLock(handle, lease.path, snapshot, lease.ownerBytes);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!errno(error, "ENOENT") && !errno(error, "ELOOP") && !(error instanceof CliError && error.code === "conflict")) throw error;
|
|
506
|
+
} finally { await handle?.close(); }
|
|
507
|
+
if (verified !== undefined) {
|
|
508
|
+
snapshot = verified;
|
|
509
|
+
await unlinkIfSame(lease.path, snapshot);
|
|
510
|
+
}
|
|
511
|
+
}),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export async function withMutationLock<T>(
|
|
516
|
+
bundleDirectory: string,
|
|
517
|
+
options: MutationLockOptions,
|
|
518
|
+
mutate: (lease: MutationLease) => Promise<T>,
|
|
519
|
+
): Promise<T> {
|
|
520
|
+
const lease = await acquireMutationLease(bundleDirectory, options);
|
|
521
|
+
try {
|
|
522
|
+
return await mutate(lease);
|
|
523
|
+
} finally {
|
|
524
|
+
try {
|
|
525
|
+
await lease.close();
|
|
526
|
+
} finally {
|
|
527
|
+
await lease.unlinkIfOwned();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module "@img/sharp-darwin-arm64/sharp.node" {
|
|
2
|
+
const addon: unknown;
|
|
3
|
+
export default addon;
|
|
4
|
+
}
|
|
5
|
+
declare module "@img/sharp-libvips-darwin-arm64/binary" {
|
|
6
|
+
const path: string;
|
|
7
|
+
export default path;
|
|
8
|
+
}
|
|
9
|
+
declare module "*.py" {
|
|
10
|
+
const source: string;
|
|
11
|
+
export default source;
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Direct import is required for Bun to embed Sharp's dynamically selected
|
|
2
|
+
// N-API addon in the compiled Apple Silicon executable.
|
|
3
|
+
import sharpAddon from "@img/sharp-darwin-arm64/sharp.node";
|
|
4
|
+
|
|
5
|
+
(globalThis as Record<symbol, unknown>)[
|
|
6
|
+
Symbol.for("slopcamera.sharp-darwin-arm64-addon/v1")
|
|
7
|
+
] = sharpAddon;
|