@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,240 @@
|
|
|
1
|
+
import sharp from "sharp"
|
|
2
|
+
|
|
3
|
+
import { BUTTON_SIZE_PX, gridForKeyCount } from "@/device/models"
|
|
4
|
+
import type { PubSub } from "@/core/pub-sub"
|
|
5
|
+
import { createPubSub } from "@/core/pub-sub"
|
|
6
|
+
import type pino from "pino"
|
|
7
|
+
|
|
8
|
+
import { BufferChangeTracker } from "./buffer-hash"
|
|
9
|
+
import { CadenceTimer, EventDebouncer } from "./screenshot-cadence"
|
|
10
|
+
|
|
11
|
+
export interface PlaywrightLike {
|
|
12
|
+
chromium: {
|
|
13
|
+
launch(opts?: { headless?: boolean; args?: string[] }): Promise<BrowserLike>
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export interface BrowserLike {
|
|
17
|
+
newContext(opts?: {
|
|
18
|
+
viewport?: { width: number; height: number }
|
|
19
|
+
}): Promise<ContextLike>
|
|
20
|
+
close(): Promise<void>
|
|
21
|
+
}
|
|
22
|
+
export interface ContextLike {
|
|
23
|
+
newPage(): Promise<PageLike>
|
|
24
|
+
close(): Promise<void>
|
|
25
|
+
}
|
|
26
|
+
export interface PageLike {
|
|
27
|
+
goto(url: string, opts?: { waitUntil?: string }): Promise<unknown>
|
|
28
|
+
screenshot(opts: { type: "png" }): Promise<Buffer>
|
|
29
|
+
close(): Promise<void>
|
|
30
|
+
route(url: string, handler: (route: RouteLike) => void): void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RouteLike {
|
|
34
|
+
request(): { url(): string }
|
|
35
|
+
continue(opts: { url: string }): void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DeviceSink {
|
|
39
|
+
getKeyCount(): number
|
|
40
|
+
fillKeyBuffer(keyIndex: number, buffer: Buffer): Promise<void>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BrowserRendererOptions {
|
|
44
|
+
readonly frontendUrl: string
|
|
45
|
+
readonly device: DeviceSink
|
|
46
|
+
readonly logger: pino.Logger
|
|
47
|
+
readonly pubSub?: PubSub
|
|
48
|
+
readonly intervalMs?: number
|
|
49
|
+
readonly eventDebounceMs?: number
|
|
50
|
+
readonly playwrightFactory?: () => PlaywrightLike | Promise<PlaywrightLike>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const DEFAULT_INTERVAL_MS = 500
|
|
54
|
+
const DEFAULT_EVENT_DEBOUNCE_MS = 50
|
|
55
|
+
|
|
56
|
+
interface Subscription {
|
|
57
|
+
unsubscribe: () => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class BrowserRenderer {
|
|
61
|
+
private readonly intervalMs: number
|
|
62
|
+
private readonly eventDebounceMs: number
|
|
63
|
+
private readonly pubSub: PubSub | undefined
|
|
64
|
+
private readonly cadence: CadenceTimer
|
|
65
|
+
private readonly debouncer: EventDebouncer
|
|
66
|
+
private readonly tracker = new BufferChangeTracker()
|
|
67
|
+
private readonly subscriptions: Subscription[] = []
|
|
68
|
+
/**
|
|
69
|
+
* Number of consecutive ticks where every key buffer was unchanged.
|
|
70
|
+
* Once it crosses `BLANK_THRESHOLD`, we suspect the React tree has been
|
|
71
|
+
* unmounted (typically by an HMR error that escaped the ErrorBoundary)
|
|
72
|
+
* and warn the user via the logger. The renderer does not abort —
|
|
73
|
+
* screenshots still flow — but the operator gets a visible signal.
|
|
74
|
+
*/
|
|
75
|
+
private staticBlankTickCount = 0
|
|
76
|
+
private browser: BrowserLike | null = null
|
|
77
|
+
private context: ContextLike | null = null
|
|
78
|
+
private page: PageLike | null = null
|
|
79
|
+
private running = false
|
|
80
|
+
private readonly logger: pino.Logger
|
|
81
|
+
|
|
82
|
+
constructor(private readonly options: BrowserRendererOptions) {
|
|
83
|
+
this.logger = options.logger.child({ component: "browser-renderer" })
|
|
84
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS
|
|
85
|
+
this.eventDebounceMs = options.eventDebounceMs ?? DEFAULT_EVENT_DEBOUNCE_MS
|
|
86
|
+
this.pubSub = options.pubSub
|
|
87
|
+
this.cadence = new CadenceTimer({
|
|
88
|
+
intervalMs: this.intervalMs,
|
|
89
|
+
onTick: () => this.tick("timer"),
|
|
90
|
+
logger: this.logger,
|
|
91
|
+
})
|
|
92
|
+
this.debouncer = new EventDebouncer({
|
|
93
|
+
delayMs: this.eventDebounceMs,
|
|
94
|
+
onFlush: () => this.tick("event"),
|
|
95
|
+
logger: this.logger,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async start(): Promise<void> {
|
|
100
|
+
if (this.running) return
|
|
101
|
+
const factory =
|
|
102
|
+
this.options.playwrightFactory ??
|
|
103
|
+
(async (): Promise<PlaywrightLike> =>
|
|
104
|
+
(await import("playwright")) as PlaywrightLike)
|
|
105
|
+
const playwright = await factory()
|
|
106
|
+
try {
|
|
107
|
+
this.browser = await playwright.chromium.launch({ headless: true })
|
|
108
|
+
} catch (err) {
|
|
109
|
+
// ponytail: hardware mode renders every deck frame in headless chromium,
|
|
110
|
+
// so a missing browser download is fatal — but Playwright's own message
|
|
111
|
+
// is a box-drawing banner wrapped in a stack trace that never mentions
|
|
112
|
+
// sirenodeck. Dev builds never auto-install it (ensurePlaywright only
|
|
113
|
+
// ran for installed trees), so this is the first thing a contributor
|
|
114
|
+
// hits when they plug in a real deck. Say what to run.
|
|
115
|
+
const message = (err as Error).message ?? ""
|
|
116
|
+
if (
|
|
117
|
+
/Executable doesn't exist|please run the following command/i.test(
|
|
118
|
+
message,
|
|
119
|
+
)
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Headless chromium is not installed — sirenodeck renders deck images with it.\n" +
|
|
123
|
+
" Fix: pnpm --filter @sirenodeck/sirenodeck exec playwright install chromium\n" +
|
|
124
|
+
` (original error: ${message.split("\n")[0] ?? ""})`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
throw err
|
|
128
|
+
}
|
|
129
|
+
const { columns, rows } = gridForKeyCount(this.options.device.getKeyCount())
|
|
130
|
+
const vpWidth = columns * BUTTON_SIZE_PX
|
|
131
|
+
const vpHeight = rows * BUTTON_SIZE_PX
|
|
132
|
+
this.context = await this.browser.newContext({
|
|
133
|
+
viewport: { width: vpWidth, height: vpHeight },
|
|
134
|
+
})
|
|
135
|
+
this.page = await this.context.newPage()
|
|
136
|
+
await this.page.goto(this.options.frontendUrl, { waitUntil: "networkidle" })
|
|
137
|
+
if (this.pubSub) {
|
|
138
|
+
this.subscriptions.push(
|
|
139
|
+
{
|
|
140
|
+
unsubscribe: this.pubSub.subscribe("runtime:activeDeck", () =>
|
|
141
|
+
this.debouncer.trigger(),
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
unsubscribe: this.pubSub.subscribe("runtime:invalidate", () =>
|
|
146
|
+
this.debouncer.trigger(),
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
this.cadence.start()
|
|
152
|
+
this.running = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Repaints every key on the next tick.
|
|
157
|
+
*
|
|
158
|
+
* ponytail: the change tracker skips keys whose bytes have not changed, so
|
|
159
|
+
* after the hardware goes away and comes back the deck would stay blank —
|
|
160
|
+
* the tracker still holds the hashes of what the OLD handle was showing.
|
|
161
|
+
* Nothing reset it before this; `runtime:invalidate` only triggers a tick.
|
|
162
|
+
*/
|
|
163
|
+
forceRedraw(): void {
|
|
164
|
+
this.tracker.reset()
|
|
165
|
+
this.debouncer.trigger()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async stop(): Promise<void> {
|
|
169
|
+
if (!this.running) return
|
|
170
|
+
this.running = false
|
|
171
|
+
this.cadence.stop()
|
|
172
|
+
this.debouncer.dispose()
|
|
173
|
+
for (const sub of this.subscriptions) sub.unsubscribe()
|
|
174
|
+
this.subscriptions.length = 0
|
|
175
|
+
if (this.page) await this.page.close()
|
|
176
|
+
if (this.context) await this.context.close()
|
|
177
|
+
if (this.browser) await this.browser.close()
|
|
178
|
+
this.page = null
|
|
179
|
+
this.context = null
|
|
180
|
+
this.browser = null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static readonly BLANK_THRESHOLD = 20
|
|
184
|
+
|
|
185
|
+
private async tick(source: "timer" | "event"): Promise<void> {
|
|
186
|
+
if (!this.page) return
|
|
187
|
+
try {
|
|
188
|
+
const png = await this.page.screenshot({ type: "png" })
|
|
189
|
+
const { columns, rows } = gridForKeyCount(
|
|
190
|
+
this.options.device.getKeyCount(),
|
|
191
|
+
)
|
|
192
|
+
const meta = await sharp(png).metadata()
|
|
193
|
+
const cellWidth = Math.floor((meta.width ?? 0) / columns)
|
|
194
|
+
const cellHeight = Math.floor((meta.height ?? 0) / rows)
|
|
195
|
+
const writes: Array<Promise<void>> = []
|
|
196
|
+
for (let r = 0; r < rows; r++) {
|
|
197
|
+
for (let c = 0; c < columns; c++) {
|
|
198
|
+
const keyIndex = r * columns + c
|
|
199
|
+
const cropped = await sharp(png)
|
|
200
|
+
.extract({
|
|
201
|
+
left: c * cellWidth,
|
|
202
|
+
top: r * cellHeight,
|
|
203
|
+
width: cellWidth,
|
|
204
|
+
height: cellHeight,
|
|
205
|
+
})
|
|
206
|
+
.resize(72, 72, { fit: "fill" })
|
|
207
|
+
.removeAlpha()
|
|
208
|
+
.raw()
|
|
209
|
+
.toBuffer()
|
|
210
|
+
if (!this.tracker.update(keyIndex, cropped)) continue
|
|
211
|
+
writes.push(this.options.device.fillKeyBuffer(keyIndex, cropped))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (writes.length > 0) {
|
|
215
|
+
await Promise.all(writes)
|
|
216
|
+
this.staticBlankTickCount = 0
|
|
217
|
+
this.logger.debug(
|
|
218
|
+
{ source, writes: writes.length },
|
|
219
|
+
"renderer tick wrote keys",
|
|
220
|
+
)
|
|
221
|
+
} else {
|
|
222
|
+
this.staticBlankTickCount += 1
|
|
223
|
+
this.logger.debug({ source }, "renderer tick no changes")
|
|
224
|
+
if (this.staticBlankTickCount === BrowserRenderer.BLANK_THRESHOLD) {
|
|
225
|
+
this.logger.warn(
|
|
226
|
+
{
|
|
227
|
+
consecutiveTicks: this.staticBlankTickCount,
|
|
228
|
+
source,
|
|
229
|
+
},
|
|
230
|
+
"frontend appears blank — React tree may have crashed (check the browser console or HMR errors)",
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
this.logger.warn({ err, source }, "renderer tick failed")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const _testInternals = { createPubSub }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
|
|
3
|
+
export const hashBuffer = (buf: Buffer): string =>
|
|
4
|
+
createHash("sha1").update(buf).digest("hex").slice(0, 16)
|
|
5
|
+
|
|
6
|
+
export class BufferChangeTracker {
|
|
7
|
+
private readonly last = new Map<number, string>()
|
|
8
|
+
|
|
9
|
+
update(keyIndex: number, buf: Buffer): boolean {
|
|
10
|
+
const hash = hashBuffer(buf)
|
|
11
|
+
const previous = this.last.get(keyIndex)
|
|
12
|
+
if (previous === hash) return false
|
|
13
|
+
this.last.set(keyIndex, hash)
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
reset(): void {
|
|
18
|
+
this.last.clear()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { WebSocket } from "ws"
|
|
2
|
+
|
|
3
|
+
import type { ConfigMutationService } from "@/config/mutation"
|
|
4
|
+
import {
|
|
5
|
+
editorMutationResultMessageSchema,
|
|
6
|
+
editorStateMessageSchema,
|
|
7
|
+
type EditorMutationMessage,
|
|
8
|
+
type EditorUndoMessage,
|
|
9
|
+
type EditorStateMessage,
|
|
10
|
+
editorValidationResultMessageSchema,
|
|
11
|
+
editorSourceValidationResultMessageSchema,
|
|
12
|
+
type EditorSourceValidationRequestMessage,
|
|
13
|
+
type EditorValidationRequestMessage,
|
|
14
|
+
type WsMessage,
|
|
15
|
+
} from "./protocol"
|
|
16
|
+
|
|
17
|
+
export interface EditorMessageHandlerOptions {
|
|
18
|
+
readonly mutationService: ConfigMutationService
|
|
19
|
+
readonly getState: () => Omit<
|
|
20
|
+
EditorStateMessage,
|
|
21
|
+
"type" | "revision" | "canUndo" | "buttonSchemas" | "surfaces"
|
|
22
|
+
> & {
|
|
23
|
+
buttonSchemas?: EditorStateMessage["buttonSchemas"]
|
|
24
|
+
surfaces?: EditorStateMessage["surfaces"]
|
|
25
|
+
revision?: number
|
|
26
|
+
}
|
|
27
|
+
readonly onChanged?: () => void | Promise<void>
|
|
28
|
+
readonly broadcast: (message: WsMessage) => void
|
|
29
|
+
readonly validateConfig?: (buttonType: string, config: unknown) => string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface EditorMessageHandler {
|
|
33
|
+
readonly onMessage: (message: WsMessage, socket: WebSocket) => void
|
|
34
|
+
readonly onConnection: (socket: WebSocket) => void
|
|
35
|
+
readonly invalidate: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const createEditorMessageHandler = (
|
|
39
|
+
options: EditorMessageHandlerOptions,
|
|
40
|
+
): EditorMessageHandler => {
|
|
41
|
+
let revision = options.getState().revision ?? 0
|
|
42
|
+
let applyQueue = Promise.resolve()
|
|
43
|
+
|
|
44
|
+
const state = (): EditorStateMessage =>
|
|
45
|
+
editorStateMessageSchema.parse({
|
|
46
|
+
type: "editor-state",
|
|
47
|
+
...options.getState(),
|
|
48
|
+
revision,
|
|
49
|
+
canUndo: options.mutationService.canUndo(),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const send = (socket: WebSocket, message: WsMessage): void => {
|
|
53
|
+
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(message))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = (
|
|
57
|
+
socket: WebSocket,
|
|
58
|
+
requestId: string,
|
|
59
|
+
ok: boolean,
|
|
60
|
+
error?: string,
|
|
61
|
+
): void => {
|
|
62
|
+
send(
|
|
63
|
+
socket,
|
|
64
|
+
editorMutationResultMessageSchema.parse({
|
|
65
|
+
type: "editor-mutation-result",
|
|
66
|
+
requestId,
|
|
67
|
+
ok,
|
|
68
|
+
revision,
|
|
69
|
+
...(error !== undefined ? { error } : {}),
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const validationResult = (
|
|
75
|
+
socket: WebSocket,
|
|
76
|
+
request: EditorValidationRequestMessage,
|
|
77
|
+
): void => {
|
|
78
|
+
const errors =
|
|
79
|
+
options.validateConfig?.(request.buttonType, request.config) ?? []
|
|
80
|
+
send(
|
|
81
|
+
socket,
|
|
82
|
+
editorValidationResultMessageSchema.parse({
|
|
83
|
+
type: "editor-validation-result",
|
|
84
|
+
requestId: request.requestId,
|
|
85
|
+
valid: errors.length === 0,
|
|
86
|
+
errors,
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sourceValidationResult = (
|
|
92
|
+
socket: WebSocket,
|
|
93
|
+
request: EditorSourceValidationRequestMessage,
|
|
94
|
+
): void => {
|
|
95
|
+
const errors = options.mutationService.validateSource(
|
|
96
|
+
request.path,
|
|
97
|
+
request.content,
|
|
98
|
+
)
|
|
99
|
+
send(
|
|
100
|
+
socket,
|
|
101
|
+
editorSourceValidationResultMessageSchema.parse({
|
|
102
|
+
type: "editor-source-validation-result",
|
|
103
|
+
requestId: request.requestId,
|
|
104
|
+
valid: errors.length === 0,
|
|
105
|
+
errors,
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const apply = async (
|
|
111
|
+
socket: WebSocket,
|
|
112
|
+
requestId: string,
|
|
113
|
+
requestRevision: number,
|
|
114
|
+
operation: () => Promise<boolean | void>,
|
|
115
|
+
): Promise<void> => {
|
|
116
|
+
const task = async (): Promise<void> => {
|
|
117
|
+
if (requestRevision !== revision) {
|
|
118
|
+
result(socket, requestId, false, "stale editor revision")
|
|
119
|
+
send(socket, state())
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const changed = await operation()
|
|
124
|
+
if (changed === false) {
|
|
125
|
+
result(socket, requestId, false, "nothing to undo")
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
revision += 1
|
|
129
|
+
await options.onChanged?.()
|
|
130
|
+
result(socket, requestId, true)
|
|
131
|
+
options.broadcast(state())
|
|
132
|
+
} catch (error) {
|
|
133
|
+
result(
|
|
134
|
+
socket,
|
|
135
|
+
requestId,
|
|
136
|
+
false,
|
|
137
|
+
error instanceof Error ? error.message : String(error),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const queued = applyQueue.then(task, task)
|
|
142
|
+
applyQueue = queued.then(
|
|
143
|
+
() => undefined,
|
|
144
|
+
() => undefined,
|
|
145
|
+
)
|
|
146
|
+
await queued
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
onMessage: (message, socket) => {
|
|
151
|
+
if (message.type === "editor-state-request") {
|
|
152
|
+
send(socket, state())
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (message.type === "editor-validation-request") {
|
|
156
|
+
validationResult(socket, message)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (message.type === "editor-source-validation-request") {
|
|
160
|
+
sourceValidationResult(socket, message)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (message.type === "editor-asset-write") {
|
|
164
|
+
void options.mutationService
|
|
165
|
+
.writeAsset(message.filename, message.data)
|
|
166
|
+
.then(() => result(socket, message.requestId, true))
|
|
167
|
+
.catch((error: unknown) =>
|
|
168
|
+
result(
|
|
169
|
+
socket,
|
|
170
|
+
message.requestId,
|
|
171
|
+
false,
|
|
172
|
+
error instanceof Error ? error.message : String(error),
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
if (message.type === "editor-mutate") {
|
|
178
|
+
void apply(socket, message.requestId, message.revision, () =>
|
|
179
|
+
options.mutationService.apply(message.mutation),
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
if (message.type === "editor-undo") {
|
|
184
|
+
void apply(socket, message.requestId, message.revision, () =>
|
|
185
|
+
options.mutationService.undo(),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
onConnection: (socket) => send(socket, state()),
|
|
190
|
+
invalidate: () => {
|
|
191
|
+
revision += 1
|
|
192
|
+
options.broadcast(state())
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type { EditorMutationMessage, EditorUndoMessage }
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { dirname, resolve } from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
export interface EmulatorServerOptions {
|
|
7
|
+
readonly port: number
|
|
8
|
+
readonly frontendEmulatorDir: string
|
|
9
|
+
readonly env?: Readonly<Record<string, string>>
|
|
10
|
+
readonly logger?: (msg: string) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface EmulatorServerHandle {
|
|
14
|
+
readonly child: ChildProcess
|
|
15
|
+
readonly port: number
|
|
16
|
+
readonly url: string
|
|
17
|
+
close(): Promise<void>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const READY_PREFIX = "READY "
|
|
21
|
+
|
|
22
|
+
export const parseReadyLine = (line: string): number | null => {
|
|
23
|
+
const trimmed = line.trim()
|
|
24
|
+
if (!trimmed.startsWith(READY_PREFIX)) return null
|
|
25
|
+
const portStr = trimmed.slice(READY_PREFIX.length).trim()
|
|
26
|
+
const port = Number.parseInt(portStr, 10)
|
|
27
|
+
return Number.isFinite(port) ? port : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const startEmulatorServer = async (
|
|
31
|
+
options: EmulatorServerOptions,
|
|
32
|
+
): Promise<EmulatorServerHandle> => {
|
|
33
|
+
const { port, frontendEmulatorDir } = options
|
|
34
|
+
const log = options.logger ?? ((): void => {})
|
|
35
|
+
const viteBin = resolve(frontendEmulatorDir, "node_modules", ".bin", "vite")
|
|
36
|
+
const cliEntry = resolve(frontendEmulatorDir, "src", "main")
|
|
37
|
+
|
|
38
|
+
const env: Record<string, string> = {
|
|
39
|
+
...(process.env as Record<string, string>),
|
|
40
|
+
...options.env,
|
|
41
|
+
SIRENO_EMULATOR_PORT: String(port),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const child = spawn(
|
|
45
|
+
"node",
|
|
46
|
+
["--import", "tsx/esm", viteBin, "--port", String(port), cliEntry],
|
|
47
|
+
{
|
|
48
|
+
cwd: frontendEmulatorDir,
|
|
49
|
+
env,
|
|
50
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return await new Promise<EmulatorServerHandle>((resolvePromise, reject) => {
|
|
55
|
+
let settled = false
|
|
56
|
+
const onData = (chunk: Buffer): void => {
|
|
57
|
+
const text = chunk.toString("utf8")
|
|
58
|
+
log(text)
|
|
59
|
+
for (const line of text.split("\n")) {
|
|
60
|
+
if (line.startsWith(READY_PREFIX)) {
|
|
61
|
+
const actualPort = Number.parseInt(
|
|
62
|
+
line.slice(READY_PREFIX.length).trim(),
|
|
63
|
+
10,
|
|
64
|
+
)
|
|
65
|
+
if (!Number.isFinite(actualPort)) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if (settled) return
|
|
69
|
+
settled = true
|
|
70
|
+
child.stdout?.off("data", onData)
|
|
71
|
+
child.stderr?.off("data", onErr)
|
|
72
|
+
resolvePromise({
|
|
73
|
+
child,
|
|
74
|
+
port: actualPort,
|
|
75
|
+
url: `http://127.0.0.1:${actualPort}`,
|
|
76
|
+
close: () => closeEmulator(child),
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const onErr = (chunk: Buffer): void => {
|
|
82
|
+
log(`[emulator stderr] ${chunk.toString("utf8")}`)
|
|
83
|
+
}
|
|
84
|
+
child.stdout?.on("data", onData)
|
|
85
|
+
child.stderr?.on("data", onErr)
|
|
86
|
+
child.once("error", (err) => {
|
|
87
|
+
if (!settled) {
|
|
88
|
+
settled = true
|
|
89
|
+
reject(err)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
child.once("exit", (code) => {
|
|
93
|
+
if (!settled) {
|
|
94
|
+
settled = true
|
|
95
|
+
reject(new Error(`emulator child exited early with code ${code}`))
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const closeEmulator = (child: ChildProcess): Promise<void> =>
|
|
102
|
+
new Promise<void>((resolveClose) => {
|
|
103
|
+
if (child.exitCode !== null) {
|
|
104
|
+
resolveClose()
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
child.once("exit", () => resolveClose())
|
|
108
|
+
child.kill("SIGTERM")
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
if (child.exitCode === null) child.kill("SIGKILL")
|
|
111
|
+
}, 2000).unref()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export const emulatorServerEntryExists = (
|
|
115
|
+
frontendEmulatorDir: string,
|
|
116
|
+
): boolean => {
|
|
117
|
+
const main = resolve(frontendEmulatorDir, "src", "main")
|
|
118
|
+
const pkg = resolve(frontendEmulatorDir, "package.json")
|
|
119
|
+
return existsSync(main) && existsSync(pkg)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const __test_internals = {
|
|
123
|
+
resolveEntry: (frontendEmulatorDir: string): string =>
|
|
124
|
+
resolve(frontendEmulatorDir, "src", "main"),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const _internalDirname = dirname(fileURLToPath(import.meta.url))
|
|
128
|
+
void _internalDirname
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isAbsolute, join } from "node:path"
|
|
2
|
+
|
|
3
|
+
import { EMOJI_RE } from "../core/icon-source"
|
|
4
|
+
|
|
5
|
+
export interface ResolveIconPathOptions {
|
|
6
|
+
readonly addonDirs?: ReadonlyMap<string, string>
|
|
7
|
+
readonly baseDirs?: ReadonlyArray<string>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ResolvedIconSource =
|
|
11
|
+
| { kind: "generic"; name: string }
|
|
12
|
+
| { kind: "asset"; fullPath: string }
|
|
13
|
+
| { kind: "emoji"; emoji: string }
|
|
14
|
+
|
|
15
|
+
export const resolveIconSource = (
|
|
16
|
+
source: string,
|
|
17
|
+
options: ResolveIconPathOptions = {},
|
|
18
|
+
): ResolvedIconSource => {
|
|
19
|
+
if (EMOJI_RE.test(source)) {
|
|
20
|
+
// Emojis don't need any path resolution — the frontend renders them
|
|
21
|
+
// directly as a span. Returning a "generic" kind would be misleading
|
|
22
|
+
// (no icon name), so we have a dedicated "emoji" kind that callers
|
|
23
|
+
// can recognize and skip path/asset bookkeeping.
|
|
24
|
+
return { kind: "emoji", emoji: source }
|
|
25
|
+
}
|
|
26
|
+
if (source.startsWith("icon://")) {
|
|
27
|
+
return { kind: "generic", name: source.slice("icon://".length) }
|
|
28
|
+
}
|
|
29
|
+
if (source.startsWith("addon://")) {
|
|
30
|
+
const match = /^addon:\/\/([^/]+)\/(.*)$/.exec(source)
|
|
31
|
+
const [, addonName, assetPath] = match ?? []
|
|
32
|
+
if (addonName !== undefined && assetPath !== undefined) {
|
|
33
|
+
const addonDir = options.addonDirs?.get(addonName)
|
|
34
|
+
if (addonDir) {
|
|
35
|
+
return { kind: "asset", fullPath: join(addonDir, assetPath) }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Cannot resolve addon icon "${source}": unknown addon dir`)
|
|
39
|
+
}
|
|
40
|
+
if (source.startsWith("builtin://")) {
|
|
41
|
+
const assetPath = source.slice("builtin://".length)
|
|
42
|
+
const builtinDir = options.addonDirs?.get("builtin")
|
|
43
|
+
if (builtinDir) {
|
|
44
|
+
return { kind: "asset", fullPath: join(builtinDir, assetPath) }
|
|
45
|
+
}
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Cannot resolve builtin icon "${source}": builtin dir not registered`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
source.startsWith("data:") ||
|
|
52
|
+
source.startsWith("http://") ||
|
|
53
|
+
source.startsWith("https://") ||
|
|
54
|
+
source.startsWith("file://")
|
|
55
|
+
) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Inline icon "${source.slice(0, 32)}…" must be pre-resolved to asset:// before reaching the Icon component`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
if (isAbsolute(source)) {
|
|
61
|
+
return { kind: "asset", fullPath: source }
|
|
62
|
+
}
|
|
63
|
+
// ponytail: returns on first baseDir; baseDirs is always 1 entry today (config dir).
|
|
64
|
+
// If multiple baseDirs ever get added, this needs a statSync existence check + iteration.
|
|
65
|
+
for (const baseDir of options.baseDirs ?? []) {
|
|
66
|
+
return { kind: "asset", fullPath: join(baseDir, source) }
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Cannot resolve icon "${source}": no base dirs configured`)
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BrowserRenderer,
|
|
3
|
+
type BrowserRendererOptions,
|
|
4
|
+
type DeviceSink,
|
|
5
|
+
type PlaywrightLike,
|
|
6
|
+
type BrowserLike,
|
|
7
|
+
type ContextLike,
|
|
8
|
+
type PageLike,
|
|
9
|
+
} from "./browser-renderer"
|
|
10
|
+
export {
|
|
11
|
+
CadenceTimer,
|
|
12
|
+
EventDebouncer,
|
|
13
|
+
type CadenceTimerOptions,
|
|
14
|
+
type EventDebouncerOptions,
|
|
15
|
+
} from "./screenshot-cadence"
|
|
16
|
+
export { BufferChangeTracker, hashBuffer } from "./buffer-hash"
|