@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,687 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
lstat,
|
|
5
|
+
open,
|
|
6
|
+
readdir,
|
|
7
|
+
readlink,
|
|
8
|
+
realpath,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import {
|
|
11
|
+
basename,
|
|
12
|
+
dirname,
|
|
13
|
+
isAbsolute,
|
|
14
|
+
join,
|
|
15
|
+
relative,
|
|
16
|
+
resolve,
|
|
17
|
+
sep,
|
|
18
|
+
} from "node:path";
|
|
19
|
+
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
import { canonicalJson } from "../core/canonical-json";
|
|
23
|
+
import {
|
|
24
|
+
ExactCapabilityBindingSchema,
|
|
25
|
+
type ExactCapabilityBinding,
|
|
26
|
+
} from "./capability-binding";
|
|
27
|
+
import { ApplicationError } from "./errors";
|
|
28
|
+
|
|
29
|
+
const MAXIMUM_RUNTIME_ENTRIES = 10_000;
|
|
30
|
+
const MAXIMUM_RUNTIME_BYTES = 8 * 1024 * 1024 * 1024;
|
|
31
|
+
const HASH_BUFFER_BYTES = 1024 * 1024;
|
|
32
|
+
const CODESIGN_MAXIMUM_OUTPUT_BYTES = 64 * 1024;
|
|
33
|
+
const CODESIGN_TIMEOUT_MS = 60_000;
|
|
34
|
+
const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
35
|
+
|
|
36
|
+
function isSafeRelativeRuntimePath(path: string): boolean {
|
|
37
|
+
if (path === ".") return true;
|
|
38
|
+
return path.length > 0
|
|
39
|
+
&& path.length <= 4_096
|
|
40
|
+
&& !path.includes("\0")
|
|
41
|
+
&& !isAbsolute(path)
|
|
42
|
+
&& path.split("/").every(part => part !== "" && part !== "." && part !== "..");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const RuntimePathSchema = z.string().refine(
|
|
46
|
+
isSafeRelativeRuntimePath,
|
|
47
|
+
"Browser runtime paths must be normalized relative paths.",
|
|
48
|
+
);
|
|
49
|
+
const RuntimeModeSchema = z.number().int().min(0).max(0o7777);
|
|
50
|
+
|
|
51
|
+
const BrowserRuntimeDirectoryEntrySchema = z.strictObject({
|
|
52
|
+
kind: z.literal("directory"),
|
|
53
|
+
mode: RuntimeModeSchema,
|
|
54
|
+
path: RuntimePathSchema,
|
|
55
|
+
});
|
|
56
|
+
const BrowserRuntimeFileEntrySchema = z.strictObject({
|
|
57
|
+
bytes: z.number().int().safe().min(0).max(MAXIMUM_RUNTIME_BYTES),
|
|
58
|
+
kind: z.literal("file"),
|
|
59
|
+
mode: RuntimeModeSchema,
|
|
60
|
+
path: RuntimePathSchema,
|
|
61
|
+
sha256: Sha256Schema,
|
|
62
|
+
});
|
|
63
|
+
const BrowserRuntimeSymlinkEntrySchema = z.strictObject({
|
|
64
|
+
kind: z.literal("symlink"),
|
|
65
|
+
mode: RuntimeModeSchema,
|
|
66
|
+
path: RuntimePathSchema,
|
|
67
|
+
target: z.string().min(1).max(4_096).refine(
|
|
68
|
+
target => !target.includes("\0") && !isAbsolute(target),
|
|
69
|
+
"Browser runtime symlink targets must be relative and NUL-free.",
|
|
70
|
+
),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const HtmlOverlayBrowserRuntimeEntrySchema = z.union([
|
|
74
|
+
BrowserRuntimeDirectoryEntrySchema,
|
|
75
|
+
BrowserRuntimeFileEntrySchema,
|
|
76
|
+
BrowserRuntimeSymlinkEntrySchema,
|
|
77
|
+
]);
|
|
78
|
+
export type HtmlOverlayBrowserRuntimeEntry = Readonly<
|
|
79
|
+
z.infer<typeof HtmlOverlayBrowserRuntimeEntrySchema>
|
|
80
|
+
>;
|
|
81
|
+
|
|
82
|
+
export interface HtmlOverlayBrowserRuntimePathIdentity {
|
|
83
|
+
readonly ctimeNs: string;
|
|
84
|
+
readonly dev: string;
|
|
85
|
+
readonly ino: string;
|
|
86
|
+
readonly mode: number;
|
|
87
|
+
readonly path: string;
|
|
88
|
+
readonly size: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const HtmlOverlayBrowserRuntimeManifestSchema = z.strictObject({
|
|
92
|
+
entries: z.array(HtmlOverlayBrowserRuntimeEntrySchema)
|
|
93
|
+
.min(1)
|
|
94
|
+
.max(MAXIMUM_RUNTIME_ENTRIES)
|
|
95
|
+
.superRefine((entries, context) => {
|
|
96
|
+
if (entries[0]?.path !== ".") {
|
|
97
|
+
context.addIssue({
|
|
98
|
+
code: "custom",
|
|
99
|
+
message: "Browser runtime manifests must begin with their root entry.",
|
|
100
|
+
path: [0, "path"],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
for (let index = 1; index < entries.length; index += 1) {
|
|
104
|
+
if (entries[index - 1]!.path >= entries[index]!.path) {
|
|
105
|
+
context.addIssue({
|
|
106
|
+
code: "custom",
|
|
107
|
+
message: "Browser runtime entries must have unique ASCII-sorted paths.",
|
|
108
|
+
path: [index, "path"],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
executableRelativePath: RuntimePathSchema,
|
|
114
|
+
layout: z.enum(["macos-app-bundle", "single-executable"]),
|
|
115
|
+
rootSha256: Sha256Schema,
|
|
116
|
+
schemaVersion: z.literal(1),
|
|
117
|
+
totalBytes: z.number().int().safe().positive().max(MAXIMUM_RUNTIME_BYTES),
|
|
118
|
+
});
|
|
119
|
+
export type HtmlOverlayBrowserRuntimeManifest = Readonly<
|
|
120
|
+
z.infer<typeof HtmlOverlayBrowserRuntimeManifestSchema>
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
export const HtmlOverlayBrowserRuntimeBindingSchema = z.strictObject({
|
|
124
|
+
capability: ExactCapabilityBindingSchema,
|
|
125
|
+
manifest: HtmlOverlayBrowserRuntimeManifestSchema,
|
|
126
|
+
provenance: z.union([
|
|
127
|
+
z.strictObject({
|
|
128
|
+
bundleIdentifier: z.enum([
|
|
129
|
+
"com.google.Chrome",
|
|
130
|
+
"com.google.Chrome.beta",
|
|
131
|
+
"com.google.Chrome.dev",
|
|
132
|
+
"com.google.Chrome.canary",
|
|
133
|
+
]),
|
|
134
|
+
designatedRequirementSha256: Sha256Schema,
|
|
135
|
+
kind: z.literal("verified-macos-code-signature"),
|
|
136
|
+
teamIdentifier: z.literal("EQHXZ8M8AV"),
|
|
137
|
+
}),
|
|
138
|
+
z.strictObject({
|
|
139
|
+
kind: z.literal("test-only-unverified"),
|
|
140
|
+
}),
|
|
141
|
+
]),
|
|
142
|
+
sourceRoot: z.string().min(1).max(4_096).refine(
|
|
143
|
+
path => isAbsolute(path) && !path.includes("\0"),
|
|
144
|
+
"Browser runtime roots must be absolute and NUL-free.",
|
|
145
|
+
),
|
|
146
|
+
});
|
|
147
|
+
export type HtmlOverlayBrowserRuntimeBinding = Readonly<
|
|
148
|
+
z.infer<typeof HtmlOverlayBrowserRuntimeBindingSchema>
|
|
149
|
+
>;
|
|
150
|
+
|
|
151
|
+
function asciiCompare(left: string, right: string): number {
|
|
152
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function throwIfRuntimeInspectionAborted(signal: AbortSignal | undefined): void {
|
|
156
|
+
if (signal?.aborted !== true) return;
|
|
157
|
+
throw signal.reason instanceof Error
|
|
158
|
+
? signal.reason
|
|
159
|
+
: new ApplicationError("cancelled", "Browser runtime inspection was cancelled.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function runtimeEntryPath(root: string, entryPath: string): string {
|
|
163
|
+
return entryPath === "." ? root : join(root, ...entryPath.split("/"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function captureHtmlOverlayBrowserRuntimePathIdentity(
|
|
167
|
+
root: string,
|
|
168
|
+
entries: readonly HtmlOverlayBrowserRuntimeEntry[],
|
|
169
|
+
signal?: AbortSignal,
|
|
170
|
+
): Promise<readonly HtmlOverlayBrowserRuntimePathIdentity[]> {
|
|
171
|
+
const identities: HtmlOverlayBrowserRuntimePathIdentity[] = [];
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
174
|
+
const details = await lstat(runtimeEntryPath(root, entry.path), {
|
|
175
|
+
bigint: true,
|
|
176
|
+
});
|
|
177
|
+
identities.push({
|
|
178
|
+
ctimeNs: details.ctimeNs.toString(),
|
|
179
|
+
dev: details.dev.toString(),
|
|
180
|
+
ino: details.ino.toString(),
|
|
181
|
+
mode: Number(details.mode & 0o177777n),
|
|
182
|
+
path: entry.path,
|
|
183
|
+
size: details.size.toString(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return identities;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function runtimeRootSha256(
|
|
190
|
+
input: Omit<HtmlOverlayBrowserRuntimeManifest, "rootSha256">,
|
|
191
|
+
): string {
|
|
192
|
+
return createHash("sha256").update(canonicalJson({
|
|
193
|
+
domain: "slopcamera.html-overlay-browser-runtime/v1",
|
|
194
|
+
...input,
|
|
195
|
+
})).digest("hex");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function pathWithinRoot(root: string, candidate: string): boolean {
|
|
199
|
+
const fromRoot = relative(root, candidate);
|
|
200
|
+
return fromRoot === ""
|
|
201
|
+
|| (!fromRoot.startsWith(`..${sep}`) && fromRoot !== ".." && !isAbsolute(fromRoot));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function hashStableFile(
|
|
205
|
+
absolutePath: string,
|
|
206
|
+
relativePath: string,
|
|
207
|
+
signal?: AbortSignal,
|
|
208
|
+
): Promise<Readonly<{ bytes: number; mode: number; sha256: string }>> {
|
|
209
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
210
|
+
const lexicalBefore = await lstat(absolutePath);
|
|
211
|
+
if (lexicalBefore.isSymbolicLink() || !lexicalBefore.isFile()) {
|
|
212
|
+
throw new ApplicationError(
|
|
213
|
+
"conflict",
|
|
214
|
+
`Browser runtime file changed while it was inspected: ${relativePath}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const handle = await open(
|
|
218
|
+
absolutePath,
|
|
219
|
+
constants.O_RDONLY
|
|
220
|
+
| (constants.O_NOFOLLOW ?? 0)
|
|
221
|
+
| (constants.O_NONBLOCK ?? 0),
|
|
222
|
+
);
|
|
223
|
+
try {
|
|
224
|
+
const before = await handle.stat();
|
|
225
|
+
if (
|
|
226
|
+
!before.isFile()
|
|
227
|
+
|| !Number.isSafeInteger(before.size)
|
|
228
|
+
|| before.size < 0
|
|
229
|
+
|| before.size > MAXIMUM_RUNTIME_BYTES
|
|
230
|
+
) {
|
|
231
|
+
throw new ApplicationError(
|
|
232
|
+
"unsafe-path",
|
|
233
|
+
`Browser runtime contains an invalid or oversized file: ${relativePath}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const hash = createHash("sha256");
|
|
237
|
+
const buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);
|
|
238
|
+
let offset = 0;
|
|
239
|
+
while (offset < before.size) {
|
|
240
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
241
|
+
const result = await handle.read(
|
|
242
|
+
buffer,
|
|
243
|
+
0,
|
|
244
|
+
Math.min(buffer.byteLength, before.size - offset),
|
|
245
|
+
offset,
|
|
246
|
+
);
|
|
247
|
+
if (result.bytesRead === 0) break;
|
|
248
|
+
hash.update(buffer.subarray(0, result.bytesRead));
|
|
249
|
+
offset += result.bytesRead;
|
|
250
|
+
}
|
|
251
|
+
const after = await handle.stat();
|
|
252
|
+
const lexicalAfter = await lstat(absolutePath);
|
|
253
|
+
if (
|
|
254
|
+
offset !== before.size
|
|
255
|
+
|| before.dev !== after.dev
|
|
256
|
+
|| before.ino !== after.ino
|
|
257
|
+
|| before.size !== after.size
|
|
258
|
+
|| before.mode !== after.mode
|
|
259
|
+
|| before.mtimeMs !== after.mtimeMs
|
|
260
|
+
|| lexicalBefore.dev !== lexicalAfter.dev
|
|
261
|
+
|| lexicalBefore.ino !== lexicalAfter.ino
|
|
262
|
+
|| lexicalBefore.size !== lexicalAfter.size
|
|
263
|
+
|| lexicalBefore.mode !== lexicalAfter.mode
|
|
264
|
+
|| lexicalBefore.mtimeMs !== lexicalAfter.mtimeMs
|
|
265
|
+
) {
|
|
266
|
+
throw new ApplicationError(
|
|
267
|
+
"conflict",
|
|
268
|
+
`Browser runtime file changed while it was hashed: ${relativePath}`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
bytes: before.size,
|
|
273
|
+
mode: before.mode & 0o7777,
|
|
274
|
+
sha256: hash.digest("hex"),
|
|
275
|
+
};
|
|
276
|
+
} finally {
|
|
277
|
+
await handle.close();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function inspectEntry(
|
|
282
|
+
root: string,
|
|
283
|
+
absolutePath: string,
|
|
284
|
+
relativePath: string,
|
|
285
|
+
entries: HtmlOverlayBrowserRuntimeEntry[],
|
|
286
|
+
signal?: AbortSignal,
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
289
|
+
if (entries.length >= MAXIMUM_RUNTIME_ENTRIES) {
|
|
290
|
+
throw new ApplicationError(
|
|
291
|
+
"invalid-data",
|
|
292
|
+
`Browser runtime exceeds ${String(MAXIMUM_RUNTIME_ENTRIES)} entries.`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const details = await lstat(absolutePath);
|
|
296
|
+
const mode = details.mode & 0o7777;
|
|
297
|
+
if (details.isSymbolicLink()) {
|
|
298
|
+
const target = await readlink(absolutePath);
|
|
299
|
+
if (
|
|
300
|
+
target.length === 0
|
|
301
|
+
|| target.length > 4_096
|
|
302
|
+
|| target.includes("\0")
|
|
303
|
+
|| isAbsolute(target)
|
|
304
|
+
|| !pathWithinRoot(root, resolve(dirname(absolutePath), target))
|
|
305
|
+
) {
|
|
306
|
+
throw new ApplicationError(
|
|
307
|
+
"unsafe-path",
|
|
308
|
+
`Browser runtime symlink escapes its copied root: ${relativePath}`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
entries.push({ kind: "symlink", mode, path: relativePath, target });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (details.isFile()) {
|
|
315
|
+
const identity = await hashStableFile(absolutePath, relativePath, signal);
|
|
316
|
+
entries.push({
|
|
317
|
+
bytes: identity.bytes,
|
|
318
|
+
kind: "file",
|
|
319
|
+
mode: identity.mode,
|
|
320
|
+
path: relativePath,
|
|
321
|
+
sha256: identity.sha256,
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!details.isDirectory()) {
|
|
326
|
+
throw new ApplicationError(
|
|
327
|
+
"unsafe-path",
|
|
328
|
+
`Browser runtime contains an unsupported filesystem entry: ${relativePath}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
entries.push({ kind: "directory", mode, path: relativePath });
|
|
332
|
+
const names = (await readdir(absolutePath)).sort(asciiCompare);
|
|
333
|
+
for (const name of names) {
|
|
334
|
+
if (name === "." || name === ".." || name.includes("/") || name.includes("\0")) {
|
|
335
|
+
throw new ApplicationError("unsafe-path", "Browser runtime contains an unsafe path name.");
|
|
336
|
+
}
|
|
337
|
+
await inspectEntry(
|
|
338
|
+
root,
|
|
339
|
+
join(absolutePath, name),
|
|
340
|
+
relativePath === "." ? name : `${relativePath}/${name}`,
|
|
341
|
+
entries,
|
|
342
|
+
signal,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function appBundleRoot(executablePath: string): string | undefined {
|
|
348
|
+
let candidate = dirname(executablePath);
|
|
349
|
+
while (true) {
|
|
350
|
+
if (basename(candidate).endsWith(".app")) return candidate;
|
|
351
|
+
const parent = dirname(candidate);
|
|
352
|
+
if (parent === candidate) return undefined;
|
|
353
|
+
candidate = parent;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const MACH_O_MAGICS = new Set([
|
|
358
|
+
"cafebabe",
|
|
359
|
+
"cafebabf",
|
|
360
|
+
"bebafeca",
|
|
361
|
+
"bfbafeca",
|
|
362
|
+
"cefaedfe",
|
|
363
|
+
"cffaedfe",
|
|
364
|
+
"feedface",
|
|
365
|
+
"feedfacf",
|
|
366
|
+
]);
|
|
367
|
+
const SUPPORTED_GOOGLE_CHROME_IDENTIFIERS = new Set([
|
|
368
|
+
"com.google.Chrome",
|
|
369
|
+
"com.google.Chrome.beta",
|
|
370
|
+
"com.google.Chrome.dev",
|
|
371
|
+
"com.google.Chrome.canary",
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
async function codesignOutput(
|
|
375
|
+
argv: readonly string[],
|
|
376
|
+
signal?: AbortSignal,
|
|
377
|
+
): Promise<Readonly<{ exitCode: number; output: string }>> {
|
|
378
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
379
|
+
const subprocess = Bun.spawn(["/usr/bin/codesign", ...argv], {
|
|
380
|
+
env: {
|
|
381
|
+
LANG: "en_US.UTF-8",
|
|
382
|
+
LC_ALL: "en_US.UTF-8",
|
|
383
|
+
PATH: "/usr/bin:/bin",
|
|
384
|
+
},
|
|
385
|
+
stderr: "pipe",
|
|
386
|
+
stdin: "ignore",
|
|
387
|
+
stdout: "pipe",
|
|
388
|
+
});
|
|
389
|
+
let timedOut = false;
|
|
390
|
+
const timeout = setTimeout(() => {
|
|
391
|
+
timedOut = true;
|
|
392
|
+
subprocess.kill("SIGKILL");
|
|
393
|
+
}, CODESIGN_TIMEOUT_MS);
|
|
394
|
+
const abort = (): void => subprocess.kill("SIGKILL");
|
|
395
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
396
|
+
if (signal?.aborted === true) abort();
|
|
397
|
+
const readBounded = async (
|
|
398
|
+
stream: ReadableStream<Uint8Array>,
|
|
399
|
+
): Promise<string> => {
|
|
400
|
+
const reader = stream.getReader();
|
|
401
|
+
const chunks: Uint8Array[] = [];
|
|
402
|
+
let bytes = 0;
|
|
403
|
+
try {
|
|
404
|
+
while (true) {
|
|
405
|
+
const result = await reader.read();
|
|
406
|
+
if (result.done) break;
|
|
407
|
+
bytes += result.value.byteLength;
|
|
408
|
+
if (bytes > CODESIGN_MAXIMUM_OUTPUT_BYTES) {
|
|
409
|
+
subprocess.kill("SIGKILL");
|
|
410
|
+
throw new ApplicationError(
|
|
411
|
+
"invalid-data",
|
|
412
|
+
"Browser code-signature output is oversized.",
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
chunks.push(result.value);
|
|
416
|
+
}
|
|
417
|
+
} finally {
|
|
418
|
+
reader.releaseLock();
|
|
419
|
+
}
|
|
420
|
+
return Buffer.concat(chunks, bytes).toString("utf8");
|
|
421
|
+
};
|
|
422
|
+
let exitCode: number;
|
|
423
|
+
let stderr: string;
|
|
424
|
+
let stdout: string;
|
|
425
|
+
try {
|
|
426
|
+
[exitCode, stderr, stdout] = await Promise.all([
|
|
427
|
+
subprocess.exited,
|
|
428
|
+
readBounded(subprocess.stderr),
|
|
429
|
+
readBounded(subprocess.stdout),
|
|
430
|
+
]);
|
|
431
|
+
} finally {
|
|
432
|
+
clearTimeout(timeout);
|
|
433
|
+
signal?.removeEventListener("abort", abort);
|
|
434
|
+
}
|
|
435
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
436
|
+
if (timedOut) {
|
|
437
|
+
throw new ApplicationError(
|
|
438
|
+
"unavailable",
|
|
439
|
+
`Browser code-signature verification exceeded ${String(CODESIGN_TIMEOUT_MS)}ms.`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const output = `${stdout}\n${stderr}`.trim();
|
|
443
|
+
return { exitCode, output };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function inspectSupportedHtmlOverlayMacBrowserProvenance(
|
|
447
|
+
bundleRoot: string,
|
|
448
|
+
executablePath: string,
|
|
449
|
+
signal?: AbortSignal,
|
|
450
|
+
): Promise<Readonly<{
|
|
451
|
+
bundleIdentifier: "com.google.Chrome" | "com.google.Chrome.beta" | "com.google.Chrome.dev" | "com.google.Chrome.canary";
|
|
452
|
+
designatedRequirementSha256: string;
|
|
453
|
+
kind: "verified-macos-code-signature";
|
|
454
|
+
teamIdentifier: "EQHXZ8M8AV";
|
|
455
|
+
}>> {
|
|
456
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
457
|
+
const handle = await open(executablePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
458
|
+
try {
|
|
459
|
+
const magic = Buffer.alloc(4);
|
|
460
|
+
const { bytesRead } = await handle.read(magic, 0, magic.byteLength, 0);
|
|
461
|
+
if (bytesRead !== magic.byteLength || !MACH_O_MAGICS.has(magic.toString("hex"))) {
|
|
462
|
+
throw new ApplicationError(
|
|
463
|
+
"unsafe-path",
|
|
464
|
+
"HTML browser app launcher is not a native Mach-O executable.",
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
} finally {
|
|
468
|
+
await handle.close();
|
|
469
|
+
}
|
|
470
|
+
if (process.platform !== "darwin") {
|
|
471
|
+
throw new ApplicationError(
|
|
472
|
+
"unavailable",
|
|
473
|
+
"This desktop build currently requires a verified macOS Chrome app runtime.",
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const [verification, details, requirement] = await Promise.all([
|
|
477
|
+
codesignOutput(["--verify", "--deep", "--verbose=2", bundleRoot], signal),
|
|
478
|
+
codesignOutput(["-dv", "--verbose=4", bundleRoot], signal),
|
|
479
|
+
codesignOutput(["-dr", "-", bundleRoot], signal),
|
|
480
|
+
]);
|
|
481
|
+
if (verification.exitCode !== 0 || details.exitCode !== 0 || requirement.exitCode !== 0) {
|
|
482
|
+
throw new ApplicationError(
|
|
483
|
+
"conflict",
|
|
484
|
+
"HTML browser app bundle failed deep code-signature verification.",
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const bundleIdentifier = /^Identifier=(.+)$/mu.exec(details.output)?.[1]?.trim();
|
|
488
|
+
const teamIdentifier = /^TeamIdentifier=(.+)$/mu.exec(details.output)?.[1]?.trim();
|
|
489
|
+
const signedExecutable = /^Executable=(.+)$/mu.exec(details.output)?.[1]?.trim();
|
|
490
|
+
const designatedRequirement = /^designated => (.+)$/mu.exec(
|
|
491
|
+
requirement.output,
|
|
492
|
+
)?.[1]?.trim();
|
|
493
|
+
const physicalExecutablePath = await realpath(executablePath);
|
|
494
|
+
const physicalSignedExecutable = signedExecutable === undefined
|
|
495
|
+
? undefined
|
|
496
|
+
: await realpath(signedExecutable).catch(() => undefined);
|
|
497
|
+
if (
|
|
498
|
+
bundleIdentifier === undefined
|
|
499
|
+
|| !SUPPORTED_GOOGLE_CHROME_IDENTIFIERS.has(bundleIdentifier)
|
|
500
|
+
|| teamIdentifier !== "EQHXZ8M8AV"
|
|
501
|
+
|| physicalSignedExecutable !== physicalExecutablePath
|
|
502
|
+
|| designatedRequirement === undefined
|
|
503
|
+
) {
|
|
504
|
+
throw new ApplicationError(
|
|
505
|
+
"unsafe-path",
|
|
506
|
+
"HTML browser app is not a directly launched supported Google Chrome distribution.",
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
bundleIdentifier: bundleIdentifier as "com.google.Chrome" | "com.google.Chrome.beta" | "com.google.Chrome.dev" | "com.google.Chrome.canary",
|
|
511
|
+
designatedRequirementSha256: createHash("sha256")
|
|
512
|
+
.update(designatedRequirement)
|
|
513
|
+
.digest("hex"),
|
|
514
|
+
kind: "verified-macos-code-signature",
|
|
515
|
+
teamIdentifier: "EQHXZ8M8AV",
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export async function assertSupportedHtmlOverlayBrowserExecutablePath(
|
|
520
|
+
executablePathInput: string,
|
|
521
|
+
signal?: AbortSignal,
|
|
522
|
+
): Promise<string> {
|
|
523
|
+
const executablePath = await realpath(executablePathInput);
|
|
524
|
+
const bundleRoot = appBundleRoot(executablePath);
|
|
525
|
+
if (bundleRoot === undefined) {
|
|
526
|
+
throw new ApplicationError(
|
|
527
|
+
"unsafe-path",
|
|
528
|
+
"HTML browser candidate is not inside a supported .app runtime.",
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
const executableRelativePath = relative(bundleRoot, executablePath)
|
|
532
|
+
.split(sep)
|
|
533
|
+
.join("/");
|
|
534
|
+
if (!/^Contents\/MacOS\/[^/]+$/u.test(executableRelativePath)) {
|
|
535
|
+
throw new ApplicationError(
|
|
536
|
+
"unsafe-path",
|
|
537
|
+
"HTML browser candidate is not its app bundle's direct native executable.",
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
await inspectSupportedHtmlOverlayMacBrowserProvenance(
|
|
541
|
+
bundleRoot,
|
|
542
|
+
executablePath,
|
|
543
|
+
signal,
|
|
544
|
+
);
|
|
545
|
+
return executablePath;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export async function inspectHtmlOverlayBrowserRuntime(
|
|
549
|
+
sourceRoot: string,
|
|
550
|
+
layout: HtmlOverlayBrowserRuntimeManifest["layout"],
|
|
551
|
+
executableRelativePath: string,
|
|
552
|
+
signal?: AbortSignal,
|
|
553
|
+
): Promise<HtmlOverlayBrowserRuntimeManifest> {
|
|
554
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
555
|
+
const entries: HtmlOverlayBrowserRuntimeEntry[] = [];
|
|
556
|
+
await inspectEntry(sourceRoot, sourceRoot, ".", entries, signal);
|
|
557
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
558
|
+
entries.sort((left, right) => asciiCompare(left.path, right.path));
|
|
559
|
+
const totalBytes = entries.reduce(
|
|
560
|
+
(total, entry) => total + (entry.kind === "file" ? entry.bytes : 0),
|
|
561
|
+
0,
|
|
562
|
+
);
|
|
563
|
+
if (!Number.isSafeInteger(totalBytes) || totalBytes < 1 || totalBytes > MAXIMUM_RUNTIME_BYTES) {
|
|
564
|
+
throw new ApplicationError("invalid-data", "Browser runtime has an invalid total byte count.");
|
|
565
|
+
}
|
|
566
|
+
const manifestWithoutRoot = {
|
|
567
|
+
entries,
|
|
568
|
+
executableRelativePath,
|
|
569
|
+
layout,
|
|
570
|
+
schemaVersion: 1 as const,
|
|
571
|
+
totalBytes,
|
|
572
|
+
};
|
|
573
|
+
return HtmlOverlayBrowserRuntimeManifestSchema.parse({
|
|
574
|
+
...manifestWithoutRoot,
|
|
575
|
+
rootSha256: runtimeRootSha256(manifestWithoutRoot),
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function assertHtmlOverlayBrowserRuntimeManifest(
|
|
580
|
+
actual: HtmlOverlayBrowserRuntimeManifest,
|
|
581
|
+
expected: HtmlOverlayBrowserRuntimeManifest,
|
|
582
|
+
label: string,
|
|
583
|
+
): void {
|
|
584
|
+
if (canonicalJson(actual) !== canonicalJson(expected)) {
|
|
585
|
+
throw new ApplicationError(
|
|
586
|
+
"conflict",
|
|
587
|
+
`Browser runtime changed ${label}; refusing to launch unbound code.`,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export async function bindHtmlOverlayBrowserRuntime(
|
|
593
|
+
capabilityInput: ExactCapabilityBinding,
|
|
594
|
+
signal?: AbortSignal,
|
|
595
|
+
options: Readonly<{
|
|
596
|
+
readonly allowUnverifiedRuntimeForTesting?: boolean;
|
|
597
|
+
readonly duringProvenanceInspectionForTesting?: () => Promise<void>;
|
|
598
|
+
}> = {},
|
|
599
|
+
): Promise<HtmlOverlayBrowserRuntimeBinding> {
|
|
600
|
+
throwIfRuntimeInspectionAborted(signal);
|
|
601
|
+
const capability = ExactCapabilityBindingSchema.parse(capabilityInput);
|
|
602
|
+
const bundleRoot = appBundleRoot(capability.executablePath);
|
|
603
|
+
if (bundleRoot === undefined && options.allowUnverifiedRuntimeForTesting !== true) {
|
|
604
|
+
throw new ApplicationError(
|
|
605
|
+
"unsafe-path",
|
|
606
|
+
"HTML browser must resolve directly to a supported .app runtime; wrapper and unbound distribution launchers are rejected.",
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
const layout = bundleRoot === undefined
|
|
610
|
+
? "single-executable" as const
|
|
611
|
+
: "macos-app-bundle" as const;
|
|
612
|
+
const sourceRoot = bundleRoot ?? capability.executablePath;
|
|
613
|
+
const executableRelativePath = bundleRoot === undefined
|
|
614
|
+
? "."
|
|
615
|
+
: relative(bundleRoot, capability.executablePath).split(sep).join("/");
|
|
616
|
+
if (!isSafeRelativeRuntimePath(executableRelativePath)) {
|
|
617
|
+
throw new ApplicationError("unsafe-path", "Browser executable is outside its runtime root.");
|
|
618
|
+
}
|
|
619
|
+
if (
|
|
620
|
+
layout === "macos-app-bundle"
|
|
621
|
+
&& !/^Contents\/MacOS\/[^/]+$/u.test(executableRelativePath)
|
|
622
|
+
) {
|
|
623
|
+
throw new ApplicationError(
|
|
624
|
+
"unsafe-path",
|
|
625
|
+
"HTML browser must be the direct native executable in its .app Contents/MacOS directory.",
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const manifestBefore = await inspectHtmlOverlayBrowserRuntime(
|
|
629
|
+
sourceRoot,
|
|
630
|
+
layout,
|
|
631
|
+
executableRelativePath,
|
|
632
|
+
signal,
|
|
633
|
+
);
|
|
634
|
+
const identityBefore = await captureHtmlOverlayBrowserRuntimePathIdentity(
|
|
635
|
+
sourceRoot,
|
|
636
|
+
manifestBefore.entries,
|
|
637
|
+
signal,
|
|
638
|
+
);
|
|
639
|
+
await options.duringProvenanceInspectionForTesting?.();
|
|
640
|
+
const provenance = options.allowUnverifiedRuntimeForTesting === true
|
|
641
|
+
? { kind: "test-only-unverified" as const }
|
|
642
|
+
: await inspectSupportedHtmlOverlayMacBrowserProvenance(
|
|
643
|
+
sourceRoot,
|
|
644
|
+
capability.executablePath,
|
|
645
|
+
signal,
|
|
646
|
+
);
|
|
647
|
+
const manifest = await inspectHtmlOverlayBrowserRuntime(
|
|
648
|
+
sourceRoot,
|
|
649
|
+
layout,
|
|
650
|
+
executableRelativePath,
|
|
651
|
+
signal,
|
|
652
|
+
);
|
|
653
|
+
const identityAfter = await captureHtmlOverlayBrowserRuntimePathIdentity(
|
|
654
|
+
sourceRoot,
|
|
655
|
+
manifest.entries,
|
|
656
|
+
signal,
|
|
657
|
+
);
|
|
658
|
+
if (
|
|
659
|
+
canonicalJson(manifest) !== canonicalJson(manifestBefore)
|
|
660
|
+
|| canonicalJson(identityAfter) !== canonicalJson(identityBefore)
|
|
661
|
+
) {
|
|
662
|
+
throw new ApplicationError(
|
|
663
|
+
"conflict",
|
|
664
|
+
"Browser runtime changed while its signed provenance was verified.",
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
const executable = manifest.entries.find(
|
|
668
|
+
entry => entry.path === executableRelativePath,
|
|
669
|
+
);
|
|
670
|
+
if (
|
|
671
|
+
executable?.kind !== "file"
|
|
672
|
+
|| (executable.mode & 0o111) === 0
|
|
673
|
+
|| executable.bytes !== capability.bytes
|
|
674
|
+
|| executable.sha256 !== capability.executableSha256
|
|
675
|
+
) {
|
|
676
|
+
throw new ApplicationError(
|
|
677
|
+
"conflict",
|
|
678
|
+
"Browser executable does not match the complete runtime manifest.",
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
return HtmlOverlayBrowserRuntimeBindingSchema.parse({
|
|
682
|
+
capability,
|
|
683
|
+
manifest,
|
|
684
|
+
provenance,
|
|
685
|
+
sourceRoot,
|
|
686
|
+
});
|
|
687
|
+
}
|