@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,114 @@
|
|
|
1
|
+
// ponytail: the CLI is the operator's interface to the daemon — not just
|
|
2
|
+
// a launcher. When the daemon starts, restarts, or stops, the CLI
|
|
3
|
+
// reads the daemon's `service.log` and surfaces any warn/error/fatal
|
|
4
|
+
// events inline so the operator doesn't have to grep a file. The
|
|
5
|
+
// daemon's pino JSON lines are easy to parse: each line is a
|
|
6
|
+
// self-describing object with `level` (30/40/50/60 = info/warn/error/fatal),
|
|
7
|
+
// `time` (ms epoch), `msg` (string), and `component` (optional).
|
|
8
|
+
//
|
|
9
|
+
// The CLI snapshots the file size at command start, then reads the
|
|
10
|
+
// appended bytes when it needs to surface events. Tail-like polling
|
|
11
|
+
// would work too, but we only need the events that accumulated during
|
|
12
|
+
// the operator's command — the operator can always run `p dev logs`
|
|
13
|
+
// for full live tail.
|
|
14
|
+
import { closeSync, existsSync, openSync, readSync, statSync } from "node:fs"
|
|
15
|
+
|
|
16
|
+
import { resolveDaemonPaths } from "./daemon"
|
|
17
|
+
|
|
18
|
+
export type DaemonLogLevel = "warn" | "error" | "fatal"
|
|
19
|
+
|
|
20
|
+
export interface DaemonEvent {
|
|
21
|
+
readonly level: DaemonLogLevel
|
|
22
|
+
readonly component: string
|
|
23
|
+
readonly message: string
|
|
24
|
+
readonly time: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RawLogEntry {
|
|
28
|
+
level?: unknown
|
|
29
|
+
time?: unknown
|
|
30
|
+
msg?: unknown
|
|
31
|
+
component?: unknown
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const LEVEL_BY_NUMBER: Record<number, DaemonLogLevel> = {
|
|
35
|
+
40: "warn",
|
|
36
|
+
50: "error",
|
|
37
|
+
60: "fatal",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isLevel = (v: unknown): v is DaemonLogLevel =>
|
|
41
|
+
v === "warn" || v === "error" || v === "fatal"
|
|
42
|
+
|
|
43
|
+
const parseEntry = (line: string): DaemonEvent | null => {
|
|
44
|
+
let raw: RawLogEntry
|
|
45
|
+
try {
|
|
46
|
+
raw = JSON.parse(line) as RawLogEntry
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
const levelNum = typeof raw.level === "number" ? raw.level : NaN
|
|
51
|
+
const level = LEVEL_BY_NUMBER[levelNum]
|
|
52
|
+
if (level === undefined) return null
|
|
53
|
+
const message = typeof raw.msg === "string" ? raw.msg : ""
|
|
54
|
+
if (message.length === 0) return null
|
|
55
|
+
const component = typeof raw.component === "string" ? raw.component : ""
|
|
56
|
+
const time = typeof raw.time === "number" ? raw.time : Date.now()
|
|
57
|
+
return { level, component, message, time }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ponytail: the only DaemonEvent surface the CLI needs for command-time
|
|
61
|
+
// inline output. If we ever want a structured `severity` field for
|
|
62
|
+
// formatting (e.g. red for error, yellow for warn), add it here.
|
|
63
|
+
const readRange = (logPath: string, start: number, end: number): string => {
|
|
64
|
+
const fd = openSync(logPath, "r")
|
|
65
|
+
try {
|
|
66
|
+
const buf = Buffer.alloc(end - start)
|
|
67
|
+
readSync(fd, buf, 0, buf.length, start)
|
|
68
|
+
return buf.toString("utf8")
|
|
69
|
+
} finally {
|
|
70
|
+
closeSync(fd)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ponytail: same rotation-resilience as `tailFile` — if the file shrinks
|
|
75
|
+
// (truncated/rotated), reset the read offset to 0. We never see the
|
|
76
|
+
// pre-rotation content twice.
|
|
77
|
+
export const readDaemonEventsSince = (
|
|
78
|
+
logPath: string,
|
|
79
|
+
sinceBytes: number,
|
|
80
|
+
): DaemonEvent[] => {
|
|
81
|
+
if (!existsSync(logPath)) return []
|
|
82
|
+
const size = statSync(logPath).size
|
|
83
|
+
if (size < sinceBytes) return [] // rotated
|
|
84
|
+
if (size === sinceBytes) return []
|
|
85
|
+
const text = readRange(logPath, sinceBytes, size)
|
|
86
|
+
const events: DaemonEvent[] = []
|
|
87
|
+
for (const raw of text.split("\n")) {
|
|
88
|
+
if (raw.length === 0) continue
|
|
89
|
+
const ev = parseEntry(raw)
|
|
90
|
+
if (ev !== null) events.push(ev)
|
|
91
|
+
}
|
|
92
|
+
return events
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ponytail: combined "size now" + "events since then" helper. The CLI
|
|
96
|
+
// records the size at command start with `snapshotDaemonLog()` and
|
|
97
|
+
// calls this at the end to surface what accumulated.
|
|
98
|
+
export const readDaemonEventsFromSnapshot = (
|
|
99
|
+
logPath: string,
|
|
100
|
+
snapshot: { readonly sinceBytes: number },
|
|
101
|
+
): DaemonEvent[] => readDaemonEventsSince(logPath, snapshot.sinceBytes)
|
|
102
|
+
|
|
103
|
+
export interface DaemonLogSnapshot {
|
|
104
|
+
readonly sinceBytes: number
|
|
105
|
+
readonly takenAt: number
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const snapshotDaemonLog = (): DaemonLogSnapshot => {
|
|
109
|
+
const logPath = `${resolveDaemonPaths().runtimeDir}/service.log`
|
|
110
|
+
if (!existsSync(logPath)) {
|
|
111
|
+
return { sinceBytes: 0, takenAt: Date.now() }
|
|
112
|
+
}
|
|
113
|
+
return { sinceBytes: statSync(logPath).size, takenAt: Date.now() }
|
|
114
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { closeSync, existsSync, openSync, readSync, statSync } from "node:fs"
|
|
2
|
+
|
|
3
|
+
import { formatHuman } from "./logger"
|
|
4
|
+
|
|
5
|
+
export interface TailFileOptions {
|
|
6
|
+
readonly logPath: string
|
|
7
|
+
readonly lines?: number
|
|
8
|
+
readonly follow?: boolean
|
|
9
|
+
readonly pollMs?: number
|
|
10
|
+
/** Prefix each line with when it was logged. On by default here. */
|
|
11
|
+
readonly timestamps?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TailHandle {
|
|
15
|
+
stop: () => void
|
|
16
|
+
promise: Promise<void>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ponytail: tail the last N lines, then poll for appended bytes. Simpler than
|
|
20
|
+
// fs.watch + inode tracking, and survives rotation (new size from 0).
|
|
21
|
+
export const tailFile = (opts: TailFileOptions): TailHandle => {
|
|
22
|
+
const {
|
|
23
|
+
logPath,
|
|
24
|
+
lines = 50,
|
|
25
|
+
follow = true,
|
|
26
|
+
pollMs = 250,
|
|
27
|
+
// ponytail: reading a log after the fact, "when did this happen" is the
|
|
28
|
+
// first question — unlike live startup output, which is read as it
|
|
29
|
+
// happens. Opposite default to the console formatter, deliberately.
|
|
30
|
+
timestamps = true,
|
|
31
|
+
} = opts
|
|
32
|
+
let stopped = false
|
|
33
|
+
let resolveDone!: () => void
|
|
34
|
+
const promise = new Promise<void>((resolve) => {
|
|
35
|
+
resolveDone = resolve
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const emit = (line: string): void => {
|
|
39
|
+
const formatted = formatHuman(line, { timestamps })
|
|
40
|
+
const out = formatted !== null ? formatted : line
|
|
41
|
+
process.stdout.write(`${out}\n`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const readRange = (start: number, end: number): string => {
|
|
45
|
+
const fd = openSync(logPath, "r")
|
|
46
|
+
try {
|
|
47
|
+
const buf = Buffer.alloc(end - start)
|
|
48
|
+
readSync(fd, buf, 0, buf.length, start)
|
|
49
|
+
return buf.toString("utf8")
|
|
50
|
+
} finally {
|
|
51
|
+
closeSync(fd)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const emitLast = (): number => {
|
|
56
|
+
if (!existsSync(logPath)) return 0
|
|
57
|
+
const text = readRange(0, statSync(logPath).size)
|
|
58
|
+
const all = text.split("\n").filter((l) => l.length > 0)
|
|
59
|
+
for (const line of all.slice(-lines)) emit(line)
|
|
60
|
+
return statSync(logPath).size
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stop = (): void => {
|
|
64
|
+
if (stopped) return
|
|
65
|
+
stopped = true
|
|
66
|
+
resolveDone()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let lastSize = emitLast()
|
|
70
|
+
|
|
71
|
+
if (follow) {
|
|
72
|
+
const interval = setInterval(() => {
|
|
73
|
+
if (stopped) {
|
|
74
|
+
clearInterval(interval)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
if (!existsSync(logPath)) {
|
|
78
|
+
lastSize = 0
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const currentSize = statSync(logPath).size
|
|
82
|
+
if (currentSize < lastSize) lastSize = 0 // rotation
|
|
83
|
+
if (currentSize === lastSize) return
|
|
84
|
+
const text = readRange(lastSize, currentSize)
|
|
85
|
+
for (const line of text.split("\n").filter((l) => l.length > 0))
|
|
86
|
+
emit(line)
|
|
87
|
+
lastSize = currentSize
|
|
88
|
+
}, pollMs)
|
|
89
|
+
process.once("SIGINT", () => {
|
|
90
|
+
clearInterval(interval)
|
|
91
|
+
process.stdout.write("\n")
|
|
92
|
+
stop()
|
|
93
|
+
})
|
|
94
|
+
} else {
|
|
95
|
+
stop()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { stop, promise }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const tailLogs = async (opts: TailFileOptions): Promise<void> => {
|
|
102
|
+
await tailFile(opts).promise
|
|
103
|
+
}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { Writable } from "node:stream"
|
|
2
|
+
|
|
3
|
+
import { log } from "@clack/prompts"
|
|
4
|
+
import pino, {
|
|
5
|
+
type Logger,
|
|
6
|
+
type LoggerOptions,
|
|
7
|
+
type SerializedError,
|
|
8
|
+
} from "pino"
|
|
9
|
+
|
|
10
|
+
export interface CreateLoggerOptions {
|
|
11
|
+
level?: LoggerOptions["level"]
|
|
12
|
+
verbose?: boolean
|
|
13
|
+
json?: boolean
|
|
14
|
+
component?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const RESET = "\u001b[0m"
|
|
18
|
+
const DIM = "\u001b[2m"
|
|
19
|
+
const RED = "\u001b[31m"
|
|
20
|
+
const GREEN = "\u001b[32m"
|
|
21
|
+
const YELLOW = "\u001b[33m"
|
|
22
|
+
const BLUE = "\u001b[34m"
|
|
23
|
+
const MAGENTA = "\u001b[35m"
|
|
24
|
+
const CYAN = "\u001b[36m"
|
|
25
|
+
const GRAY = "\u001b[90m"
|
|
26
|
+
|
|
27
|
+
const LEVEL_COLOR: Record<number, string> = {
|
|
28
|
+
10: GRAY,
|
|
29
|
+
20: GRAY,
|
|
30
|
+
30: CYAN,
|
|
31
|
+
40: YELLOW,
|
|
32
|
+
50: RED,
|
|
33
|
+
60: RED,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const LEVEL_LABEL: Record<number, string> = {
|
|
37
|
+
10: "TRACE",
|
|
38
|
+
20: "DEBUG",
|
|
39
|
+
30: "INFO",
|
|
40
|
+
40: "WARN",
|
|
41
|
+
50: "ERROR",
|
|
42
|
+
60: "FATAL",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const colorize = (color: string, text: string): string =>
|
|
46
|
+
process.stdout.isTTY ? `${color}${text}${RESET}` : text
|
|
47
|
+
|
|
48
|
+
const COMPONENT_COLOR: Record<string, string> = {
|
|
49
|
+
runtime: CYAN,
|
|
50
|
+
methods: CYAN,
|
|
51
|
+
executor: CYAN,
|
|
52
|
+
"state-publisher": CYAN,
|
|
53
|
+
real: GREEN,
|
|
54
|
+
emulator: GREEN,
|
|
55
|
+
"ws-bridge": MAGENTA,
|
|
56
|
+
"addon-handler": MAGENTA,
|
|
57
|
+
"active-app": YELLOW,
|
|
58
|
+
"key-macro": YELLOW,
|
|
59
|
+
clipboard: YELLOW,
|
|
60
|
+
notification: YELLOW,
|
|
61
|
+
session: YELLOW,
|
|
62
|
+
"browser-renderer": BLUE,
|
|
63
|
+
"emulator-server": BLUE,
|
|
64
|
+
daemon: BLUE,
|
|
65
|
+
requirements: BLUE,
|
|
66
|
+
orchestrator: CYAN,
|
|
67
|
+
cli: GRAY,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const colorForComponent = (component: string): string =>
|
|
71
|
+
COMPONENT_COLOR[component] ?? GRAY
|
|
72
|
+
|
|
73
|
+
const CONTEXT_FIELDS = [
|
|
74
|
+
"frontendUrl",
|
|
75
|
+
"wsUrl",
|
|
76
|
+
"tool",
|
|
77
|
+
"sessionType",
|
|
78
|
+
"platform",
|
|
79
|
+
"executor",
|
|
80
|
+
"deckId",
|
|
81
|
+
"position",
|
|
82
|
+
"gesture",
|
|
83
|
+
"host",
|
|
84
|
+
"port",
|
|
85
|
+
"addon",
|
|
86
|
+
"source",
|
|
87
|
+
"message",
|
|
88
|
+
"addonDecks",
|
|
89
|
+
"userDecks",
|
|
90
|
+
"addonConfigEntries",
|
|
91
|
+
"icon",
|
|
92
|
+
"fullPath",
|
|
93
|
+
] as const
|
|
94
|
+
|
|
95
|
+
const FORWARDED_CONTEXT_FIELDS = [
|
|
96
|
+
"component",
|
|
97
|
+
"deckId",
|
|
98
|
+
"position",
|
|
99
|
+
"addonName",
|
|
100
|
+
"gesture",
|
|
101
|
+
"keyIndex",
|
|
102
|
+
"reason",
|
|
103
|
+
] as const
|
|
104
|
+
|
|
105
|
+
export interface FormatHumanOptions {
|
|
106
|
+
/**
|
|
107
|
+
* Prefix each line with the time it was logged.
|
|
108
|
+
*
|
|
109
|
+
* ponytail: off by default, because live startup output is read as it
|
|
110
|
+
* happens and the clock adds noise (see the comment below). `sirenodeck
|
|
111
|
+
* logs` is the opposite case — you are reading a record after the fact and
|
|
112
|
+
* "when did this happen" is the first question — so it turns this on.
|
|
113
|
+
*/
|
|
114
|
+
readonly timestamps?: boolean
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const formatLogTime = (value: unknown): string | null => {
|
|
118
|
+
// pino writes `time` as epoch millis; tolerate an ISO string too.
|
|
119
|
+
const ms =
|
|
120
|
+
typeof value === "number"
|
|
121
|
+
? value
|
|
122
|
+
: typeof value === "string"
|
|
123
|
+
? Date.parse(value)
|
|
124
|
+
: Number.NaN
|
|
125
|
+
if (!Number.isFinite(ms)) return null
|
|
126
|
+
const d = new Date(ms)
|
|
127
|
+
const pad = (n: number, w = 2): string => String(n).padStart(w, "0")
|
|
128
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const formatHuman = (
|
|
132
|
+
jsonLine: string,
|
|
133
|
+
options: FormatHumanOptions = {},
|
|
134
|
+
): string | null => {
|
|
135
|
+
let entry: Record<string, unknown>
|
|
136
|
+
try {
|
|
137
|
+
entry = JSON.parse(jsonLine) as Record<string, unknown>
|
|
138
|
+
} catch {
|
|
139
|
+
return jsonLine
|
|
140
|
+
}
|
|
141
|
+
const levelNum = typeof entry["level"] === "number" ? entry["level"] : 30
|
|
142
|
+
const level = LEVEL_LABEL[levelNum] ?? "INFO"
|
|
143
|
+
const levelColor = LEVEL_COLOR[levelNum] ?? CYAN
|
|
144
|
+
const msg = typeof entry["msg"] === "string" ? entry["msg"] : ""
|
|
145
|
+
// ponytail: operator-facing logs drop the [HH:MM:SS] prefix and the
|
|
146
|
+
// continuation-line indent. Timestamps belong in service.log (where the
|
|
147
|
+
// structured `time` field stays intact for forensics); the terminal only
|
|
148
|
+
// needs enough info to read the line at a glance.
|
|
149
|
+
const head = colorize(levelColor, level.padEnd(5))
|
|
150
|
+
const time = options.timestamps === true ? formatLogTime(entry["time"]) : null
|
|
151
|
+
const timeTag = time !== null ? `${colorize(DIM, time)} ` : ""
|
|
152
|
+
|
|
153
|
+
const component =
|
|
154
|
+
typeof entry["component"] === "string" ? entry["component"] : ""
|
|
155
|
+
const componentTag =
|
|
156
|
+
component.length > 0
|
|
157
|
+
? ` ${colorize(colorForComponent(component), `[${component}]`)}`
|
|
158
|
+
: ""
|
|
159
|
+
|
|
160
|
+
const ctxParts: string[] = []
|
|
161
|
+
for (const key of CONTEXT_FIELDS) {
|
|
162
|
+
const value = entry[key]
|
|
163
|
+
if (value === undefined || value === null) continue
|
|
164
|
+
const display = typeof value === "string" ? value : JSON.stringify(value)
|
|
165
|
+
if (display.length === 0) continue
|
|
166
|
+
ctxParts.push(`${colorize(DIM, `${key}:`)} ${display}`)
|
|
167
|
+
}
|
|
168
|
+
const err = entry["err"]
|
|
169
|
+
if (err !== null && typeof err === "object") {
|
|
170
|
+
const e = err as { type?: unknown; message?: unknown }
|
|
171
|
+
const errType = typeof e.type === "string" ? e.type : "Error"
|
|
172
|
+
const errMsg = typeof e.message === "string" ? e.message : ""
|
|
173
|
+
if (errMsg.length > 0) {
|
|
174
|
+
ctxParts.push(
|
|
175
|
+
`${colorize(DIM, "err:")} ${colorize(RED, `${errType}: ${errMsg}`)}`,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const ctxStr = ctxParts.length > 0 ? ` (${ctxParts.join(", ")})` : ""
|
|
180
|
+
return `${timeTag}${head}${componentTag} ${msg}${ctxStr}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ponytail: CLI startup logs (between intro/outro) should use the same
|
|
184
|
+
// clack tool as the banner: log.info / log.warn / log.error. Those tools
|
|
185
|
+
// render with a dim │ border + level-appropriate icon (● / ▲ / ■) and no
|
|
186
|
+
// "INFO"/"WARN" text label — matching the banner's visual language.
|
|
187
|
+
// HumanWritable maps pino levels to those tools so `logger.info(...)`
|
|
188
|
+
// calls made between intro() and outro() automatically get the border and
|
|
189
|
+
// icon. Outside a banner (or when stdout is not a TTY), fall back to the
|
|
190
|
+
// plain formatHuman line so piped output stays greppable.
|
|
191
|
+
class HumanWritable extends Writable {
|
|
192
|
+
override _write(
|
|
193
|
+
chunk: Buffer | string,
|
|
194
|
+
_encoding: BufferEncoding,
|
|
195
|
+
callback: (error?: Error | null) => void,
|
|
196
|
+
): void {
|
|
197
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8")
|
|
198
|
+
const lines = text.split("\n")
|
|
199
|
+
const useClack = process.stdout.isTTY && !isServiceMode()
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (line.length === 0) continue
|
|
202
|
+
if (useClack) {
|
|
203
|
+
let entry: Record<string, unknown> | null = null
|
|
204
|
+
try {
|
|
205
|
+
entry = JSON.parse(line) as Record<string, unknown>
|
|
206
|
+
} catch {
|
|
207
|
+
// not JSON — fall back to plain
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
entry !== null &&
|
|
211
|
+
typeof entry["level"] === "number" &&
|
|
212
|
+
typeof entry["msg"] === "string"
|
|
213
|
+
) {
|
|
214
|
+
const levelNum = entry["level"] as number
|
|
215
|
+
const component =
|
|
216
|
+
typeof entry["component"] === "string"
|
|
217
|
+
? ` [${entry["component"]}]`
|
|
218
|
+
: ""
|
|
219
|
+
// ponytail: keep CONTEXT_FIELDS + err inline so operators still see
|
|
220
|
+
// deckId / position / err details on the terminal, just without the
|
|
221
|
+
// "INFO"/"WARN" text label — the icon + color from log.* conveys level.
|
|
222
|
+
const ctxParts: string[] = []
|
|
223
|
+
for (const key of CONTEXT_FIELDS) {
|
|
224
|
+
const value = entry[key]
|
|
225
|
+
if (value === undefined || value === null) continue
|
|
226
|
+
const display =
|
|
227
|
+
typeof value === "string" ? value : JSON.stringify(value)
|
|
228
|
+
if (display.length === 0) continue
|
|
229
|
+
ctxParts.push(`${key}: ${display}`)
|
|
230
|
+
}
|
|
231
|
+
const err = entry["err"]
|
|
232
|
+
if (err !== null && typeof err === "object") {
|
|
233
|
+
const e = err as { type?: unknown; message?: unknown }
|
|
234
|
+
const errType = typeof e.type === "string" ? e.type : "Error"
|
|
235
|
+
const errMsg = typeof e.message === "string" ? e.message : ""
|
|
236
|
+
if (errMsg.length > 0) ctxParts.push(`err: ${errType}: ${errMsg}`)
|
|
237
|
+
}
|
|
238
|
+
const ctxStr = ctxParts.length > 0 ? ` (${ctxParts.join(", ")})` : ""
|
|
239
|
+
const msg = `${entry["msg"] as string}${component}${ctxStr}`
|
|
240
|
+
if (levelNum >= 50) log.error(msg)
|
|
241
|
+
else if (levelNum >= 40) log.warn(msg)
|
|
242
|
+
else log.info(msg)
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const formatted = formatHuman(line)
|
|
247
|
+
if (formatted !== null) {
|
|
248
|
+
process.stdout.write(`${formatted}\n`)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
callback()
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const errorSerializer = (
|
|
256
|
+
err: Error & { issues?: unknown; type?: string },
|
|
257
|
+
): SerializedError => {
|
|
258
|
+
const out = {
|
|
259
|
+
type: err.name || "Error",
|
|
260
|
+
message: err.message,
|
|
261
|
+
stack: process.env["SIRENO_LOG_VERBOSE"] === "1" ? (err.stack ?? "") : "",
|
|
262
|
+
raw: err,
|
|
263
|
+
}
|
|
264
|
+
if (err.type !== undefined) (out as { type?: string }).type = err.type
|
|
265
|
+
if (err.issues !== undefined)
|
|
266
|
+
(out as { issues?: unknown }).issues = err.issues
|
|
267
|
+
return out as SerializedError
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ponytail: stray INVOCATION_ID + JOURNAL_STREAM in a user's shell (from a
|
|
271
|
+
// prior systemd context) makes human-format detection false-positive. Match
|
|
272
|
+
// the same heuristic as isUnderServiceManager: ppid === 1 is the only reliable
|
|
273
|
+
// signal that we're actually being supervised by init.
|
|
274
|
+
import { readFileSync } from "node:fs"
|
|
275
|
+
|
|
276
|
+
export const isOrphanedToInit = (): boolean => {
|
|
277
|
+
if (process.platform !== "linux") return true
|
|
278
|
+
try {
|
|
279
|
+
const stat = readFileSync("/proc/self/stat", "utf8")
|
|
280
|
+
const closeParen = stat.lastIndexOf(")")
|
|
281
|
+
const tail = stat.slice(closeParen + 2)
|
|
282
|
+
const ppid = Number.parseInt(tail.split(" ")[1] ?? "", 10)
|
|
283
|
+
if (!Number.isFinite(ppid)) return false
|
|
284
|
+
// ppid === 1 covers system-level services and true orphans; a systemd
|
|
285
|
+
// --user unit's parent is the user manager itself (not pid 1), and
|
|
286
|
+
// treating that as "not a service" made the daemon try `systemctl
|
|
287
|
+
// restart` on its own unit — a self-restart loop under Restart=.
|
|
288
|
+
// Interactive shells never have systemd as a direct parent, so the
|
|
289
|
+
// stray-env false-positive defense still holds.
|
|
290
|
+
if (ppid === 1) return true
|
|
291
|
+
return readFileSync(`/proc/${ppid}/comm`, "utf8").trim() === "systemd"
|
|
292
|
+
} catch {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ponytail: factory so tests can inject a mocked isOrphanedToInit without
|
|
298
|
+
// hitting ESM binding issues (the closure above captures the real one).
|
|
299
|
+
export interface ServiceModeDeps {
|
|
300
|
+
readonly isOrphaned: () => boolean
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** launchd job label, matching the plist written by service/install.ts. */
|
|
304
|
+
const SERVICE_LABEL = "sirenodeck"
|
|
305
|
+
|
|
306
|
+
export const createIsServiceMode = (deps: ServiceModeDeps) => (): boolean => {
|
|
307
|
+
if (process.env["SIRENO_DAEMON_CHILD"]) return true
|
|
308
|
+
if (process.env["LAUNCH_JOB_NAME"]) return deps.isOrphaned()
|
|
309
|
+
// Twin of createIsUnderServiceManager in cli/commands/spawn-daemon.ts —
|
|
310
|
+
// these must stay in lockstep. LAUNCH_JOB_NAME is the pre-10.10 variable;
|
|
311
|
+
// bootstrap/kickstart sets XPC_SERVICE_NAME to the job label instead.
|
|
312
|
+
// Matched against our own label because other launchd jobs (Terminal.app)
|
|
313
|
+
// export theirs into the shells they start.
|
|
314
|
+
const xpcName = process.env["XPC_SERVICE_NAME"]
|
|
315
|
+
if (xpcName !== undefined && xpcName.includes(SERVICE_LABEL)) return true
|
|
316
|
+
// INVOCATION_ID is systemd-scoped: user-unit children always have it, and
|
|
317
|
+
// it can only leak into a shell when that shell's ANCESTOR is a user unit
|
|
318
|
+
// (terminal-server etc). isOrphaned() requires our DIRECT parent to be
|
|
319
|
+
// systemd, which a shell never is — so the pair is false-positive-proof.
|
|
320
|
+
// JOURNAL_STREAM proved unreliable: user units don't always export it.
|
|
321
|
+
if (process.env["INVOCATION_ID"] && deps.isOrphaned()) return true
|
|
322
|
+
return false
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const isServiceMode = createIsServiceMode({
|
|
326
|
+
isOrphaned: isOrphanedToInit,
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
export const createLogger = (options: CreateLoggerOptions = {}): Logger => {
|
|
330
|
+
const { level, verbose = false, json = false, component } = options
|
|
331
|
+
|
|
332
|
+
if (verbose) process.env["SIRENO_LOG_VERBOSE"] = "1"
|
|
333
|
+
if (json) process.env["SIRENO_LOG_JSON"] = "1"
|
|
334
|
+
|
|
335
|
+
const wantRaw = json || isServiceMode() || !process.stdout.isTTY
|
|
336
|
+
|
|
337
|
+
const loggerOptions: LoggerOptions = {
|
|
338
|
+
name: "sirenodeck",
|
|
339
|
+
level: level ?? (verbose ? "debug" : "info"),
|
|
340
|
+
serializers: {
|
|
341
|
+
err: errorSerializer,
|
|
342
|
+
},
|
|
343
|
+
redact: {
|
|
344
|
+
paths: ["err.raw"],
|
|
345
|
+
censor: "[hidden]",
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (component === undefined) {
|
|
350
|
+
return makeLogger(loggerOptions, wantRaw)
|
|
351
|
+
}
|
|
352
|
+
return makeLogger(loggerOptions, wantRaw).child({ component })
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const makeLogger = (loggerOptions: LoggerOptions, wantRaw: boolean): Logger => {
|
|
356
|
+
if (wantRaw) {
|
|
357
|
+
return pino(loggerOptions)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const dest = new HumanWritable()
|
|
361
|
+
const teeStream = {
|
|
362
|
+
write(chunk: string): void {
|
|
363
|
+
dest.write(chunk)
|
|
364
|
+
try {
|
|
365
|
+
const parsed = JSON.parse(chunk) as {
|
|
366
|
+
level?: number
|
|
367
|
+
time?: number
|
|
368
|
+
msg?: string
|
|
369
|
+
component?: unknown
|
|
370
|
+
deckId?: unknown
|
|
371
|
+
position?: unknown
|
|
372
|
+
addonName?: unknown
|
|
373
|
+
gesture?: unknown
|
|
374
|
+
keyIndex?: unknown
|
|
375
|
+
reason?: unknown
|
|
376
|
+
}
|
|
377
|
+
if (
|
|
378
|
+
typeof parsed.level === "number" &&
|
|
379
|
+
typeof parsed.time === "number" &&
|
|
380
|
+
typeof parsed.msg === "string"
|
|
381
|
+
) {
|
|
382
|
+
const levelName = levelNameFromNumber(parsed.level)
|
|
383
|
+
if (levelName !== null) {
|
|
384
|
+
const payload: Record<string, unknown> = {
|
|
385
|
+
level: levelName,
|
|
386
|
+
msg: parsed.msg,
|
|
387
|
+
ts: parsed.time,
|
|
388
|
+
}
|
|
389
|
+
for (const key of FORWARDED_CONTEXT_FIELDS) {
|
|
390
|
+
const v = parsed[key]
|
|
391
|
+
if (v === undefined || v === null) continue
|
|
392
|
+
if (typeof v === "string" || typeof v === "number") {
|
|
393
|
+
payload[key] = v
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
;(process.emit as unknown as (e: string, p: unknown) => void)(
|
|
397
|
+
"sireno:log",
|
|
398
|
+
payload,
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} catch {
|
|
403
|
+
// ignore non-JSON log lines
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
return pino(loggerOptions, teeStream as unknown as pino.DestinationStream)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const levelNameFromNumber = (level: number): string | null => {
|
|
411
|
+
if (level >= 60) return "fatal"
|
|
412
|
+
if (level >= 50) return "error"
|
|
413
|
+
if (level >= 40) return "warn"
|
|
414
|
+
if (level >= 30) return "info"
|
|
415
|
+
if (level >= 20) return "debug"
|
|
416
|
+
return "trace"
|
|
417
|
+
}
|