@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,57 @@
|
|
|
1
|
+
import { homedir } from "node:os"
|
|
2
|
+
|
|
3
|
+
export const defaultResolveHome = (p: string): string => {
|
|
4
|
+
if (p === "~") return homedir()
|
|
5
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
6
|
+
return homedir() + p.slice(1)
|
|
7
|
+
}
|
|
8
|
+
return p
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const BUILTIN_CLI_ICONS: ReadonlySet<string> = new Set<string>([
|
|
12
|
+
"play",
|
|
13
|
+
"pause",
|
|
14
|
+
"next",
|
|
15
|
+
"previous",
|
|
16
|
+
"stop",
|
|
17
|
+
"settings",
|
|
18
|
+
"back",
|
|
19
|
+
"home",
|
|
20
|
+
"lock",
|
|
21
|
+
"unlock",
|
|
22
|
+
"wifi",
|
|
23
|
+
"wifi-off",
|
|
24
|
+
"battery-full",
|
|
25
|
+
"battery-half",
|
|
26
|
+
"battery-low",
|
|
27
|
+
"battery-charging",
|
|
28
|
+
"volume-high",
|
|
29
|
+
"volume-mid",
|
|
30
|
+
"volume-low",
|
|
31
|
+
"volume-mute",
|
|
32
|
+
"cpu",
|
|
33
|
+
"memory",
|
|
34
|
+
"clock",
|
|
35
|
+
"calendar",
|
|
36
|
+
"sun",
|
|
37
|
+
"moon",
|
|
38
|
+
"spotify",
|
|
39
|
+
"chrome",
|
|
40
|
+
"firefox",
|
|
41
|
+
"discord",
|
|
42
|
+
"slack",
|
|
43
|
+
"vscode",
|
|
44
|
+
"terminal",
|
|
45
|
+
"folder",
|
|
46
|
+
"file",
|
|
47
|
+
"power",
|
|
48
|
+
"refresh",
|
|
49
|
+
"search",
|
|
50
|
+
"star",
|
|
51
|
+
"heart",
|
|
52
|
+
"check",
|
|
53
|
+
"close",
|
|
54
|
+
"add",
|
|
55
|
+
"remove",
|
|
56
|
+
"menu",
|
|
57
|
+
])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { RawButtonEntry, RawConfig, RawDeckDef } from "./schemas"
|
|
2
|
+
|
|
3
|
+
const buttonKey = (entry: RawButtonEntry, idx: number): string => {
|
|
4
|
+
if (typeof entry === "string") return `s:${idx}:${entry}`
|
|
5
|
+
return `o:${idx}:${JSON.stringify(entry)}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const stableValue = (value: unknown): unknown => {
|
|
9
|
+
if (Array.isArray(value)) return value.map(stableValue)
|
|
10
|
+
if (typeof value !== "object" || value === null) return value
|
|
11
|
+
return Object.fromEntries(
|
|
12
|
+
Object.entries(value)
|
|
13
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
14
|
+
.map(([key, entry]) => [key, stableValue(entry)]),
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sameButtons = (a: RawDeckDef, b: RawDeckDef): boolean => {
|
|
19
|
+
if (a.buttons.length !== b.buttons.length) return false
|
|
20
|
+
for (let i = 0; i < a.buttons.length; i += 1) {
|
|
21
|
+
if (buttonKey(a.buttons[i]!, i) !== buttonKey(b.buttons[i]!, i)) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const decksChanged = (prev: RawConfig, next: RawConfig): boolean => {
|
|
29
|
+
const prevKeys = Object.keys(prev.decks)
|
|
30
|
+
const nextKeys = Object.keys(next.decks)
|
|
31
|
+
if (prevKeys.length !== nextKeys.length) return true
|
|
32
|
+
for (const id of prevKeys) {
|
|
33
|
+
if (!Object.prototype.hasOwnProperty.call(next.decks, id)) return true
|
|
34
|
+
}
|
|
35
|
+
for (const id of nextKeys) {
|
|
36
|
+
const prevDeck = prev.decks[id]
|
|
37
|
+
const nextDeck = next.decks[id]
|
|
38
|
+
if (prevDeck === undefined || nextDeck === undefined) return true
|
|
39
|
+
if (
|
|
40
|
+
JSON.stringify(stableValue(prevDeck)) !==
|
|
41
|
+
JSON.stringify(stableValue(nextDeck))
|
|
42
|
+
)
|
|
43
|
+
return true
|
|
44
|
+
if (!sameButtons(prevDeck, nextDeck)) return true
|
|
45
|
+
}
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Returns true for every config change that can affect runtime/editor state. */
|
|
50
|
+
export const configChanged = (prev: RawConfig, next: RawConfig): boolean =>
|
|
51
|
+
JSON.stringify(stableValue(prev)) !== JSON.stringify(stableValue(next))
|
|
52
|
+
|
|
53
|
+
// Kept as the explicit old name used by the initial corrective tests.
|
|
54
|
+
export const onlyDecksChanged = decksChanged
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { dirname, isAbsolute, resolve as resolvePath } from "node:path"
|
|
3
|
+
|
|
4
|
+
import { getOriginalCwd } from "@/cli/cwd"
|
|
5
|
+
|
|
6
|
+
export interface FindConfigOptions {
|
|
7
|
+
cwd?: string
|
|
8
|
+
explicitPath?: string
|
|
9
|
+
envVar?: string
|
|
10
|
+
homeDir: string
|
|
11
|
+
xdgConfigHome?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_CONFIG_FILENAME = "config.yml"
|
|
15
|
+
export const XDG_CONFIG_SUBDIR = "sirenodeck"
|
|
16
|
+
|
|
17
|
+
const resolvePath_ = (p: string, cwd: string): string =>
|
|
18
|
+
isAbsolute(p) ? p : resolvePath(cwd, p)
|
|
19
|
+
|
|
20
|
+
// ponytail: precedence is cli arg → ~/.config/sirenodeck/config.yml →
|
|
21
|
+
// ./config.yml (run folder / cwd). Parent-directory walk removed: the run
|
|
22
|
+
// folder is the canonical place for a project config, and silently walking
|
|
23
|
+
// up makes a "missing config" failure look like a "found some other config"
|
|
24
|
+
// one. The previous walk also kept selectors scripts out of the canoe.
|
|
25
|
+
export const findConfigPath = (options: FindConfigOptions): string | null => {
|
|
26
|
+
const cwd = options.cwd ?? getOriginalCwd()
|
|
27
|
+
if (options.explicitPath) {
|
|
28
|
+
const abs = resolvePath_(options.explicitPath, cwd)
|
|
29
|
+
return existsSync(abs) ? abs : null
|
|
30
|
+
}
|
|
31
|
+
if (options.envVar) {
|
|
32
|
+
const abs = resolvePath_(options.envVar, cwd)
|
|
33
|
+
if (existsSync(abs)) return abs
|
|
34
|
+
}
|
|
35
|
+
const xdgRoot =
|
|
36
|
+
options.xdgConfigHome ?? resolvePath(options.homeDir, ".config")
|
|
37
|
+
const xdgConfig = resolvePath(
|
|
38
|
+
xdgRoot,
|
|
39
|
+
XDG_CONFIG_SUBDIR,
|
|
40
|
+
DEFAULT_CONFIG_FILENAME,
|
|
41
|
+
)
|
|
42
|
+
if (existsSync(xdgConfig)) return xdgConfig
|
|
43
|
+
const cwdConfig = resolvePath(cwd, DEFAULT_CONFIG_FILENAME)
|
|
44
|
+
if (existsSync(cwdConfig)) return cwdConfig
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ponytail: kept for the parent-walk test fixture — call sites should use
|
|
49
|
+
// findConfigPath. Removing the export would break the existing test, so
|
|
50
|
+
// the helper stays here as an explicit escape hatch with no callers.
|
|
51
|
+
export const walkUpForConfig = (
|
|
52
|
+
startDir: string,
|
|
53
|
+
maxDepth: number,
|
|
54
|
+
): string | null => {
|
|
55
|
+
let current = startDir
|
|
56
|
+
for (let depth = 0; depth <= maxDepth; depth += 1) {
|
|
57
|
+
const candidate = resolvePath(current, DEFAULT_CONFIG_FILENAME)
|
|
58
|
+
if (existsSync(candidate)) return candidate
|
|
59
|
+
const parent = dirname(current)
|
|
60
|
+
if (parent === current) return null
|
|
61
|
+
current = parent
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isAbsolute, resolve as resolvePath } from "node:path"
|
|
2
|
+
|
|
3
|
+
export type IconSource =
|
|
4
|
+
| { kind: "path"; absolutePath: string }
|
|
5
|
+
| { kind: "cli-builtin"; id: string }
|
|
6
|
+
| { kind: "builtin-addon"; addonName: string; subPath: string }
|
|
7
|
+
| { kind: "addon"; addonName: string; subPath: string }
|
|
8
|
+
|
|
9
|
+
export interface ResolveIconRefContext {
|
|
10
|
+
configDir: string
|
|
11
|
+
resolveHome(p: string): string
|
|
12
|
+
builtinIconIds: ReadonlySet<string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CLI_BUILTIN_PREFIX = "icon://"
|
|
16
|
+
const BUILTIN_ADDON_PREFIX = "builtin://"
|
|
17
|
+
const ADDON_PREFIX = "addon://"
|
|
18
|
+
|
|
19
|
+
const startsWith = (s: string, prefix: string): boolean => s.startsWith(prefix)
|
|
20
|
+
|
|
21
|
+
export const isLocalIconPath = (ref: string): boolean => {
|
|
22
|
+
if (startsWith(ref, "./") || startsWith(ref, "../") || startsWith(ref, "/"))
|
|
23
|
+
return true
|
|
24
|
+
if (startsWith(ref, "~/") || startsWith(ref, "~\\")) return true
|
|
25
|
+
if (/[\\/]/.test(ref)) return true
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const resolveIconRef = (
|
|
30
|
+
ref: string,
|
|
31
|
+
ctx: ResolveIconRefContext,
|
|
32
|
+
): IconSource => {
|
|
33
|
+
if (startsWith(ref, CLI_BUILTIN_PREFIX)) {
|
|
34
|
+
const id = ref.slice(CLI_BUILTIN_PREFIX.length)
|
|
35
|
+
if (id.length === 0) {
|
|
36
|
+
throw new Error(`Invalid CLI-builtin icon ref: ${ref} (missing id)`)
|
|
37
|
+
}
|
|
38
|
+
if (!ctx.builtinIconIds.has(id)) {
|
|
39
|
+
throw new Error(`Unknown CLI-builtin icon: ${id}`)
|
|
40
|
+
}
|
|
41
|
+
return { kind: "cli-builtin", id }
|
|
42
|
+
}
|
|
43
|
+
if (startsWith(ref, BUILTIN_ADDON_PREFIX)) {
|
|
44
|
+
const rest = ref.slice(BUILTIN_ADDON_PREFIX.length)
|
|
45
|
+
const slash = rest.indexOf("/")
|
|
46
|
+
if (slash <= 0 || slash === rest.length - 1) {
|
|
47
|
+
throw new Error(`Invalid builtin:// addon ref: ${ref}`)
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
kind: "builtin-addon",
|
|
51
|
+
addonName: rest.slice(0, slash),
|
|
52
|
+
subPath: rest.slice(slash + 1),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (startsWith(ref, ADDON_PREFIX)) {
|
|
56
|
+
const rest = ref.slice(ADDON_PREFIX.length)
|
|
57
|
+
const slash = rest.indexOf("/")
|
|
58
|
+
if (slash <= 0 || slash === rest.length - 1) {
|
|
59
|
+
throw new Error(`Invalid addon:// ref: ${ref}`)
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
kind: "addon",
|
|
63
|
+
addonName: rest.slice(0, slash),
|
|
64
|
+
subPath: rest.slice(slash + 1),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (isLocalIconPath(ref)) {
|
|
68
|
+
const expanded = startsWith(ref, "~") ? ctx.resolveHome(ref) : ref
|
|
69
|
+
const absolutePath = isAbsolute(expanded)
|
|
70
|
+
? expanded
|
|
71
|
+
: resolvePath(ctx.configDir, expanded)
|
|
72
|
+
return { kind: "path", absolutePath }
|
|
73
|
+
}
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unrecognized icon ref: ${ref} — must be a relative path, icon://<id>, builtin://<addon>/<path>, or addon://<addon>/<path>`,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const iconSourceToString = (src: IconSource): string => {
|
|
80
|
+
switch (src.kind) {
|
|
81
|
+
case "path":
|
|
82
|
+
return src.absolutePath
|
|
83
|
+
case "cli-builtin":
|
|
84
|
+
return `icon://${src.id}`
|
|
85
|
+
case "builtin-addon":
|
|
86
|
+
return `builtin://${src.addonName}/${src.subPath}`
|
|
87
|
+
case "addon":
|
|
88
|
+
return `addon://${src.addonName}/${src.subPath}`
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from "node:fs"
|
|
2
|
+
import {
|
|
3
|
+
basename,
|
|
4
|
+
dirname,
|
|
5
|
+
isAbsolute,
|
|
6
|
+
resolve as resolvePath,
|
|
7
|
+
} from "node:path"
|
|
8
|
+
|
|
9
|
+
export class IncludeResolutionError extends Error {
|
|
10
|
+
readonly issues: { message: string; path?: string }[]
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
message: string,
|
|
14
|
+
issues: { message: string; path?: string }[] = [],
|
|
15
|
+
) {
|
|
16
|
+
super(message)
|
|
17
|
+
this.name = "IncludeResolutionError"
|
|
18
|
+
this.issues = issues
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const INCLUDE_RE = /^(\s*)(.*?)\s*!include\s+(\S+)(.*)$/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolves symlinks so two spellings of the same directory compare equal.
|
|
26
|
+
*
|
|
27
|
+
* ponytail: the containment check below is a plain string prefix, so a config
|
|
28
|
+
* reached through a symlinked parent failed it — the defining file had already
|
|
29
|
+
* been canonicalised while an absolute `!include` had not, and the two never
|
|
30
|
+
* shared a prefix. On macOS that is every config under a temp dir, since /var
|
|
31
|
+
* is a symlink to /private/var, but it applies to any symlinked config
|
|
32
|
+
* directory. Falls back to the literal path when the target does not exist yet
|
|
33
|
+
* (a missing include must be reported as missing, not as an escape attempt).
|
|
34
|
+
*/
|
|
35
|
+
const canonical = (p: string): string => {
|
|
36
|
+
try {
|
|
37
|
+
return realpathSync.native(p)
|
|
38
|
+
} catch {
|
|
39
|
+
try {
|
|
40
|
+
return resolvePath(realpathSync.native(dirname(p)), basename(p))
|
|
41
|
+
} catch {
|
|
42
|
+
return p
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isUnder = (candidate: string, root: string): boolean =>
|
|
48
|
+
candidate.startsWith(root.endsWith("/") ? root : `${root}/`)
|
|
49
|
+
|
|
50
|
+
const resolveIncludePath = (
|
|
51
|
+
pathStr: string,
|
|
52
|
+
definingFilePath: string,
|
|
53
|
+
): string => {
|
|
54
|
+
const lexicalRoot = resolvePath(dirname(definingFilePath))
|
|
55
|
+
const includePath = isAbsolute(pathStr)
|
|
56
|
+
? pathStr
|
|
57
|
+
: resolvePath(lexicalRoot, pathStr)
|
|
58
|
+
|
|
59
|
+
// ponytail: containment is judged LEXICALLY, and that is the whole point.
|
|
60
|
+
// This check exists to stop `../../etc/passwd`, not to police where the
|
|
61
|
+
// user's own files live. Resolving symlinks first broke a perfectly ordinary
|
|
62
|
+
// setup — a `demos -> ../../repo/demos` symlink placed inside the config
|
|
63
|
+
// directory on purpose — by reporting the user's deliberate choice as a path
|
|
64
|
+
// traversal attempt and refusing to start the daemon.
|
|
65
|
+
//
|
|
66
|
+
// The canonical forms are still accepted, because the defining file may
|
|
67
|
+
// arrive already realpath'd while an absolute !include has not (on macOS
|
|
68
|
+
// /var is a symlink to /private/var, so this is every config under a temp
|
|
69
|
+
// dir). Any of the spellings matching is enough; `../` escapes match none.
|
|
70
|
+
const canonicalRoot = canonical(lexicalRoot)
|
|
71
|
+
const contained =
|
|
72
|
+
isUnder(includePath, lexicalRoot) ||
|
|
73
|
+
isUnder(includePath, canonicalRoot) ||
|
|
74
|
+
isUnder(canonical(includePath), canonicalRoot)
|
|
75
|
+
|
|
76
|
+
if (!contained) {
|
|
77
|
+
throw new IncludeResolutionError(
|
|
78
|
+
`!include path escapes config directory: ${includePath} (from ${definingFilePath})`,
|
|
79
|
+
[
|
|
80
|
+
{
|
|
81
|
+
message: `path traversal blocked: ${pathStr}`,
|
|
82
|
+
path: definingFilePath,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
return includePath
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const processLine = (
|
|
91
|
+
line: string,
|
|
92
|
+
definingFilePath: string,
|
|
93
|
+
visited: Set<string>,
|
|
94
|
+
replacements: ReadonlyMap<string, string>,
|
|
95
|
+
): string[] => {
|
|
96
|
+
const match = line.match(INCLUDE_RE)
|
|
97
|
+
if (match === null) return [line]
|
|
98
|
+
const indent = match[1] ?? ""
|
|
99
|
+
const before = match[2] ?? ""
|
|
100
|
+
const pathStr = match[3] ?? ""
|
|
101
|
+
if (pathStr.length === 0) {
|
|
102
|
+
throw new IncludeResolutionError(
|
|
103
|
+
`Empty !include path at ${definingFilePath}`,
|
|
104
|
+
[{ message: "empty path after !include", path: definingFilePath }],
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
// ponytail: !include must stay within the defining file's directory.
|
|
108
|
+
const includePath = resolveIncludePath(pathStr, definingFilePath)
|
|
109
|
+
if (visited.has(includePath)) {
|
|
110
|
+
const cycle = [...visited, includePath].join(" -> ")
|
|
111
|
+
throw new IncludeResolutionError(`Circular include detected: ${cycle}`, [
|
|
112
|
+
{ message: `cycle: ${cycle}`, path: definingFilePath },
|
|
113
|
+
])
|
|
114
|
+
}
|
|
115
|
+
let raw = replacements.get(includePath)
|
|
116
|
+
if (raw === undefined) {
|
|
117
|
+
try {
|
|
118
|
+
raw = readFileSync(includePath, "utf8")
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
121
|
+
throw new IncludeResolutionError(
|
|
122
|
+
`Included file not found: ${includePath} (from ${definingFilePath})`,
|
|
123
|
+
[{ message: `not found: ${includePath}`, path: definingFilePath }],
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
throw err
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const next = new Set(visited)
|
|
130
|
+
next.add(includePath)
|
|
131
|
+
const inlined = inlineIncludes(raw, includePath, next, replacements)
|
|
132
|
+
if (before.trim().length === 0) {
|
|
133
|
+
return inlined.split("\n")
|
|
134
|
+
}
|
|
135
|
+
if (before.trim().endsWith(":")) {
|
|
136
|
+
const childIndent = `${indent} `
|
|
137
|
+
const childContent = inlined
|
|
138
|
+
.split("\n")
|
|
139
|
+
.map((l) => (l.length === 0 ? l : childIndent + l))
|
|
140
|
+
.join("\n")
|
|
141
|
+
return [`${indent}${before}`, childContent]
|
|
142
|
+
}
|
|
143
|
+
throw new IncludeResolutionError(
|
|
144
|
+
`Unsupported !include position in line "${line}" (from ${definingFilePath})`,
|
|
145
|
+
[{ message: `unsupported !include position`, path: definingFilePath }],
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const inlineIncludes = (
|
|
150
|
+
text: string,
|
|
151
|
+
definingFilePath: string,
|
|
152
|
+
visited: Set<string>,
|
|
153
|
+
replacements: ReadonlyMap<string, string>,
|
|
154
|
+
): string => {
|
|
155
|
+
const lines = text.split("\n")
|
|
156
|
+
const out: string[] = []
|
|
157
|
+
for (const line of lines) {
|
|
158
|
+
out.push(...processLine(line, definingFilePath, visited, replacements))
|
|
159
|
+
}
|
|
160
|
+
return out.join("\n")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const resolveIncludes = (
|
|
164
|
+
text: string,
|
|
165
|
+
definingFilePath: string,
|
|
166
|
+
replacements: ReadonlyMap<string, string> = new Map(),
|
|
167
|
+
): string =>
|
|
168
|
+
inlineIncludes(
|
|
169
|
+
text,
|
|
170
|
+
definingFilePath,
|
|
171
|
+
new Set([definingFilePath]),
|
|
172
|
+
replacements,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
const discoverFromFile = (filePath: string, visited: Set<string>): string[] => {
|
|
176
|
+
const canonicalPath = realpathSync(filePath)
|
|
177
|
+
if (visited.has(canonicalPath)) {
|
|
178
|
+
throw new IncludeResolutionError(
|
|
179
|
+
`Circular include detected: ${canonicalPath}`,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
const next = new Set(visited)
|
|
183
|
+
next.add(canonicalPath)
|
|
184
|
+
const sources = [canonicalPath]
|
|
185
|
+
for (const line of readFileSync(canonicalPath, "utf8").split("\n")) {
|
|
186
|
+
const match = line.match(INCLUDE_RE)
|
|
187
|
+
if (match === null) continue
|
|
188
|
+
const pathStr = match[3]
|
|
189
|
+
if (pathStr === undefined || pathStr.length === 0) {
|
|
190
|
+
throw new IncludeResolutionError(
|
|
191
|
+
`Empty !include path at ${canonicalPath}`,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
const includePath = resolveIncludePath(pathStr, canonicalPath)
|
|
195
|
+
sources.push(...discoverFromFile(includePath, next))
|
|
196
|
+
}
|
|
197
|
+
return [...new Set(sources)]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Returns the canonical files reachable through !include, including root. */
|
|
201
|
+
export const discoverIncludeGraph = (rootPath: string): string[] =>
|
|
202
|
+
discoverFromFile(rootPath, new Set())
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./builtin-icons"
|
|
2
|
+
export * from "./discovery"
|
|
3
|
+
export * from "./icon-resolver"
|
|
4
|
+
export * from "./include-resolver"
|
|
5
|
+
export * from "./loader"
|
|
6
|
+
export * from "./mutation"
|
|
7
|
+
export * from "./reference-expander"
|
|
8
|
+
export * from "./schemas"
|
|
9
|
+
export * from "./validation"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { dirname, isAbsolute, resolve as resolvePath } from "node:path"
|
|
3
|
+
|
|
4
|
+
import { parseDocument, YAMLParseError } from "yaml"
|
|
5
|
+
|
|
6
|
+
import { getOriginalCwd } from "@/cli/cwd"
|
|
7
|
+
|
|
8
|
+
import { IncludeResolutionError, resolveIncludes } from "./include-resolver"
|
|
9
|
+
import { RawConfigSchema, type RawConfig } from "./schemas"
|
|
10
|
+
import { expandButtonReferences } from "./reference-expander"
|
|
11
|
+
|
|
12
|
+
export interface LoadConfigOptions {
|
|
13
|
+
configPath: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LoadConfigResult {
|
|
17
|
+
config: RawConfig
|
|
18
|
+
configDir: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LineLocation {
|
|
22
|
+
line: number
|
|
23
|
+
col: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ConfigError {
|
|
27
|
+
message: string
|
|
28
|
+
location?: LineLocation
|
|
29
|
+
path?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class ConfigLoadError extends Error {
|
|
33
|
+
readonly issues: ConfigError[]
|
|
34
|
+
|
|
35
|
+
constructor(message: string, issues: ConfigError[] = []) {
|
|
36
|
+
super(message)
|
|
37
|
+
this.name = "ConfigLoadError"
|
|
38
|
+
this.issues = issues
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const formatLineCol = (loc: LineLocation): string =>
|
|
43
|
+
`line ${loc.line + 1}, col ${loc.col + 1}`
|
|
44
|
+
|
|
45
|
+
const convertYamlErrors = (err: unknown): ConfigError[] => {
|
|
46
|
+
if (err instanceof YAMLParseError) {
|
|
47
|
+
const linePos = err.linePos?.[0]
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
message: err.message,
|
|
51
|
+
...(linePos
|
|
52
|
+
? { location: { line: linePos.line, col: linePos.col } }
|
|
53
|
+
: {}),
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(err)) {
|
|
58
|
+
return err.map((e) => {
|
|
59
|
+
const linePos = e.linePos?.[0]
|
|
60
|
+
return {
|
|
61
|
+
message: e.message ?? String(e),
|
|
62
|
+
...(linePos
|
|
63
|
+
? { location: { line: linePos.line, col: linePos.col } }
|
|
64
|
+
: {}),
|
|
65
|
+
} satisfies ConfigError
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
return [{ message: err instanceof Error ? err.message : String(err) }]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const loadConfigFile = (configPath: string): unknown => {
|
|
72
|
+
const absolutePath = isAbsolute(configPath)
|
|
73
|
+
? configPath
|
|
74
|
+
: resolvePath(getOriginalCwd(), configPath)
|
|
75
|
+
let raw: string
|
|
76
|
+
try {
|
|
77
|
+
raw = readFileSync(absolutePath, "utf8")
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
80
|
+
throw new ConfigLoadError(`Config file not found: ${absolutePath}`)
|
|
81
|
+
}
|
|
82
|
+
throw err
|
|
83
|
+
}
|
|
84
|
+
const doc = parseDocument(raw, { keepSourceTokens: true })
|
|
85
|
+
const errors = doc.errors
|
|
86
|
+
// ponytail: textual include inlining — pre-process before yaml parses so
|
|
87
|
+
// included content participates in normal yaml merge semantics. Cycle
|
|
88
|
+
// detection and missing-file errors are raised here, before schema validation.
|
|
89
|
+
let inlinedRaw: string
|
|
90
|
+
try {
|
|
91
|
+
inlinedRaw = resolveIncludes(raw, absolutePath)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err instanceof IncludeResolutionError) {
|
|
94
|
+
throw new ConfigLoadError(err.message, err.issues)
|
|
95
|
+
}
|
|
96
|
+
throw err
|
|
97
|
+
}
|
|
98
|
+
if (inlinedRaw !== raw) {
|
|
99
|
+
const inlinedDoc = parseDocument(inlinedRaw, { keepSourceTokens: true })
|
|
100
|
+
const inlinedErrors = inlinedDoc.errors
|
|
101
|
+
if (inlinedErrors.length > 0) {
|
|
102
|
+
throw new ConfigLoadError(
|
|
103
|
+
`YAML parse errors in ${absolutePath} (after inlining includes):\n${inlinedErrors
|
|
104
|
+
.map((e) => ` - ${e.message}`)
|
|
105
|
+
.join("\n")}`,
|
|
106
|
+
convertYamlErrors(inlinedErrors),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return inlinedDoc.toJSON()
|
|
110
|
+
}
|
|
111
|
+
if (errors.length > 0) {
|
|
112
|
+
throw new ConfigLoadError(
|
|
113
|
+
`YAML parse errors in ${absolutePath}:\n${errors.map((e) => ` - ${e.message}`).join("\n")}`,
|
|
114
|
+
convertYamlErrors(errors),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
return doc.toJSON()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ponytail: zod 4's z.union() surfaces a top-level "Invalid input"
|
|
121
|
+
// with the per-variant reasons buried under `errors: Array<Array<issue>>`.
|
|
122
|
+
// We flatten those variant arrays into the top-level issue list so users
|
|
123
|
+
// see the actual cause (e.g. "Unrecognized key: \"src\"") instead of a
|
|
124
|
+
// useless top-level generic.
|
|
125
|
+
type RawZodIssue = {
|
|
126
|
+
message: string
|
|
127
|
+
path?: Array<string | number>
|
|
128
|
+
errors?: ReadonlyArray<ReadonlyArray<RawZodIssue>>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const flattenZodIssues = (
|
|
132
|
+
rawIssues: ReadonlyArray<RawZodIssue>,
|
|
133
|
+
basePath: ReadonlyArray<string | number> = [],
|
|
134
|
+
): ConfigError[] => {
|
|
135
|
+
const out: ConfigError[] = []
|
|
136
|
+
for (const issue of rawIssues) {
|
|
137
|
+
const path = [...basePath, ...(issue.path ?? [])]
|
|
138
|
+
if (issue.errors !== undefined && issue.errors.length > 0) {
|
|
139
|
+
// ponytail: union variant issues — flatten ALL variants' issues so
|
|
140
|
+
// the user sees every possible mismatch. Filter out empty arrays
|
|
141
|
+
// (variants that matched nothing).
|
|
142
|
+
for (const variant of issue.errors) {
|
|
143
|
+
if (variant.length > 0) {
|
|
144
|
+
out.push(...flattenZodIssues(variant, path))
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
out.push({
|
|
150
|
+
message: issue.message,
|
|
151
|
+
...(path.length > 0 ? { path: path.join(".") } : {}),
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const reportZodIssues = (issues: ConfigError[]): string =>
|
|
158
|
+
issues
|
|
159
|
+
.map((issue) => {
|
|
160
|
+
const parts = [issue.message]
|
|
161
|
+
if (issue.location) parts.push(`@ ${formatLineCol(issue.location)}`)
|
|
162
|
+
if (issue.path) parts.push(`(at ${issue.path})`)
|
|
163
|
+
return ` - ${parts.join(" ")}`
|
|
164
|
+
})
|
|
165
|
+
.join("\n")
|
|
166
|
+
|
|
167
|
+
export const loadConfig = ({
|
|
168
|
+
configPath,
|
|
169
|
+
}: LoadConfigOptions): LoadConfigResult => {
|
|
170
|
+
const configDir = dirname(configPath)
|
|
171
|
+
const raw = loadConfigFile(configPath)
|
|
172
|
+
const expanded = expandButtonReferences(raw, configDir)
|
|
173
|
+
const result = RawConfigSchema.safeParse(expanded)
|
|
174
|
+
if (!result.success) {
|
|
175
|
+
// ponytail: recurse into unionErrors so the user sees the actual
|
|
176
|
+
// discriminator failure (e.g. "Unrecognized key 'src'") instead of
|
|
177
|
+
// a useless top-level "Invalid input".
|
|
178
|
+
const issues = flattenZodIssues(
|
|
179
|
+
result.error.issues as ReadonlyArray<RawZodIssue>,
|
|
180
|
+
)
|
|
181
|
+
throw new ConfigLoadError(
|
|
182
|
+
`Invalid config at ${configPath}:\n${reportZodIssues(issues)}`,
|
|
183
|
+
issues,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
return { config: result.data, configDir }
|
|
187
|
+
}
|