@sirenodeck/sirenodeck 0.1.1-alpha.0
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/README.md +69 -0
- package/bin/_wrapper-shared.js +42 -0
- package/bin/dev.js +95 -0
- package/bin/sirenodeck.js +42 -0
- package/config/default-config.yml +117 -0
- package/config/starter-config.yml +54 -0
- package/dist/main.mjs +64702 -0
- package/package.json +140 -0
- package/src/action/executor.ts +156 -0
- package/src/action/index.ts +7 -0
- package/src/addon/api-types.ts +15 -0
- package/src/addon/api.ts +461 -0
- package/src/addon/check-runner.ts +84 -0
- package/src/addon/index.ts +5 -0
- package/src/addon/loader.ts +441 -0
- package/src/addon/registry.ts +230 -0
- package/src/addon/spec.ts +69 -0
- package/src/api/addon.ts +33 -0
- package/src/api/asset-url.ts +24 -0
- package/src/api/index.ts +50 -0
- package/src/api/protocol-internal.ts +656 -0
- package/src/api/protocol.ts +2 -0
- package/src/api/react/index.ts +11 -0
- package/src/api/react/registry.ts +91 -0
- package/src/api/react/use-addon-channel.ts +26 -0
- package/src/api/react/use-button-state.tsx +23 -0
- package/src/api/react/use-deck.ts +24 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/logo72x72.png +0 -0
- package/src/assets/logoFull.png +0 -0
- package/src/builtin-addons/brightness/README.md +51 -0
- package/src/builtin-addons/brightness/buttons/brightness/backend.ts +13 -0
- package/src/builtin-addons/brightness/buttons/brightness/config.ts +10 -0
- package/src/builtin-addons/brightness/buttons/brightness/frontend.tsx +50 -0
- package/src/builtin-addons/brightness/domain/macos.ts +46 -0
- package/src/builtin-addons/brightness/index.d.ts +5 -0
- package/src/builtin-addons/brightness/index.ts +20 -0
- package/src/builtin-addons/brightness/poller.ts +27 -0
- package/src/builtin-addons/brightness/providers/darwin.ts +107 -0
- package/src/builtin-addons/brightness/providers/index.ts +81 -0
- package/src/builtin-addons/brightness/providers/linux.ts +108 -0
- package/src/builtin-addons/brightness/providers/windows.ts +77 -0
- package/src/builtin-addons/brightness/sirenodeck.json +6 -0
- package/src/builtin-addons/coding-agents/assets/claude-code.svg +1 -0
- package/src/builtin-addons/coding-agents/assets/opencode-dark-square.svg +1 -0
- package/src/builtin-addons/coding-agents/buttons/agent/backend.ts +82 -0
- package/src/builtin-addons/coding-agents/buttons/agent/config.ts +13 -0
- package/src/builtin-addons/coding-agents/buttons/agent/frontend.tsx +130 -0
- package/src/builtin-addons/coding-agents/buttons/agent/pages.tsx +150 -0
- package/src/builtin-addons/coding-agents/buttons/summary/backend.ts +48 -0
- package/src/builtin-addons/coding-agents/buttons/summary/config.ts +11 -0
- package/src/builtin-addons/coding-agents/buttons/summary/frontend.tsx +254 -0
- package/src/builtin-addons/coding-agents/decks/agents.ts +69 -0
- package/src/builtin-addons/coding-agents/global/backend.ts +402 -0
- package/src/builtin-addons/coding-agents/global-entry.ts +7 -0
- package/src/builtin-addons/coding-agents/hook/script.ts +123 -0
- package/src/builtin-addons/coding-agents/index.ts +15 -0
- package/src/builtin-addons/coding-agents/manifest.ts +115 -0
- package/src/builtin-addons/coding-agents/providers/claude-code.ts +470 -0
- package/src/builtin-addons/coding-agents/providers/claude-instances.ts +184 -0
- package/src/builtin-addons/coding-agents/providers/opencode-instances.ts +108 -0
- package/src/builtin-addons/coding-agents/providers/opencode.ts +636 -0
- package/src/builtin-addons/coding-agents/providers/registry.ts +120 -0
- package/src/builtin-addons/coding-agents/providers/spawn.ts +112 -0
- package/src/builtin-addons/coding-agents/shared/claude-hook-events.ts +55 -0
- package/src/builtin-addons/coding-agents/shared/claude-status.ts +236 -0
- package/src/builtin-addons/coding-agents/shared/live-count.ts +39 -0
- package/src/builtin-addons/coding-agents/shared/notifier.ts +58 -0
- package/src/builtin-addons/coding-agents/shared/opencode-status.ts +82 -0
- package/src/builtin-addons/coding-agents/shared/snapshot.ts +66 -0
- package/src/builtin-addons/coding-agents/shared/state.ts +72 -0
- package/src/builtin-addons/coding-agents/sirenodeck.json +6 -0
- package/src/builtin-addons/coding-agents/types/types.ts +33 -0
- package/src/builtin-addons/core/README.md +89 -0
- package/src/builtin-addons/core/buttons/action/backend.ts +7 -0
- package/src/builtin-addons/core/buttons/action/config.ts +13 -0
- package/src/builtin-addons/core/buttons/action/frontend.tsx +16 -0
- package/src/builtin-addons/core/buttons/change-deck/backend.ts +18 -0
- package/src/builtin-addons/core/buttons/change-deck/config.ts +15 -0
- package/src/builtin-addons/core/buttons/change-deck/frontend.tsx +18 -0
- package/src/builtin-addons/core/buttons/media-sample/backend.ts +8 -0
- package/src/builtin-addons/core/buttons/media-sample/config.ts +7 -0
- package/src/builtin-addons/core/buttons/media-sample/frontend.tsx +2 -0
- package/src/builtin-addons/core/buttons/page-nav/backend.ts +28 -0
- package/src/builtin-addons/core/buttons/page-nav/config.ts +9 -0
- package/src/builtin-addons/core/buttons/page-nav/frontend.tsx +68 -0
- package/src/builtin-addons/core/buttons/toggle/backend.ts +119 -0
- package/src/builtin-addons/core/buttons/toggle/config.ts +57 -0
- package/src/builtin-addons/core/buttons/toggle/frontend.tsx +57 -0
- package/src/builtin-addons/core/buttons/toggle/global-service.ts +174 -0
- package/src/builtin-addons/core/index.ts +120 -0
- package/src/builtin-addons/core/sirenodeck.json +6 -0
- package/src/builtin-addons/date-time/README.md +73 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/config.ts +4 -0
- package/src/builtin-addons/date-time/buttons/analog-clock/frontend.tsx +88 -0
- package/src/builtin-addons/date-time/buttons/custom/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/custom/config.ts +9 -0
- package/src/builtin-addons/date-time/buttons/custom/frontend.tsx +26 -0
- package/src/builtin-addons/date-time/buttons/date/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/date/config.ts +10 -0
- package/src/builtin-addons/date-time/buttons/date/frontend.tsx +42 -0
- package/src/builtin-addons/date-time/buttons/time/backend.ts +10 -0
- package/src/builtin-addons/date-time/buttons/time/config.ts +9 -0
- package/src/builtin-addons/date-time/buttons/time/frontend.tsx +29 -0
- package/src/builtin-addons/date-time/index.d.ts +5 -0
- package/src/builtin-addons/date-time/index.ts +30 -0
- package/src/builtin-addons/date-time/poller.ts +11 -0
- package/src/builtin-addons/date-time/shared/format-date.ts +36 -0
- package/src/builtin-addons/date-time/shared/format.ts +105 -0
- package/src/builtin-addons/date-time/shared/use-now.ts +10 -0
- package/src/builtin-addons/date-time/sirenodeck.json +6 -0
- package/src/builtin-addons/emoji-selector/README.md +45 -0
- package/src/builtin-addons/emoji-selector/assets/launcher.svg +10 -0
- package/src/builtin-addons/emoji-selector/assets/smileys.svg +8 -0
- package/src/builtin-addons/emoji-selector/buttons/category/backend.ts +14 -0
- package/src/builtin-addons/emoji-selector/buttons/category/config.ts +8 -0
- package/src/builtin-addons/emoji-selector/buttons/category/frontend.tsx +19 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/backend.ts +7 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/config.ts +9 -0
- package/src/builtin-addons/emoji-selector/buttons/emoji/frontend.tsx +11 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/backend.ts +13 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/config.ts +6 -0
- package/src/builtin-addons/emoji-selector/buttons/launcher/frontend.tsx +31 -0
- package/src/builtin-addons/emoji-selector/data/categories.ts +1084 -0
- package/src/builtin-addons/emoji-selector/decks/index.ts +137 -0
- package/src/builtin-addons/emoji-selector/index.d.ts +5 -0
- package/src/builtin-addons/emoji-selector/index.ts +29 -0
- package/src/builtin-addons/emoji-selector/sirenodeck.json +6 -0
- package/src/builtin-addons/emoji-selector/support.ts +49 -0
- package/src/builtin-addons/index.ts +13 -0
- package/src/builtin-addons/internal-settings/README.md +26 -0
- package/src/builtin-addons/internal-settings/backend.ts +58 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/backend.ts +7 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/config.ts +13 -0
- package/src/builtin-addons/internal-settings/buttons/app-info/frontend.tsx +18 -0
- package/src/builtin-addons/internal-settings/buttons/brightness/common/frontend.tsx +50 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/backend.ts +16 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/config.ts +6 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-down/frontend.tsx +8 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/backend.ts +16 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/config.ts +6 -0
- package/src/builtin-addons/internal-settings/buttons/brightness-up/frontend.tsx +8 -0
- package/src/builtin-addons/internal-settings/decks/settings.ts +25 -0
- package/src/builtin-addons/internal-settings/index.d.ts +4 -0
- package/src/builtin-addons/internal-settings/index.ts +53 -0
- package/src/builtin-addons/internal-settings/logo72x72.png +0 -0
- package/src/builtin-addons/internal-settings/sirenodeck.json +6 -0
- package/src/builtin-addons/media/README.md +45 -0
- package/src/builtin-addons/media/backend.ts +143 -0
- package/src/builtin-addons/media/buttons/media-player/backend.ts +19 -0
- package/src/builtin-addons/media/buttons/media-player/components/MediaStatus.tsx +25 -0
- package/src/builtin-addons/media/buttons/media-player/components/MediaSurface.tsx +52 -0
- package/src/builtin-addons/media/buttons/media-player/components/ProgressBar.tsx +21 -0
- package/src/builtin-addons/media/buttons/media-player/components/status-meta.ts +36 -0
- package/src/builtin-addons/media/buttons/media-player/config.ts +4 -0
- package/src/builtin-addons/media/buttons/media-player/frontend.tsx +29 -0
- package/src/builtin-addons/media/buttons/volume/common/frontend.tsx +52 -0
- package/src/builtin-addons/media/buttons/volume/mute/backend.ts +13 -0
- package/src/builtin-addons/media/buttons/volume/mute/config.ts +4 -0
- package/src/builtin-addons/media/buttons/volume/mute/frontend.tsx +26 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/backend.ts +14 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/config.ts +6 -0
- package/src/builtin-addons/media/buttons/volume/volume-down/frontend.tsx +10 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/backend.ts +14 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/config.ts +6 -0
- package/src/builtin-addons/media/buttons/volume/volume-up/frontend.tsx +8 -0
- package/src/builtin-addons/media/checks.ts +104 -0
- package/src/builtin-addons/media/executor.ts +36 -0
- package/src/builtin-addons/media/index.d.ts +5 -0
- package/src/builtin-addons/media/index.ts +53 -0
- package/src/builtin-addons/media/progress.ts +16 -0
- package/src/builtin-addons/media/providers/darwin.ts +391 -0
- package/src/builtin-addons/media/providers/index.ts +27 -0
- package/src/builtin-addons/media/providers/linux.ts +196 -0
- package/src/builtin-addons/media/providers/types.ts +48 -0
- package/src/builtin-addons/media/providers/windows.ts +121 -0
- package/src/builtin-addons/media/sirenodeck.json +6 -0
- package/src/builtin-addons/media/state.ts +19 -0
- package/src/builtin-addons/register-builtins.ts +65 -0
- package/src/builtin-addons/session/README.md +14 -0
- package/src/builtin-addons/session/buttons/session-info/backend.ts +8 -0
- package/src/builtin-addons/session/buttons/session-info/config.ts +4 -0
- package/src/builtin-addons/session/buttons/session-info/frontend.tsx +2 -0
- package/src/builtin-addons/session/buttons/time/backend.ts +7 -0
- package/src/builtin-addons/session/buttons/time/config.ts +6 -0
- package/src/builtin-addons/session/buttons/time/frontend.tsx +2 -0
- package/src/builtin-addons/session/index.d.ts +5 -0
- package/src/builtin-addons/session/index.ts +18 -0
- package/src/builtin-addons/session/sirenodeck.json +6 -0
- package/src/builtin-addons/system-status/README.md +169 -0
- package/src/builtin-addons/system-status/buttons/_shared.ts +131 -0
- package/src/builtin-addons/system-status/buttons/system-status/frontend.tsx +224 -0
- package/src/builtin-addons/system-status/buttons/system-status/schemas.ts +67 -0
- package/src/builtin-addons/system-status/domain/catalog.ts +53 -0
- package/src/builtin-addons/system-status/domain/chart-sampler.ts +58 -0
- package/src/builtin-addons/system-status/domain/display-metrics.ts +128 -0
- package/src/builtin-addons/system-status/domain/index.ts +10 -0
- package/src/builtin-addons/system-status/domain/live-metrics.ts +1021 -0
- package/src/builtin-addons/system-status/domain/metric-ids.ts +0 -0
- package/src/builtin-addons/system-status/index.ts +16 -0
- package/src/builtin-addons/system-status/manifest.ts +105 -0
- package/src/builtin-addons/system-status/shared/metrics-catalog.ts +316 -0
- package/src/builtin-addons/system-status/sirenodeck.json +6 -0
- package/src/builtin-addons/test-buildin/decks/action-buttons-test.ts +56 -0
- package/src/builtin-addons/test-buildin/index.d.ts +5 -0
- package/src/builtin-addons/test-buildin/index.ts +16 -0
- package/src/builtin-addons/value-display/README.md +59 -0
- package/src/builtin-addons/value-display/backend.ts +86 -0
- package/src/builtin-addons/value-display/buttons/value-display/backend.ts +17 -0
- package/src/builtin-addons/value-display/buttons/value-display/config.ts +36 -0
- package/src/builtin-addons/value-display/buttons/value-display/frontend.tsx +54 -0
- package/src/builtin-addons/value-display/index.d.ts +5 -0
- package/src/builtin-addons/value-display/index.ts +23 -0
- package/src/builtin-addons/value-display/sirenodeck.json +6 -0
- package/src/builtin-addons/weather/README.md +46 -0
- package/src/builtin-addons/weather/backend.ts +121 -0
- package/src/builtin-addons/weather/buttons/weather/backend.ts +27 -0
- package/src/builtin-addons/weather/buttons/weather/config.ts +29 -0
- package/src/builtin-addons/weather/buttons/weather/frontend.tsx +63 -0
- package/src/builtin-addons/weather/buttons/weather/icons.tsx +88 -0
- package/src/builtin-addons/weather/buttons/weather/pages.tsx +109 -0
- package/src/builtin-addons/weather/domain/codes.ts +35 -0
- package/src/builtin-addons/weather/domain/fetch.ts +47 -0
- package/src/builtin-addons/weather/index.d.ts +5 -0
- package/src/builtin-addons/weather/index.ts +23 -0
- package/src/builtin-addons/weather/poller.ts +70 -0
- package/src/builtin-addons/weather/provider/city-key.ts +9 -0
- package/src/builtin-addons/weather/provider/codes.ts +35 -0
- package/src/builtin-addons/weather/provider/fetch.ts +97 -0
- package/src/builtin-addons/weather/provider/geocode.ts +67 -0
- package/src/builtin-addons/weather/provider/types.ts +42 -0
- package/src/builtin-addons/weather/sirenodeck.json +6 -0
- package/src/cli/coding-agents-onboarding.ts +434 -0
- package/src/cli/commands/addon-decks.ts +441 -0
- package/src/cli/commands/addon-registry.ts +639 -0
- package/src/cli/commands/emulator-mode.ts +289 -0
- package/src/cli/commands/install.ts +128 -0
- package/src/cli/commands/logs.ts +74 -0
- package/src/cli/commands/network-bind.ts +111 -0
- package/src/cli/commands/pipeline/helpers.ts +75 -0
- package/src/cli/commands/pipeline/preflight.ts +20 -0
- package/src/cli/commands/port-identity.ts +152 -0
- package/src/cli/commands/reload.ts +37 -0
- package/src/cli/commands/restart.ts +139 -0
- package/src/cli/commands/run.ts +2270 -0
- package/src/cli/commands/service/install.ts +121 -0
- package/src/cli/commands/service/render-template.ts +158 -0
- package/src/cli/commands/service-manager.ts +184 -0
- package/src/cli/commands/spawn-daemon.ts +251 -0
- package/src/cli/commands/start.ts +1061 -0
- package/src/cli/commands/status.ts +110 -0
- package/src/cli/commands/stop.ts +121 -0
- package/src/cli/commands/subprocess-supervisor.ts +203 -0
- package/src/cli/commands/system-requirements.ts +699 -0
- package/src/cli/cwd.ts +28 -0
- package/src/cli/first-run.ts +149 -0
- package/src/cli/http-server.ts +244 -0
- package/src/cli/index.ts +355 -0
- package/src/cli/install-root.ts +26 -0
- package/src/cli/main.ts +194 -0
- package/src/cli/package-manager.ts +175 -0
- package/src/cli/probe-deps.ts +45 -0
- package/src/cli/prompt.ts +22 -0
- package/src/cli/startup-display.ts +452 -0
- package/src/config/builtin-icons.ts +57 -0
- package/src/config/config-diff.ts +54 -0
- package/src/config/discovery.ts +64 -0
- package/src/config/icon-resolver.ts +90 -0
- package/src/config/include-resolver.ts +202 -0
- package/src/config/index.ts +9 -0
- package/src/config/loader.ts +187 -0
- package/src/config/mutation.ts +514 -0
- package/src/config/reference-expander.ts +84 -0
- package/src/config/schemas.ts +223 -0
- package/src/config/validation.ts +254 -0
- package/src/core/asset-id.ts +21 -0
- package/src/core/gesture-state.ts +344 -0
- package/src/core/icon-asset-registry.ts +127 -0
- package/src/core/icon-source.ts +86 -0
- package/src/core/index.ts +30 -0
- package/src/core/mime.ts +16 -0
- package/src/core/pagination.ts +90 -0
- package/src/core/pub-sub.ts +126 -0
- package/src/core/settings.ts +153 -0
- package/src/core/store.ts +88 -0
- package/src/core/watcher.ts +73 -0
- package/src/deck/addon-handler-bridge.ts +516 -0
- package/src/deck/deck-config.ts +271 -0
- package/src/deck/editor-surfaces.ts +87 -0
- package/src/deck/host-context.ts +18 -0
- package/src/deck/index.ts +102 -0
- package/src/deck/macro-parse.ts +182 -0
- package/src/deck/methods.ts +348 -0
- package/src/deck/paginate-deck.ts +98 -0
- package/src/deck/position-buttons.ts +57 -0
- package/src/deck/runtime/index.ts +5 -0
- package/src/deck/runtime/runtime.ts +1060 -0
- package/src/deck/runtime-subscriptions.ts +45 -0
- package/src/deck/system-back-injection.ts +62 -0
- package/src/deck/system-buttons/registry.tsx +97 -0
- package/src/deck/system-buttons/types.ts +14 -0
- package/src/device/black-frame.ts +73 -0
- package/src/device/index.ts +13 -0
- package/src/device/linux-udev.ts +36 -0
- package/src/device/models.ts +89 -0
- package/src/device/reconnecting.ts +193 -0
- package/src/device/registry.ts +38 -0
- package/src/device/stream-deck.ts +155 -0
- package/src/index.ts +74 -0
- package/src/outputClient/emulator.ts +357 -0
- package/src/outputClient/index.ts +26 -0
- package/src/outputClient/open-browser.ts +26 -0
- package/src/outputClient/real.ts +393 -0
- package/src/outputClient/types.ts +83 -0
- package/src/render/browser-renderer.ts +240 -0
- package/src/render/buffer-hash.ts +20 -0
- package/src/render/editor-handler.ts +197 -0
- package/src/render/emulator-server.ts +128 -0
- package/src/render/icon-source-resolver.ts +69 -0
- package/src/render/index.ts +16 -0
- package/src/render/protocol.ts +61 -0
- package/src/render/push-raw-image.ts +100 -0
- package/src/render/screenshot-cadence.ts +87 -0
- package/src/render/state-publisher.ts +110 -0
- package/src/render/vite-server.ts +138 -0
- package/src/render/ws-bridge.ts +315 -0
- package/src/system/device-selection.ts +80 -0
- package/src/system/glob-match.ts +74 -0
- package/src/system/providers/active-app/darwin.ts +117 -0
- package/src/system/providers/active-app/linux.ts +245 -0
- package/src/system/providers/active-app/wayland-gnome.ts +303 -0
- package/src/system/providers/active-app/windows.ts +94 -0
- package/src/system/providers/active-app.ts +97 -0
- package/src/system/providers/clipboard/darwin.ts +65 -0
- package/src/system/providers/clipboard/linux.ts +163 -0
- package/src/system/providers/clipboard/windows.ts +94 -0
- package/src/system/providers/clipboard.ts +84 -0
- package/src/system/providers/error.ts +16 -0
- package/src/system/providers/key-macro/darwin.ts +221 -0
- package/src/system/providers/key-macro/linux.ts +651 -0
- package/src/system/providers/key-macro/parser.ts +215 -0
- package/src/system/providers/key-macro/windows.ts +476 -0
- package/src/system/providers/key-macro.ts +79 -0
- package/src/system/providers/notification/darwin.ts +36 -0
- package/src/system/providers/notification/linux.ts +115 -0
- package/src/system/providers/notification/windows.ts +72 -0
- package/src/system/providers/notification.ts +75 -0
- package/src/system/providers/session/darwin.ts +82 -0
- package/src/system/providers/session/linux.ts +175 -0
- package/src/system/providers/session/windows.ts +73 -0
- package/src/system/providers/session.ts +89 -0
- package/src/system/providers/shared.ts +65 -0
- package/src/system/requirements.ts +235 -0
- package/src/system/setup-wizard/config-seed.ts +30 -0
- package/src/system/setup-wizard/format.ts +275 -0
- package/src/system/setup-wizard/index.ts +45 -0
- package/src/system/setup-wizard/open-settings.ts +26 -0
- package/src/system/setup-wizard/plan.ts +230 -0
- package/src/system/setup-wizard/probe-cache.ts +34 -0
- package/src/system/setup-wizard/probe.ts +486 -0
- package/src/system/setup-wizard/runtime-features.ts +85 -0
- package/src/system/setup-wizard/sudo.ts +178 -0
- package/src/system/setup-wizard/types.ts +133 -0
- package/src/system/virtual-stream-deck.ts +67 -0
- package/src/themes/css.ts +210 -0
- package/src/themes/default/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/default/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Italic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Light.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Thin.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/IBM_Plex_Mono/OFL.txt +93 -0
- package/src/themes/default/assets/OFL.txt +93 -0
- package/src/themes/default/assets/README.txt +106 -0
- package/src/themes/default/assets/static/IBMPlexSans-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_Condensed-ThinItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Bold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-BoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLight.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Italic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Light.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-LightItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Medium.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-MediumItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Regular.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBold.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Thin.ttf +0 -0
- package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ThinItalic.ttf +0 -0
- package/src/themes/default/components.css +81 -0
- package/src/themes/default/sirenodeck.json +170 -0
- package/src/themes/index.ts +14 -0
- package/src/themes/light/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/light/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
- package/src/themes/light/assets/IBM_Plex_Mono/OFL.txt +93 -0
- package/src/themes/light/assets/OFL.txt +93 -0
- package/src/themes/light/assets/README.txt +106 -0
- package/src/themes/light/assets/static/IBMPlexSans-Light.ttf +0 -0
- package/src/themes/light/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
- package/src/themes/light/assets/static/IBMPlexSans-Thin.ttf +0 -0
- package/src/themes/light/components.css +70 -0
- package/src/themes/light/sirenodeck.json +170 -0
- package/src/themes/loader.ts +343 -0
- package/src/themes/manifest.ts +164 -0
- package/src/themes/use-resolved-theme.tsx +32 -0
- package/src/ui/ButtonFrame.tsx +230 -0
- package/src/ui/contexts/AssetCacheContext.tsx +43 -0
- package/src/ui/index.ts +20 -0
- package/src/ui/primitives/Chip.tsx +53 -0
- package/src/ui/primitives/Icon.tsx +209 -0
- package/src/ui/primitives/Label.tsx +82 -0
- package/src/ui/primitives/ProgressBar.tsx +38 -0
- package/src/ui/primitives/TapIndicator.tsx +67 -0
- package/src/ui/primitives/Text.tsx +810 -0
- package/src/ui/primitives/index.ts +6 -0
- package/src/ui/surfaces/BarsSurface.tsx +211 -0
- package/src/ui/surfaces/IconLabelProgressSurface.tsx +133 -0
- package/src/ui/surfaces/IconLabelSurface.tsx +59 -0
- package/src/ui/surfaces/LabelValueListSurface.tsx +202 -0
- package/src/ui/surfaces/PaginatedSurface.tsx +164 -0
- package/src/ui/surfaces/SplitActionSurface.tsx +98 -0
- package/src/ui/surfaces/TemporaryErrorSurface.tsx +68 -0
- package/src/ui/surfaces/ValueChart.tsx +218 -0
- package/src/ui/surfaces/index.ts +32 -0
- package/src/ui/surfaces/paginated-schema.ts +22 -0
- package/src/ui/theme-presentation-builder.ts +184 -0
- package/src/ui/theme-presentation.tsx +146 -0
- package/src/ui/utils/cn.ts +12 -0
- package/src/util/cache-paths.ts +38 -0
- package/src/util/config-diff.ts +16 -0
- package/src/util/daemon-control.ts +85 -0
- package/src/util/daemon.ts +575 -0
- package/src/util/device-config.ts +56 -0
- package/src/util/errors.ts +13 -0
- package/src/util/log-reader.ts +114 -0
- package/src/util/log-tail.ts +103 -0
- package/src/util/logger.ts +417 -0
- package/src/util/process-title.ts +79 -0
- package/src/util/single-instance.ts +202 -0
- package/src/version.ts +10 -0
- package/src/vite/index.ts +6 -0
- package/src/vite/virtual-modules.ts +631 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { resolve as resolvePath, join } from "node:path"
|
|
3
|
+
import { pathToFileURL } from "node:url"
|
|
4
|
+
|
|
5
|
+
import { addonNpmRoot } from "@/util/cache-paths"
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
AddonJsonManifest,
|
|
9
|
+
AddonLoadIssue,
|
|
10
|
+
AddonManifestV1,
|
|
11
|
+
LoadedTheme,
|
|
12
|
+
} from "./api"
|
|
13
|
+
import type { RawAddonEntry } from "@/config/schemas"
|
|
14
|
+
import {
|
|
15
|
+
addonRootExists,
|
|
16
|
+
isLocalAddonSpec,
|
|
17
|
+
isNpmAddonSpec,
|
|
18
|
+
normalizeAddonEntry,
|
|
19
|
+
resolveLocalAddonRoot,
|
|
20
|
+
} from "./spec"
|
|
21
|
+
|
|
22
|
+
export interface LoadAddonsOptions {
|
|
23
|
+
entries: RawAddonEntry[]
|
|
24
|
+
configDir: string
|
|
25
|
+
homeDir: string
|
|
26
|
+
currentApiVersion: number
|
|
27
|
+
cacheDir?: string
|
|
28
|
+
projectDir?: string
|
|
29
|
+
globalPackageRoots?: ReadonlyArray<string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ResolvedExternalAddon {
|
|
33
|
+
manifest: AddonManifestV1
|
|
34
|
+
source: { kind: "local" | "npm"; specifier: string; resolvedPath: string }
|
|
35
|
+
// ponytail: resolved module path the bridge should import to load this
|
|
36
|
+
// addon's frontend + globalService. Same file exports both; one path
|
|
37
|
+
// serves both import slots.
|
|
38
|
+
entryPath: string
|
|
39
|
+
// ponytail: when the manifest declares `frontendEntry`, the BROWSER loads
|
|
40
|
+
// this instead of `entryPath`. The Node bundle has the host's UI primitives
|
|
41
|
+
// stubbed out (Node cannot import the host's .tsx sources), so rendering it
|
|
42
|
+
// in the browser draws those components as nothing. Null when the addon
|
|
43
|
+
// ships a single bundle, in which case callers fall back to entryPath.
|
|
44
|
+
browserEntryPath: string | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface LoadAddonsResult {
|
|
48
|
+
addons: ResolvedExternalAddon[]
|
|
49
|
+
themes: LoadedTheme[]
|
|
50
|
+
issues: AddonLoadIssue[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const recordIssue = (issues: AddonLoadIssue[], issue: AddonLoadIssue): void => {
|
|
54
|
+
issues.push(issue)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const importAddonModule = async (resolvedPath: string): Promise<unknown> => {
|
|
58
|
+
const url = pathToFileURL(resolvedPath).href
|
|
59
|
+
return import(url)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const readJsonManifest = (root: string): AddonJsonManifest | null => {
|
|
63
|
+
const manifestPath = join(root, "sirenodeck.json")
|
|
64
|
+
if (!existsSync(manifestPath)) return null
|
|
65
|
+
let raw: unknown
|
|
66
|
+
try {
|
|
67
|
+
raw = JSON.parse(readFileSync(manifestPath, "utf8")) as unknown
|
|
68
|
+
} catch {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
if (raw === null || typeof raw !== "object") return null
|
|
72
|
+
const obj = raw as Record<string, unknown>
|
|
73
|
+
if (obj["apiVersion"] !== 1) return null
|
|
74
|
+
const kind = obj["kind"]
|
|
75
|
+
if (kind !== "addon" && kind !== "theme") return null
|
|
76
|
+
const name = obj["name"]
|
|
77
|
+
const entry = obj["entry"]
|
|
78
|
+
if (typeof name !== "string" || typeof entry !== "string") return null
|
|
79
|
+
// Optional; ignored entirely when absent, so older manifests are unaffected.
|
|
80
|
+
const frontendEntry = obj["frontendEntry"]
|
|
81
|
+
return {
|
|
82
|
+
kind,
|
|
83
|
+
apiVersion: 1,
|
|
84
|
+
name,
|
|
85
|
+
entry,
|
|
86
|
+
...(typeof frontendEntry === "string" && frontendEntry.length > 0
|
|
87
|
+
? { frontendEntry }
|
|
88
|
+
: {}),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ponytail: resolve the manifest's optional `frontendEntry` against the addon
|
|
93
|
+
// root. Returns null when the field is absent OR the file is missing, so a
|
|
94
|
+
// stale manifest degrades to the single-bundle behaviour instead of handing
|
|
95
|
+
// the browser a path that 404s.
|
|
96
|
+
const resolveBrowserEntry = (
|
|
97
|
+
root: string,
|
|
98
|
+
json: AddonJsonManifest,
|
|
99
|
+
): string | null => {
|
|
100
|
+
if (json.frontendEntry === undefined) return null
|
|
101
|
+
const candidate = join(root, json.frontendEntry)
|
|
102
|
+
return existsSync(candidate) ? candidate : null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolveEntryPaths = (
|
|
106
|
+
root: string,
|
|
107
|
+
json: AddonJsonManifest,
|
|
108
|
+
): { ts: string; js: string } => {
|
|
109
|
+
const entryExt = json.entry
|
|
110
|
+
const extTs = entryExt.endsWith(".ts")
|
|
111
|
+
? join(root, entryExt)
|
|
112
|
+
: join(root, `${entryExt}.ts`)
|
|
113
|
+
const extJs = join(root, entryExt)
|
|
114
|
+
return { ts: extTs, js: extJs }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ponytail: ESM-CJS interop wraps CJS exports as `mod.default`, and some
|
|
118
|
+
// addons also wrap the manifest under a named key (e.g. chrome-overlay uses
|
|
119
|
+
// `module.exports = { manifest: {...} }`). Some combine both: the CJS wrapper
|
|
120
|
+
// becomes `mod.default`, then chrome-overlay's `.manifest` lives one level
|
|
121
|
+
// down. Walk at most two levels deep — never deeper, to avoid accidental
|
|
122
|
+
// infinite recursion on hostile modules.
|
|
123
|
+
const unwrapManifestExport = (mod: unknown): unknown => {
|
|
124
|
+
let cur: unknown = mod
|
|
125
|
+
for (let i = 0; i < 2; i += 1) {
|
|
126
|
+
if (cur === null || typeof cur !== "object") return cur
|
|
127
|
+
const rec = cur as Record<string, unknown>
|
|
128
|
+
const candidate = rec["manifest"] ?? rec["default"]
|
|
129
|
+
if (
|
|
130
|
+
candidate !== undefined &&
|
|
131
|
+
typeof candidate === "object" &&
|
|
132
|
+
candidate !== null &&
|
|
133
|
+
typeof (candidate as Record<string, unknown>)["apiVersion"] ===
|
|
134
|
+
"number" &&
|
|
135
|
+
typeof (candidate as Record<string, unknown>)["name"] === "string"
|
|
136
|
+
) {
|
|
137
|
+
return candidate
|
|
138
|
+
}
|
|
139
|
+
if (candidate !== undefined) {
|
|
140
|
+
cur = candidate
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
return cur
|
|
144
|
+
}
|
|
145
|
+
return cur
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const validateAndLoadEntry = async (
|
|
149
|
+
source: string,
|
|
150
|
+
tsPath: string,
|
|
151
|
+
jsPath: string,
|
|
152
|
+
issues: AddonLoadIssue[],
|
|
153
|
+
): Promise<{ manifest: AddonManifestV1; entryPath: string } | null> => {
|
|
154
|
+
const candidatePath = existsSync(tsPath) ? tsPath : jsPath
|
|
155
|
+
if (!existsSync(candidatePath)) {
|
|
156
|
+
recordIssue(issues, {
|
|
157
|
+
level: "error",
|
|
158
|
+
source,
|
|
159
|
+
message: `Entry file not found: tried ${tsPath} and ${jsPath}`,
|
|
160
|
+
})
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
let mod: unknown
|
|
164
|
+
try {
|
|
165
|
+
mod = await importAddonModule(candidatePath)
|
|
166
|
+
} catch (err) {
|
|
167
|
+
recordIssue(issues, {
|
|
168
|
+
level: "error",
|
|
169
|
+
source,
|
|
170
|
+
message: `Failed to import addon entry: ${err instanceof Error ? err.message : String(err)}`,
|
|
171
|
+
})
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
const exported = unwrapManifestExport(mod)
|
|
175
|
+
if (
|
|
176
|
+
exported === null ||
|
|
177
|
+
typeof exported !== "object" ||
|
|
178
|
+
typeof (exported as Record<string, unknown>)["apiVersion"] !== "number" ||
|
|
179
|
+
typeof (exported as Record<string, unknown>)["name"] !== "string"
|
|
180
|
+
) {
|
|
181
|
+
const gotKeys =
|
|
182
|
+
exported !== null && typeof exported === "object"
|
|
183
|
+
? Object.keys(exported as Record<string, unknown>).join(",")
|
|
184
|
+
: typeof exported
|
|
185
|
+
recordIssue(issues, {
|
|
186
|
+
level: "error",
|
|
187
|
+
source,
|
|
188
|
+
message: `Entry at ${candidatePath} did not export a valid AddonManifestV1 (apiVersion + name required). Got keys: [${gotKeys}]`,
|
|
189
|
+
})
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
const manifest = exported as AddonManifestV1
|
|
193
|
+
if (Object.keys(manifest.buttonTypes ?? {}).length === 0 && !manifest.decks) {
|
|
194
|
+
recordIssue(issues, {
|
|
195
|
+
level: "error",
|
|
196
|
+
source,
|
|
197
|
+
message: `Addon ${source} defines no buttons or decks`,
|
|
198
|
+
})
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
return { manifest, entryPath: candidatePath }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const loadLocalAddon = async (
|
|
205
|
+
source: string,
|
|
206
|
+
configDir: string,
|
|
207
|
+
homeDir: string,
|
|
208
|
+
issues: AddonLoadIssue[],
|
|
209
|
+
currentApi: number,
|
|
210
|
+
): Promise<ResolvedExternalAddon | null> => {
|
|
211
|
+
const root = resolveLocalAddonRoot(source, configDir, homeDir)
|
|
212
|
+
if (!isLocalAddonSpec(source)) {
|
|
213
|
+
recordIssue(issues, {
|
|
214
|
+
level: "error",
|
|
215
|
+
source,
|
|
216
|
+
message: `Spec does not look like a local path: ${source}`,
|
|
217
|
+
})
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
if (!addonRootExists(root)) {
|
|
221
|
+
recordIssue(issues, {
|
|
222
|
+
level: "error",
|
|
223
|
+
source,
|
|
224
|
+
message: `Local addon path does not exist: ${root}`,
|
|
225
|
+
})
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
const json = readJsonManifest(root)
|
|
229
|
+
if (json === null) {
|
|
230
|
+
recordIssue(issues, {
|
|
231
|
+
level: "error",
|
|
232
|
+
source,
|
|
233
|
+
message: `Missing or invalid sirenodeck.json at ${root}`,
|
|
234
|
+
})
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
if (json.kind === "theme") {
|
|
238
|
+
// ponytail: 3rd-party themes are loaded via the themes loader, not the
|
|
239
|
+
// addons loader. A theme author drops their package into
|
|
240
|
+
// `packages/cli/src/themes/<name>/` (or wires a custom themeDir via
|
|
241
|
+
// the CLI) and the discovery loop picks it up. Rejecting theme-kind
|
|
242
|
+
// entries from addons[] keeps the contracts clean and surfaces a
|
|
243
|
+
// clear error to anyone trying to put a theme in the wrong place.
|
|
244
|
+
recordIssue(issues, {
|
|
245
|
+
level: "warning",
|
|
246
|
+
source,
|
|
247
|
+
message: `Theme '${json.name}' declared via addons[] — drop themes into packages/cli/src/themes/<name>/ or pass --theme-dir; they aren't addons.`,
|
|
248
|
+
})
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
if (json.apiVersion !== currentApi) {
|
|
252
|
+
recordIssue(issues, {
|
|
253
|
+
level: "warning",
|
|
254
|
+
source,
|
|
255
|
+
message: `Addon apiVersion mismatch: expected ${currentApi}, got ${json.apiVersion}`,
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
const { ts, js } = resolveEntryPaths(root, json)
|
|
259
|
+
const loaded = await validateAndLoadEntry(source, ts, js, issues)
|
|
260
|
+
if (!loaded) return null
|
|
261
|
+
return {
|
|
262
|
+
manifest: loaded.manifest,
|
|
263
|
+
source: { kind: "local", specifier: source, resolvedPath: root },
|
|
264
|
+
entryPath: loaded.entryPath,
|
|
265
|
+
browserEntryPath: resolveBrowserEntry(root, json),
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface ParsedSpecifier {
|
|
270
|
+
packageName: string
|
|
271
|
+
version: string | null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// npm package name spec — scoped names + optional @version
|
|
275
|
+
const NPM_SPECIFIER =
|
|
276
|
+
/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-z0-9][a-z0-9._-]*)?$/i
|
|
277
|
+
const NPM_VERSION = /^[a-z0-9][a-z0-9._-]*$/i
|
|
278
|
+
|
|
279
|
+
const parseNpmSpecifier = (spec: string): ParsedSpecifier | null => {
|
|
280
|
+
if (typeof spec !== "string" || !NPM_SPECIFIER.test(spec)) return null
|
|
281
|
+
const atIdx = spec.lastIndexOf("@")
|
|
282
|
+
if (atIdx <= 0) {
|
|
283
|
+
return { packageName: spec, version: null }
|
|
284
|
+
}
|
|
285
|
+
const packageName = spec.slice(0, atIdx)
|
|
286
|
+
const version = spec.slice(atIdx + 1)
|
|
287
|
+
if (version.length === 0 || !NPM_VERSION.test(version)) return null
|
|
288
|
+
return { packageName, version }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const readInstalledPackageJson = (
|
|
292
|
+
installPath: string,
|
|
293
|
+
): {
|
|
294
|
+
name: string
|
|
295
|
+
version: string
|
|
296
|
+
apiVersion: number
|
|
297
|
+
} | null => {
|
|
298
|
+
const pkgPath = resolvePath(installPath, "package.json")
|
|
299
|
+
if (!existsSync(pkgPath)) return null
|
|
300
|
+
let raw: unknown
|
|
301
|
+
try {
|
|
302
|
+
raw = JSON.parse(readFileSync(pkgPath, "utf8")) as unknown
|
|
303
|
+
} catch {
|
|
304
|
+
return null
|
|
305
|
+
}
|
|
306
|
+
if (raw === null || typeof raw !== "object") return null
|
|
307
|
+
const obj = raw as Record<string, unknown>
|
|
308
|
+
const name = typeof obj["name"] === "string" ? (obj["name"] as string) : null
|
|
309
|
+
const version =
|
|
310
|
+
typeof obj["version"] === "string" ? (obj["version"] as string) : null
|
|
311
|
+
const apiVersion = obj["sirenoAddonApiVersion"]
|
|
312
|
+
if (name === null || version === null) return null
|
|
313
|
+
if (typeof apiVersion !== "number") return null
|
|
314
|
+
return { name, version, apiVersion }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const loadNpmAddon = async (
|
|
318
|
+
source: string,
|
|
319
|
+
cacheDir: string,
|
|
320
|
+
projectDir: string | undefined,
|
|
321
|
+
globalPackageRoots: ReadonlyArray<string>,
|
|
322
|
+
issues: AddonLoadIssue[],
|
|
323
|
+
currentApi: number,
|
|
324
|
+
): Promise<ResolvedExternalAddon | null> => {
|
|
325
|
+
const parsed = parseNpmSpecifier(source)
|
|
326
|
+
if (parsed === null) {
|
|
327
|
+
recordIssue(issues, {
|
|
328
|
+
level: "error",
|
|
329
|
+
source,
|
|
330
|
+
message: "Invalid npm specifier",
|
|
331
|
+
})
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const roots = [
|
|
336
|
+
...(projectDir !== undefined ? [join(projectDir, "node_modules")] : []),
|
|
337
|
+
...globalPackageRoots,
|
|
338
|
+
addonNpmRoot(cacheDir),
|
|
339
|
+
]
|
|
340
|
+
const installPath = roots
|
|
341
|
+
.map((root) => join(root, parsed.packageName))
|
|
342
|
+
.find((path) => {
|
|
343
|
+
const pkg = readInstalledPackageJson(path)
|
|
344
|
+
return (
|
|
345
|
+
pkg !== null &&
|
|
346
|
+
(parsed.version === null || pkg.version === parsed.version)
|
|
347
|
+
)
|
|
348
|
+
})
|
|
349
|
+
const pkg =
|
|
350
|
+
installPath === undefined ? null : readInstalledPackageJson(installPath)
|
|
351
|
+
|
|
352
|
+
if (installPath === undefined) {
|
|
353
|
+
recordIssue(issues, {
|
|
354
|
+
level: "error",
|
|
355
|
+
source,
|
|
356
|
+
message: `Package '${source}' is not installed. Run 'sirenodeck install ${source}'.`,
|
|
357
|
+
})
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
if (pkg === null) return null
|
|
361
|
+
const json = readJsonManifest(installPath)
|
|
362
|
+
if (json === null) {
|
|
363
|
+
recordIssue(issues, {
|
|
364
|
+
level: "error",
|
|
365
|
+
source,
|
|
366
|
+
message: `Missing or invalid sirenodeck.json at ${installPath}`,
|
|
367
|
+
})
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
if (json.apiVersion !== currentApi) {
|
|
371
|
+
recordIssue(issues, {
|
|
372
|
+
level: "warning",
|
|
373
|
+
source,
|
|
374
|
+
message: `Addon apiVersion mismatch: expected ${currentApi}, got ${json.apiVersion}`,
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const { ts, js } = resolveEntryPaths(installPath, json)
|
|
379
|
+
const loaded = await validateAndLoadEntry(source, ts, js, issues)
|
|
380
|
+
if (!loaded) return null
|
|
381
|
+
return {
|
|
382
|
+
manifest: loaded.manifest,
|
|
383
|
+
source: { kind: "npm", specifier: source, resolvedPath: installPath },
|
|
384
|
+
entryPath: loaded.entryPath,
|
|
385
|
+
browserEntryPath: resolveBrowserEntry(installPath, json),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export const loadAddons = async ({
|
|
390
|
+
entries,
|
|
391
|
+
configDir,
|
|
392
|
+
homeDir,
|
|
393
|
+
currentApiVersion,
|
|
394
|
+
cacheDir,
|
|
395
|
+
projectDir,
|
|
396
|
+
globalPackageRoots = [],
|
|
397
|
+
}: LoadAddonsOptions): Promise<LoadAddonsResult> => {
|
|
398
|
+
const issues: AddonLoadIssue[] = []
|
|
399
|
+
const addons: ResolvedExternalAddon[] = []
|
|
400
|
+
const themes: LoadedTheme[] = []
|
|
401
|
+
for (const rawEntry of entries) {
|
|
402
|
+
const entry = normalizeAddonEntry(rawEntry)
|
|
403
|
+
if (!entry.enabled) {
|
|
404
|
+
recordIssue(issues, {
|
|
405
|
+
level: "info",
|
|
406
|
+
source: entry.source,
|
|
407
|
+
message: "Addon disabled in config",
|
|
408
|
+
})
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
if (entry.isLocal || isLocalAddonSpec(entry.source)) {
|
|
412
|
+
const loaded = await loadLocalAddon(
|
|
413
|
+
entry.source,
|
|
414
|
+
configDir,
|
|
415
|
+
homeDir,
|
|
416
|
+
issues,
|
|
417
|
+
currentApiVersion,
|
|
418
|
+
)
|
|
419
|
+
if (loaded) addons.push(loaded)
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
if (isNpmAddonSpec(entry.source) && cacheDir !== undefined) {
|
|
423
|
+
const loaded = await loadNpmAddon(
|
|
424
|
+
entry.source,
|
|
425
|
+
cacheDir,
|
|
426
|
+
projectDir,
|
|
427
|
+
entry.global ? globalPackageRoots : [],
|
|
428
|
+
issues,
|
|
429
|
+
currentApiVersion,
|
|
430
|
+
)
|
|
431
|
+
if (loaded) addons.push(loaded)
|
|
432
|
+
continue
|
|
433
|
+
}
|
|
434
|
+
recordIssue(issues, {
|
|
435
|
+
level: "error",
|
|
436
|
+
source: entry.source,
|
|
437
|
+
message: `Unknown addon spec: '${entry.source}'. Use a local path (./my-addon) or an npm specifier (my-addon, @scope/my-addon, my-addon@1.2.3).`,
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
return { addons, themes, issues }
|
|
441
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AddonButtonTypeDef,
|
|
3
|
+
AddonDeckDefinition,
|
|
4
|
+
AddonManifestV1,
|
|
5
|
+
LoadedTheme,
|
|
6
|
+
} from "./api"
|
|
7
|
+
import type { GestureKind } from "@/core/gesture-state"
|
|
8
|
+
|
|
9
|
+
export class AddonRegistry {
|
|
10
|
+
private readonly addonsByName = new Map<string, AddonManifestV1>()
|
|
11
|
+
private readonly buttonsByType = new Map<
|
|
12
|
+
string,
|
|
13
|
+
{ addonName: string; def: AddonButtonTypeDef }
|
|
14
|
+
>()
|
|
15
|
+
private readonly decksByType = new Map<
|
|
16
|
+
string,
|
|
17
|
+
{ addonName: string; def: AddonDeckDefinition }
|
|
18
|
+
>()
|
|
19
|
+
private readonly themesByName = new Map<string, LoadedTheme>()
|
|
20
|
+
|
|
21
|
+
load(manifest: AddonManifestV1): void {
|
|
22
|
+
const name = manifest.name
|
|
23
|
+
if (this.addonsByName.has(name)) {
|
|
24
|
+
throw new Error(`Duplicate addon name: ${name}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ponytail: hard cutover from legacy Record<key, {type, createDecks}>
|
|
28
|
+
// shape. Addons shipping the old JS shape (e.g. the 3 Phase-10 addons
|
|
29
|
+
// before phase-11 migration) hit this branch with a clear migration
|
|
30
|
+
// hint. TypeScript-shape manifests fail at type-check, never runtime.
|
|
31
|
+
if (
|
|
32
|
+
manifest.decks !== undefined &&
|
|
33
|
+
!Array.isArray(manifest.decks) &&
|
|
34
|
+
typeof manifest.decks === "object" &&
|
|
35
|
+
manifest.decks !== null
|
|
36
|
+
) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Addon '${name}' uses legacy decks format (Record keyed by '<addon>:<deck>'). ` +
|
|
39
|
+
`Migrate to an array: [{ id: '<addon>:<deck>', ...static fields }, ...]. ` +
|
|
40
|
+
`See phase-11 docs.`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const buttonType of Object.keys(manifest.buttonTypes)) {
|
|
45
|
+
if (!buttonType.startsWith(`${name}:`)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Button type '${buttonType}' in addon '${name}' must be prefixed with '${name}:'`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const [buttonType, def] of Object.entries(manifest.buttonTypes)) {
|
|
53
|
+
const service = def.service
|
|
54
|
+
if (!service) continue
|
|
55
|
+
const hasOnTap = typeof service.onTap === "function"
|
|
56
|
+
const hasOnDblTap = typeof service.onDblTap === "function"
|
|
57
|
+
const hasOnHold = typeof service.onHold === "function"
|
|
58
|
+
if (!hasOnTap && !hasOnDblTap && !hasOnHold) continue
|
|
59
|
+
const allowed = service.gestureHandlers ?? []
|
|
60
|
+
const toStrip: string[] = []
|
|
61
|
+
if (hasOnTap && !allowed.includes("tap" as GestureKind))
|
|
62
|
+
toStrip.push("onTap")
|
|
63
|
+
if (hasOnDblTap && !allowed.includes("dbl-tap" as GestureKind))
|
|
64
|
+
toStrip.push("onDblTap")
|
|
65
|
+
if (hasOnHold && !allowed.includes("hold" as GestureKind))
|
|
66
|
+
toStrip.push("onHold")
|
|
67
|
+
if (toStrip.length > 0) {
|
|
68
|
+
console.warn(
|
|
69
|
+
`[sirenodeck] addon "${name}" button "${buttonType}" declares [${toStrip.join(", ")}] ` +
|
|
70
|
+
`but not in gestureHandlers (default-deny). Stripping undeclared handlers. ` +
|
|
71
|
+
`Add gestureHandlers: [${toStrip.map((h) => `'${h.replace("on", "").toLowerCase()}'`).join(", ")}] to silence this.`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.addonsByName.set(name, manifest)
|
|
77
|
+
for (const [buttonType, def] of Object.entries(manifest.buttonTypes)) {
|
|
78
|
+
if (this.buttonsByType.has(buttonType)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Duplicate button type '${buttonType}' in addon ${name}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
this.buttonsByType.set(buttonType, { addonName: name, def })
|
|
84
|
+
if (buttonType === `${name}:${name}`) {
|
|
85
|
+
this.buttonsByType.set(name, { addonName: name, def })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const entry of manifest.decks ?? []) {
|
|
90
|
+
// ponytail: three shapes — static, single-dynamic, multi-dynamic. The
|
|
91
|
+
// id (or each key returned from createDecks) becomes the deck-type key.
|
|
92
|
+
// Validation: ids must start with `<addon>:` prefix.
|
|
93
|
+
const prefix = `${name}:`
|
|
94
|
+
if (
|
|
95
|
+
typeof entry.createDeck === "function" &&
|
|
96
|
+
typeof entry.createDecks === "function"
|
|
97
|
+
) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Addon '${name}' deck entry has both createDeck and createDecks; choose one.`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
if (typeof entry.createDecks === "function") {
|
|
103
|
+
const def: AddonDeckDefinition = {
|
|
104
|
+
type: `${name}:__multi__`,
|
|
105
|
+
createDecks: entry.createDecks,
|
|
106
|
+
}
|
|
107
|
+
const syntheticKey = `${name}:__multi__`
|
|
108
|
+
if (this.decksByType.has(syntheticKey)) {
|
|
109
|
+
throw new Error(`Duplicate deck '${syntheticKey}' in addon ${name}`)
|
|
110
|
+
}
|
|
111
|
+
this.decksByType.set(syntheticKey, { addonName: name, def })
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (typeof entry.createDeck === "function") {
|
|
115
|
+
if (entry.id === undefined || entry.id.length === 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Addon '${name}' deck entry with createDeck must have an id.`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
if (!entry.id.startsWith(prefix)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Deck '${entry.id}' in addon '${name}' must be prefixed with '${prefix}'.`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
const def: AddonDeckDefinition = {
|
|
126
|
+
type: entry.id,
|
|
127
|
+
createDecks: (ctx) => ({ [entry.id]: entry.createDeck(ctx) }),
|
|
128
|
+
}
|
|
129
|
+
if (this.decksByType.has(entry.id)) {
|
|
130
|
+
throw new Error(`Duplicate deck '${entry.id}' in addon ${name}`)
|
|
131
|
+
}
|
|
132
|
+
this.decksByType.set(entry.id, { addonName: name, def })
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
// ponytail: static — the entry IS the deck.
|
|
136
|
+
if (entry.id === undefined || entry.id.length === 0) {
|
|
137
|
+
throw new Error(`Addon '${name}' static deck entry must have an id.`)
|
|
138
|
+
}
|
|
139
|
+
if (!entry.id.startsWith(prefix)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Deck '${entry.id}' in addon '${name}' must be prefixed with '${prefix}'.`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
const def: AddonDeckDefinition = {
|
|
145
|
+
type: entry.id,
|
|
146
|
+
createDecks: () => ({ [entry.id]: entry }),
|
|
147
|
+
}
|
|
148
|
+
if (this.decksByType.has(entry.id)) {
|
|
149
|
+
throw new Error(`Duplicate deck '${entry.id}' in addon ${name}`)
|
|
150
|
+
}
|
|
151
|
+
this.decksByType.set(entry.id, { addonName: name, def })
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getAddon(name: string): AddonManifestV1 | undefined {
|
|
156
|
+
return this.addonsByName.get(name)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
listAddons(): AddonManifestV1[] {
|
|
160
|
+
return Array.from(this.addonsByName.values())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getButtonType(
|
|
164
|
+
type: string,
|
|
165
|
+
): { addonName: string; def: AddonButtonTypeDef } | undefined {
|
|
166
|
+
return this.buttonsByType.get(type)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getDeckType(
|
|
170
|
+
type: string,
|
|
171
|
+
): { addonName: string; def: AddonDeckDefinition } | undefined {
|
|
172
|
+
return this.decksByType.get(type)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
hasButtonType(type: string): boolean {
|
|
176
|
+
return this.buttonsByType.has(type)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
hasDeckType(type: string): boolean {
|
|
180
|
+
return this.decksByType.has(type)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
listDeckTypes(): ReadonlyArray<{ id: string; addonName: string }> {
|
|
184
|
+
return [...this.decksByType.entries()].map(([id, v]) => ({
|
|
185
|
+
id,
|
|
186
|
+
addonName: v.addonName,
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
loadTheme(theme: LoadedTheme): void {
|
|
191
|
+
if (this.themesByName.has(theme.name)) {
|
|
192
|
+
throw new Error(`Duplicate theme name: ${theme.name}`)
|
|
193
|
+
}
|
|
194
|
+
this.themesByName.set(theme.name, theme)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getTheme(name: string): LoadedTheme | undefined {
|
|
198
|
+
return this.themesByName.get(name)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
listThemes(): LoadedTheme[] {
|
|
202
|
+
return Array.from(this.themesByName.values())
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
hasTheme(name: string): boolean {
|
|
206
|
+
return this.themesByName.has(name)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
resolveActiveTheme(name: string | undefined): LoadedTheme {
|
|
210
|
+
const target = name ?? "default"
|
|
211
|
+
const theme = this.themesByName.get(target)
|
|
212
|
+
if (!theme) {
|
|
213
|
+
const available = this.listThemes()
|
|
214
|
+
.map((t) => t.name)
|
|
215
|
+
.sort()
|
|
216
|
+
.join(", ")
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Theme '${target}' is not registered. Available themes: ${available || "(none)"}`,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
return theme
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
reset(): void {
|
|
225
|
+
this.addonsByName.clear()
|
|
226
|
+
this.buttonsByType.clear()
|
|
227
|
+
this.decksByType.clear()
|
|
228
|
+
this.themesByName.clear()
|
|
229
|
+
}
|
|
230
|
+
}
|