@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,402 @@
|
|
|
1
|
+
// ponytail: see packages/addons/app-shortcuts/src/index.ts for context.
|
|
2
|
+
import type {
|
|
3
|
+
AddonGlobalPoller,
|
|
4
|
+
AddonGlobalService,
|
|
5
|
+
AddonGlobalSubscription,
|
|
6
|
+
AddonServiceContext,
|
|
7
|
+
AddonServiceMethod,
|
|
8
|
+
} from "../types/types.js"
|
|
9
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs"
|
|
10
|
+
import { join } from "node:path"
|
|
11
|
+
import { fileURLToPath } from "node:url"
|
|
12
|
+
import { registerIconForDeck, getAssetByPath } from "@/core/icon-asset-registry"
|
|
13
|
+
import { resolveDaemonPaths } from "@/util/daemon"
|
|
14
|
+
import {
|
|
15
|
+
agentKey,
|
|
16
|
+
CHANNEL,
|
|
17
|
+
EMPTY_SNAPSHOT,
|
|
18
|
+
type Agent,
|
|
19
|
+
type AgentsSnapshot,
|
|
20
|
+
type ProviderId,
|
|
21
|
+
} from "../shared/state.js"
|
|
22
|
+
import { mergeSnapshot, listAgents } from "../shared/snapshot.js"
|
|
23
|
+
import { deckTarget, setLiveCount } from "../shared/live-count.js"
|
|
24
|
+
import { NotificationThrottle } from "../shared/notifier.js"
|
|
25
|
+
import {
|
|
26
|
+
loadProviders,
|
|
27
|
+
type ProviderRegistryConfig,
|
|
28
|
+
} from "../providers/registry.js"
|
|
29
|
+
|
|
30
|
+
export const POLLER_INTERVAL_MS = 2000
|
|
31
|
+
|
|
32
|
+
// ponytail: a wedged provider (hung fetch, wedged serve process) must not
|
|
33
|
+
// freeze the poll loop — race each snapshot and keep the last-known list.
|
|
34
|
+
const PROVIDER_SNAPSHOT_TIMEOUT_MS = 10_000
|
|
35
|
+
|
|
36
|
+
interface GlobalState {
|
|
37
|
+
providers: Map<ProviderId, import("../shared/state.js").AgentProvider>
|
|
38
|
+
spawnedChild: { kill: () => Promise<void> } | null
|
|
39
|
+
unsubscribers: Array<() => void>
|
|
40
|
+
lastSnapshot: AgentsSnapshot
|
|
41
|
+
lastSeenStatus: Map<string, import("../shared/state.js").AgentStatus>
|
|
42
|
+
throttle: NotificationThrottle
|
|
43
|
+
context: AddonServiceContext | null
|
|
44
|
+
icons: Partial<Record<ProviderId, string>>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const state: GlobalState = {
|
|
48
|
+
providers: new Map(),
|
|
49
|
+
spawnedChild: null,
|
|
50
|
+
unsubscribers: [],
|
|
51
|
+
lastSnapshot: EMPTY_SNAPSHOT,
|
|
52
|
+
lastSeenStatus: new Map(),
|
|
53
|
+
throttle: new NotificationThrottle(),
|
|
54
|
+
context: null,
|
|
55
|
+
icons: {},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ponytail: deck re-materialization tracking — when the live agent count
|
|
59
|
+
// changes, ask the host to rebuild the (dynamic) agents deck so page count
|
|
60
|
+
// follows reality. Debounced to avoid rebuild storms during rapid churn.
|
|
61
|
+
let lastLiveCount = -1
|
|
62
|
+
let rebuildTimer: ReturnType<typeof setTimeout> | null = null
|
|
63
|
+
|
|
64
|
+
const REBUILD_DEBOUNCE_MS = 1500
|
|
65
|
+
|
|
66
|
+
const syncLiveCountAndMaybeRebuild = (): void => {
|
|
67
|
+
const count = listAgents(state.lastSnapshot).length
|
|
68
|
+
setLiveCount(count)
|
|
69
|
+
if (count === lastLiveCount) return
|
|
70
|
+
const wasEmpty = lastLiveCount <= 0
|
|
71
|
+
lastLiveCount = count
|
|
72
|
+
if (rebuildTimer !== null) clearTimeout(rebuildTimer)
|
|
73
|
+
|
|
74
|
+
// ponytail: the deck bakes one tile per live agent at materialisation time,
|
|
75
|
+
// so it is only right if a rebuild happens AFTER the first provider scan
|
|
76
|
+
// lands. The debounce below is there to absorb churn, but it also applied to
|
|
77
|
+
// the very first 0 -> N transition — and the deck is first materialised at
|
|
78
|
+
// startup when the count is still 0. Whether you got one tile or seven then
|
|
79
|
+
// depended on how the first scan raced a single debounced rebuild, which is
|
|
80
|
+
// why the deck could show a lone tile while the summary counted seven.
|
|
81
|
+
//
|
|
82
|
+
// Agents appearing from nothing is not churn; rebuild for it at once and
|
|
83
|
+
// keep the debounce for everything after.
|
|
84
|
+
if (wasEmpty && count > 0) {
|
|
85
|
+
rebuildTimer = null
|
|
86
|
+
state.context?.requestDeckRebuild?.()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
rebuildTimer = setTimeout(() => {
|
|
90
|
+
rebuildTimer = null
|
|
91
|
+
state.context?.requestDeckRebuild?.()
|
|
92
|
+
}, REBUILD_DEBOUNCE_MS)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ponytail: register both provider logos as WS assets (absolute paths, so no
|
|
96
|
+
// addonDirs needed) and publish their `asset://` ids in the snapshot. This is
|
|
97
|
+
// the only path that works in BOTH dev (vite middleware) and real (screenshots)
|
|
98
|
+
// mode — a bare `addon://` <img> would 404 in the real HTTP server.
|
|
99
|
+
const registerProviderIcons = (): Partial<Record<ProviderId, string>> => {
|
|
100
|
+
const assets: Array<[ProviderId, string]> = [
|
|
101
|
+
["opencode", "assets/opencode-dark-square.svg"],
|
|
102
|
+
["claude-code", "assets/claude-code.svg"],
|
|
103
|
+
]
|
|
104
|
+
const resolved: Array<[ProviderId, string]> = []
|
|
105
|
+
for (const [id, rel] of assets) {
|
|
106
|
+
try {
|
|
107
|
+
const full = fileURLToPath(new URL(`../${rel}`, import.meta.url))
|
|
108
|
+
registerIconForDeck(
|
|
109
|
+
[{ id: `${id}-logo`, type: "icon", config: { icon: full } }],
|
|
110
|
+
{},
|
|
111
|
+
)
|
|
112
|
+
const asset = getAssetByPath(full)
|
|
113
|
+
if (asset !== undefined) resolved.push([id, `asset://${asset.id}`])
|
|
114
|
+
} catch {
|
|
115
|
+
// icon registration is best-effort; tiles fall back to text labels
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Object.fromEntries(resolved) as Partial<Record<ProviderId, string>>
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- reboot survival -------------------------------------------------------
|
|
122
|
+
// ponytail: persists lastSnapshot + lastSeenStatus under dataDir
|
|
123
|
+
// (XDG_STATE_HOME) so a systemd auto-restart after reboot rehydrates instead
|
|
124
|
+
// of replaying every agent as a fresh notification and showing an empty deck
|
|
125
|
+
// for one poll tick. No staleness cap — mergeSnapshot replaces per-provider
|
|
126
|
+
// lists wholesale on first live fetch, so stale data self-heals within ~2s.
|
|
127
|
+
|
|
128
|
+
const PERSISTED_FILE = "coding-agents-state.json"
|
|
129
|
+
|
|
130
|
+
interface PersistedState {
|
|
131
|
+
snapshot: AgentsSnapshot
|
|
132
|
+
lastSeenStatus: Array<[string, import("../shared/state.js").AgentStatus]>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const persistedStateFile = (): string | null => {
|
|
136
|
+
try {
|
|
137
|
+
return join(resolveDaemonPaths().dataDir, PERSISTED_FILE)
|
|
138
|
+
} catch {
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const persistState = (): void => {
|
|
144
|
+
const file = persistedStateFile()
|
|
145
|
+
if (file === null) return
|
|
146
|
+
try {
|
|
147
|
+
const payload: PersistedState = {
|
|
148
|
+
snapshot: state.lastSnapshot,
|
|
149
|
+
lastSeenStatus: [...state.lastSeenStatus],
|
|
150
|
+
}
|
|
151
|
+
const tmp = `${file}.tmp.${process.pid}`
|
|
152
|
+
writeFileSync(tmp, JSON.stringify(payload), "utf8")
|
|
153
|
+
renameSync(tmp, file)
|
|
154
|
+
} catch {
|
|
155
|
+
// best-effort — disk full, perms, sandbox; never break the poll loop
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const hydratePersisted = (): void => {
|
|
160
|
+
const file = persistedStateFile()
|
|
161
|
+
if (file === null) return
|
|
162
|
+
try {
|
|
163
|
+
if (!existsSync(file)) return
|
|
164
|
+
const raw = JSON.parse(
|
|
165
|
+
readFileSync(file, "utf8"),
|
|
166
|
+
) as Partial<PersistedState>
|
|
167
|
+
if (
|
|
168
|
+
raw.snapshot &&
|
|
169
|
+
typeof raw.snapshot === "object" &&
|
|
170
|
+
typeof raw.snapshot.byProvider === "object" &&
|
|
171
|
+
raw.snapshot.byProvider !== null
|
|
172
|
+
) {
|
|
173
|
+
// recompute attention + generatedAt from real statuses instead of
|
|
174
|
+
// trusting the serialized copy
|
|
175
|
+
state.lastSnapshot = mergeSnapshot(EMPTY_SNAPSHOT, {
|
|
176
|
+
opencode: raw.snapshot.byProvider["opencode"] ?? [],
|
|
177
|
+
"claude-code": raw.snapshot.byProvider["claude-code"] ?? [],
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
if (Array.isArray(raw.lastSeenStatus)) {
|
|
181
|
+
state.lastSeenStatus = new Map(raw.lastSeenStatus)
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// corrupt or unreadable → cold start; first publish overwrites the file
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const setStateFromProviders = async (): Promise<void> => {
|
|
189
|
+
const next: Record<ProviderId, Agent[]> = {
|
|
190
|
+
opencode: [],
|
|
191
|
+
"claude-code": [],
|
|
192
|
+
}
|
|
193
|
+
for (const [id, provider] of state.providers) {
|
|
194
|
+
const lastKnown = state.lastSnapshot.byProvider[id] ?? []
|
|
195
|
+
try {
|
|
196
|
+
const agents = await Promise.race([
|
|
197
|
+
provider.fetchSnapshot(
|
|
198
|
+
state.context?.signal ?? new AbortController().signal,
|
|
199
|
+
),
|
|
200
|
+
new Promise<readonly Agent[]>((resolve) =>
|
|
201
|
+
setTimeout(() => resolve(lastKnown), PROVIDER_SNAPSHOT_TIMEOUT_MS),
|
|
202
|
+
),
|
|
203
|
+
])
|
|
204
|
+
next[id] = [...agents]
|
|
205
|
+
} catch {
|
|
206
|
+
// provider transiently unavailable — keep last-known (don't wipe)
|
|
207
|
+
next[id] = [...lastKnown]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
state.lastSnapshot = mergeSnapshot(state.lastSnapshot, next)
|
|
211
|
+
persistState()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const fireNotices = (snapshot: AgentsSnapshot): void => {
|
|
215
|
+
for (const list of Object.values(snapshot.byProvider)) {
|
|
216
|
+
for (const a of list) {
|
|
217
|
+
const k = agentKey(a)
|
|
218
|
+
const prev = state.lastSeenStatus.get(k)
|
|
219
|
+
if (prev === a.status) continue
|
|
220
|
+
state.lastSeenStatus.set(k, a.status)
|
|
221
|
+
state.throttle.evaluate({
|
|
222
|
+
providerId: a.providerId,
|
|
223
|
+
sessionId: a.sessionId,
|
|
224
|
+
status: a.status,
|
|
225
|
+
title: `${a.providerId === "opencode" ? "OpenCode" : "Claude Code"}: ${humanStatus(a.status)}`,
|
|
226
|
+
body: a.title,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const humanStatus = (s: import("../shared/state.js").AgentStatus): string => {
|
|
233
|
+
switch (s) {
|
|
234
|
+
case "idle":
|
|
235
|
+
return "Idle"
|
|
236
|
+
case "running":
|
|
237
|
+
return "Running"
|
|
238
|
+
case "waiting":
|
|
239
|
+
return "Waiting"
|
|
240
|
+
case "waiting_for_human":
|
|
241
|
+
return "Needs approval"
|
|
242
|
+
case "error":
|
|
243
|
+
return "Error"
|
|
244
|
+
case "compacting":
|
|
245
|
+
return "Compacting"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const publishSnapshot = (): AgentsSnapshot => {
|
|
250
|
+
syncLiveCountAndMaybeRebuild()
|
|
251
|
+
const payload: AgentsSnapshot = {
|
|
252
|
+
...state.lastSnapshot,
|
|
253
|
+
icons: state.icons,
|
|
254
|
+
}
|
|
255
|
+
state.context?.publish(payload)
|
|
256
|
+
return payload
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const poller: AddonGlobalPoller = {
|
|
260
|
+
id: "agents",
|
|
261
|
+
channel: CHANNEL,
|
|
262
|
+
intervalMs: POLLER_INTERVAL_MS,
|
|
263
|
+
poll: async (ctx: AddonServiceContext) => {
|
|
264
|
+
state.context = ctx
|
|
265
|
+
await setStateFromProviders()
|
|
266
|
+
fireNotices(state.lastSnapshot)
|
|
267
|
+
return publishSnapshot()
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const subscription: AddonGlobalSubscription = {
|
|
272
|
+
channel: CHANNEL,
|
|
273
|
+
subscribe: (ctx: AddonServiceContext) => {
|
|
274
|
+
state.context = ctx
|
|
275
|
+
const unsubs: Array<() => void> = []
|
|
276
|
+
for (const [id, provider] of state.providers) {
|
|
277
|
+
const unsub = provider.subscribe(ctx.signal, () => {
|
|
278
|
+
void (async () => {
|
|
279
|
+
await setStateFromProviders()
|
|
280
|
+
fireNotices(state.lastSnapshot)
|
|
281
|
+
publishSnapshot()
|
|
282
|
+
})()
|
|
283
|
+
})
|
|
284
|
+
unsubs.push(unsub)
|
|
285
|
+
// record for unbind on addon unload (provider id captured for log only)
|
|
286
|
+
void id
|
|
287
|
+
}
|
|
288
|
+
state.unsubscribers = unsubs
|
|
289
|
+
return {
|
|
290
|
+
unsubscribe: () => {
|
|
291
|
+
for (const u of unsubs) {
|
|
292
|
+
try {
|
|
293
|
+
u()
|
|
294
|
+
} catch {
|
|
295
|
+
// ignore — provider may have already closed
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
state.unsubscribers = []
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const globalService: AddonGlobalService = {
|
|
305
|
+
pollers: [poller],
|
|
306
|
+
subscriptions: [subscription],
|
|
307
|
+
methods: {
|
|
308
|
+
getSnapshot: (() => state.lastSnapshot) as AddonServiceMethod,
|
|
309
|
+
getDeckTarget: (() => deckTarget()) as AddonServiceMethod,
|
|
310
|
+
dismissAttention: ((key: unknown) => {
|
|
311
|
+
if (typeof key !== "string") return
|
|
312
|
+
const [providerId, sessionId] = key.split(":")
|
|
313
|
+
if (!providerId || !sessionId) return
|
|
314
|
+
state.throttle.forget(providerId, sessionId)
|
|
315
|
+
state.lastSeenStatus.delete(key)
|
|
316
|
+
}) as AddonServiceMethod,
|
|
317
|
+
},
|
|
318
|
+
onLoad: async (ctx: AddonServiceContext, config?: unknown): Promise<void> => {
|
|
319
|
+
state.context = ctx
|
|
320
|
+
state.icons = registerProviderIcons()
|
|
321
|
+
hydratePersisted()
|
|
322
|
+
// ponytail: SIRENO_CAPTURE_FAKE_AGENTS=1 — capture-only stub. Bypasses
|
|
323
|
+
// provider polling and injects a deterministic agent list so web captures
|
|
324
|
+
// show realistic "Agents" tiles without a live opencode / claude-code.
|
|
325
|
+
if (process.env["SIRENO_CAPTURE_FAKE_AGENTS"] === "1") {
|
|
326
|
+
const now = Date.now()
|
|
327
|
+
const fake: Agent[] = [
|
|
328
|
+
{
|
|
329
|
+
sessionId: "sireno-capture-opencode-1",
|
|
330
|
+
providerId: "opencode" as ProviderId,
|
|
331
|
+
title: "Refactor capture pipeline",
|
|
332
|
+
status: "running" as const,
|
|
333
|
+
directory: "/works/sirenodeck",
|
|
334
|
+
updatedAt: now,
|
|
335
|
+
createdAt: now - 1000 * 60 * 12,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
sessionId: "sireno-capture-opencode-2",
|
|
339
|
+
providerId: "opencode" as ProviderId,
|
|
340
|
+
title: "Fix flaky session tests",
|
|
341
|
+
status: "idle" as const,
|
|
342
|
+
directory: "/works/sirenodeck/packages/cli",
|
|
343
|
+
updatedAt: now - 1000 * 60 * 4,
|
|
344
|
+
createdAt: now - 1000 * 60 * 40,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
sessionId: "sireno-capture-claude-1",
|
|
348
|
+
providerId: "claude-code" as ProviderId,
|
|
349
|
+
title: "Migrate themes",
|
|
350
|
+
status: "waiting_for_human" as const,
|
|
351
|
+
directory: "/works/sirenodeck/packages/web",
|
|
352
|
+
updatedAt: now - 1000 * 60 * 2,
|
|
353
|
+
createdAt: now - 1000 * 60 * 90,
|
|
354
|
+
},
|
|
355
|
+
]
|
|
356
|
+
state.lastSnapshot = mergeSnapshot(state.lastSnapshot, {
|
|
357
|
+
opencode: fake.filter((a) => a.providerId === "opencode"),
|
|
358
|
+
"claude-code": fake.filter((a) => a.providerId === "claude-code"),
|
|
359
|
+
})
|
|
360
|
+
publishSnapshot()
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
state.lastSnapshot = mergeSnapshot(state.lastSnapshot, {
|
|
364
|
+
opencode: [],
|
|
365
|
+
"claude-code": [],
|
|
366
|
+
})
|
|
367
|
+
const cfg: ProviderRegistryConfig = (config as
|
|
368
|
+
| ProviderRegistryConfig
|
|
369
|
+
| undefined) ?? {
|
|
370
|
+
opencodeUrl: "http://127.0.0.1:4096",
|
|
371
|
+
spawnOpencodeIfMissing: true,
|
|
372
|
+
}
|
|
373
|
+
const loaded = await loadProviders(cfg, ctx.signal)
|
|
374
|
+
state.providers = loaded.providers as Map<ProviderId, never>
|
|
375
|
+
state.spawnedChild = loaded.spawnedChild
|
|
376
|
+
await setStateFromProviders()
|
|
377
|
+
publishSnapshot()
|
|
378
|
+
},
|
|
379
|
+
onUnload: (): void => {
|
|
380
|
+
for (const u of state.unsubscribers) {
|
|
381
|
+
try {
|
|
382
|
+
u()
|
|
383
|
+
} catch {
|
|
384
|
+
// ignore
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
state.unsubscribers = []
|
|
388
|
+
if (state.spawnedChild) {
|
|
389
|
+
void state.spawnedChild.kill().catch(() => {})
|
|
390
|
+
state.spawnedChild = null
|
|
391
|
+
}
|
|
392
|
+
state.providers.clear()
|
|
393
|
+
state.lastSnapshot = EMPTY_SNAPSHOT
|
|
394
|
+
state.lastSeenStatus.clear()
|
|
395
|
+
state.throttle.reset()
|
|
396
|
+
state.icons = {}
|
|
397
|
+
state.context = null
|
|
398
|
+
if (rebuildTimer !== null) clearTimeout(rebuildTimer)
|
|
399
|
+
rebuildTimer = null
|
|
400
|
+
lastLiveCount = -1
|
|
401
|
+
},
|
|
402
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Node-side entrypoint for the addon's global service. The bridge imports
|
|
2
|
+
// this file (via the scanner's globalServiceEntry), so node-only imports
|
|
3
|
+
// (fs, daemon paths) stay OUT of the browser graph — index.ts is imported
|
|
4
|
+
// by the frontend's virtual addons/registry module, and any node builtin
|
|
5
|
+
// reachable from there crashes the whole app ("externalized for browser
|
|
6
|
+
// compatibility").
|
|
7
|
+
export { globalService } from "./global/backend.js"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { CLAUDE_HOOK_REGISTRATIONS } from "../shared/claude-hook-events.js"
|
|
2
|
+
|
|
3
|
+
export const CLAUDE_HOOK_MARKER =
|
|
4
|
+
"SIRENODECK_INTEGRATION_ID=coding-agents-claude"
|
|
5
|
+
export const CLAUDE_HOOK_VERSION = 1
|
|
6
|
+
export const CLAUDE_HOOK_FILENAME = "sirenodeck-claude-hook.mjs"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The hook script Claude Code runs. Node rather than sh: it needs real JSON
|
|
10
|
+
* parsing and an atomic write, and `node -e ''` is cheaper here than the
|
|
11
|
+
* python3 an existing third-party hook on this machine already pays.
|
|
12
|
+
*
|
|
13
|
+
* Invariants, in rough order of how badly they break the user if violated:
|
|
14
|
+
* - it always exits 0. A non-zero hook exit surfaces as an error inside the
|
|
15
|
+
* user's Claude Code session.
|
|
16
|
+
* - it never writes to stdout. Several events treat stdout as a decision
|
|
17
|
+
* payload or as context to feed the model.
|
|
18
|
+
* - it does nothing at all for subagents, so harness activity is aggregated
|
|
19
|
+
* into the parent session rather than surfacing as its own agent.
|
|
20
|
+
*/
|
|
21
|
+
export const claudeHookSource = `#!/usr/bin/env node
|
|
22
|
+
// ${CLAUDE_HOOK_MARKER}-v${CLAUDE_HOOK_VERSION}
|
|
23
|
+
// Managed by Sireno Deck. Reinstalling or updating the integration overwrites
|
|
24
|
+
// this file — add custom hooks beside it rather than editing it.
|
|
25
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs"
|
|
26
|
+
import { execFileSync } from "node:child_process"
|
|
27
|
+
import { homedir } from "node:os"
|
|
28
|
+
import { join } from "node:path"
|
|
29
|
+
|
|
30
|
+
const STATUS_BY_EVENT = ${JSON.stringify(
|
|
31
|
+
Object.fromEntries(CLAUDE_HOOK_REGISTRATIONS.map((r) => [r.event, r.status])),
|
|
32
|
+
null,
|
|
33
|
+
2,
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
const dir = join(
|
|
37
|
+
process.env.XDG_STATE_HOME || join(homedir(), ".local", "state"),
|
|
38
|
+
"sirenodeck",
|
|
39
|
+
"coding-agents",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const readStdin = async () => {
|
|
43
|
+
const chunks = []
|
|
44
|
+
for await (const chunk of process.stdin) chunks.push(chunk)
|
|
45
|
+
return Buffer.concat(chunks).toString("utf8")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Claude Code runs hooks through a shell, so process.ppid is the shell, not
|
|
49
|
+
// claude. Walk up a few hops looking for it. Failure is not fatal: the reader
|
|
50
|
+
// falls back to a time-based ceiling when pid is null.
|
|
51
|
+
const findClaudePid = () => {
|
|
52
|
+
let pid = process.ppid
|
|
53
|
+
for (let hop = 0; hop < 5 && pid > 1; hop += 1) {
|
|
54
|
+
try {
|
|
55
|
+
const out = execFileSync("ps", ["-o", "ppid=,comm=", "-p", String(pid)], {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
timeout: 1000,
|
|
58
|
+
}).trim()
|
|
59
|
+
const match = out.match(/^(\\d+)\\s+(.*)$/)
|
|
60
|
+
if (match === null) return null
|
|
61
|
+
if (/(^|\\/)claude$/.test(match[2].trim())) return pid
|
|
62
|
+
pid = Number(match[1])
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const main = async () => {
|
|
71
|
+
const event = process.argv[2] || ""
|
|
72
|
+
const status = STATUS_BY_EVENT[event]
|
|
73
|
+
if (status === undefined) return
|
|
74
|
+
|
|
75
|
+
let payload = {}
|
|
76
|
+
try {
|
|
77
|
+
const raw = await readStdin()
|
|
78
|
+
if (raw.trim()) payload = JSON.parse(raw)
|
|
79
|
+
} catch {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Subagents are aggregated into their parent session, never reported alone.
|
|
84
|
+
if (payload.agent_id) return
|
|
85
|
+
|
|
86
|
+
const sessionId = payload.session_id
|
|
87
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return
|
|
88
|
+
const target = join(dir, "claude-" + sessionId + ".json")
|
|
89
|
+
|
|
90
|
+
if (status === null) {
|
|
91
|
+
try {
|
|
92
|
+
unlinkSync(target)
|
|
93
|
+
} catch {
|
|
94
|
+
// Already gone.
|
|
95
|
+
}
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lease = {
|
|
100
|
+
v: 1,
|
|
101
|
+
harness: "claude-code",
|
|
102
|
+
sessionId,
|
|
103
|
+
pid: findClaudePid(),
|
|
104
|
+
cwd: typeof payload.cwd === "string" ? payload.cwd : process.cwd(),
|
|
105
|
+
state: status,
|
|
106
|
+
event,
|
|
107
|
+
updatedAt: Date.now(),
|
|
108
|
+
}
|
|
109
|
+
if (typeof payload.transcript_path === "string") {
|
|
110
|
+
lease.transcriptPath = payload.transcript_path
|
|
111
|
+
}
|
|
112
|
+
if (typeof payload.permission_mode === "string") {
|
|
113
|
+
lease.permissionMode = payload.permission_mode
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
117
|
+
const tmp = target + ".tmp"
|
|
118
|
+
writeFileSync(tmp, JSON.stringify(lease), { mode: 0o600 })
|
|
119
|
+
renameSync(tmp, target)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch(() => {}).finally(() => process.exit(0))
|
|
123
|
+
`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AddonRegistry } from "@/addon/registry"
|
|
2
|
+
|
|
3
|
+
import { manifest } from "./manifest.js"
|
|
4
|
+
|
|
5
|
+
// ponytail: the global service lives in global-entry.ts, NOT here — this
|
|
6
|
+
// module is imported by the frontend's virtual addons/registry, and node
|
|
7
|
+
// builtins reachable from index.ts crash the browser bundle. The bridge
|
|
8
|
+
// finds the node-side entry via the scanner's globalServiceEntry.
|
|
9
|
+
|
|
10
|
+
export const registerCodingAgentsAddon = (registry: AddonRegistry): void => {
|
|
11
|
+
registry.load(manifest)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { manifest } from "./manifest.js"
|
|
15
|
+
export default manifest
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// ponytail: see packages/addons/app-shortcuts/src/index.ts for context.
|
|
2
|
+
// This module is imported by the frontend's virtual addons/registry, so the
|
|
3
|
+
// import graph reachable from here must stay browser-safe: no static imports
|
|
4
|
+
// of node builtins or node-only modules. Node-side pieces (globalService,
|
|
5
|
+
// provider registry) live behind global-entry.ts / dynamic imports.
|
|
6
|
+
import agentBackend from "./buttons/agent/backend.js"
|
|
7
|
+
import agentFrontend from "./buttons/agent/frontend.js"
|
|
8
|
+
import summaryBackend from "./buttons/summary/backend.js"
|
|
9
|
+
import summaryFrontend from "./buttons/summary/frontend.js"
|
|
10
|
+
import { createAgentsDecks } from "./decks/agents.js"
|
|
11
|
+
import type { AddonManifestV1 } from "./types/types.js"
|
|
12
|
+
|
|
13
|
+
const probeOpencodeReachable = async (): Promise<{
|
|
14
|
+
available: boolean
|
|
15
|
+
reason?: string
|
|
16
|
+
}> => {
|
|
17
|
+
for (const url of ["http://127.0.0.1:4096", "http://127.0.0.1:4095"]) {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`${url}/global/health`, {
|
|
20
|
+
signal: AbortSignal.timeout(2000),
|
|
21
|
+
})
|
|
22
|
+
if (!res.ok) continue
|
|
23
|
+
const body = (await res.json()) as { healthy?: boolean }
|
|
24
|
+
if (body.healthy === true) return { available: true }
|
|
25
|
+
} catch {
|
|
26
|
+
// Try the other supported local server port.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { available: false, reason: "unreachable" }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const probeClaudeProjectsDir = async (): Promise<{
|
|
33
|
+
available: boolean
|
|
34
|
+
reason?: string
|
|
35
|
+
}> => {
|
|
36
|
+
const { readdir } = await import("node:fs/promises")
|
|
37
|
+
const path = `${process.env["HOME"] ?? "~"}/.claude/projects`
|
|
38
|
+
try {
|
|
39
|
+
await readdir(path)
|
|
40
|
+
return { available: true }
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
available: false,
|
|
44
|
+
reason: err instanceof Error ? err.message : `${path} unreadable`,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const manifest = {
|
|
50
|
+
apiVersion: 1,
|
|
51
|
+
name: "coding-agents",
|
|
52
|
+
buttonTypes: {
|
|
53
|
+
"coding-agents:summary": {
|
|
54
|
+
frontend:
|
|
55
|
+
summaryFrontend as unknown as AddonManifestV1["buttonTypes"][string]["frontend"],
|
|
56
|
+
service:
|
|
57
|
+
summaryBackend as unknown as AddonManifestV1["buttonTypes"][string]["service"],
|
|
58
|
+
name: "summary",
|
|
59
|
+
},
|
|
60
|
+
"coding-agents:agent": {
|
|
61
|
+
frontend:
|
|
62
|
+
agentFrontend as unknown as AddonManifestV1["buttonTypes"][string]["frontend"],
|
|
63
|
+
service:
|
|
64
|
+
agentBackend as unknown as AddonManifestV1["buttonTypes"][string]["service"],
|
|
65
|
+
name: "agent",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
decks: [
|
|
69
|
+
{
|
|
70
|
+
createDecks:
|
|
71
|
+
createAgentsDecks as unknown as AddonManifestV1["decks"] extends ReadonlyArray<
|
|
72
|
+
infer D
|
|
73
|
+
>
|
|
74
|
+
? D extends { createDecks?: infer F }
|
|
75
|
+
? F
|
|
76
|
+
: never
|
|
77
|
+
: never,
|
|
78
|
+
},
|
|
79
|
+
] as unknown as AddonManifestV1["decks"],
|
|
80
|
+
checks: [
|
|
81
|
+
{ name: "opencode-reachable", check: probeOpencodeReachable },
|
|
82
|
+
{
|
|
83
|
+
name: "opencode-installed",
|
|
84
|
+
// ponytail: dynamic import keeps providers/registry (node-only) out of
|
|
85
|
+
// the browser graph — the check body only ever runs on the daemon side.
|
|
86
|
+
check: async () => {
|
|
87
|
+
const { opencodeInstalled } = await import("./providers/registry.js")
|
|
88
|
+
const installed = await opencodeInstalled()
|
|
89
|
+
return installed
|
|
90
|
+
? { available: true }
|
|
91
|
+
: { available: false, reason: "opencode CLI not found on $PATH" }
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{ name: "claude-code-projects-readable", check: probeClaudeProjectsDir },
|
|
95
|
+
{
|
|
96
|
+
// ponytail: distinguishes "reading transcripts and guessing" from
|
|
97
|
+
// "being told". Without the hooks the provider still works, but status
|
|
98
|
+
// is derived from file tails and lags a turn behind.
|
|
99
|
+
name: "claude-code-hooks",
|
|
100
|
+
check: async () => {
|
|
101
|
+
const { isClaudeHookInstalled, isClaudeHookEnabled } =
|
|
102
|
+
await import("@/cli/coding-agents-onboarding")
|
|
103
|
+
return isClaudeHookInstalled() && isClaudeHookEnabled()
|
|
104
|
+
? { available: true }
|
|
105
|
+
: {
|
|
106
|
+
available: false,
|
|
107
|
+
reason:
|
|
108
|
+
"Claude Code session hooks not installed — status falls back to reading transcripts. Run `sirenodeck system-requirements` to add them.",
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
} satisfies AddonManifestV1
|
|
114
|
+
|
|
115
|
+
export default manifest
|