@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,120 @@
|
|
|
1
|
+
// ponytail: see packages/addons/app-shortcuts/src/index.ts for context.
|
|
2
|
+
import type { AgentProvider } from "../shared/state.js"
|
|
3
|
+
import { ClaudeCodeProvider } from "./claude-code.js"
|
|
4
|
+
import { OpenCodeProvider } from "./opencode.js"
|
|
5
|
+
|
|
6
|
+
export interface ProviderRegistryConfig {
|
|
7
|
+
readonly opencodeUrl: string
|
|
8
|
+
readonly claudeCodeProjectsDir?: string
|
|
9
|
+
readonly spawnOpencodeIfMissing: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LoadProvidersResult {
|
|
13
|
+
readonly providers: ReadonlyMap<string, AgentProvider>
|
|
14
|
+
readonly spawnedChild: { kill: () => Promise<void> } | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoadProvidersDeps {
|
|
18
|
+
readonly ensureOpencodeServer?: typeof import("./spawn").ensureOpencodeServer
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DEFAULT_OPENCODE_URL = "http://127.0.0.1:4096"
|
|
22
|
+
const LEGACY_OPENCODE_URL = "http://127.0.0.1:4095"
|
|
23
|
+
|
|
24
|
+
export const loadProviders = async (
|
|
25
|
+
config: ProviderRegistryConfig,
|
|
26
|
+
signal: AbortSignal,
|
|
27
|
+
deps: LoadProvidersDeps = {},
|
|
28
|
+
): Promise<LoadProvidersResult> => {
|
|
29
|
+
const providers = new Map<string, AgentProvider>()
|
|
30
|
+
let spawnedChild: { kill: () => Promise<void> } | null = null
|
|
31
|
+
const configuredUrl = config.opencodeUrl ?? DEFAULT_OPENCODE_URL
|
|
32
|
+
const reachableUrl = await resolveOpencodeUrl(configuredUrl, signal)
|
|
33
|
+
const opencodeUrl = reachableUrl ?? configuredUrl
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
providers.set("opencode", new OpenCodeProvider({ baseUrl: opencodeUrl }))
|
|
37
|
+
} catch {
|
|
38
|
+
// ponytail: client construction is synchronous and should not throw;
|
|
39
|
+
// if SDK import is broken at runtime, we just skip opencode.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!providers.has("opencode") || reachableUrl === null) {
|
|
43
|
+
if (!(await opencodeInstalled())) {
|
|
44
|
+
// CLI not on $PATH — skip spawn entirely; requirement check
|
|
45
|
+
// surfaces "opencode not installed" distinctly.
|
|
46
|
+
providers.delete("opencode")
|
|
47
|
+
} else if (config.spawnOpencodeIfMissing && !signal.aborted) {
|
|
48
|
+
try {
|
|
49
|
+
const ensure =
|
|
50
|
+
deps.ensureOpencodeServer ??
|
|
51
|
+
(await import("./spawn")).ensureOpencodeServer
|
|
52
|
+
const spawned = await ensure(opencodeUrl, signal)
|
|
53
|
+
if (spawned) {
|
|
54
|
+
spawnedChild = spawned.child
|
|
55
|
+
providers.set(
|
|
56
|
+
"opencode",
|
|
57
|
+
new OpenCodeProvider({ baseUrl: spawned.baseUrl }),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// spawn failed; opencode will remain absent from the registry
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
providers.delete("opencode")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
providers.set(
|
|
70
|
+
"claude-code",
|
|
71
|
+
new ClaudeCodeProvider({
|
|
72
|
+
...(config.claudeCodeProjectsDir !== undefined
|
|
73
|
+
? { projectsDir: config.claudeCodeProjectsDir }
|
|
74
|
+
: {}),
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
} catch {
|
|
78
|
+
// claude-code has no native deps; skip if chokidar somehow fails
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { providers, spawnedChild }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const resolveOpencodeUrl = async (
|
|
85
|
+
preferredUrl: string,
|
|
86
|
+
signal: AbortSignal,
|
|
87
|
+
): Promise<string | null> => {
|
|
88
|
+
const candidates =
|
|
89
|
+
preferredUrl === DEFAULT_OPENCODE_URL
|
|
90
|
+
? [preferredUrl, LEGACY_OPENCODE_URL]
|
|
91
|
+
: [preferredUrl]
|
|
92
|
+
for (const url of candidates) {
|
|
93
|
+
if (await probeOpencodeHealth(url, signal)) return url
|
|
94
|
+
}
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const probeOpencodeHealth = async (
|
|
99
|
+
baseUrl: string,
|
|
100
|
+
signal: AbortSignal,
|
|
101
|
+
): Promise<boolean> => {
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(`${baseUrl}/global/health`, { signal })
|
|
104
|
+
if (!res.ok) return false
|
|
105
|
+
const body = (await res.json()) as { healthy?: boolean }
|
|
106
|
+
return body.healthy === true
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ponytail: the opencode CLI must be on $PATH for `opencode serve` to be
|
|
113
|
+
// spawnable. Surfaced via manifest checks so the banner distinguishes
|
|
114
|
+
// "CLI not installed" from "installed but no server running".
|
|
115
|
+
export const opencodeInstalled = async (): Promise<boolean> => {
|
|
116
|
+
const { execFile } = await import("node:child_process")
|
|
117
|
+
return new Promise<boolean>((resolve) => {
|
|
118
|
+
execFile("which", ["opencode"], (err) => resolve(err === null))
|
|
119
|
+
})
|
|
120
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execa, type ExecaError } from "execa"
|
|
2
|
+
|
|
3
|
+
export class NotReachableError extends Error {
|
|
4
|
+
override readonly name = "NotReachableError"
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SpawnedServer {
|
|
11
|
+
readonly baseUrl: string
|
|
12
|
+
readonly child: { kill: () => Promise<void> }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ExecaChild {
|
|
16
|
+
kill: () => Promise<unknown>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SpawnDeps {
|
|
20
|
+
readonly fetchImpl?: typeof fetch
|
|
21
|
+
readonly execaImpl?: (cmd: string, args: ReadonlyArray<string>) => ExecaChild
|
|
22
|
+
readonly sleep?: (ms: number) => Promise<void>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const defaultSleep = (ms: number): Promise<void> =>
|
|
26
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
27
|
+
|
|
28
|
+
export const ensureOpencodeServer = async (
|
|
29
|
+
baseUrl: string,
|
|
30
|
+
signal: AbortSignal,
|
|
31
|
+
deps: SpawnDeps = {},
|
|
32
|
+
): Promise<SpawnedServer | null> => {
|
|
33
|
+
const fetchImpl = deps.fetchImpl ?? fetch
|
|
34
|
+
const sleep = deps.sleep ?? defaultSleep
|
|
35
|
+
|
|
36
|
+
if (await isReachable(baseUrl, fetchImpl, signal)) return null
|
|
37
|
+
|
|
38
|
+
if (signal.aborted) throw new AbortError()
|
|
39
|
+
|
|
40
|
+
const port = parsePort(baseUrl)
|
|
41
|
+
const child: ExecaChild = deps.execaImpl
|
|
42
|
+
? deps.execaImpl("opencode", ["serve", "--port", String(port)])
|
|
43
|
+
: (execa("opencode", [
|
|
44
|
+
"serve",
|
|
45
|
+
"--port",
|
|
46
|
+
String(port),
|
|
47
|
+
]) as unknown as ExecaChild)
|
|
48
|
+
|
|
49
|
+
// ponytail: execa's returned promise rejects when the child exits
|
|
50
|
+
// non-zero (e.g. a broken opencode-ai postinstall exits 1 instantly).
|
|
51
|
+
// We never await `child` directly — reachability is polled via
|
|
52
|
+
// /global/health — so the floating rejection would surface as
|
|
53
|
+
// unhandledRejection and kill the daemon. Attach a sink; failures are
|
|
54
|
+
// reported by the probe loop below and the addon's checks entry.
|
|
55
|
+
void Promise.resolve(child).catch(() => undefined)
|
|
56
|
+
|
|
57
|
+
const deadline = Date.now() + 5000
|
|
58
|
+
while (Date.now() < deadline) {
|
|
59
|
+
if (signal.aborted) {
|
|
60
|
+
await child.kill().catch(() => undefined)
|
|
61
|
+
throw new AbortError()
|
|
62
|
+
}
|
|
63
|
+
if (await isReachable(baseUrl, fetchImpl, signal)) {
|
|
64
|
+
return {
|
|
65
|
+
baseUrl,
|
|
66
|
+
child: {
|
|
67
|
+
kill: async () => {
|
|
68
|
+
await child.kill().catch(() => undefined)
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
await sleep(200)
|
|
74
|
+
}
|
|
75
|
+
await child.kill().catch(() => undefined)
|
|
76
|
+
throw new NotReachableError(
|
|
77
|
+
`opencode serve did not become reachable at ${baseUrl} within 5 s`,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isReachable = async (
|
|
82
|
+
baseUrl: string,
|
|
83
|
+
fetchImpl: typeof fetch,
|
|
84
|
+
signal: AbortSignal,
|
|
85
|
+
): Promise<boolean> => {
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetchImpl(`${baseUrl}/global/health`, { signal })
|
|
88
|
+
if (!res.ok) return false
|
|
89
|
+
const body = (await res.json()) as { healthy?: boolean }
|
|
90
|
+
return body.healthy === true
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if ((err as ExecaError | undefined)?.shortMessage) return false
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parsePort = (baseUrl: string): number => {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = new URL(baseUrl)
|
|
100
|
+
const port = Number.parseInt(parsed.port, 10)
|
|
101
|
+
return Number.isFinite(port) && port > 0 ? port : 4096
|
|
102
|
+
} catch {
|
|
103
|
+
return 4096
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class AbortError extends Error {
|
|
108
|
+
override readonly name = "AbortError"
|
|
109
|
+
constructor() {
|
|
110
|
+
super("aborted")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AgentStatus } from "./state.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code hook events this integration registers, and the status each
|
|
5
|
+
* implies. Every event below was verified present in the installed binary.
|
|
6
|
+
*
|
|
7
|
+
* Two deliberate omissions:
|
|
8
|
+
*
|
|
9
|
+
* - `PostToolUse` is not registered. `PreToolUse` already proves the session
|
|
10
|
+
* is running and `Stop` returns it to idle, so registering both would
|
|
11
|
+
* roughly double the hook invocations for no extra signal.
|
|
12
|
+
* - `Notification/idle_prompt` is not registered. It fires about a minute
|
|
13
|
+
* after `Stop` on a session that is already idle; mapping it to
|
|
14
|
+
* `waiting_for_human` would turn every idle session into an attention state
|
|
15
|
+
* and make the summary tile meaningless.
|
|
16
|
+
*
|
|
17
|
+
* `SubagentStart`/`SubagentStop` are likewise absent: subagents are aggregated
|
|
18
|
+
* into their parent session, never surfaced separately.
|
|
19
|
+
*/
|
|
20
|
+
export interface ClaudeHookRegistration {
|
|
21
|
+
readonly event: string
|
|
22
|
+
/** Claude Code matcher, when the event supports one. */
|
|
23
|
+
readonly matcher?: string
|
|
24
|
+
/** `null` means "remove the lease" rather than set a status. */
|
|
25
|
+
readonly status: AgentStatus | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const CLAUDE_HOOK_REGISTRATIONS: ReadonlyArray<ClaudeHookRegistration> =
|
|
29
|
+
[
|
|
30
|
+
{
|
|
31
|
+
event: "SessionStart",
|
|
32
|
+
matcher: "startup|resume|clear|fork",
|
|
33
|
+
status: "idle",
|
|
34
|
+
},
|
|
35
|
+
{ event: "UserPromptSubmit", status: "running" },
|
|
36
|
+
{ event: "PreToolUse", matcher: "*", status: "running" },
|
|
37
|
+
{ event: "PermissionRequest", status: "waiting_for_human" },
|
|
38
|
+
{
|
|
39
|
+
event: "Notification",
|
|
40
|
+
matcher: "permission_prompt",
|
|
41
|
+
status: "waiting_for_human",
|
|
42
|
+
},
|
|
43
|
+
{ event: "PreCompact", status: "compacting" },
|
|
44
|
+
{ event: "PostCompact", status: "running" },
|
|
45
|
+
{ event: "Stop", status: "idle" },
|
|
46
|
+
{ event: "StopFailure", status: "error" },
|
|
47
|
+
{ event: "SessionEnd", status: null },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
export const statusForHookEvent = (
|
|
51
|
+
event: string,
|
|
52
|
+
): AgentStatus | null | undefined => {
|
|
53
|
+
const hit = CLAUDE_HOOK_REGISTRATIONS.find((r) => r.event === event)
|
|
54
|
+
return hit === undefined ? undefined : hit.status
|
|
55
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { AgentStatus } from "./state.js"
|
|
2
|
+
|
|
3
|
+
export interface ClaudeJsonlEntry {
|
|
4
|
+
readonly type: string
|
|
5
|
+
readonly subtype?: string
|
|
6
|
+
readonly message?: {
|
|
7
|
+
readonly role?: string
|
|
8
|
+
readonly content?: unknown
|
|
9
|
+
}
|
|
10
|
+
readonly toolUse?: unknown
|
|
11
|
+
readonly toolResult?: unknown
|
|
12
|
+
readonly permissionRequest?: unknown
|
|
13
|
+
readonly error?: { readonly message?: string }
|
|
14
|
+
readonly rateLimit?: unknown
|
|
15
|
+
readonly costUSD?: number
|
|
16
|
+
readonly cwd?: string
|
|
17
|
+
readonly timestamp?: string | number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ponytail: real claude jsonl has none of the v1 phantom shapes. The
|
|
21
|
+
// signals that exist (verified against ~/.claude/projects) are:
|
|
22
|
+
// • message.content as an ARRAY of blocks — tool_use blocks carry id+name
|
|
23
|
+
// • user tool_result blocks resolve a pending tool_use
|
|
24
|
+
// • system entries with subtype "turn_duration" mark the turn finished
|
|
25
|
+
// • progress entries stream while a tool/step runs
|
|
26
|
+
// Stalled (needs-human) heuristics below still work on the edge where a
|
|
27
|
+
// permission prompt ends the turn as a pending tool_use.
|
|
28
|
+
const IDLE_AFTER_MS = 30 * 60 * 1000
|
|
29
|
+
// ponytail: a pending non-AskUserQuestion tool longer than this is stuck on
|
|
30
|
+
// a permission prompt or a slow tool → surface as "waiting".
|
|
31
|
+
const STALLED_MS = 90 * 1000
|
|
32
|
+
|
|
33
|
+
interface ToolUseBlock {
|
|
34
|
+
readonly type?: string
|
|
35
|
+
readonly id?: string
|
|
36
|
+
readonly name?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const textFromContent = (content: unknown, max = 60): string | undefined => {
|
|
40
|
+
if (typeof content === "string") {
|
|
41
|
+
return content.length > max ? `${content.slice(0, max - 1)}…` : content
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(content)) {
|
|
44
|
+
const parts: string[] = []
|
|
45
|
+
for (const part of content) {
|
|
46
|
+
if (typeof part === "string") {
|
|
47
|
+
parts.push(part)
|
|
48
|
+
} else if (
|
|
49
|
+
part !== null &&
|
|
50
|
+
typeof part === "object" &&
|
|
51
|
+
"type" in part &&
|
|
52
|
+
(part as { type: unknown }).type === "text"
|
|
53
|
+
) {
|
|
54
|
+
const text = (part as { text?: unknown }).text
|
|
55
|
+
if (typeof text === "string") parts.push(text)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const joined = parts.join(" ")
|
|
59
|
+
return joined.length > max ? `${joined.slice(0, max - 1)}…` : joined
|
|
60
|
+
}
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const lastTimestamp = (
|
|
65
|
+
entries: readonly ClaudeJsonlEntry[],
|
|
66
|
+
): number | undefined => {
|
|
67
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
68
|
+
const ts = entries[i]?.timestamp
|
|
69
|
+
if (typeof ts === "number") return ts
|
|
70
|
+
if (typeof ts === "string") {
|
|
71
|
+
const parsed = Date.parse(ts)
|
|
72
|
+
if (!Number.isNaN(parsed)) return parsed
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const firstTimestamp = (
|
|
79
|
+
entries: readonly ClaudeJsonlEntry[],
|
|
80
|
+
): number | undefined => {
|
|
81
|
+
for (const e of entries) {
|
|
82
|
+
const ts = e?.timestamp
|
|
83
|
+
if (typeof ts === "number") return ts
|
|
84
|
+
if (typeof ts === "string") {
|
|
85
|
+
const parsed = Date.parse(ts)
|
|
86
|
+
if (!Number.isNaN(parsed)) return parsed
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const lastAssistantText = (
|
|
93
|
+
entries: readonly ClaudeJsonlEntry[],
|
|
94
|
+
): string | undefined => {
|
|
95
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
96
|
+
const e = entries[i]
|
|
97
|
+
if (e?.type !== "assistant") continue
|
|
98
|
+
const text = textFromContent(e.message?.content)
|
|
99
|
+
if (text !== undefined) return text
|
|
100
|
+
}
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface PendingTool {
|
|
105
|
+
readonly id: string
|
|
106
|
+
readonly name: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ponytail: track tool_use blocks until their matching tool_result. A tool
|
|
110
|
+
// still pending at the end of the stream is what the agent is doing right now.
|
|
111
|
+
const pendingToolUses = (
|
|
112
|
+
entries: readonly ClaudeJsonlEntry[],
|
|
113
|
+
): readonly PendingTool[] => {
|
|
114
|
+
const pending = new Map<string, string>()
|
|
115
|
+
for (const e of entries) {
|
|
116
|
+
if (e.type === "assistant") {
|
|
117
|
+
const content = e.message?.content
|
|
118
|
+
if (Array.isArray(content)) {
|
|
119
|
+
for (const block of content) {
|
|
120
|
+
if (
|
|
121
|
+
typeof block !== "object" ||
|
|
122
|
+
block === null ||
|
|
123
|
+
(block as ToolUseBlock).type !== "tool_use"
|
|
124
|
+
) {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
const b = block as ToolUseBlock
|
|
128
|
+
if (typeof b.id === "string" && typeof b.name === "string") {
|
|
129
|
+
pending.set(b.id, b.name)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else if (e.type === "user") {
|
|
134
|
+
const content = e.message?.content
|
|
135
|
+
if (Array.isArray(content)) {
|
|
136
|
+
for (const block of content) {
|
|
137
|
+
if (
|
|
138
|
+
typeof block !== "object" ||
|
|
139
|
+
block === null ||
|
|
140
|
+
(block as { type?: string }).type !== "tool_result"
|
|
141
|
+
) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
const id = (block as { tool_use_id?: unknown }).tool_use_id
|
|
145
|
+
if (typeof id === "string") pending.delete(id)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return [...pending.entries()].map(([id, name]) => ({ id, name }))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ClaudeDeriveResult {
|
|
154
|
+
readonly status: AgentStatus
|
|
155
|
+
readonly preview?: string
|
|
156
|
+
readonly updatedAt: number
|
|
157
|
+
readonly cost?: number
|
|
158
|
+
readonly cwd?: string
|
|
159
|
+
readonly createdAt?: number
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const deriveClaudeStatus = (
|
|
163
|
+
entries: readonly ClaudeJsonlEntry[],
|
|
164
|
+
now: number = Date.now(),
|
|
165
|
+
// ponytail: some transcripts carry no timestamps at all (three exist on the
|
|
166
|
+
// reporting machine, one last touched a week ago). Falling back to `now` made
|
|
167
|
+
// them permanently "live" — always inside the recency window, always on the
|
|
168
|
+
// deck. The caller passes the file's mtime instead, which is the honest
|
|
169
|
+
// answer to "when did this session last do anything".
|
|
170
|
+
fallbackTimestamp?: number,
|
|
171
|
+
): ClaudeDeriveResult | null => {
|
|
172
|
+
if (entries.length === 0) return null
|
|
173
|
+
|
|
174
|
+
const last = entries[entries.length - 1]!
|
|
175
|
+
const ts = lastTimestamp(entries) ?? fallbackTimestamp ?? now
|
|
176
|
+
let status: AgentStatus = "idle"
|
|
177
|
+
let preview: string | undefined
|
|
178
|
+
|
|
179
|
+
const pending = pendingToolUses(entries)
|
|
180
|
+
const pendingAsk = pending.find((t) => t.name === "AskUserQuestion")
|
|
181
|
+
if (pendingAsk !== undefined) {
|
|
182
|
+
status = "waiting_for_human"
|
|
183
|
+
preview = "asking for your input"
|
|
184
|
+
} else if (last.type === "error") {
|
|
185
|
+
status = "error"
|
|
186
|
+
preview =
|
|
187
|
+
(typeof last.error?.message === "string" && last.error.message) ||
|
|
188
|
+
"session error"
|
|
189
|
+
} else if (typeof last.rateLimit !== "undefined") {
|
|
190
|
+
status = "waiting"
|
|
191
|
+
} else if (last.type === "system" && last.subtype === "turn_duration") {
|
|
192
|
+
// ponytail: turn finished — agent await
|
|
193
|
+
status = "idle"
|
|
194
|
+
preview = lastAssistantText(entries) ?? preview
|
|
195
|
+
} else if (last.type === "progress") {
|
|
196
|
+
status = "running"
|
|
197
|
+
} else if (pending.length > 0) {
|
|
198
|
+
// A non-AskUserQuestion tool is still open: running now, "waiting" once
|
|
199
|
+
// it stalls (permission prompt / hung tool).
|
|
200
|
+
const stalled = now - ts > STALLED_MS
|
|
201
|
+
status = stalled ? "waiting" : "running"
|
|
202
|
+
const toolName = pending[pending.length - 1]?.name
|
|
203
|
+
preview = toolName !== undefined ? `running ${toolName}` : undefined
|
|
204
|
+
} else if (last.type === "user") {
|
|
205
|
+
// Agent hasn't replied yet — mid-turn.
|
|
206
|
+
status = "running"
|
|
207
|
+
} else if (last.type === "assistant") {
|
|
208
|
+
preview = textFromContent(last.message?.content)
|
|
209
|
+
status = "idle"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (now - ts > IDLE_AFTER_MS) {
|
|
213
|
+
status = "idle"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ponytail: costUSD is per-assistant-message; sum gives a rough session
|
|
217
|
+
// total. Not every version files cost on every message — absent values
|
|
218
|
+
// just don't accrue.
|
|
219
|
+
let cost = 0
|
|
220
|
+
for (const e of entries) {
|
|
221
|
+
if (typeof e.costUSD === "number" && Number.isFinite(e.costUSD)) {
|
|
222
|
+
cost += e.costUSD
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const cwd = entries.find((e) => typeof e.cwd === "string")?.cwd
|
|
226
|
+
const createdAt = firstTimestamp(entries)
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
status,
|
|
230
|
+
preview,
|
|
231
|
+
updatedAt: ts,
|
|
232
|
+
...(cost > 0 ? { cost } : {}),
|
|
233
|
+
...(cwd !== undefined && cwd.length > 0 ? { cwd } : {}),
|
|
234
|
+
...(createdAt !== undefined ? { createdAt } : {}),
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ponytail: bridge between the global backend (which sees live agent counts)
|
|
2
|
+
// and createAgentsDecks (which materializes the deck once per rebuild). The
|
|
3
|
+
// backend records the count and page shape here; the deck factory reads them
|
|
4
|
+
// to decide how many pages to materialize.
|
|
5
|
+
// ponytail: the backend and the deck factory are loaded through two module
|
|
6
|
+
// graphs (register-builtins package import vs addon-loader file-path import),
|
|
7
|
+
// so plain module state duplicates. Keep the single store on globalThis so
|
|
8
|
+
// both graphs share one set of counters.
|
|
9
|
+
export const AGENTS_DECK_BASE = "coding-agents:agents"
|
|
10
|
+
|
|
11
|
+
interface LiveCountStore {
|
|
12
|
+
liveCount: number
|
|
13
|
+
pageCount: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const GLOBAL_KEY = "__sirenoCodingAgentsLiveCount"
|
|
17
|
+
|
|
18
|
+
const store = (): LiveCountStore => {
|
|
19
|
+
const g = globalThis as unknown as Record<string, LiveCountStore | undefined>
|
|
20
|
+
g[GLOBAL_KEY] ??= { liveCount: 0, pageCount: 1 }
|
|
21
|
+
return g[GLOBAL_KEY] as LiveCountStore
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const setLiveCount = (n: number): void => {
|
|
25
|
+
store().liveCount = n
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const getLiveCount = (): number => store().liveCount
|
|
29
|
+
|
|
30
|
+
export const setPageCount = (n: number): void => {
|
|
31
|
+
store().pageCount = n
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const getPageCount = (): number => store().pageCount
|
|
35
|
+
|
|
36
|
+
// ponytail: the deck id the summary button should navigate to. Pagination
|
|
37
|
+
// names pages `base-pN`; a single page keeps the base id.
|
|
38
|
+
export const deckTarget = (): string =>
|
|
39
|
+
store().pageCount > 1 ? `${AGENTS_DECK_BASE}-p1` : AGENTS_DECK_BASE
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AgentStatus } from "./state.js"
|
|
2
|
+
|
|
3
|
+
interface NotifierHooks {
|
|
4
|
+
readonly notify: (args: {
|
|
5
|
+
title: string
|
|
6
|
+
body: string
|
|
7
|
+
sound?: boolean
|
|
8
|
+
}) => Promise<void> | void
|
|
9
|
+
readonly now: () => number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class NotificationThrottle {
|
|
13
|
+
readonly #hooks: NotifierHooks
|
|
14
|
+
readonly #last: Map<string, AgentStatus> = new Map<string, AgentStatus>()
|
|
15
|
+
|
|
16
|
+
constructor(hooks: Partial<NotifierHooks> = {}) {
|
|
17
|
+
this.#hooks = {
|
|
18
|
+
now: hooks.now ?? (() => Date.now()),
|
|
19
|
+
notify:
|
|
20
|
+
hooks.notify ??
|
|
21
|
+
(() => {
|
|
22
|
+
// ponytail: no-op fallback so tests / null-provider mode don't crash.
|
|
23
|
+
}),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ponytail: keys on (providerId, sessionId, status) so the same agent
|
|
28
|
+
// transitioning into the same status twice doesn't re-fire — but a
|
|
29
|
+
// different status for the same agent always notifies.
|
|
30
|
+
evaluate(input: {
|
|
31
|
+
providerId: string
|
|
32
|
+
sessionId: string
|
|
33
|
+
status: AgentStatus
|
|
34
|
+
title: string
|
|
35
|
+
body: string
|
|
36
|
+
sound?: boolean
|
|
37
|
+
attentionOnly?: boolean
|
|
38
|
+
}): boolean {
|
|
39
|
+
const { providerId, sessionId, status, title, body, sound } = input
|
|
40
|
+
if (input.attentionOnly !== false) {
|
|
41
|
+
if (status !== "waiting_for_human" && status !== "error") return false
|
|
42
|
+
}
|
|
43
|
+
const key = `${providerId}:${sessionId}`
|
|
44
|
+
const last = this.#last.get(key)
|
|
45
|
+
if (last === status) return false
|
|
46
|
+
this.#last.set(key, status)
|
|
47
|
+
void this.#hooks.notify({ title, body, sound })
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
forget(providerId: string, sessionId: string): void {
|
|
52
|
+
this.#last.delete(`${providerId}:${sessionId}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
reset(): void {
|
|
56
|
+
this.#last.clear()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AgentStatus } from "./state.js"
|
|
2
|
+
|
|
3
|
+
export interface OpencodeEventStatusBusy {
|
|
4
|
+
readonly type: "busy"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface OpencodeEventStatusIdle {
|
|
8
|
+
readonly type: "idle"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OpencodeEventStatusRetry {
|
|
12
|
+
readonly type: "retry"
|
|
13
|
+
readonly attempt: number
|
|
14
|
+
readonly message: string
|
|
15
|
+
readonly next: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type OpencodeSessionStatus =
|
|
19
|
+
| OpencodeEventStatusIdle
|
|
20
|
+
| OpencodeEventStatusBusy
|
|
21
|
+
| OpencodeEventStatusRetry
|
|
22
|
+
|
|
23
|
+
export type OpencodeEvent =
|
|
24
|
+
| {
|
|
25
|
+
readonly type: "session.status"
|
|
26
|
+
readonly properties: {
|
|
27
|
+
readonly sessionID: string
|
|
28
|
+
readonly status: OpencodeSessionStatus
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
readonly type: "session.idle"
|
|
33
|
+
readonly properties: { readonly sessionID: string }
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
readonly type: "session.compacted"
|
|
37
|
+
readonly properties: { readonly sessionID: string }
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
readonly type: "session.error"
|
|
41
|
+
readonly properties: {
|
|
42
|
+
readonly sessionID: string
|
|
43
|
+
readonly message?: string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
readonly type: "permission.updated"
|
|
48
|
+
readonly properties: {
|
|
49
|
+
readonly sessionID: string
|
|
50
|
+
readonly id: string
|
|
51
|
+
readonly status: "pending" | "approved" | "denied"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const opencodeEventToStatus = (
|
|
56
|
+
evt: OpencodeEvent,
|
|
57
|
+
): { readonly status: AgentStatus; readonly sessionId: string } | null => {
|
|
58
|
+
if (evt.type === "session.status") {
|
|
59
|
+
const s = evt.properties.status
|
|
60
|
+
if (s.type === "busy")
|
|
61
|
+
return { status: "running", sessionId: evt.properties.sessionID }
|
|
62
|
+
if (s.type === "idle")
|
|
63
|
+
return { status: "idle", sessionId: evt.properties.sessionID }
|
|
64
|
+
if (s.type === "retry")
|
|
65
|
+
return { status: "waiting", sessionId: evt.properties.sessionID }
|
|
66
|
+
}
|
|
67
|
+
if (evt.type === "session.idle") {
|
|
68
|
+
return { status: "idle", sessionId: evt.properties.sessionID }
|
|
69
|
+
}
|
|
70
|
+
if (evt.type === "session.compacted") {
|
|
71
|
+
return { status: "compacting", sessionId: evt.properties.sessionID }
|
|
72
|
+
}
|
|
73
|
+
if (evt.type === "session.error") {
|
|
74
|
+
return { status: "error", sessionId: evt.properties.sessionID }
|
|
75
|
+
}
|
|
76
|
+
if (evt.type === "permission.updated") {
|
|
77
|
+
const status: AgentStatus =
|
|
78
|
+
evt.properties.status === "pending" ? "waiting_for_human" : "idle"
|
|
79
|
+
return { status, sessionId: evt.properties.sessionID }
|
|
80
|
+
}
|
|
81
|
+
return null
|
|
82
|
+
}
|