@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,575 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
fchmodSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
statSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs"
|
|
14
|
+
import { randomBytes } from "node:crypto"
|
|
15
|
+
import { homedir, tmpdir } from "node:os"
|
|
16
|
+
import { join } from "node:path"
|
|
17
|
+
import { getuid, platform } from "node:process"
|
|
18
|
+
|
|
19
|
+
const getUid = (): number | null => {
|
|
20
|
+
try {
|
|
21
|
+
if (typeof getuid !== "function") return null
|
|
22
|
+
return getuid()
|
|
23
|
+
} catch {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
import type pino from "pino"
|
|
29
|
+
|
|
30
|
+
export interface DaemonPaths {
|
|
31
|
+
// XDG_RUNTIME_DIR — tmpfs in Linux user scope; vanishes on reboot.
|
|
32
|
+
// pidFile identifies the current daemon process.
|
|
33
|
+
// and child PIDs are re-spawned on every boot, so neither is worth
|
|
34
|
+
// persisting. childrenFile is XDG_RUNTIME_DIR too — the SPA/vite/playwright
|
|
35
|
+
// children re-derive on boot anyway, so the file's value across reboots
|
|
36
|
+
// is post-crash child tracking only.
|
|
37
|
+
runtimeDir: string
|
|
38
|
+
pidFile: string
|
|
39
|
+
controlSocket: string
|
|
40
|
+
childrenFile: string
|
|
41
|
+
// XDG_STATE_HOME — persistent. Survives reboot. Holds the bits the
|
|
42
|
+
// daemon needs to hand the same live experience back to the user on
|
|
43
|
+
// next boot: last published snapshot (provider agent counts, statuses,
|
|
44
|
+
// attention flags), restart-mode preferences, and the resolved config
|
|
45
|
+
// path pointer so the daemon re-loads the user's config without
|
|
46
|
+
// requiring a fresh `p dev` invocation.
|
|
47
|
+
dataDir: string
|
|
48
|
+
configPathFile: string
|
|
49
|
+
flagsFile: string
|
|
50
|
+
runtimeStateFile: string
|
|
51
|
+
// Ponytail: stateFile is the legacy alias for runtimeStateFile —
|
|
52
|
+
// callers from older builds reference this name. Kept as a re-export
|
|
53
|
+
// of `runtimeStateFile` so the value is shared regardless of which
|
|
54
|
+
// an addon reads.
|
|
55
|
+
stateFile: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const writeAtomic = (path: string, content: string, mode: number): void => {
|
|
59
|
+
const tmp = `${path}.tmp.${process.pid}`
|
|
60
|
+
const fd = openSync(tmp, "w", mode)
|
|
61
|
+
try {
|
|
62
|
+
writeFileSync(fd, content, { encoding: "utf8" })
|
|
63
|
+
fchmodSync(fd, mode)
|
|
64
|
+
} finally {
|
|
65
|
+
closeSync(fd)
|
|
66
|
+
}
|
|
67
|
+
renameSync(tmp, path)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RuntimeFlags {
|
|
71
|
+
emulator: boolean
|
|
72
|
+
remote?: boolean
|
|
73
|
+
deviceModel?: string
|
|
74
|
+
port?: number
|
|
75
|
+
httpPort: number
|
|
76
|
+
config?: string
|
|
77
|
+
noAutoOpen?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DAEMON_NAME = "sirenodeck"
|
|
81
|
+
|
|
82
|
+
const defaultRuntimeDir = (): string => {
|
|
83
|
+
const xdg = process.env["XDG_RUNTIME_DIR"]
|
|
84
|
+
if (xdg) return xdg
|
|
85
|
+
|
|
86
|
+
switch (platform) {
|
|
87
|
+
case "darwin":
|
|
88
|
+
return join(homedir(), "Library", "Application Support", DAEMON_NAME)
|
|
89
|
+
case "win32":
|
|
90
|
+
return join(process.env["LOCALAPPDATA"] ?? tmpdir(), DAEMON_NAME)
|
|
91
|
+
default: {
|
|
92
|
+
// ponytail: getuid is undefined-typed on win32 per @types/node, but this
|
|
93
|
+
// branch only runs on linux; the runtime check satisfies the type.
|
|
94
|
+
const uid = typeof getuid === "function" ? getuid() : null
|
|
95
|
+
return uid !== null
|
|
96
|
+
? join(tmpdir(), `sirenodeck-${uid}`)
|
|
97
|
+
: join(tmpdir(), DAEMON_NAME)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ponytail: persistent storage location. On Linux, XDG_STATE_HOME
|
|
103
|
+
// defaults to ~/.local/state; macOS uses Library/Application Support;
|
|
104
|
+
// Windows uses %LOCALAPPDATA%. Always prefer the env var when set so
|
|
105
|
+
// CI / sandboxed environments can override.
|
|
106
|
+
const defaultDataDir = (): string => {
|
|
107
|
+
const xdg = process.env["XDG_STATE_HOME"]
|
|
108
|
+
if (xdg) return join(xdg, DAEMON_NAME)
|
|
109
|
+
switch (platform) {
|
|
110
|
+
case "darwin":
|
|
111
|
+
return join(
|
|
112
|
+
process.env["XDG_DATA_HOME"] ??
|
|
113
|
+
join(homedir(), "Library", "Application Support"),
|
|
114
|
+
DAEMON_NAME,
|
|
115
|
+
)
|
|
116
|
+
case "win32":
|
|
117
|
+
return join(process.env["LOCALAPPDATA"] ?? tmpdir(), DAEMON_NAME)
|
|
118
|
+
default:
|
|
119
|
+
return join(
|
|
120
|
+
process.env["XDG_STATE_HOME"] ??
|
|
121
|
+
process.env["XDG_DATA_HOME"] ??
|
|
122
|
+
join(homedir(), ".local", "state"),
|
|
123
|
+
DAEMON_NAME,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ponytail: AF_UNIX paths are capped by `sun_path` — 104 bytes on macOS/BSD,
|
|
129
|
+
// 108 on Linux — and `bind()` fails with a bare EINVAL when you exceed it, with
|
|
130
|
+
// nothing in the message naming the length as the cause. The macOS default
|
|
131
|
+
// runtime dir (~/Library/Application Support/sirenodeck) already spends ~70 of
|
|
132
|
+
// those bytes, so a long username or an XDG_RUNTIME_DIR override blows the cap.
|
|
133
|
+
// Fall back to a short, uid-scoped path under the system temp dir, which is the
|
|
134
|
+
// same place Linux puts it when XDG_RUNTIME_DIR is unset.
|
|
135
|
+
const SUN_PATH_MAX = platform === "linux" ? 108 : 104
|
|
136
|
+
|
|
137
|
+
export const resolveSocketPath = (
|
|
138
|
+
runtimeDir: string,
|
|
139
|
+
basename = "",
|
|
140
|
+
): string => {
|
|
141
|
+
const preferred = join(runtimeDir, `${DAEMON_NAME}${basename}.sock`)
|
|
142
|
+
if (platform === "win32") return preferred
|
|
143
|
+
if (Buffer.byteLength(preferred, "utf8") < SUN_PATH_MAX) return preferred
|
|
144
|
+
const uid = typeof getuid === "function" ? getuid() : 0
|
|
145
|
+
return join(tmpdir(), `${DAEMON_NAME}-${uid}${basename}.sock`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const resolveControlSocket = (runtimeDir: string): string =>
|
|
149
|
+
resolveSocketPath(runtimeDir)
|
|
150
|
+
|
|
151
|
+
export const resolveDaemonPaths = (): DaemonPaths => {
|
|
152
|
+
const runtimeDir = defaultRuntimeDir()
|
|
153
|
+
if (!existsSync(runtimeDir)) {
|
|
154
|
+
mkdirSync(runtimeDir, { recursive: true })
|
|
155
|
+
if (platform !== "win32") {
|
|
156
|
+
chmodSync(runtimeDir, 0o700)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const dataDir = defaultDataDir()
|
|
160
|
+
if (!existsSync(dataDir)) {
|
|
161
|
+
mkdirSync(dataDir, { recursive: true })
|
|
162
|
+
if (platform !== "win32") {
|
|
163
|
+
chmodSync(dataDir, 0o700)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const runtimeStateFile = join(dataDir, RUNTIME_STATE_FILE)
|
|
167
|
+
return {
|
|
168
|
+
runtimeDir,
|
|
169
|
+
pidFile: join(runtimeDir, `${DAEMON_NAME}.pid`),
|
|
170
|
+
controlSocket: resolveControlSocket(runtimeDir),
|
|
171
|
+
childrenFile: join(runtimeDir, `${DAEMON_NAME}.children.json`),
|
|
172
|
+
// ponytail: restart-survival — files that have to outlive a reboot
|
|
173
|
+
// live under dataDir (XDG_STATE_HOME). runtime-state.json is the live
|
|
174
|
+
// snapshot the daemon rehydrates from; flags.json is the user's
|
|
175
|
+
// restart-mode preferences (emulator vs real, remote, port); the
|
|
176
|
+
// config-path pointer lets the daemon re-load the same user config
|
|
177
|
+
// without requiring a fresh invocation. config and flags used to live
|
|
178
|
+
// in tmpfs — they now persist.
|
|
179
|
+
dataDir,
|
|
180
|
+
configPathFile: join(dataDir, `${DAEMON_NAME}.config`),
|
|
181
|
+
flagsFile: join(dataDir, `${DAEMON_NAME}.flags.json`),
|
|
182
|
+
runtimeStateFile,
|
|
183
|
+
stateFile: runtimeStateFile,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface StartLock {
|
|
188
|
+
readonly release: () => void
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const acquireStartLock = (
|
|
192
|
+
paths = resolveDaemonPaths(),
|
|
193
|
+
): StartLock | null => {
|
|
194
|
+
const lockFile = `${paths.pidFile}.lock`
|
|
195
|
+
let fd: number
|
|
196
|
+
try {
|
|
197
|
+
fd = openSync(lockFile, "wx", 0o600)
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err
|
|
200
|
+
// ponytail: lock already exists. Two ways out:
|
|
201
|
+
// 1. The PID inside it is dead (previous daemon crashed or was
|
|
202
|
+
// SIGKILL'd without releasing). Clear and re-acquire immediately.
|
|
203
|
+
// 2. The lock is stale by mtime (>60s) — same outcome, defensive
|
|
204
|
+
// fallback if the PID is unreadable or the live-check is unsure.
|
|
205
|
+
// Either case unblocks a fast retry that would otherwise have to wait
|
|
206
|
+
// out the full window. Without this, the user's exact symptom
|
|
207
|
+
// ("another start is already in progress") recurs after every daemon
|
|
208
|
+
// crash during a session of repeated restarts.
|
|
209
|
+
const holderPid = readLockHolderPid(lockFile)
|
|
210
|
+
const holderDead = holderPid !== null && !isRunning(holderPid)
|
|
211
|
+
const ageMs = safeStatAgeMs(lockFile)
|
|
212
|
+
if (holderDead || (ageMs !== null && ageMs > 60_000)) {
|
|
213
|
+
try {
|
|
214
|
+
unlinkSync(lockFile)
|
|
215
|
+
fd = openSync(lockFile, "wx", 0o600)
|
|
216
|
+
} catch {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
return null
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (fd === undefined!) return null
|
|
224
|
+
writeFileSync(fd, `${process.pid}\n`, { encoding: "utf8" })
|
|
225
|
+
return {
|
|
226
|
+
release: () => {
|
|
227
|
+
try {
|
|
228
|
+
closeSync(fd!)
|
|
229
|
+
unlinkSync(lockFile)
|
|
230
|
+
} catch {
|
|
231
|
+
// ignore
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const readLockHolderPid = (lockFile: string): number | null => {
|
|
238
|
+
try {
|
|
239
|
+
const raw = readFileSync(lockFile, "utf8").trim()
|
|
240
|
+
const pid = Number.parseInt(raw, 10)
|
|
241
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null
|
|
242
|
+
} catch {
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const safeStatAgeMs = (lockFile: string): number | null => {
|
|
248
|
+
try {
|
|
249
|
+
return Date.now() - statSync(lockFile).mtimeMs
|
|
250
|
+
} catch {
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const removeStartLock = (paths = resolveDaemonPaths()): void => {
|
|
256
|
+
const lockFile = `${paths.pidFile}.lock`
|
|
257
|
+
if (existsSync(lockFile)) {
|
|
258
|
+
try {
|
|
259
|
+
unlinkSync(lockFile)
|
|
260
|
+
} catch {
|
|
261
|
+
// ignore — best-effort cleanup of an orphaned lock from a dead daemon
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const readPid = (paths = resolveDaemonPaths()): number | null => {
|
|
267
|
+
if (!existsSync(paths.pidFile)) return null
|
|
268
|
+
const raw = readFileSync(paths.pidFile, "utf8").trim()
|
|
269
|
+
const pid = Number.parseInt(raw, 10)
|
|
270
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const writePid = (pid: number, paths = resolveDaemonPaths()): void => {
|
|
274
|
+
writeAtomic(paths.pidFile, `${pid}\n`, 0o600)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const removePidFile = (paths = resolveDaemonPaths()): void => {
|
|
278
|
+
if (existsSync(paths.pidFile)) unlinkSync(paths.pidFile)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Liveness, and nothing else: does this pid exist and can we signal it?
|
|
283
|
+
*
|
|
284
|
+
* ponytail: this used to additionally require the target's cmdline to contain
|
|
285
|
+
* a per-daemon sentinel, which is a test it could never pass. The sentinel was
|
|
286
|
+
* written only into the daemon's OWN environment, never into its argv, and the
|
|
287
|
+
* daemon rewrites its process title to `sirenodeck:dm` — so on macOS
|
|
288
|
+
* `ps -o command=` returns that title and on Linux the argv never carried the
|
|
289
|
+
* token either. Any caller that had inherited SIRENO_DAEMON_SENTINEL from the
|
|
290
|
+
* daemon therefore saw a live daemon as dead, and the damage was not cosmetic:
|
|
291
|
+
* `stopExisting` took its "stale pid file" branch, deleted the file and
|
|
292
|
+
* returned WITHOUT killing the daemon, leaving start free to boot a second one
|
|
293
|
+
* alongside the first. Both then ran, fighting over the same ports.
|
|
294
|
+
*
|
|
295
|
+
* Liveness and identity are separate questions. This answers the first; the
|
|
296
|
+
* `isOurDaemon` fingerprint in port-identity answers the second; and the
|
|
297
|
+
* single-instance lock, not a pid comparison, is what actually keeps two
|
|
298
|
+
* daemons apart.
|
|
299
|
+
*/
|
|
300
|
+
export const isRunning = (pid: number): boolean => {
|
|
301
|
+
if (pid <= 0) return false
|
|
302
|
+
try {
|
|
303
|
+
process.kill(pid, 0)
|
|
304
|
+
return true
|
|
305
|
+
} catch (error) {
|
|
306
|
+
// EPERM means the pid exists but belongs to another user — still running.
|
|
307
|
+
return (error as NodeJS.ErrnoException).code === "EPERM"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export const generateToken = (): string => randomBytes(32).toString("base64url")
|
|
312
|
+
|
|
313
|
+
export interface ChildrenState {
|
|
314
|
+
pids: number[]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const readChildren = (
|
|
318
|
+
paths = resolveDaemonPaths(),
|
|
319
|
+
): ChildrenState | null => {
|
|
320
|
+
if (!existsSync(paths.childrenFile)) return null
|
|
321
|
+
try {
|
|
322
|
+
const raw = readFileSync(paths.childrenFile, "utf8")
|
|
323
|
+
const parsed = JSON.parse(raw) as unknown
|
|
324
|
+
if (
|
|
325
|
+
parsed !== null &&
|
|
326
|
+
typeof parsed === "object" &&
|
|
327
|
+
"pids" in parsed &&
|
|
328
|
+
Array.isArray((parsed as { pids: unknown }).pids)
|
|
329
|
+
) {
|
|
330
|
+
const pids = (parsed as { pids: unknown[] }).pids
|
|
331
|
+
.map((p) => Number.parseInt(String(p), 10))
|
|
332
|
+
.filter((p) => Number.isFinite(p) && p > 0)
|
|
333
|
+
return { pids }
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
return null
|
|
337
|
+
}
|
|
338
|
+
return null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export const writeChildren = (
|
|
342
|
+
state: ChildrenState,
|
|
343
|
+
paths = resolveDaemonPaths(),
|
|
344
|
+
): void => {
|
|
345
|
+
writeAtomic(paths.childrenFile, JSON.stringify(state), 0o600)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export const removeChildrenFile = (paths = resolveDaemonPaths()): void => {
|
|
349
|
+
if (existsSync(paths.childrenFile)) unlinkSync(paths.childrenFile)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export const readConfigPath = (paths = resolveDaemonPaths()): string | null => {
|
|
353
|
+
if (!existsSync(paths.configPathFile)) return null
|
|
354
|
+
const raw = readFileSync(paths.configPathFile, "utf8").trim()
|
|
355
|
+
return raw.length > 0 ? raw : null
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const writeConfigPath = (
|
|
359
|
+
configPath: string,
|
|
360
|
+
paths = resolveDaemonPaths(),
|
|
361
|
+
): void => {
|
|
362
|
+
writeAtomic(paths.configPathFile, `${configPath}\n`, 0o600)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export const removeConfigPathFile = (paths = resolveDaemonPaths()): void => {
|
|
366
|
+
if (existsSync(paths.configPathFile)) unlinkSync(paths.configPathFile)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export const readFlags = (
|
|
370
|
+
paths = resolveDaemonPaths(),
|
|
371
|
+
): RuntimeFlags | null => {
|
|
372
|
+
if (!existsSync(paths.flagsFile)) return null
|
|
373
|
+
try {
|
|
374
|
+
const raw = readFileSync(paths.flagsFile, "utf8")
|
|
375
|
+
const parsed = JSON.parse(raw) as Partial<RuntimeFlags>
|
|
376
|
+
if (
|
|
377
|
+
typeof parsed.emulator !== "boolean" ||
|
|
378
|
+
typeof parsed.httpPort !== "number"
|
|
379
|
+
) {
|
|
380
|
+
return null
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
emulator: parsed.emulator,
|
|
384
|
+
httpPort: parsed.httpPort,
|
|
385
|
+
...(typeof parsed.remote === "boolean" ? { remote: parsed.remote } : {}),
|
|
386
|
+
...(typeof parsed.deviceModel === "string"
|
|
387
|
+
? { deviceModel: parsed.deviceModel }
|
|
388
|
+
: {}),
|
|
389
|
+
...(typeof parsed.port === "number" ? { port: parsed.port } : {}),
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
return null
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export const writeFlags = (
|
|
397
|
+
flags: RuntimeFlags,
|
|
398
|
+
paths = resolveDaemonPaths(),
|
|
399
|
+
): void => {
|
|
400
|
+
writeAtomic(paths.flagsFile, JSON.stringify(flags), 0o600)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export const removeFlagsFile = (paths = resolveDaemonPaths()): void => {
|
|
404
|
+
if (existsSync(paths.flagsFile)) unlinkSync(paths.flagsFile)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface RuntimeState {
|
|
408
|
+
readonly configUiUrl: string
|
|
409
|
+
readonly wsUrl: string
|
|
410
|
+
readonly frontendUrl: string
|
|
411
|
+
readonly lanHost: string
|
|
412
|
+
readonly addresses: ReadonlyArray<string>
|
|
413
|
+
readonly emulatorMode: boolean
|
|
414
|
+
readonly remote: boolean
|
|
415
|
+
readonly startedAt: number
|
|
416
|
+
readonly theme?: string
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export const RUNTIME_STATE_FILE = "runtime-state.json"
|
|
420
|
+
|
|
421
|
+
export const writeRuntimeState = (
|
|
422
|
+
state: RuntimeState,
|
|
423
|
+
paths = resolveDaemonPaths(),
|
|
424
|
+
): void => {
|
|
425
|
+
writeAtomic(paths.runtimeStateFile, JSON.stringify(state, null, 2), 0o600)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export const readRuntimeState = (
|
|
429
|
+
paths = resolveDaemonPaths(),
|
|
430
|
+
): RuntimeState | null => {
|
|
431
|
+
const filePath = paths.runtimeStateFile
|
|
432
|
+
if (!existsSync(filePath)) return null
|
|
433
|
+
try {
|
|
434
|
+
const raw = readFileSync(filePath, "utf8")
|
|
435
|
+
const parsed = JSON.parse(raw) as unknown
|
|
436
|
+
if (
|
|
437
|
+
parsed !== null &&
|
|
438
|
+
typeof parsed === "object" &&
|
|
439
|
+
typeof (parsed as RuntimeState).configUiUrl === "string" &&
|
|
440
|
+
typeof (parsed as RuntimeState).wsUrl === "string" &&
|
|
441
|
+
typeof (parsed as RuntimeState).frontendUrl === "string" &&
|
|
442
|
+
typeof (parsed as RuntimeState).lanHost === "string" &&
|
|
443
|
+
Array.isArray((parsed as RuntimeState).addresses) &&
|
|
444
|
+
typeof (parsed as RuntimeState).emulatorMode === "boolean" &&
|
|
445
|
+
typeof (parsed as RuntimeState).remote === "boolean"
|
|
446
|
+
) {
|
|
447
|
+
const p = parsed as RuntimeState
|
|
448
|
+
return {
|
|
449
|
+
configUiUrl: p.configUiUrl,
|
|
450
|
+
wsUrl: p.wsUrl,
|
|
451
|
+
frontendUrl: p.frontendUrl,
|
|
452
|
+
lanHost: p.lanHost,
|
|
453
|
+
addresses: p.addresses,
|
|
454
|
+
emulatorMode: p.emulatorMode,
|
|
455
|
+
remote: p.remote,
|
|
456
|
+
startedAt: typeof p.startedAt === "number" ? p.startedAt : Date.now(),
|
|
457
|
+
theme: typeof p.theme === "string" ? p.theme : "default",
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
return null
|
|
462
|
+
}
|
|
463
|
+
return null
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export const removeRuntimeStateFile = (paths = resolveDaemonPaths()): void => {
|
|
467
|
+
if (existsSync(paths.runtimeStateFile)) {
|
|
468
|
+
unlinkSync(paths.runtimeStateFile)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ponytail: child tracking is best-effort — orphans from a hard kill are reaped
|
|
473
|
+
// ponytail: terminateChildren first tries the pid as a process (the child
|
|
474
|
+
// itself) and then falls back to the pid as a NEGATIVE pid (process group
|
|
475
|
+
// leader). When vite is spawned with `detached: true` its pid IS the pgid,
|
|
476
|
+
// so kill(-pgid, SIGTERM) takes down the whole group in one shot — that's
|
|
477
|
+
// how we guarantee the frontend port doesn't stay bound by orphans after
|
|
478
|
+
// the daemon dies. Windows ignores the negative pid form silently.
|
|
479
|
+
const signalChildGroup = (pid: number, signal: NodeJS.Signals): void => {
|
|
480
|
+
try {
|
|
481
|
+
process.kill(pid, signal)
|
|
482
|
+
} catch {
|
|
483
|
+
// pid already dead or no permission — try as process group
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
process.kill(-pid, signal)
|
|
487
|
+
} catch {
|
|
488
|
+
// not a pgid or already gone
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export const terminateChildren = async ({
|
|
493
|
+
timeoutMs = 5000,
|
|
494
|
+
logger,
|
|
495
|
+
paths = resolveDaemonPaths(),
|
|
496
|
+
}: {
|
|
497
|
+
timeoutMs?: number
|
|
498
|
+
logger?: pino.Logger
|
|
499
|
+
paths?: DaemonPaths
|
|
500
|
+
}): Promise<void> => {
|
|
501
|
+
const state = readChildren(paths)
|
|
502
|
+
if (state === null || state.pids.length === 0) return
|
|
503
|
+
|
|
504
|
+
const alive = state.pids.filter((p) => isRunning(p))
|
|
505
|
+
if (alive.length === 0) {
|
|
506
|
+
writeChildren({ pids: [] }, paths)
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Phase 1: SIGTERM all
|
|
511
|
+
for (const pid of alive) {
|
|
512
|
+
signalChildGroup(pid, "SIGTERM")
|
|
513
|
+
logger?.debug({ pid }, "daemon: sent SIGTERM to child (+ group)")
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Wait for graceful shutdown
|
|
517
|
+
const deadline = Date.now() + timeoutMs
|
|
518
|
+
let remaining = alive.filter((p) => isRunning(p))
|
|
519
|
+
while (remaining.length > 0 && Date.now() < deadline) {
|
|
520
|
+
await new Promise((r) => setTimeout(r, 200))
|
|
521
|
+
remaining = remaining.filter((p) => isRunning(p))
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Phase 2: SIGKILL stragglers
|
|
525
|
+
for (const pid of remaining) {
|
|
526
|
+
signalChildGroup(pid, "SIGKILL")
|
|
527
|
+
logger?.debug({ pid }, "daemon: sent SIGKILL to child (+ group)")
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Prune dead from tracked list
|
|
531
|
+
const stillAlive = state.pids.filter((p) => isRunning(p))
|
|
532
|
+
writeChildren({ pids: stillAlive }, paths)
|
|
533
|
+
if (stillAlive.length < state.pids.length) {
|
|
534
|
+
logger?.info(
|
|
535
|
+
{ killed: state.pids.length - stillAlive.length },
|
|
536
|
+
"daemon: cleaned up terminated children",
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export const appendChild = (
|
|
542
|
+
pid: number,
|
|
543
|
+
paths = resolveDaemonPaths(),
|
|
544
|
+
): void => {
|
|
545
|
+
const current = readChildren(paths) ?? { pids: [] }
|
|
546
|
+
if (current.pids.includes(pid)) return
|
|
547
|
+
writeChildren({ pids: [...current.pids, pid] }, paths)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export const removeChild = (
|
|
551
|
+
pid: number,
|
|
552
|
+
paths = resolveDaemonPaths(),
|
|
553
|
+
): void => {
|
|
554
|
+
const current = readChildren(paths)
|
|
555
|
+
if (current === null) return
|
|
556
|
+
const next = current.pids.filter((p) => p !== pid)
|
|
557
|
+
if (next.length === current.pids.length) return
|
|
558
|
+
writeChildren({ pids: next }, paths)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export const pruneStaleChildren = (
|
|
562
|
+
paths = resolveDaemonPaths(),
|
|
563
|
+
logger?: pino.Logger,
|
|
564
|
+
): number => {
|
|
565
|
+
const current = readChildren(paths)
|
|
566
|
+
if (current === null) return 0
|
|
567
|
+
const alive = current.pids.filter((p) => isRunning(p))
|
|
568
|
+
const pruned = current.pids.length - alive.length
|
|
569
|
+
if (pruned === 0) return 0
|
|
570
|
+
writeChildren({ pids: alive }, paths)
|
|
571
|
+
for (const dead of current.pids.filter((p) => !isRunning(p))) {
|
|
572
|
+
logger?.info({ pid: dead }, "daemon: pruned stale child pid")
|
|
573
|
+
}
|
|
574
|
+
return pruned
|
|
575
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeFileSync,
|
|
3
|
+
renameSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
} from "node:fs"
|
|
9
|
+
import { dirname } from "node:path"
|
|
10
|
+
|
|
11
|
+
export interface DeviceConfig {
|
|
12
|
+
readonly serial: string
|
|
13
|
+
readonly path: string
|
|
14
|
+
readonly model: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoadDeviceConfigOptions {
|
|
18
|
+
readonly xdgConfigHome: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SaveDeviceConfigOptions extends LoadDeviceConfigOptions {
|
|
22
|
+
readonly config: DeviceConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const configPathFor = (xdgConfigHome: string): string =>
|
|
26
|
+
`${xdgConfigHome}/sirenodeck/device.json`
|
|
27
|
+
|
|
28
|
+
export const loadDeviceConfig = (
|
|
29
|
+
options: LoadDeviceConfigOptions,
|
|
30
|
+
): DeviceConfig | null => {
|
|
31
|
+
const path = configPathFor(options.xdgConfigHome)
|
|
32
|
+
if (!existsSync(path)) return null
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(path, "utf8")
|
|
35
|
+
const parsed = JSON.parse(raw) as Partial<DeviceConfig>
|
|
36
|
+
if (
|
|
37
|
+
typeof parsed.serial === "string" &&
|
|
38
|
+
typeof parsed.path === "string" &&
|
|
39
|
+
typeof parsed.model === "string"
|
|
40
|
+
) {
|
|
41
|
+
return { serial: parsed.serial, path: parsed.path, model: parsed.model }
|
|
42
|
+
}
|
|
43
|
+
return null
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const saveDeviceConfig = (options: SaveDeviceConfigOptions): void => {
|
|
50
|
+
const path = configPathFor(options.xdgConfigHome)
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
52
|
+
const tmp = `${path}.tmp`
|
|
53
|
+
writeFileSync(tmp, JSON.stringify(options.config, null, 2), "utf8")
|
|
54
|
+
renameSync(tmp, path)
|
|
55
|
+
if (existsSync(tmp)) unlinkSync(tmp)
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class NotImplementedError extends Error {
|
|
2
|
+
constructor(feature: string) {
|
|
3
|
+
super(`Not implemented: ${feature}`)
|
|
4
|
+
this.name = "NotImplementedError"
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class ConfigError extends Error {
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message)
|
|
11
|
+
this.name = "ConfigError"
|
|
12
|
+
}
|
|
13
|
+
}
|