@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,639 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { dirname, join, resolve as resolvePath } from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
import type { AddonPoller, AddonPollerChannel } from "@/addon/api-types"
|
|
7
|
+
|
|
8
|
+
export interface ScannedDeck {
|
|
9
|
+
readonly id: string
|
|
10
|
+
readonly paginated: boolean
|
|
11
|
+
readonly buttons: number
|
|
12
|
+
readonly internal: boolean
|
|
13
|
+
readonly hasTrigger: boolean
|
|
14
|
+
readonly generated?: boolean
|
|
15
|
+
readonly isOverlay?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ScannedAddon {
|
|
19
|
+
readonly name: string
|
|
20
|
+
readonly types: ReadonlyArray<string>
|
|
21
|
+
/**
|
|
22
|
+
* The module the DAEMON imports (Node). Its host-UI specifiers are stubbed
|
|
23
|
+
* at build time so plain Node can load it for the manifest and global
|
|
24
|
+
* service.
|
|
25
|
+
*/
|
|
26
|
+
readonly frontendEntry: string | null
|
|
27
|
+
/**
|
|
28
|
+
* The module the BROWSER loads, when the addon ships a separate bundle.
|
|
29
|
+
*
|
|
30
|
+
* ponytail: these two were one field, set to `browserEntryPath ?? entryPath`.
|
|
31
|
+
* The browser bundle deliberately leaves `@sirenodeck/sirenodeck/ui/*`
|
|
32
|
+
* external, which Node cannot resolve — so the daemon's `import()` threw
|
|
33
|
+
* into a silent catch, no button handler was registered, and every tap on
|
|
34
|
+
* an addon button did nothing at all. They must stay separate.
|
|
35
|
+
*/
|
|
36
|
+
readonly browserEntry?: string | null
|
|
37
|
+
readonly publishIntervalMs: number | null
|
|
38
|
+
readonly pollerEntry: string | null
|
|
39
|
+
readonly buttonTypes: Readonly<Record<string, ScannedButtonType>>
|
|
40
|
+
readonly deckTypes: Readonly<Record<string, string>>
|
|
41
|
+
readonly source: "json" | "regex"
|
|
42
|
+
readonly globalServiceEntry: string | null
|
|
43
|
+
readonly decks: ReadonlyArray<ScannedDeck>
|
|
44
|
+
readonly path?: string
|
|
45
|
+
readonly internal?: boolean
|
|
46
|
+
readonly defaultButton?: string | null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ScannedButtonType {
|
|
50
|
+
readonly exportName: string
|
|
51
|
+
readonly internal: boolean
|
|
52
|
+
readonly defaultConfig?: unknown
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AddonFrontendRef {
|
|
56
|
+
readonly name: string
|
|
57
|
+
readonly frontendEntry: string | null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const buildAddonByType = (
|
|
61
|
+
scanned: ReadonlyArray<ScannedAddon>,
|
|
62
|
+
): Map<string, AddonFrontendRef> => {
|
|
63
|
+
const map = new Map<string, AddonFrontendRef>()
|
|
64
|
+
for (const addon of scanned) {
|
|
65
|
+
for (const type of addon.types) {
|
|
66
|
+
if (!map.has(type)) {
|
|
67
|
+
map.set(type, { name: addon.name, frontendEntry: addon.frontendEntry })
|
|
68
|
+
}
|
|
69
|
+
if (type === `${addon.name}:${addon.name}` && !map.has(addon.name)) {
|
|
70
|
+
map.set(addon.name, {
|
|
71
|
+
name: addon.name,
|
|
72
|
+
frontendEntry: addon.frontendEntry,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return map
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const collectBuiltinAddonRegistry = async (): Promise<{
|
|
81
|
+
scanned: ReadonlyArray<ScannedAddon>
|
|
82
|
+
byType: Map<string, AddonFrontendRef>
|
|
83
|
+
}> => {
|
|
84
|
+
const scanned = await scanBuiltinAddons()
|
|
85
|
+
return { scanned, byType: buildAddonByType(scanned) }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Where the builtin addons live on disk, from wherever this module is running.
|
|
92
|
+
*
|
|
93
|
+
* ponytail: this used to be a single hard-coded hop — `../../builtin-addons` —
|
|
94
|
+
* which is right only when the module runs from source at `src/cli/commands/`.
|
|
95
|
+
* The published CLI runs the bundle at `dist/main.mjs`, where the same hop
|
|
96
|
+
* lands on `<pkg>/../builtin-addons`: a path that does not exist. The scan
|
|
97
|
+
* below then returned an empty array without a word, and the consequences were
|
|
98
|
+
* entirely silent and entirely fatal to the deck — every builtin lost its
|
|
99
|
+
* frontend entry, so it was absent from `SIRENO_ADDONS`, absent from the
|
|
100
|
+
* frontend's `virtual:sireno/addons/registry`, and `ButtonSurface` rendered
|
|
101
|
+
* `null` for it. The deck came up with blank keys, no error anywhere, and the
|
|
102
|
+
* only buttons that survived were the hardcoded system ones and addons loaded
|
|
103
|
+
* from an absolute path outside the package.
|
|
104
|
+
*
|
|
105
|
+
* The package ships `src/` beside `dist/` (see the `files` field in
|
|
106
|
+
* package.json), so the sources are present in both layouts; only the number
|
|
107
|
+
* of hops differs. Try each layout and take the one that is actually there.
|
|
108
|
+
*/
|
|
109
|
+
export const resolveBuiltinDir = (
|
|
110
|
+
moduleDir: string,
|
|
111
|
+
exists: (path: string) => boolean = existsSync,
|
|
112
|
+
): string => {
|
|
113
|
+
const candidates = [
|
|
114
|
+
// running from source: src/cli/commands → src/builtin-addons
|
|
115
|
+
resolvePath(moduleDir, "..", "..", "builtin-addons"),
|
|
116
|
+
// running from the bundle: dist → src/builtin-addons
|
|
117
|
+
resolvePath(moduleDir, "..", "src", "builtin-addons"),
|
|
118
|
+
// bundled alongside, if a future build ever emits them next to the entry
|
|
119
|
+
resolvePath(moduleDir, "builtin-addons"),
|
|
120
|
+
]
|
|
121
|
+
return candidates.find((candidate) => exists(candidate)) ?? candidates[0]!
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const builtinDir = resolveBuiltinDir(here)
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The text of the modules an entry file re-exports, one hop deep.
|
|
128
|
+
*
|
|
129
|
+
* ponytail: the regex scan used to read only the entry file itself, which is
|
|
130
|
+
* empty of button types whenever the entry is a barrel — `system-status` and
|
|
131
|
+
* `coding-agents` both just re-export `./manifest`. Those two addons have a
|
|
132
|
+
* `sirenodeck.json`, so the JSON path was meant to cover them by importing the
|
|
133
|
+
* entry and reading `buttonTypes` off the module. That works under tsx and
|
|
134
|
+
* fails under the published bundle, because `await import()` on a `.ts` file
|
|
135
|
+
* is not something plain node can do — and the failure is swallowed by a catch
|
|
136
|
+
* that leaves the type list empty. The result was two addons whose buttons
|
|
137
|
+
* rendered blank only in a real install, which is the worst place to find out.
|
|
138
|
+
*
|
|
139
|
+
* Reading the re-exported file's text needs no loader, so it works in both.
|
|
140
|
+
* One hop is enough for a barrel and keeps this from walking a module graph.
|
|
141
|
+
*/
|
|
142
|
+
const reexportedSources = (addonDir: string, entrySource: string): string[] => {
|
|
143
|
+
const out: string[] = []
|
|
144
|
+
const seen = new Set<string>()
|
|
145
|
+
for (const match of entrySource.matchAll(
|
|
146
|
+
/(?:^|\n)\s*(?:export|import)\b[^"']*from\s*["'](\.[^"']+)["']/g,
|
|
147
|
+
)) {
|
|
148
|
+
const spec = match[1]
|
|
149
|
+
if (spec === undefined || seen.has(spec)) continue
|
|
150
|
+
seen.add(spec)
|
|
151
|
+
const base = resolvePath(addonDir, spec)
|
|
152
|
+
// TypeScript's ESM imports name the emitted `.js`, so `./manifest.js` is
|
|
153
|
+
// `manifest.ts` on disk — `coding-agents` imports exactly that way, and
|
|
154
|
+
// without the swap its barrel looked like it re-exported nothing.
|
|
155
|
+
const withoutJs = base.replace(/\.(js|jsx|mjs|cjs)$/, "")
|
|
156
|
+
for (const candidate of [
|
|
157
|
+
base,
|
|
158
|
+
`${base}.ts`,
|
|
159
|
+
`${base}.tsx`,
|
|
160
|
+
`${withoutJs}.ts`,
|
|
161
|
+
`${withoutJs}.tsx`,
|
|
162
|
+
join(base, "index.ts"),
|
|
163
|
+
join(base, "index.tsx"),
|
|
164
|
+
]) {
|
|
165
|
+
if (!existsSync(candidate)) continue
|
|
166
|
+
try {
|
|
167
|
+
out.push(readFileSync(candidate, "utf8"))
|
|
168
|
+
} catch {
|
|
169
|
+
// unreadable sibling — the entry's own text still stands
|
|
170
|
+
}
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const scanAddonDir = async (
|
|
178
|
+
addonDir: string,
|
|
179
|
+
addonName: string,
|
|
180
|
+
): Promise<ScannedAddon | null> => {
|
|
181
|
+
const jsonScanned = await scanAddonJsonManifest(addonDir, addonName)
|
|
182
|
+
if (jsonScanned !== null && jsonScanned.types.length > 0) return jsonScanned
|
|
183
|
+
const indexPath = join(addonDir, "index.ts")
|
|
184
|
+
const indexTsxPath = join(addonDir, "index.tsx")
|
|
185
|
+
// ponytail: the text fallback used to insist on an `index.ts`, so an addon
|
|
186
|
+
// whose manifest names a different entry had no fallback at all — it simply
|
|
187
|
+
// returned the empty JSON scan. `coding-agents` has no index.ts and declares
|
|
188
|
+
// `manifest.ts`, which is why its two buttons were missing from the registry
|
|
189
|
+
// in a published install while every other builtin recovered. When the
|
|
190
|
+
// manifest names an entry, that is the file to read.
|
|
191
|
+
const manifestEntry = jsonScanned?.frontendEntry ?? null
|
|
192
|
+
const indexFile = existsSync(indexPath)
|
|
193
|
+
? indexPath
|
|
194
|
+
: existsSync(indexTsxPath)
|
|
195
|
+
? indexTsxPath
|
|
196
|
+
: manifestEntry !== null && existsSync(manifestEntry)
|
|
197
|
+
? manifestEntry
|
|
198
|
+
: null
|
|
199
|
+
if (indexFile === null) return jsonScanned
|
|
200
|
+
let raw: string
|
|
201
|
+
try {
|
|
202
|
+
raw = readFileSync(indexFile, "utf8")
|
|
203
|
+
} catch {
|
|
204
|
+
return jsonScanned
|
|
205
|
+
}
|
|
206
|
+
const allSources = [raw, ...reexportedSources(dirname(indexFile), raw)]
|
|
207
|
+
const scanFrom = (source: string): Set<string> => {
|
|
208
|
+
const out = new Set<string>()
|
|
209
|
+
for (const m of source.matchAll(
|
|
210
|
+
/type:\s*["']([a-z0-9-]+:[a-z0-9-]+)["']/gi,
|
|
211
|
+
)) {
|
|
212
|
+
if (m[1] !== undefined) out.add(m[1])
|
|
213
|
+
}
|
|
214
|
+
for (const m of source.matchAll(
|
|
215
|
+
/["']([a-z0-9-]+:[a-z0-9-]+)["']\s*:\s*\{/g,
|
|
216
|
+
)) {
|
|
217
|
+
if (m[1] !== undefined) out.add(m[1])
|
|
218
|
+
}
|
|
219
|
+
return out
|
|
220
|
+
}
|
|
221
|
+
// Scan the re-exported modules too: a barrel entry declares no types of its
|
|
222
|
+
// own, which is exactly how `system-status` and `coding-agents` came up empty.
|
|
223
|
+
const types = new Set<string>()
|
|
224
|
+
for (const source of allSources)
|
|
225
|
+
for (const type of scanFrom(source)) types.add(type)
|
|
226
|
+
let publishIntervalMs: number | null = null
|
|
227
|
+
for (const src of allSources) {
|
|
228
|
+
const match = src.match(/publishIntervalMs:\s*(\d+)/)
|
|
229
|
+
if (match !== null) {
|
|
230
|
+
publishIntervalMs = Number.parseInt(match[1]!, 10)
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const pollerEntry = existsSync(join(addonDir, "poller.ts"))
|
|
235
|
+
? join(addonDir, "poller.ts")
|
|
236
|
+
: null
|
|
237
|
+
// Across every source, for the same reason as the type scan: a barrel entry
|
|
238
|
+
// declares nothing itself, and `system-status` keeps its globalService in
|
|
239
|
+
// `manifest.ts`. Testing only the entry text left it with no backend at all,
|
|
240
|
+
// so its buttons rendered but never received a metric to show.
|
|
241
|
+
const hasGlobalService = allSources.some((source) =>
|
|
242
|
+
/\bglobalService\b/.test(source),
|
|
243
|
+
)
|
|
244
|
+
if (types.size === 0 && pollerEntry === null && !hasGlobalService)
|
|
245
|
+
return jsonScanned
|
|
246
|
+
const buttonTypes: Record<string, ScannedButtonType> = {}
|
|
247
|
+
const entryMapRegex =
|
|
248
|
+
/["']([a-z0-9-]+:[a-z0-9-]+)["']\s*:\s*\{\s*(?=[^}]*frontend\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*))/g
|
|
249
|
+
for (const src of allSources) {
|
|
250
|
+
for (const m of src.matchAll(entryMapRegex)) {
|
|
251
|
+
const type = m[1]
|
|
252
|
+
const exportName = m[2]
|
|
253
|
+
if (
|
|
254
|
+
type !== undefined &&
|
|
255
|
+
exportName !== undefined &&
|
|
256
|
+
!buttonTypes[type]
|
|
257
|
+
) {
|
|
258
|
+
buttonTypes[type] = { exportName, internal: false }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
name: addonName,
|
|
264
|
+
types: [...types],
|
|
265
|
+
frontendEntry: indexFile,
|
|
266
|
+
publishIntervalMs,
|
|
267
|
+
pollerEntry,
|
|
268
|
+
buttonTypes,
|
|
269
|
+
deckTypes: {},
|
|
270
|
+
source: "regex",
|
|
271
|
+
// ponytail: prefer a dedicated global-entry.ts — index.ts is imported by
|
|
272
|
+
// the frontend's virtual addons/registry, so a global backend reachable
|
|
273
|
+
// from it puts node builtins in the browser graph and crashes the app.
|
|
274
|
+
// A global-entry.ts next to index.ts keeps the node side out of the
|
|
275
|
+
// browser bundle entirely.
|
|
276
|
+
globalServiceEntry: existsSync(join(addonDir, "global-entry.ts"))
|
|
277
|
+
? join(addonDir, "global-entry.ts")
|
|
278
|
+
: hasGlobalService && indexFile !== null
|
|
279
|
+
? indexFile
|
|
280
|
+
: null,
|
|
281
|
+
decks: [],
|
|
282
|
+
internal: true,
|
|
283
|
+
path: addonDir,
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const scanAddonJsonManifest = async (
|
|
288
|
+
addonDir: string,
|
|
289
|
+
addonName: string,
|
|
290
|
+
): Promise<ScannedAddon | null> => {
|
|
291
|
+
const jsonPath = join(addonDir, "sirenodeck.json")
|
|
292
|
+
if (!existsSync(jsonPath)) return null
|
|
293
|
+
let raw: unknown
|
|
294
|
+
try {
|
|
295
|
+
raw = JSON.parse(readFileSync(jsonPath, "utf8")) as unknown
|
|
296
|
+
} catch {
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
if (raw === null || typeof raw !== "object") return null
|
|
300
|
+
const obj = raw as Record<string, unknown>
|
|
301
|
+
if (obj["kind"] !== "addon") return null
|
|
302
|
+
if (obj["apiVersion"] !== 1) return null
|
|
303
|
+
const name = obj["name"]
|
|
304
|
+
const entry = obj["entry"]
|
|
305
|
+
if (typeof name !== "string" || typeof entry !== "string") return null
|
|
306
|
+
if (name !== addonName) return null
|
|
307
|
+
|
|
308
|
+
const entryPath = resolvePath(addonDir, entry)
|
|
309
|
+
const types = new Set<string>()
|
|
310
|
+
const buttonTypes: Record<string, ScannedButtonType> = {}
|
|
311
|
+
let publishIntervalMs: number | null = null
|
|
312
|
+
let hasGlobalService = false
|
|
313
|
+
const decks: Array<{
|
|
314
|
+
id: string
|
|
315
|
+
hasTrigger: boolean
|
|
316
|
+
paginated: boolean
|
|
317
|
+
buttons: number
|
|
318
|
+
internal: boolean
|
|
319
|
+
}> = []
|
|
320
|
+
try {
|
|
321
|
+
const mod = (await import(entryPath)) as {
|
|
322
|
+
default?: {
|
|
323
|
+
buttonTypes?: Record<string, unknown>
|
|
324
|
+
decks?: unknown
|
|
325
|
+
publishIntervalMs?: number
|
|
326
|
+
globalService?: unknown
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const exported =
|
|
330
|
+
mod !== null &&
|
|
331
|
+
typeof mod === "object" &&
|
|
332
|
+
"default" in mod &&
|
|
333
|
+
mod.default !== undefined
|
|
334
|
+
? mod.default
|
|
335
|
+
: mod
|
|
336
|
+
const candidate =
|
|
337
|
+
exported !== null && typeof exported === "object"
|
|
338
|
+
? (exported as {
|
|
339
|
+
buttonTypes?: Record<string, unknown>
|
|
340
|
+
decks?: unknown
|
|
341
|
+
publishIntervalMs?: number
|
|
342
|
+
globalService?: unknown
|
|
343
|
+
})
|
|
344
|
+
: null
|
|
345
|
+
if (candidate !== null) {
|
|
346
|
+
for (const [type, def] of Object.entries(candidate.buttonTypes ?? {})) {
|
|
347
|
+
types.add(type)
|
|
348
|
+
const d = def as Record<string, unknown> | null
|
|
349
|
+
const service =
|
|
350
|
+
d !== null && typeof d === "object"
|
|
351
|
+
? (d["service"] as Record<string, unknown> | undefined)
|
|
352
|
+
: undefined
|
|
353
|
+
const internal =
|
|
354
|
+
service?.["internal"] === true || d?.["internal"] === true
|
|
355
|
+
buttonTypes[type] = { exportName: "default", internal }
|
|
356
|
+
}
|
|
357
|
+
if (typeof candidate.publishIntervalMs === "number") {
|
|
358
|
+
publishIntervalMs = candidate.publishIntervalMs
|
|
359
|
+
}
|
|
360
|
+
if (candidate.globalService !== undefined) {
|
|
361
|
+
hasGlobalService = true
|
|
362
|
+
}
|
|
363
|
+
if (candidate.decks !== null && typeof candidate.decks === "object") {
|
|
364
|
+
const isArray = Array.isArray(candidate.decks)
|
|
365
|
+
if (isArray) {
|
|
366
|
+
for (const entry of candidate.decks as Array<
|
|
367
|
+
Record<string, unknown>
|
|
368
|
+
>) {
|
|
369
|
+
if (entry === null || typeof entry !== "object") continue
|
|
370
|
+
// Static entry: { id: string, buttons?: [], trigger?: {...}, ... }
|
|
371
|
+
if (
|
|
372
|
+
typeof entry["id"] === "string" &&
|
|
373
|
+
entry["createDeck"] === undefined &&
|
|
374
|
+
entry["createDecks"] === undefined
|
|
375
|
+
) {
|
|
376
|
+
const trigger = entry["trigger"] as
|
|
377
|
+
| Record<string, unknown>
|
|
378
|
+
| undefined
|
|
379
|
+
const hasTrigger =
|
|
380
|
+
trigger !== undefined &&
|
|
381
|
+
(trigger["process_name"] !== undefined ||
|
|
382
|
+
trigger["window_name"] !== undefined)
|
|
383
|
+
decks.push({
|
|
384
|
+
id: entry["id"] as string,
|
|
385
|
+
hasTrigger,
|
|
386
|
+
paginated: entry["paginated"] === true,
|
|
387
|
+
buttons: Array.isArray(entry["buttons"])
|
|
388
|
+
? (entry["buttons"] as unknown[]).length
|
|
389
|
+
: 0,
|
|
390
|
+
internal: entry["internal"] === true,
|
|
391
|
+
})
|
|
392
|
+
continue
|
|
393
|
+
}
|
|
394
|
+
// Single-dynamic: { createDeck: fn } — id is the entry's id field
|
|
395
|
+
if (
|
|
396
|
+
typeof entry["createDeck"] === "function" &&
|
|
397
|
+
typeof entry["id"] === "string"
|
|
398
|
+
) {
|
|
399
|
+
const trigger = entry["trigger"] as
|
|
400
|
+
| Record<string, unknown>
|
|
401
|
+
| undefined
|
|
402
|
+
const hasTrigger =
|
|
403
|
+
trigger !== undefined &&
|
|
404
|
+
(trigger["process_name"] !== undefined ||
|
|
405
|
+
trigger["window_name"] !== undefined)
|
|
406
|
+
decks.push({
|
|
407
|
+
id: entry["id"] as string,
|
|
408
|
+
hasTrigger,
|
|
409
|
+
paginated: entry["paginated"] === true,
|
|
410
|
+
buttons: 0,
|
|
411
|
+
internal: entry["internal"] === true,
|
|
412
|
+
})
|
|
413
|
+
continue
|
|
414
|
+
}
|
|
415
|
+
// Multi-dynamic: { createDecks: fn } — id unknown without calling it; skip
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
// Legacy Record<string, AddonDeckDefinition> form
|
|
419
|
+
for (const [deckId, deckDef] of Object.entries(
|
|
420
|
+
candidate.decks as Record<string, unknown>,
|
|
421
|
+
)) {
|
|
422
|
+
if (deckDef === null || typeof deckDef !== "object") continue
|
|
423
|
+
const d = deckDef as Record<string, unknown>
|
|
424
|
+
const trigger = d["trigger"] as Record<string, unknown> | undefined
|
|
425
|
+
const hasTrigger =
|
|
426
|
+
trigger !== undefined &&
|
|
427
|
+
(trigger["process_name"] !== undefined ||
|
|
428
|
+
trigger["window_name"] !== undefined)
|
|
429
|
+
const paginated = d["paginated"] === true
|
|
430
|
+
const buttonCount = Array.isArray(d["buttons"])
|
|
431
|
+
? d["buttons"].length
|
|
432
|
+
: 0
|
|
433
|
+
const deckInternal = d["internal"] === true
|
|
434
|
+
decks.push({
|
|
435
|
+
id: deckId,
|
|
436
|
+
hasTrigger,
|
|
437
|
+
paginated,
|
|
438
|
+
buttons: buttonCount,
|
|
439
|
+
internal: deckInternal,
|
|
440
|
+
})
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
// Fall through with empty types — caller will fall back to regex scan.
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
name: addonName,
|
|
451
|
+
types: [...types],
|
|
452
|
+
frontendEntry: entryPath,
|
|
453
|
+
publishIntervalMs,
|
|
454
|
+
pollerEntry: null,
|
|
455
|
+
buttonTypes,
|
|
456
|
+
deckTypes: {},
|
|
457
|
+
source: "json",
|
|
458
|
+
// ponytail: prefer a dedicated global-entry.ts when present (mirrors the
|
|
459
|
+
// regex path's rule at line 153). A browser-safe manifest can't export a
|
|
460
|
+
// node-only globalService, so this entry file is where it lives.
|
|
461
|
+
globalServiceEntry: existsSync(join(addonDir, "global-entry.ts"))
|
|
462
|
+
? join(addonDir, "global-entry.ts")
|
|
463
|
+
: hasGlobalService
|
|
464
|
+
? entryPath
|
|
465
|
+
: null,
|
|
466
|
+
decks,
|
|
467
|
+
internal: true,
|
|
468
|
+
path: addonDir,
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const scanBuiltinAddons = async (): Promise<ReadonlyArray<ScannedAddon>> => {
|
|
473
|
+
if (!existsSync(builtinDir)) {
|
|
474
|
+
// Never normal: without these the deck renders blank keys. Say so loudly
|
|
475
|
+
// rather than returning an empty list and letting the UI explain it.
|
|
476
|
+
process.emitWarning(
|
|
477
|
+
`sirenodeck: builtin addons not found at ${builtinDir} — every builtin button will render empty`,
|
|
478
|
+
"SirenoDeckBuiltinsMissing",
|
|
479
|
+
)
|
|
480
|
+
return []
|
|
481
|
+
}
|
|
482
|
+
const entries = readdirSync(builtinDir, { withFileTypes: true })
|
|
483
|
+
const out: ScannedAddon[] = []
|
|
484
|
+
for (const entry of entries) {
|
|
485
|
+
if (!entry.isDirectory()) continue
|
|
486
|
+
const scanned = await scanAddonDir(join(builtinDir, entry.name), entry.name)
|
|
487
|
+
if (scanned !== null) out.push(scanned)
|
|
488
|
+
}
|
|
489
|
+
return out
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export interface ButtonValidationIssue {
|
|
493
|
+
readonly addon: string
|
|
494
|
+
readonly button: string
|
|
495
|
+
readonly message: string
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const validateButtonConfigExport = (
|
|
499
|
+
addonName: string,
|
|
500
|
+
buttonDir: string,
|
|
501
|
+
buttonName: string,
|
|
502
|
+
): ButtonValidationIssue | null => {
|
|
503
|
+
const backendPath = join(buttonDir, "backend.ts")
|
|
504
|
+
const configPath = join(buttonDir, "config.ts")
|
|
505
|
+
|
|
506
|
+
if (!existsSync(backendPath)) {
|
|
507
|
+
return {
|
|
508
|
+
addon: addonName,
|
|
509
|
+
button: buttonName,
|
|
510
|
+
message: "missing backend.ts",
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (!existsSync(configPath)) {
|
|
514
|
+
return {
|
|
515
|
+
addon: addonName,
|
|
516
|
+
button: buttonName,
|
|
517
|
+
message: "missing config.ts",
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
let backendSrc: string
|
|
522
|
+
try {
|
|
523
|
+
backendSrc = readFileSync(backendPath, "utf8")
|
|
524
|
+
} catch {
|
|
525
|
+
return {
|
|
526
|
+
addon: addonName,
|
|
527
|
+
button: buttonName,
|
|
528
|
+
message: "could not read backend.ts",
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const usesAddonButtonService =
|
|
533
|
+
/import\s+type\s+\{[^}]*AddonButtonService[^}]*\}\s+from\s+["']@\/addon\/api["']/.test(
|
|
534
|
+
backendSrc,
|
|
535
|
+
)
|
|
536
|
+
if (usesAddonButtonService) return null
|
|
537
|
+
|
|
538
|
+
if (
|
|
539
|
+
!/import\s*\{[^}]*\bconfigSchema\b[^}]*\}\s*from\s*["']\.\/config(\.js)?["']/.test(
|
|
540
|
+
backendSrc,
|
|
541
|
+
)
|
|
542
|
+
) {
|
|
543
|
+
return {
|
|
544
|
+
addon: addonName,
|
|
545
|
+
button: buttonName,
|
|
546
|
+
message: 'backend.ts does not import { configSchema } from "./config"',
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let configSrc: string
|
|
551
|
+
try {
|
|
552
|
+
configSrc = readFileSync(configPath, "utf8")
|
|
553
|
+
} catch {
|
|
554
|
+
return {
|
|
555
|
+
addon: addonName,
|
|
556
|
+
button: buttonName,
|
|
557
|
+
message: "could not read config.ts",
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!/export\s+const\s+configSchema\b/.test(configSrc)) {
|
|
562
|
+
return {
|
|
563
|
+
addon: addonName,
|
|
564
|
+
button: buttonName,
|
|
565
|
+
message: 'config.ts does not export "configSchema" as a named const',
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return null
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export const validateBuiltinButtonConfigs =
|
|
573
|
+
(): ReadonlyArray<ButtonValidationIssue> => {
|
|
574
|
+
if (!existsSync(builtinDir)) return []
|
|
575
|
+
const issues: ButtonValidationIssue[] = []
|
|
576
|
+
for (const addonEntry of readdirSync(builtinDir, { withFileTypes: true })) {
|
|
577
|
+
if (!addonEntry.isDirectory()) continue
|
|
578
|
+
const addonName = addonEntry.name
|
|
579
|
+
const addonDir = join(builtinDir, addonName)
|
|
580
|
+
const buttonsDir = join(addonDir, "buttons")
|
|
581
|
+
if (!existsSync(buttonsDir)) continue
|
|
582
|
+
for (const btnEntry of readdirSync(buttonsDir, { withFileTypes: true })) {
|
|
583
|
+
if (!btnEntry.isDirectory()) continue
|
|
584
|
+
const btnDir = join(buttonsDir, btnEntry.name)
|
|
585
|
+
if (!existsSync(join(btnDir, "backend.ts"))) continue
|
|
586
|
+
const issue = validateButtonConfigExport(
|
|
587
|
+
addonName,
|
|
588
|
+
btnDir,
|
|
589
|
+
btnEntry.name,
|
|
590
|
+
)
|
|
591
|
+
if (issue !== null) issues.push(issue)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return issues
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const matchesManifest = (
|
|
598
|
+
publishIntervalMs: number | null,
|
|
599
|
+
addonName: string,
|
|
600
|
+
): boolean => {
|
|
601
|
+
if (publishIntervalMs === null) return false
|
|
602
|
+
return Boolean(addonName)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export interface DiscoveredAddonPoller {
|
|
606
|
+
readonly addonName: string
|
|
607
|
+
readonly channels: ReadonlyArray<AddonPollerChannel>
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export interface AddonPollerDependencies {
|
|
611
|
+
readonly executor?: unknown
|
|
612
|
+
readonly mediaProvider?: unknown
|
|
613
|
+
readonly brightnessProvider?: unknown
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export const discoverAddonPollers = async (
|
|
617
|
+
deps: AddonPollerDependencies,
|
|
618
|
+
scanned: ReadonlyArray<ScannedAddon>,
|
|
619
|
+
): Promise<ReadonlyArray<DiscoveredAddonPoller>> => {
|
|
620
|
+
const out: DiscoveredAddonPoller[] = []
|
|
621
|
+
for (const addon of scanned) {
|
|
622
|
+
if (addon.pollerEntry === null) continue
|
|
623
|
+
if (!matchesManifest(addon.publishIntervalMs, addon.name)) continue
|
|
624
|
+
try {
|
|
625
|
+
const mod = (await import(addon.pollerEntry)) as {
|
|
626
|
+
createPoller?: (deps: AddonPollerDependencies) => AddonPoller
|
|
627
|
+
}
|
|
628
|
+
if (typeof mod.createPoller !== "function") continue
|
|
629
|
+
const poller = mod.createPoller(deps)
|
|
630
|
+
if (poller.channels.length === 0) continue
|
|
631
|
+
out.push({ addonName: addon.name, channels: poller.channels })
|
|
632
|
+
} catch {
|
|
633
|
+
// Poller failed to load; skip.
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return out
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export { scanBuiltinAddons }
|