@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,538 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
APPLE_EMOJI_ASSET_ROOT,
|
|
6
|
+
APPLE_EMOJI_CATALOG_PATH,
|
|
7
|
+
APPLE_EMOJI_MANIFEST_PATH,
|
|
8
|
+
EMOJI_ASSET_GENERATION_COMMAND,
|
|
9
|
+
} from "../core/emoji";
|
|
10
|
+
import { CliError } from "./errors";
|
|
11
|
+
|
|
12
|
+
export const EMOJI_GENERATION_COMMAND = EMOJI_ASSET_GENERATION_COMMAND;
|
|
13
|
+
|
|
14
|
+
export type EmojiVariant = "color" | "duotone";
|
|
15
|
+
export type EmojiAssetProvider = "apple-emoji-pack" | "brand-catalog";
|
|
16
|
+
|
|
17
|
+
interface EmojiCatalogItem {
|
|
18
|
+
readonly emoji: string;
|
|
19
|
+
readonly group: string;
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly sourcePngSha256: string;
|
|
23
|
+
readonly subgroup: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface EmojiManifestItem {
|
|
27
|
+
readonly colorSvgSha256: string;
|
|
28
|
+
readonly duotoneSvgSha256: string;
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly sourcePngSha256: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface EmojiCatalog {
|
|
34
|
+
readonly fontSha256: string;
|
|
35
|
+
readonly items: readonly EmojiCatalogItem[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface EmojiManifest {
|
|
39
|
+
readonly catalogSha256: string;
|
|
40
|
+
readonly fontSha256: string;
|
|
41
|
+
readonly items: readonly EmojiManifestItem[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface BrandEmojiItem {
|
|
45
|
+
readonly variant: EmojiVariant;
|
|
46
|
+
readonly bytes: number;
|
|
47
|
+
readonly codePointId: string;
|
|
48
|
+
readonly domain: string;
|
|
49
|
+
readonly emoji: string;
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly sha256: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface EmojiAssetState {
|
|
55
|
+
readonly assetRoot: string;
|
|
56
|
+
readonly catalogCount: number;
|
|
57
|
+
readonly catalogPath: string;
|
|
58
|
+
readonly generationCommand: string;
|
|
59
|
+
readonly installedCount: number;
|
|
60
|
+
readonly manifestPath: string;
|
|
61
|
+
readonly provenance: "checked" | "missing" | "invalid";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface EmojiSearchResult {
|
|
65
|
+
readonly available: Readonly<Record<EmojiVariant, boolean>>;
|
|
66
|
+
readonly emoji: string;
|
|
67
|
+
readonly group: string;
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly provider: EmojiAssetProvider;
|
|
71
|
+
readonly subgroup: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ResolvedEmojiAsset extends EmojiSearchResult {
|
|
75
|
+
readonly path: string;
|
|
76
|
+
readonly sha256: string;
|
|
77
|
+
readonly variant: EmojiVariant;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function record(value: unknown): Readonly<Record<string, unknown>> {
|
|
81
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
82
|
+
throw new CliError("invalid-data", "Emoji metadata must be a JSON object.");
|
|
83
|
+
}
|
|
84
|
+
return value as Readonly<Record<string, unknown>>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stringField(value: Readonly<Record<string, unknown>>, field: string): string {
|
|
88
|
+
const item = value[field];
|
|
89
|
+
if (typeof item !== "string" || item === "") {
|
|
90
|
+
throw new CliError("invalid-data", `Emoji metadata field ${field} must be a non-empty string.`);
|
|
91
|
+
}
|
|
92
|
+
return item;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function arrayField(value: Readonly<Record<string, unknown>>, field: string): readonly unknown[] {
|
|
96
|
+
const item = value[field];
|
|
97
|
+
if (!Array.isArray(item)) {
|
|
98
|
+
throw new CliError("invalid-data", `Emoji metadata field ${field} must be an array.`);
|
|
99
|
+
}
|
|
100
|
+
return item;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function integerField(value: Readonly<Record<string, unknown>>, field: string): number {
|
|
104
|
+
const item = value[field];
|
|
105
|
+
if (typeof item !== "number" || !Number.isSafeInteger(item) || item <= 0) {
|
|
106
|
+
throw new CliError("invalid-data", `Emoji metadata field ${field} must be a positive safe integer.`);
|
|
107
|
+
}
|
|
108
|
+
return item;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseCatalog(value: unknown): EmojiCatalog {
|
|
112
|
+
const root = record(value);
|
|
113
|
+
const font = record(root.font);
|
|
114
|
+
return {
|
|
115
|
+
fontSha256: stringField(font, "sha256"),
|
|
116
|
+
items: arrayField(root, "items").map((rawItem) => {
|
|
117
|
+
const item = record(rawItem);
|
|
118
|
+
return {
|
|
119
|
+
emoji: stringField(item, "emoji"),
|
|
120
|
+
group: stringField(item, "group"),
|
|
121
|
+
id: stringField(item, "id"),
|
|
122
|
+
name: stringField(item, "name"),
|
|
123
|
+
sourcePngSha256: stringField(item, "sourcePngSha256"),
|
|
124
|
+
subgroup: stringField(item, "subgroup"),
|
|
125
|
+
};
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseManifest(value: unknown): EmojiManifest {
|
|
131
|
+
const root = record(value);
|
|
132
|
+
const provenance = record(root.provenance);
|
|
133
|
+
return {
|
|
134
|
+
catalogSha256: stringField(root, "catalogSha256"),
|
|
135
|
+
fontSha256: stringField(provenance, "fontSha256"),
|
|
136
|
+
items: arrayField(root, "items").map((rawItem) => {
|
|
137
|
+
const item = record(rawItem);
|
|
138
|
+
return {
|
|
139
|
+
colorSvgSha256: stringField(item, "colorSvgSha256"),
|
|
140
|
+
duotoneSvgSha256: stringField(item, "duotoneSvgSha256"),
|
|
141
|
+
id: stringField(item, "id"),
|
|
142
|
+
sourcePngSha256: stringField(item, "sourcePngSha256"),
|
|
143
|
+
};
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function fileSha256(path: string): Promise<string> {
|
|
149
|
+
return createHash("sha256").update(await readFile(path)).digest("hex");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
153
|
+
try {
|
|
154
|
+
return (await stat(path)).isFile();
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function paths(repositoryRoot: string): {
|
|
161
|
+
readonly assetRoot: string;
|
|
162
|
+
readonly catalogPath: string;
|
|
163
|
+
readonly manifestPath: string;
|
|
164
|
+
} {
|
|
165
|
+
return {
|
|
166
|
+
assetRoot: join(repositoryRoot, APPLE_EMOJI_ASSET_ROOT),
|
|
167
|
+
catalogPath: join(repositoryRoot, APPLE_EMOJI_CATALOG_PATH),
|
|
168
|
+
manifestPath: join(repositoryRoot, APPLE_EMOJI_MANIFEST_PATH),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function brandPaths(repositoryRoot: string): { readonly assetRoot: string; readonly manifestPath: string } {
|
|
173
|
+
const assetRoot = join(repositoryRoot, "apps", "desktop", "assets", "brand-emoji");
|
|
174
|
+
return { assetRoot, manifestPath: join(assetRoot, "manifest.json") };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function readBrandItems(repositoryRoot: string): Promise<{
|
|
178
|
+
readonly assetRoot: string;
|
|
179
|
+
readonly items: readonly BrandEmojiItem[];
|
|
180
|
+
}> {
|
|
181
|
+
const localPaths = brandPaths(repositoryRoot);
|
|
182
|
+
let input: unknown;
|
|
183
|
+
try {
|
|
184
|
+
input = JSON.parse(await readFile(localPaths.manifestPath, "utf8")) as unknown;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw new CliError("invalid-data", `Brand emoji manifest is unavailable or invalid: ${String(error)}`);
|
|
187
|
+
}
|
|
188
|
+
const manifest = record(input);
|
|
189
|
+
if (manifest.formatVersion !== 1) throw new CliError("invalid-data", "Brand emoji manifest version must be 1.");
|
|
190
|
+
const items = arrayField(manifest, "assets").map((raw): BrandEmojiItem => {
|
|
191
|
+
const item = record(raw);
|
|
192
|
+
const domain = stringField(item, "domain");
|
|
193
|
+
const path = stringField(item, "path");
|
|
194
|
+
const sha256 = stringField(item, "sha256");
|
|
195
|
+
const codePointId = stringField(item, "codePointID");
|
|
196
|
+
const variant = stringField(item, "variant");
|
|
197
|
+
if (variant !== "color" && variant !== "duotone") {
|
|
198
|
+
throw new CliError("invalid-data", `Brand emoji variant is invalid for ${domain}.`);
|
|
199
|
+
}
|
|
200
|
+
if (!/^[a-z0-9.-]+\.svg$/u.test(path) || path !== `${domain}.svg`) {
|
|
201
|
+
throw new CliError("invalid-data", `Brand emoji asset path is invalid for ${domain}.`);
|
|
202
|
+
}
|
|
203
|
+
if (!/^[a-f0-9]{64}$/u.test(sha256) || !/^[a-f0-9]+(?:-[a-f0-9]+)*$/u.test(codePointId)) {
|
|
204
|
+
throw new CliError("invalid-data", `Brand emoji hashes or code points are invalid for ${domain}.`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
variant,
|
|
208
|
+
bytes: integerField(item, "bytes"),
|
|
209
|
+
codePointId,
|
|
210
|
+
domain,
|
|
211
|
+
emoji: stringField(item, "emoji"),
|
|
212
|
+
path,
|
|
213
|
+
sha256,
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
if (new Set(items.map(({ domain }) => domain)).size !== items.length) {
|
|
217
|
+
throw new CliError("invalid-data", "Brand emoji manifest contains duplicate domains.");
|
|
218
|
+
}
|
|
219
|
+
return { assetRoot: localPaths.assetRoot, items };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function readMetadata(repositoryRoot: string): Promise<{
|
|
223
|
+
readonly assetRoot: string;
|
|
224
|
+
readonly catalog: EmojiCatalog;
|
|
225
|
+
readonly catalogPath: string;
|
|
226
|
+
readonly manifest: EmojiManifest;
|
|
227
|
+
readonly manifestPath: string;
|
|
228
|
+
}> {
|
|
229
|
+
const localPaths = paths(repositoryRoot);
|
|
230
|
+
let catalogBytes: Buffer;
|
|
231
|
+
let manifestBytes: Buffer;
|
|
232
|
+
try {
|
|
233
|
+
[catalogBytes, manifestBytes] = await Promise.all([
|
|
234
|
+
readFile(localPaths.catalogPath),
|
|
235
|
+
readFile(localPaths.manifestPath),
|
|
236
|
+
]);
|
|
237
|
+
} catch {
|
|
238
|
+
throw new CliError(
|
|
239
|
+
"unavailable",
|
|
240
|
+
`Emoji catalog metadata is missing. Run: ${EMOJI_GENERATION_COMMAND}`,
|
|
241
|
+
{ generationCommand: EMOJI_GENERATION_COMMAND },
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
let catalogUnknown: unknown;
|
|
245
|
+
let manifestUnknown: unknown;
|
|
246
|
+
try {
|
|
247
|
+
catalogUnknown = JSON.parse(catalogBytes.toString());
|
|
248
|
+
manifestUnknown = JSON.parse(manifestBytes.toString());
|
|
249
|
+
} catch {
|
|
250
|
+
throw new CliError("invalid-data", "Emoji catalog metadata is not valid JSON.");
|
|
251
|
+
}
|
|
252
|
+
const catalog = parseCatalog(catalogUnknown);
|
|
253
|
+
const manifest = parseManifest(manifestUnknown);
|
|
254
|
+
const catalogSha256 = createHash("sha256").update(catalogBytes).digest("hex");
|
|
255
|
+
if (manifest.catalogSha256 !== catalogSha256 || manifest.fontSha256 !== catalog.fontSha256) {
|
|
256
|
+
throw new CliError(
|
|
257
|
+
"invalid-data",
|
|
258
|
+
`Emoji provenance is stale. Run: ${EMOJI_GENERATION_COMMAND}`,
|
|
259
|
+
{ generationCommand: EMOJI_GENERATION_COMMAND },
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (catalog.items.length !== manifest.items.length) {
|
|
263
|
+
throw new CliError("invalid-data", "Emoji catalog and asset manifest counts differ.");
|
|
264
|
+
}
|
|
265
|
+
const catalogById = new Map(catalog.items.map((item) => [item.id, item] as const));
|
|
266
|
+
for (const item of manifest.items) {
|
|
267
|
+
const catalogItem = catalogById.get(item.id);
|
|
268
|
+
if (catalogItem === undefined || catalogItem.sourcePngSha256 !== item.sourcePngSha256) {
|
|
269
|
+
throw new CliError("invalid-data", `Emoji manifest provenance differs for ${item.id}.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { ...localPaths, catalog, manifest };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function variantAvailability(
|
|
276
|
+
assetRoot: string,
|
|
277
|
+
id: string,
|
|
278
|
+
): Promise<Readonly<Record<EmojiVariant, boolean>>> {
|
|
279
|
+
const [color, duotone] = await Promise.all([
|
|
280
|
+
fileExists(join(assetRoot, id, "color.svg")),
|
|
281
|
+
fileExists(join(assetRoot, id, "duotone.svg")),
|
|
282
|
+
]);
|
|
283
|
+
return { color, duotone };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeHexQuery(query: string): string | undefined {
|
|
287
|
+
const normalized = query.trim().toLowerCase()
|
|
288
|
+
.replaceAll("u+", "")
|
|
289
|
+
.replaceAll("0x", "")
|
|
290
|
+
.replaceAll(/[_\s]+/gu, "-");
|
|
291
|
+
return /^[0-9a-f]{1,6}(?:-[0-9a-f]{1,6})*$/u.test(normalized) ? normalized : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function matchingItems(items: readonly EmojiCatalogItem[], query: string): readonly EmojiCatalogItem[] {
|
|
295
|
+
const trimmed = query.trim();
|
|
296
|
+
if (trimmed === "") throw new CliError("usage", "Emoji query cannot be empty.");
|
|
297
|
+
const normalizedHex = normalizeHexQuery(trimmed);
|
|
298
|
+
const exact = items.filter((item) =>
|
|
299
|
+
item.emoji === trimmed
|
|
300
|
+
|| item.id === normalizedHex
|
|
301
|
+
|| item.name.toLocaleLowerCase() === trimmed.toLocaleLowerCase()
|
|
302
|
+
);
|
|
303
|
+
if (exact.length > 0) return exact;
|
|
304
|
+
const folded = trimmed.toLocaleLowerCase();
|
|
305
|
+
return items.filter((item) =>
|
|
306
|
+
item.name.toLocaleLowerCase().includes(folded)
|
|
307
|
+
|| item.group.toLocaleLowerCase().includes(folded)
|
|
308
|
+
|| item.subgroup.toLocaleLowerCase().includes(folded)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function result(
|
|
313
|
+
item: EmojiCatalogItem,
|
|
314
|
+
available: Readonly<Record<EmojiVariant, boolean>>,
|
|
315
|
+
): EmojiSearchResult {
|
|
316
|
+
return {
|
|
317
|
+
available,
|
|
318
|
+
emoji: item.emoji,
|
|
319
|
+
group: item.group,
|
|
320
|
+
id: item.id,
|
|
321
|
+
name: item.name,
|
|
322
|
+
provider: "apple-emoji-pack",
|
|
323
|
+
subgroup: item.subgroup,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function brandResult(item: BrandEmojiItem): EmojiSearchResult {
|
|
328
|
+
return {
|
|
329
|
+
available: { color: item.variant === "color", duotone: item.variant === "duotone" },
|
|
330
|
+
emoji: item.emoji,
|
|
331
|
+
group: "Bundled brands",
|
|
332
|
+
id: item.codePointId,
|
|
333
|
+
name: item.domain,
|
|
334
|
+
provider: "brand-catalog",
|
|
335
|
+
subgroup: item.domain,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function matchesBrandDomain(domain: string, folded: string): boolean {
|
|
340
|
+
return domain === folded || domain.replace(/\.com$/u, "") === folded || domain.replaceAll(".", "") === folded;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function matchingBrandItems(items: readonly BrandEmojiItem[], query: string): readonly BrandEmojiItem[] {
|
|
344
|
+
const trimmed = query.trim().replace(/^brand:/iu, "");
|
|
345
|
+
if (trimmed === "") throw new CliError("usage", "Emoji query cannot be empty.");
|
|
346
|
+
const folded = trimmed.toLocaleLowerCase();
|
|
347
|
+
const normalizedHex = normalizeHexQuery(trimmed);
|
|
348
|
+
const exact = items.filter((item) =>
|
|
349
|
+
item.emoji === trimmed
|
|
350
|
+
|| item.codePointId === normalizedHex
|
|
351
|
+
|| matchesBrandDomain(item.domain, folded)
|
|
352
|
+
);
|
|
353
|
+
return exact.length > 0 ? exact : items.filter((item) => item.domain.includes(folded));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export async function inspectEmojiAssets(repositoryRoot: string): Promise<EmojiAssetState> {
|
|
357
|
+
const localPaths = paths(repositoryRoot);
|
|
358
|
+
try {
|
|
359
|
+
const metadata = await readMetadata(repositoryRoot);
|
|
360
|
+
let installedCount = 0;
|
|
361
|
+
try {
|
|
362
|
+
const entries = await readdir(metadata.assetRoot, { withFileTypes: true });
|
|
363
|
+
installedCount = entries.filter((entry) => entry.isDirectory()).length;
|
|
364
|
+
} catch {
|
|
365
|
+
installedCount = 0;
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
...localPaths,
|
|
369
|
+
catalogCount: metadata.catalog.items.length,
|
|
370
|
+
generationCommand: EMOJI_GENERATION_COMMAND,
|
|
371
|
+
installedCount,
|
|
372
|
+
provenance: "checked",
|
|
373
|
+
};
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return {
|
|
376
|
+
...localPaths,
|
|
377
|
+
catalogCount: 0,
|
|
378
|
+
generationCommand: EMOJI_GENERATION_COMMAND,
|
|
379
|
+
installedCount: 0,
|
|
380
|
+
provenance: error instanceof CliError && error.code === "unavailable" ? "missing" : "invalid",
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export async function searchEmojiAssets(
|
|
386
|
+
repositoryRoot: string,
|
|
387
|
+
query: string,
|
|
388
|
+
limit: number,
|
|
389
|
+
variant?: EmojiVariant,
|
|
390
|
+
provider: EmojiAssetProvider | "all" = "all",
|
|
391
|
+
): Promise<readonly EmojiSearchResult[]> {
|
|
392
|
+
const results: EmojiSearchResult[] = [];
|
|
393
|
+
if (provider === "all" || provider === "apple-emoji-pack") {
|
|
394
|
+
try {
|
|
395
|
+
const metadata = await readMetadata(repositoryRoot);
|
|
396
|
+
const matches = matchingItems(metadata.catalog.items, query).slice(0, limit);
|
|
397
|
+
results.push(...await Promise.all(matches.map(async (item) => {
|
|
398
|
+
const availability = await variantAvailability(metadata.assetRoot, item.id);
|
|
399
|
+
if (variant === undefined) return result(item, availability);
|
|
400
|
+
return result(item, {
|
|
401
|
+
color: variant === "color" && availability.color,
|
|
402
|
+
duotone: variant === "duotone" && availability.duotone,
|
|
403
|
+
});
|
|
404
|
+
})));
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (provider === "apple-emoji-pack" || !(error instanceof CliError && error.code === "unavailable")) throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (provider === "all" || provider === "brand-catalog") {
|
|
410
|
+
const brand = await readBrandItems(repositoryRoot);
|
|
411
|
+
results.push(...matchingBrandItems(brand.items, query).map(brandResult).filter((item) =>
|
|
412
|
+
variant === undefined || item.available[variant]
|
|
413
|
+
));
|
|
414
|
+
}
|
|
415
|
+
return results.sort((left, right) =>
|
|
416
|
+
Number(right.name.toLocaleLowerCase() === query.toLocaleLowerCase())
|
|
417
|
+
- Number(left.name.toLocaleLowerCase() === query.toLocaleLowerCase())
|
|
418
|
+
|| left.provider.localeCompare(right.provider)
|
|
419
|
+
|| left.name.localeCompare(right.name)
|
|
420
|
+
).slice(0, limit);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function resolveAppleEmojiAsset(
|
|
424
|
+
repositoryRoot: string,
|
|
425
|
+
query: string,
|
|
426
|
+
variant: EmojiVariant = "color",
|
|
427
|
+
): Promise<ResolvedEmojiAsset> {
|
|
428
|
+
const metadata = await readMetadata(repositoryRoot);
|
|
429
|
+
const matches = matchingItems(metadata.catalog.items, query);
|
|
430
|
+
if (matches.length === 0) throw new CliError("not-found", `No emoji matches ${query}.`);
|
|
431
|
+
if (matches.length > 1) {
|
|
432
|
+
throw new CliError(
|
|
433
|
+
"conflict",
|
|
434
|
+
`Emoji query ${query} is ambiguous: ${matches.slice(0, 8).map(({ emoji, name }) => `${emoji} ${name}`).join(", ")}.`,
|
|
435
|
+
{ matches: matches.slice(0, 20).map(({ emoji, id, name }) => ({ emoji, id, name })) },
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const item = matches[0]!;
|
|
439
|
+
const manifestItem = metadata.manifest.items.find(({ id }) => id === item.id);
|
|
440
|
+
if (manifestItem === undefined) {
|
|
441
|
+
throw new CliError("invalid-data", `Emoji manifest omits ${item.id}.`);
|
|
442
|
+
}
|
|
443
|
+
const assetPath = join(metadata.assetRoot, item.id, `${variant}.svg`);
|
|
444
|
+
if (!await fileExists(assetPath)) {
|
|
445
|
+
throw new CliError(
|
|
446
|
+
"unavailable",
|
|
447
|
+
`Local ${variant} asset for ${item.emoji} ${item.name} is missing. Run: ${EMOJI_GENERATION_COMMAND}`,
|
|
448
|
+
{ generationCommand: EMOJI_GENERATION_COMMAND, id: item.id, variant },
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const [assetRootReal, assetReal] = await Promise.all([realpath(metadata.assetRoot), realpath(assetPath)]);
|
|
452
|
+
const assetRelative = relative(assetRootReal, assetReal);
|
|
453
|
+
if (assetRelative.startsWith("..") || isAbsolute(assetRelative)) {
|
|
454
|
+
throw new CliError("unsafe-path", `Emoji asset ${item.id}/${variant}.svg escapes the generated asset root.`);
|
|
455
|
+
}
|
|
456
|
+
const actualSha256 = await fileSha256(assetReal);
|
|
457
|
+
const expectedSha256 = variant === "color"
|
|
458
|
+
? manifestItem.colorSvgSha256
|
|
459
|
+
: manifestItem.duotoneSvgSha256;
|
|
460
|
+
if (actualSha256 !== expectedSha256) {
|
|
461
|
+
throw new CliError(
|
|
462
|
+
"invalid-data",
|
|
463
|
+
`Local ${variant} asset for ${item.emoji} failed its checked hash. Run: ${EMOJI_GENERATION_COMMAND}`,
|
|
464
|
+
{ actualSha256, expectedSha256, generationCommand: EMOJI_GENERATION_COMMAND },
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
...result(item, await variantAvailability(metadata.assetRoot, item.id)),
|
|
469
|
+
path: assetReal,
|
|
470
|
+
provider: "apple-emoji-pack",
|
|
471
|
+
sha256: actualSha256,
|
|
472
|
+
variant,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function resolveBrandEmojiAsset(
|
|
477
|
+
repositoryRoot: string,
|
|
478
|
+
query: string,
|
|
479
|
+
variant?: EmojiVariant,
|
|
480
|
+
): Promise<ResolvedEmojiAsset> {
|
|
481
|
+
const metadata = await readBrandItems(repositoryRoot);
|
|
482
|
+
const matches = matchingBrandItems(metadata.items, query);
|
|
483
|
+
if (matches.length === 0) throw new CliError("not-found", `No brand emoji matches ${query}.`);
|
|
484
|
+
if (matches.length > 1) {
|
|
485
|
+
throw new CliError(
|
|
486
|
+
"conflict",
|
|
487
|
+
`Brand emoji query ${query} is ambiguous: ${matches.slice(0, 8).map(({ domain }) => domain).join(", ")}.`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const item = matches[0]!;
|
|
491
|
+
const selectedVariant = variant ?? item.variant;
|
|
492
|
+
if (selectedVariant !== item.variant) {
|
|
493
|
+
throw new CliError("unavailable", `Brand emoji ${item.domain} provides the checked ${item.variant} variant only.`);
|
|
494
|
+
}
|
|
495
|
+
const [assetRootReal, assetReal] = await Promise.all([
|
|
496
|
+
realpath(metadata.assetRoot),
|
|
497
|
+
realpath(join(metadata.assetRoot, item.path)),
|
|
498
|
+
]);
|
|
499
|
+
const assetRelative = relative(assetRootReal, assetReal);
|
|
500
|
+
if (assetRelative.startsWith("..") || isAbsolute(assetRelative)) {
|
|
501
|
+
throw new CliError("unsafe-path", `Brand emoji asset ${item.path} escapes its checked asset root.`);
|
|
502
|
+
}
|
|
503
|
+
const details = await stat(assetReal);
|
|
504
|
+
const actualSha256 = await fileSha256(assetReal);
|
|
505
|
+
if (!details.isFile() || details.size !== item.bytes || actualSha256 !== item.sha256) {
|
|
506
|
+
throw new CliError("invalid-data", `Brand emoji asset ${item.domain} failed its checked size or hash.`);
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
...brandResult(item),
|
|
510
|
+
path: assetReal,
|
|
511
|
+
sha256: actualSha256,
|
|
512
|
+
variant: selectedVariant,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export async function resolveEmojiAsset(
|
|
517
|
+
repositoryRoot: string,
|
|
518
|
+
query: string,
|
|
519
|
+
variant?: EmojiVariant,
|
|
520
|
+
provider: EmojiAssetProvider | "auto" = "auto",
|
|
521
|
+
): Promise<ResolvedEmojiAsset> {
|
|
522
|
+
if (provider === "apple-emoji-pack") return await resolveAppleEmojiAsset(repositoryRoot, query, variant);
|
|
523
|
+
if (provider === "brand-catalog") return await resolveBrandEmojiAsset(repositoryRoot, query, variant);
|
|
524
|
+
const brand = await readBrandItems(repositoryRoot);
|
|
525
|
+
const folded = query.trim().toLocaleLowerCase();
|
|
526
|
+
const explicitlyBrand = folded.startsWith("brand:") || brand.items.some((item) =>
|
|
527
|
+
matchesBrandDomain(item.domain, folded)
|
|
528
|
+
);
|
|
529
|
+
if (explicitlyBrand) return await resolveBrandEmojiAsset(repositoryRoot, query, variant);
|
|
530
|
+
try {
|
|
531
|
+
return await resolveAppleEmojiAsset(repositoryRoot, query, variant);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (!(error instanceof CliError && (error.code === "not-found" || error.code === "unavailable"))) throw error;
|
|
534
|
+
const brandMatches = matchingBrandItems(brand.items, query);
|
|
535
|
+
if (brandMatches.length !== 1) throw error;
|
|
536
|
+
return await resolveBrandEmojiAsset(repositoryRoot, query, variant);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationError,
|
|
3
|
+
type ApplicationErrorCode,
|
|
4
|
+
errorMessage,
|
|
5
|
+
} from "../application/errors";
|
|
6
|
+
|
|
7
|
+
export { ApplicationError as CliError, errorMessage };
|
|
8
|
+
export type CliErrorCode = ApplicationErrorCode;
|
|
9
|
+
|
|
10
|
+
export const EXIT_CODE: Readonly<Record<CliErrorCode, number>> = {
|
|
11
|
+
usage: 2,
|
|
12
|
+
"not-found": 3,
|
|
13
|
+
conflict: 4,
|
|
14
|
+
unavailable: 5,
|
|
15
|
+
"unsafe-path": 6,
|
|
16
|
+
"invalid-data": 7,
|
|
17
|
+
subprocess: 8,
|
|
18
|
+
"unsupported-plan": 9,
|
|
19
|
+
"authorization-required": 10,
|
|
20
|
+
cancelled: 11,
|
|
21
|
+
ambiguous: 12,
|
|
22
|
+
incompatible: 13,
|
|
23
|
+
internal: 1,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function asCliError(error: unknown): ApplicationError {
|
|
27
|
+
if (error instanceof ApplicationError) return error;
|
|
28
|
+
return new ApplicationError("internal", errorMessage(error));
|
|
29
|
+
}
|