@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,175 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
export type PackageManager = "pnpm" | "npm" | "yarn"
|
|
6
|
+
|
|
7
|
+
const managers: PackageManager[] = ["pnpm", "npm", "yarn"]
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ponytail: every probe in this module runs a package manager we did not write,
|
|
11
|
+
* found on `PATH`, purely to ask it a question. A broken one must not be able to
|
|
12
|
+
* take the daemon down with it, so the probes are bounded on three axes:
|
|
13
|
+
*
|
|
14
|
+
* - `timeout` + `killSignal`, because a hung manager used to hang startup
|
|
15
|
+
* forever with no output at all. A corrupt pnpm shim that re-`exec`s itself
|
|
16
|
+
* made `pnpm root --global` never return; `execFileSync` has no timeout by
|
|
17
|
+
* default, so the daemon simply stopped there, before it bound a port or lit
|
|
18
|
+
* a single key. SIGTERM is not enough — a manager that is busy spawning does
|
|
19
|
+
* not get round to handling it — so the deadline is enforced with SIGKILL.
|
|
20
|
+
* - `maxBuffer`, so a manager that writes without end fails the probe instead
|
|
21
|
+
* of growing the daemon's heap.
|
|
22
|
+
* - no network. These probes only ask "are you there?" and "where do you keep
|
|
23
|
+
* global packages?". Corepack reads that as licence to go and fetch the
|
|
24
|
+
* manager first — probing for `yarn` downloaded Yarn 1.22.22 on a machine
|
|
25
|
+
* that had never asked for it. A read-only question installs nothing.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 10_000
|
|
28
|
+
const PROBE_KILL_SIGNAL = "SIGKILL"
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `SIRENO_PACKAGE_MANAGER_TIMEOUT_MS` raises the deadline for a machine where a
|
|
32
|
+
* cold Corepack legitimately needs longer than ten seconds to answer, and lets
|
|
33
|
+
* the tests use a deadline they can wait for. Anything unparseable or
|
|
34
|
+
* non-positive falls back to the default rather than disabling the bound.
|
|
35
|
+
*/
|
|
36
|
+
export const packageManagerProbeTimeoutMs = (): number => {
|
|
37
|
+
const raw = process.env["SIRENO_PACKAGE_MANAGER_TIMEOUT_MS"]
|
|
38
|
+
const parsed = raw === undefined ? Number.NaN : Number.parseInt(raw, 10)
|
|
39
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
40
|
+
? parsed
|
|
41
|
+
: DEFAULT_PROBE_TIMEOUT_MS
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const probeEnv = (): NodeJS.ProcessEnv => ({
|
|
45
|
+
...process.env,
|
|
46
|
+
COREPACK_ENABLE_NETWORK: "0",
|
|
47
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const probeOptions = () =>
|
|
51
|
+
({
|
|
52
|
+
timeout: packageManagerProbeTimeoutMs(),
|
|
53
|
+
killSignal: PROBE_KILL_SIGNAL,
|
|
54
|
+
maxBuffer: 1024 * 1024,
|
|
55
|
+
}) as const
|
|
56
|
+
|
|
57
|
+
const hasCommand = (command: string): boolean => {
|
|
58
|
+
try {
|
|
59
|
+
execFileSync(command, ["--version"], {
|
|
60
|
+
...probeOptions(),
|
|
61
|
+
stdio: "ignore",
|
|
62
|
+
env: probeEnv(),
|
|
63
|
+
})
|
|
64
|
+
return true
|
|
65
|
+
} catch {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const detectPackageManager = (projectDir: string): PackageManager => {
|
|
71
|
+
const lockfile = managers.find((manager) =>
|
|
72
|
+
existsSync(
|
|
73
|
+
join(
|
|
74
|
+
projectDir,
|
|
75
|
+
manager === "pnpm"
|
|
76
|
+
? "pnpm-lock.yaml"
|
|
77
|
+
: manager === "npm"
|
|
78
|
+
? "package-lock.json"
|
|
79
|
+
: "yarn.lock",
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
if (lockfile !== undefined && hasCommand(lockfile)) return lockfile
|
|
84
|
+
const available = managers.find(hasCommand)
|
|
85
|
+
if (available === undefined)
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Install pnpm, npm, or yarn before installing an addon or theme.",
|
|
88
|
+
)
|
|
89
|
+
return available
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const assertPackageManager = (manager: PackageManager): void => {
|
|
93
|
+
if (!hasCommand(manager))
|
|
94
|
+
throw new Error(`Package manager '${manager}' is not installed.`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const packageManagerArgs = (
|
|
98
|
+
manager: PackageManager,
|
|
99
|
+
specifier: string,
|
|
100
|
+
global: boolean,
|
|
101
|
+
): string[] => {
|
|
102
|
+
if (global) {
|
|
103
|
+
return manager === "pnpm"
|
|
104
|
+
? ["add", "--global", specifier]
|
|
105
|
+
: manager === "npm"
|
|
106
|
+
? ["install", "--global", specifier]
|
|
107
|
+
: ["global", "add", specifier]
|
|
108
|
+
}
|
|
109
|
+
return manager === "pnpm"
|
|
110
|
+
? ["add", "--save-exact", specifier]
|
|
111
|
+
: manager === "npm"
|
|
112
|
+
? ["install", "--save-exact", specifier]
|
|
113
|
+
: ["add", "--exact", specifier]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type RootProbe =
|
|
117
|
+
| { readonly kind: "root"; readonly root: string }
|
|
118
|
+
| { readonly kind: "unavailable" }
|
|
119
|
+
| { readonly kind: "timeout" }
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Only the deadline is worth reporting. A manager that is absent, that exits
|
|
123
|
+
* non-zero, or that declines the question ("the global bin directory is not in
|
|
124
|
+
* PATH", or Corepack refusing a manager this project does not pin) has answered
|
|
125
|
+
* — it simply has no root to offer, which is the ordinary case on a machine
|
|
126
|
+
* that uses one of the three. Warning about those would fire on every start.
|
|
127
|
+
*
|
|
128
|
+
* Being killed at the deadline is different: the manager never answered at all,
|
|
129
|
+
* and before the deadline existed that is precisely what hung the daemon.
|
|
130
|
+
*/
|
|
131
|
+
const probeRoot = (manager: PackageManager): RootProbe => {
|
|
132
|
+
try {
|
|
133
|
+
const root = execFileSync(manager, ["root", "--global"], {
|
|
134
|
+
...probeOptions(),
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
env: probeEnv(),
|
|
137
|
+
}).trim()
|
|
138
|
+
return root.length > 0 ? { kind: "root", root } : { kind: "unavailable" }
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const err = error as NodeJS.ErrnoException & { signal?: string | null }
|
|
141
|
+
// `execFileSync` reports the deadline as the signal it killed the child
|
|
142
|
+
// with; `ETIMEDOUT` covers the platforms that surface it as an error code.
|
|
143
|
+
return err.signal === PROBE_KILL_SIGNAL || err.code === "ETIMEDOUT"
|
|
144
|
+
? { kind: "timeout" }
|
|
145
|
+
: { kind: "unavailable" }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const globalPackageRoot = (manager: PackageManager): string | null => {
|
|
150
|
+
const probe = probeRoot(manager)
|
|
151
|
+
return probe.kind === "root" ? probe.root : null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface GlobalPackageRootProbe {
|
|
155
|
+
readonly roots: ReadonlyArray<string>
|
|
156
|
+
readonly timedOut: ReadonlyArray<PackageManager>
|
|
157
|
+
readonly timeoutMs: number
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Ask every package manager where it keeps its global packages, so an addon or
|
|
162
|
+
* theme installed globally can be resolved from there. Managers that cannot
|
|
163
|
+
* answer contribute nothing; the ones that ran out of time are named, because
|
|
164
|
+
* a silent ten-second stall per manager needs explaining.
|
|
165
|
+
*/
|
|
166
|
+
export const probeGlobalPackageRoots = (): GlobalPackageRootProbe => {
|
|
167
|
+
const roots: string[] = []
|
|
168
|
+
const timedOut: PackageManager[] = []
|
|
169
|
+
for (const manager of managers) {
|
|
170
|
+
const probe = probeRoot(manager)
|
|
171
|
+
if (probe.kind === "root") roots.push(probe.root)
|
|
172
|
+
else if (probe.kind === "timeout") timedOut.push(manager)
|
|
173
|
+
}
|
|
174
|
+
return { roots, timedOut, timeoutMs: packageManagerProbeTimeoutMs() }
|
|
175
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { homedir } from "node:os"
|
|
3
|
+
|
|
4
|
+
import type { CommandExecutor } from "@/system/providers/shared"
|
|
5
|
+
import type { ProbeDeps } from "@/system/setup-wizard"
|
|
6
|
+
|
|
7
|
+
// ponytail: shared probe deps for the startup banner and the first-run check.
|
|
8
|
+
// Both call sites use these identical deps, so probeAllCached's single-slot
|
|
9
|
+
// TTL cache returns the same report to both — the banner's probe and the
|
|
10
|
+
// first-run probe don't double the subprocess cost.
|
|
11
|
+
|
|
12
|
+
export const wizardCommandExecutor: CommandExecutor = {
|
|
13
|
+
async run(command, args, options) {
|
|
14
|
+
const { execa } = await import("execa")
|
|
15
|
+
const result = await execa(command, [...args], {
|
|
16
|
+
reject: false,
|
|
17
|
+
timeout: options?.timeoutMs ?? 5_000,
|
|
18
|
+
})
|
|
19
|
+
return {
|
|
20
|
+
exitCode: result.exitCode ?? -1,
|
|
21
|
+
stdout: result.stdout ?? "",
|
|
22
|
+
stderr: result.stderr ?? "",
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const buildStandardProbeDeps = (): ProbeDeps => {
|
|
28
|
+
const home = homedir()
|
|
29
|
+
const xdgConfigHome = process.env["XDG_CONFIG_HOME"] ?? `${home}/.config`
|
|
30
|
+
return {
|
|
31
|
+
platform: process.platform,
|
|
32
|
+
homeDir: home,
|
|
33
|
+
xdgConfigHome,
|
|
34
|
+
env: process.env,
|
|
35
|
+
executor: wizardCommandExecutor,
|
|
36
|
+
fileExists: (p) => existsSync(p),
|
|
37
|
+
readFile: (p) => {
|
|
38
|
+
try {
|
|
39
|
+
return readFileSync(p, "utf8")
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ponytail: thin re-export of @clack/prompts so backend modules
|
|
2
|
+
// (`packages/cli/src/cli/...`) can pull clack's primitives without
|
|
3
|
+
// each one importing the upstream package name. Lives in `cli/` not
|
|
4
|
+
// `ui/` because the `ui/` tree is reserved for React surface code
|
|
5
|
+
// consumed by `packages/cli/frontend/` and `packages/cli/config-ui/`.
|
|
6
|
+
//
|
|
7
|
+
// Anything that imports this file MUST be in the CLI backend — never
|
|
8
|
+
// the frontend or emulator SPAs.
|
|
9
|
+
export {
|
|
10
|
+
intro,
|
|
11
|
+
outro,
|
|
12
|
+
note,
|
|
13
|
+
log,
|
|
14
|
+
tasks,
|
|
15
|
+
spinner,
|
|
16
|
+
select,
|
|
17
|
+
confirm,
|
|
18
|
+
text,
|
|
19
|
+
password,
|
|
20
|
+
isCancel,
|
|
21
|
+
cancel,
|
|
22
|
+
} from "@clack/prompts"
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { connect, type Server, createServer, type Socket } from "node:net"
|
|
2
|
+
|
|
3
|
+
import qrcode from "qrcode"
|
|
4
|
+
|
|
5
|
+
import { listDevices, type DeviceDescriptor } from "@/device"
|
|
6
|
+
import type { AddonCheckOutcome } from "@/addon/check-runner"
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
probeAllCached,
|
|
10
|
+
probeCommandExecution,
|
|
11
|
+
probeInternetAccess,
|
|
12
|
+
resetProbeCache,
|
|
13
|
+
type RuntimeFeatureProbe,
|
|
14
|
+
type SystemReport,
|
|
15
|
+
} from "@/system/setup-wizard"
|
|
16
|
+
|
|
17
|
+
import { cancel, intro, log, outro } from "@/cli/prompt"
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
readDaemonEventsFromSnapshot,
|
|
21
|
+
type DaemonEvent,
|
|
22
|
+
type DaemonLogSnapshot,
|
|
23
|
+
} from "@/util/log-reader"
|
|
24
|
+
import {
|
|
25
|
+
readRuntimeState,
|
|
26
|
+
resolveDaemonPaths,
|
|
27
|
+
type RuntimeState,
|
|
28
|
+
} from "@/util/daemon"
|
|
29
|
+
import { requestDaemonToken } from "@/util/daemon-control"
|
|
30
|
+
|
|
31
|
+
import { buildStandardProbeDeps } from "./probe-deps"
|
|
32
|
+
|
|
33
|
+
interface BannerOptions {
|
|
34
|
+
readonly emulator: boolean
|
|
35
|
+
readonly deviceModel?: string
|
|
36
|
+
readonly port?: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface BannerDeps {
|
|
40
|
+
readonly probeAll: typeof probeAllCached
|
|
41
|
+
readonly probeExec: typeof probeCommandExecution
|
|
42
|
+
readonly probeHttp: typeof probeInternetAccess
|
|
43
|
+
readonly listDevices: typeof listDevices
|
|
44
|
+
readonly resetCache: typeof resetProbeCache
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const defaultBannerDeps: BannerDeps = {
|
|
48
|
+
probeAll: probeAllCached,
|
|
49
|
+
probeExec: probeCommandExecution,
|
|
50
|
+
probeHttp: probeInternetAccess,
|
|
51
|
+
listDevices,
|
|
52
|
+
resetCache: resetProbeCache,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const isLogSuppressed = (
|
|
56
|
+
argv: Readonly<{ quiet?: boolean; logLevel?: string }>,
|
|
57
|
+
): boolean =>
|
|
58
|
+
argv.quiet === true || argv.logLevel === "silent" || argv.logLevel === "none"
|
|
59
|
+
|
|
60
|
+
const DEFAULT_PORT = 52937
|
|
61
|
+
const READY_TIMEOUT_MS = 30_000
|
|
62
|
+
const READY_INTERVAL_MS = 100
|
|
63
|
+
const RUNTIME_STATE_TIMEOUT_MS = 30_000
|
|
64
|
+
const PORT_FREE_TIMEOUT_MS = 3_000
|
|
65
|
+
|
|
66
|
+
const checkTcp = (
|
|
67
|
+
host: string,
|
|
68
|
+
port: number,
|
|
69
|
+
timeoutMs: number,
|
|
70
|
+
): Promise<boolean> =>
|
|
71
|
+
new Promise((resolve) => {
|
|
72
|
+
const sock: Socket = connect(port, host)
|
|
73
|
+
let settled = false
|
|
74
|
+
const finish = (ok: boolean): void => {
|
|
75
|
+
if (settled) return
|
|
76
|
+
settled = true
|
|
77
|
+
sock.destroy()
|
|
78
|
+
resolve(ok)
|
|
79
|
+
}
|
|
80
|
+
sock.once("connect", () => finish(true))
|
|
81
|
+
sock.once("error", () => finish(false))
|
|
82
|
+
setTimeout(() => finish(false), timeoutMs)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// ponytail: wrapper around `checkTcp` that polls until the daemon's
|
|
86
|
+
// WS port accepts a connection, or until `timeoutMs` elapses. The
|
|
87
|
+
// caller decides what to do on timeout (CLI: fail loudly with the
|
|
88
|
+
// accumulated daemon events). The TCP probe is intentionally cheap —
|
|
89
|
+
// a single `connect()` per interval — and Node handles the OS-level
|
|
90
|
+
// `ECONNREFUSED` on a closed port without holding the event loop.
|
|
91
|
+
export const waitForDaemonReady = async (
|
|
92
|
+
port: number = DEFAULT_PORT,
|
|
93
|
+
timeoutMs: number = READY_TIMEOUT_MS,
|
|
94
|
+
): Promise<void> => {
|
|
95
|
+
const deadline = Date.now() + timeoutMs
|
|
96
|
+
while (Date.now() < deadline) {
|
|
97
|
+
if (await checkTcp("127.0.0.1", port, READY_INTERVAL_MS)) return
|
|
98
|
+
await new Promise((r) => setTimeout(r, READY_INTERVAL_MS))
|
|
99
|
+
}
|
|
100
|
+
throw new DaemonNotReadyError(port, timeoutMs)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class DaemonNotReadyError extends Error {
|
|
104
|
+
readonly port: number
|
|
105
|
+
readonly timeoutMs: number
|
|
106
|
+
constructor(port: number, timeoutMs: number) {
|
|
107
|
+
super(
|
|
108
|
+
`daemon: port ${port} did not accept connections within ${timeoutMs}ms — daemon may have failed to start`,
|
|
109
|
+
)
|
|
110
|
+
this.name = "DaemonNotReadyError"
|
|
111
|
+
this.port = port
|
|
112
|
+
this.timeoutMs = timeoutMs
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ponytail: the daemon writes `runtime-state.json` only after its WS
|
|
117
|
+
// bridge + vite supervisors are up. Polling for the file is the
|
|
118
|
+
// canonical "fully ready" signal — much stronger than TCP-on-port
|
|
119
|
+
// (which fires the moment the WS server binds, before supervisors
|
|
120
|
+
// have stabilized). Returns the parsed state, or `null` on timeout.
|
|
121
|
+
export const waitForRuntimeState = async (
|
|
122
|
+
timeoutMs: number = RUNTIME_STATE_TIMEOUT_MS,
|
|
123
|
+
notBefore?: number,
|
|
124
|
+
): Promise<RuntimeState | null> => {
|
|
125
|
+
const deadline = Date.now() + timeoutMs
|
|
126
|
+
while (Date.now() < deadline) {
|
|
127
|
+
const state = readRuntimeState()
|
|
128
|
+
if (
|
|
129
|
+
state !== null &&
|
|
130
|
+
(notBefore === undefined || state.startedAt >= notBefore)
|
|
131
|
+
) {
|
|
132
|
+
return state
|
|
133
|
+
}
|
|
134
|
+
await new Promise((r) => setTimeout(r, READY_INTERVAL_MS))
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ponytail: bind a `Server` to the target port. If `EADDRINUSE` fires,
|
|
140
|
+
// the port is bound by some other process (most likely the daemon we
|
|
141
|
+
// just stopped, with a stuck TIME_WAIT or an orphan vite). If the
|
|
142
|
+
// bind succeeds, the port is genuinely free. Used by `stop` to
|
|
143
|
+
// confirm the daemon has fully released its socket.
|
|
144
|
+
export const waitForPortFree = async (
|
|
145
|
+
port: number,
|
|
146
|
+
timeoutMs: number = PORT_FREE_TIMEOUT_MS,
|
|
147
|
+
): Promise<boolean> => {
|
|
148
|
+
const deadline = Date.now() + timeoutMs
|
|
149
|
+
while (Date.now() < deadline) {
|
|
150
|
+
const ok = await tryBind(port)
|
|
151
|
+
if (ok) return true
|
|
152
|
+
await new Promise((r) => setTimeout(r, READY_INTERVAL_MS))
|
|
153
|
+
}
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tryBind = (port: number): Promise<boolean> =>
|
|
158
|
+
new Promise<boolean>((resolve) => {
|
|
159
|
+
let server: Server | null = null
|
|
160
|
+
const cleanup = (ok: boolean): void => {
|
|
161
|
+
if (server !== null) {
|
|
162
|
+
server.close()
|
|
163
|
+
server = null
|
|
164
|
+
}
|
|
165
|
+
resolve(ok)
|
|
166
|
+
}
|
|
167
|
+
server = createServer()
|
|
168
|
+
server.once("error", () => cleanup(false))
|
|
169
|
+
server.once("listening", () => cleanup(true))
|
|
170
|
+
try {
|
|
171
|
+
server.listen(port, "127.0.0.1")
|
|
172
|
+
} catch {
|
|
173
|
+
cleanup(false)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
interface FeatureItem {
|
|
178
|
+
readonly name: string
|
|
179
|
+
readonly available: boolean
|
|
180
|
+
readonly reason?: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const formatFeaturesLine = (items: ReadonlyArray<FeatureItem>): string =>
|
|
184
|
+
items
|
|
185
|
+
.map((it) =>
|
|
186
|
+
it.available
|
|
187
|
+
? `[✓ ${it.name}]`
|
|
188
|
+
: `[✗ ${it.name}${it.reason !== undefined ? ` — ${it.reason}` : ""}]`,
|
|
189
|
+
)
|
|
190
|
+
.join(" ")
|
|
191
|
+
|
|
192
|
+
const toFeatureItem = (
|
|
193
|
+
name: string,
|
|
194
|
+
probe: RuntimeFeatureProbe,
|
|
195
|
+
): FeatureItem =>
|
|
196
|
+
probe.reason !== undefined
|
|
197
|
+
? { name, available: probe.available, reason: probe.reason }
|
|
198
|
+
: { name, available: probe.available }
|
|
199
|
+
|
|
200
|
+
const deviceLabel = (
|
|
201
|
+
emulator: boolean,
|
|
202
|
+
deviceModel: string | undefined,
|
|
203
|
+
devices: ReadonlyArray<DeviceDescriptor>,
|
|
204
|
+
): string => {
|
|
205
|
+
if (emulator) {
|
|
206
|
+
return `Emulator (${deviceModel ?? "mk2"})`
|
|
207
|
+
}
|
|
208
|
+
if (devices.length === 0) {
|
|
209
|
+
return "detecting…"
|
|
210
|
+
}
|
|
211
|
+
const d = devices[0]
|
|
212
|
+
return d !== undefined ? `${d.model} (${d.id})` : "detecting…"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface BannerResult {
|
|
216
|
+
readonly featuresLine: string
|
|
217
|
+
readonly deviceLabel: string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const buildStartupBanner = async (
|
|
221
|
+
options: BannerOptions,
|
|
222
|
+
argv: Readonly<{ quiet?: boolean; logLevel?: string }>,
|
|
223
|
+
deps: BannerDeps = defaultBannerDeps,
|
|
224
|
+
): Promise<BannerResult | null> => {
|
|
225
|
+
if (isLogSuppressed(argv) || !process.stdout.isTTY) return null
|
|
226
|
+
|
|
227
|
+
deps.resetCache()
|
|
228
|
+
|
|
229
|
+
const [report, exec, http, devices] = await Promise.all([
|
|
230
|
+
deps.probeAll(buildStandardProbeDeps()),
|
|
231
|
+
deps.probeExec(),
|
|
232
|
+
deps.probeHttp(),
|
|
233
|
+
deps.listDevices(),
|
|
234
|
+
])
|
|
235
|
+
|
|
236
|
+
// ponytail: drop the misleading "media — chromium not installed" probe from
|
|
237
|
+
// the system-feature line. That probe is for Playwright tests (frontend
|
|
238
|
+
// chromium install), not the media addon. The actual media tooling check
|
|
239
|
+
// (playerctl / wpctl / osascript / powershell) lives in the addon-checks
|
|
240
|
+
// section printed after the URL — see runBuiltinAddonChecks / printAddonCheckResults.
|
|
241
|
+
const items: FeatureItem[] = [
|
|
242
|
+
toFeatureItem("keystrokes", toSystemCap(report, "keyMacro")),
|
|
243
|
+
toFeatureItem("clipboard", toSystemCap(report, "clipboard")),
|
|
244
|
+
toFeatureItem("notifications", toSystemCap(report, "notification")),
|
|
245
|
+
toFeatureItem("active-win", toSystemCap(report, "activeApp")),
|
|
246
|
+
toFeatureItem("exec", exec),
|
|
247
|
+
toFeatureItem("http", http),
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
const featuresLine = formatFeaturesLine(items)
|
|
251
|
+
const label = deviceLabel(options.emulator, options.deviceModel, devices)
|
|
252
|
+
|
|
253
|
+
intro("Starting SirenoDeck")
|
|
254
|
+
log.info(`Device: ${label}`)
|
|
255
|
+
log.info(featuresLine)
|
|
256
|
+
return { featuresLine, deviceLabel: label }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const toSystemCap = (
|
|
260
|
+
report: SystemReport,
|
|
261
|
+
key: "keyMacro" | "clipboard" | "notification" | "activeApp",
|
|
262
|
+
): RuntimeFeatureProbe => {
|
|
263
|
+
const cap = report.capabilities[key]
|
|
264
|
+
if (cap.available) return { available: true }
|
|
265
|
+
return {
|
|
266
|
+
available: false,
|
|
267
|
+
reason:
|
|
268
|
+
cap.missing.length > 0
|
|
269
|
+
? cap.missing.join(", ")
|
|
270
|
+
: cap.reason.length > 0
|
|
271
|
+
? cap.reason
|
|
272
|
+
: undefined,
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ponytail: warning / error lines from the daemon, surfaced inline. The
|
|
277
|
+
// CLI is the operator's interface to the daemon — they shouldn't have
|
|
278
|
+
// to grep `service.log` to find out what went wrong during start /
|
|
279
|
+
// ponytail: clack's log.* tools give us a color-coded icon + │ indent
|
|
280
|
+
// for free, matching the rest of the banner. Available → log.info,
|
|
281
|
+
// warn/fatal/error → log.warn/log.error. No manual ANSI escapes; no
|
|
282
|
+
// trailing (HH:MM:SS) — timestamps stay in service.log for correlation.
|
|
283
|
+
export const printDaemonEvents = (events: ReadonlyArray<DaemonEvent>): void => {
|
|
284
|
+
if (events.length === 0) return
|
|
285
|
+
for (const ev of events) {
|
|
286
|
+
const ctx = ev.component.length > 0 ? ` [${ev.component}]` : ""
|
|
287
|
+
const line = `${ev.message}${ctx}`
|
|
288
|
+
if (ev.level === "fatal" || ev.level === "error") log.error(line)
|
|
289
|
+
else if (ev.level === "warn") log.warn(line)
|
|
290
|
+
else log.info(line)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ponytail: the URL the operator needs to open in their browser. The
|
|
295
|
+
// token is regenerated per daemon session, so we always show it in the
|
|
296
|
+
// URL — operators copy-paste the URL into the browser. For `--remote`
|
|
297
|
+
// we use the existing QR banner (phone-friendly); for plain
|
|
298
|
+
// `--emulator` we print a plain text URL — no QR/LAN section, since the daemon only binds 127.0.0.1.
|
|
299
|
+
// For real (hardware) mode we print the Config UI URL. The Config UI owns the
|
|
300
|
+
// clickable preview and embeds the frontend, so the raw frontend URL is not an
|
|
301
|
+
// operator-facing startup endpoint.
|
|
302
|
+
export const printDaemonUrl = async (
|
|
303
|
+
state: RuntimeState,
|
|
304
|
+
tokenOrOutput: string | ((text: string) => void) = "",
|
|
305
|
+
output: (text: string) => void = (text) => process.stdout.write(text),
|
|
306
|
+
): Promise<void> => {
|
|
307
|
+
const token = typeof tokenOrOutput === "string" ? tokenOrOutput : ""
|
|
308
|
+
const write = typeof tokenOrOutput === "function" ? tokenOrOutput : output
|
|
309
|
+
if (state.emulatorMode) {
|
|
310
|
+
const port = state.configUiUrl.split(":").pop() ?? ""
|
|
311
|
+
const buildUrl = (host: string, deckOnly: boolean): string => {
|
|
312
|
+
const params = new URLSearchParams()
|
|
313
|
+
if (token.length > 0) params.set("token", token)
|
|
314
|
+
if (deckOnly) params.set("deckOnly", "1")
|
|
315
|
+
return `http://${host}:${port}?${params.toString()}`
|
|
316
|
+
}
|
|
317
|
+
const localUrl = buildUrl("127.0.0.1", false)
|
|
318
|
+
write(` Config UI: ${localUrl}#/config\n`)
|
|
319
|
+
write(`\n Emulator: ${localUrl}\n`)
|
|
320
|
+
if (state.remote && state.addresses.length > 0) {
|
|
321
|
+
const isTty = Boolean(process.stdout.isTTY)
|
|
322
|
+
if (isTty) {
|
|
323
|
+
write("\n Emulator (LAN):\n")
|
|
324
|
+
for (const addr of state.addresses) {
|
|
325
|
+
const url = buildUrl(addr, true)
|
|
326
|
+
const qr = await qrcode.toString(url, {
|
|
327
|
+
type: "terminal",
|
|
328
|
+
small: true,
|
|
329
|
+
})
|
|
330
|
+
write(`\n${qr} ${url}\n`)
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
for (const addr of state.addresses) {
|
|
334
|
+
write(` ${addr}: ${buildUrl(addr, false)}\n`)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
write("\n Manage with: `p dev status`, `p dev reload`, `p dev stop`.\n")
|
|
339
|
+
} else {
|
|
340
|
+
const configUiUrl = state.configUiUrl
|
|
341
|
+
const configUiWithToken =
|
|
342
|
+
token.length > 0
|
|
343
|
+
? `${configUiUrl}${configUiUrl.includes("?") ? "&" : "?"}token=${token}`
|
|
344
|
+
: configUiUrl
|
|
345
|
+
const configUi = `${configUiWithToken}#/config`
|
|
346
|
+
const content = ` Config UI: ${configUi} `
|
|
347
|
+
const border = `+${"-".repeat(content.length)}+`
|
|
348
|
+
write(`\n${border}\n|${content}|\n${border}\n`)
|
|
349
|
+
write("\n Manage with: `p dev status`, `p dev reload`, `p dev stop`.\n")
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ponytail: the load-bearing function for the CLI's "wait until fully
|
|
354
|
+
// started, then show the URL" flow. The CLI calls this from the
|
|
355
|
+
// `start` handler and surfaces events + URL inline. On any failure
|
|
356
|
+
// path the caller exits non-zero — operators expect the CLI to fail
|
|
357
|
+
// loudly if the daemon didn't come up, not silently exit 0.
|
|
358
|
+
export interface StartOutcome {
|
|
359
|
+
readonly state: RuntimeState | null
|
|
360
|
+
readonly token: string | null
|
|
361
|
+
readonly events: ReadonlyArray<DaemonEvent>
|
|
362
|
+
readonly tcpReady: boolean
|
|
363
|
+
readonly runtimeReady: boolean
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export interface WaitForStartOptions {
|
|
367
|
+
readonly port: number
|
|
368
|
+
readonly tcpTimeoutMs: number
|
|
369
|
+
readonly runtimeTimeoutMs: number
|
|
370
|
+
readonly logPath: string
|
|
371
|
+
readonly logSnapshot: DaemonLogSnapshot
|
|
372
|
+
readonly notBefore?: number
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export const waitForFullStart = async (
|
|
376
|
+
options: WaitForStartOptions,
|
|
377
|
+
): Promise<StartOutcome> => {
|
|
378
|
+
let tcpReady = false
|
|
379
|
+
try {
|
|
380
|
+
await waitForDaemonReady(options.port, options.tcpTimeoutMs)
|
|
381
|
+
tcpReady = true
|
|
382
|
+
} catch (err) {
|
|
383
|
+
if (!(err instanceof DaemonNotReadyError)) throw err
|
|
384
|
+
}
|
|
385
|
+
const runtimeReady = tcpReady
|
|
386
|
+
const state = runtimeReady
|
|
387
|
+
? await waitForRuntimeState(options.runtimeTimeoutMs, options.notBefore)
|
|
388
|
+
: null
|
|
389
|
+
const events = readDaemonEventsFromSnapshot(
|
|
390
|
+
options.logPath,
|
|
391
|
+
options.logSnapshot,
|
|
392
|
+
)
|
|
393
|
+
const token = runtimeReady
|
|
394
|
+
? await requestDaemonToken(resolveDaemonPaths())
|
|
395
|
+
: null
|
|
396
|
+
return { state, token, events, tcpReady, runtimeReady }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ponytail: per-addon requirement check results, surfaced inline next
|
|
400
|
+
// to the URL. Clack's log.* tools give us a color-coded icon + │ indent
|
|
401
|
+
// for free. Available checks → log.info (green ✓), failed → log.warn
|
|
402
|
+
// (yellow ✗) with the reason inline. One log line per outcome so the
|
|
403
|
+
// status banner shows every check independently. Grouping by addon
|
|
404
|
+
// is preserved by the addon name in the message prefix.
|
|
405
|
+
export const printAddonCheckResults = (
|
|
406
|
+
outcomes: ReadonlyArray<AddonCheckOutcome>,
|
|
407
|
+
): void => {
|
|
408
|
+
if (outcomes.length === 0) return
|
|
409
|
+
for (const outcome of outcomes) {
|
|
410
|
+
const reason = outcome.reason !== undefined ? ` — ${outcome.reason}` : ""
|
|
411
|
+
if (outcome.available) {
|
|
412
|
+
log.info(`${outcome.addonName}/${outcome.checkName}`)
|
|
413
|
+
} else {
|
|
414
|
+
log.warn(`${outcome.addonName}/${outcome.checkName}${reason}`)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export const printStartupComplete = (): void => {
|
|
420
|
+
if (!process.stdout.isTTY) return
|
|
421
|
+
outro("✓ Sireno Deck started")
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export const printStartupFailed = (err: unknown): void => {
|
|
425
|
+
if (!process.stdout.isTTY) return
|
|
426
|
+
cancel(
|
|
427
|
+
`✗ Failed to start SirenoDeck: ${err instanceof Error ? err.message : String(err)}`,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export const printRestartComplete = (): void => {
|
|
432
|
+
if (!process.stdout.isTTY) return
|
|
433
|
+
outro("✓ Sireno Deck restarted")
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export const printRestartFailed = (err: unknown): void => {
|
|
437
|
+
if (!process.stdout.isTTY) return
|
|
438
|
+
cancel(
|
|
439
|
+
`✗ Failed to restart SirenoDeck: ${err instanceof Error ? err.message : String(err)}`,
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export const printStopComplete = (portFree: boolean): void => {
|
|
444
|
+
if (!process.stdout.isTTY) return
|
|
445
|
+
if (portFree) {
|
|
446
|
+
outro("✓ Sireno Deck stopped")
|
|
447
|
+
} else {
|
|
448
|
+
cancel(
|
|
449
|
+
"✗ Sireno Deck process exited but port 52937 is still bound — see `p dev status` for orphans",
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
}
|