@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.
Files changed (485) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/bin/_wrapper-shared.js +42 -0
  4. package/bin/dev.js +95 -0
  5. package/bin/sirenodeck.js +42 -0
  6. package/config/default-config.yml +117 -0
  7. package/config/starter-config.yml +54 -0
  8. package/dist/main.mjs +64702 -0
  9. package/package.json +140 -0
  10. package/src/action/executor.ts +156 -0
  11. package/src/action/index.ts +7 -0
  12. package/src/addon/api-types.ts +15 -0
  13. package/src/addon/api.ts +461 -0
  14. package/src/addon/check-runner.ts +84 -0
  15. package/src/addon/index.ts +5 -0
  16. package/src/addon/loader.ts +441 -0
  17. package/src/addon/registry.ts +230 -0
  18. package/src/addon/spec.ts +69 -0
  19. package/src/api/addon.ts +33 -0
  20. package/src/api/asset-url.ts +24 -0
  21. package/src/api/index.ts +50 -0
  22. package/src/api/protocol-internal.ts +656 -0
  23. package/src/api/protocol.ts +2 -0
  24. package/src/api/react/index.ts +11 -0
  25. package/src/api/react/registry.ts +91 -0
  26. package/src/api/react/use-addon-channel.ts +26 -0
  27. package/src/api/react/use-button-state.tsx +23 -0
  28. package/src/api/react/use-deck.ts +24 -0
  29. package/src/assets/logo.png +0 -0
  30. package/src/assets/logo72x72.png +0 -0
  31. package/src/assets/logoFull.png +0 -0
  32. package/src/builtin-addons/brightness/README.md +51 -0
  33. package/src/builtin-addons/brightness/buttons/brightness/backend.ts +13 -0
  34. package/src/builtin-addons/brightness/buttons/brightness/config.ts +10 -0
  35. package/src/builtin-addons/brightness/buttons/brightness/frontend.tsx +50 -0
  36. package/src/builtin-addons/brightness/domain/macos.ts +46 -0
  37. package/src/builtin-addons/brightness/index.d.ts +5 -0
  38. package/src/builtin-addons/brightness/index.ts +20 -0
  39. package/src/builtin-addons/brightness/poller.ts +27 -0
  40. package/src/builtin-addons/brightness/providers/darwin.ts +107 -0
  41. package/src/builtin-addons/brightness/providers/index.ts +81 -0
  42. package/src/builtin-addons/brightness/providers/linux.ts +108 -0
  43. package/src/builtin-addons/brightness/providers/windows.ts +77 -0
  44. package/src/builtin-addons/brightness/sirenodeck.json +6 -0
  45. package/src/builtin-addons/coding-agents/assets/claude-code.svg +1 -0
  46. package/src/builtin-addons/coding-agents/assets/opencode-dark-square.svg +1 -0
  47. package/src/builtin-addons/coding-agents/buttons/agent/backend.ts +82 -0
  48. package/src/builtin-addons/coding-agents/buttons/agent/config.ts +13 -0
  49. package/src/builtin-addons/coding-agents/buttons/agent/frontend.tsx +130 -0
  50. package/src/builtin-addons/coding-agents/buttons/agent/pages.tsx +150 -0
  51. package/src/builtin-addons/coding-agents/buttons/summary/backend.ts +48 -0
  52. package/src/builtin-addons/coding-agents/buttons/summary/config.ts +11 -0
  53. package/src/builtin-addons/coding-agents/buttons/summary/frontend.tsx +254 -0
  54. package/src/builtin-addons/coding-agents/decks/agents.ts +69 -0
  55. package/src/builtin-addons/coding-agents/global/backend.ts +402 -0
  56. package/src/builtin-addons/coding-agents/global-entry.ts +7 -0
  57. package/src/builtin-addons/coding-agents/hook/script.ts +123 -0
  58. package/src/builtin-addons/coding-agents/index.ts +15 -0
  59. package/src/builtin-addons/coding-agents/manifest.ts +115 -0
  60. package/src/builtin-addons/coding-agents/providers/claude-code.ts +470 -0
  61. package/src/builtin-addons/coding-agents/providers/claude-instances.ts +184 -0
  62. package/src/builtin-addons/coding-agents/providers/opencode-instances.ts +108 -0
  63. package/src/builtin-addons/coding-agents/providers/opencode.ts +636 -0
  64. package/src/builtin-addons/coding-agents/providers/registry.ts +120 -0
  65. package/src/builtin-addons/coding-agents/providers/spawn.ts +112 -0
  66. package/src/builtin-addons/coding-agents/shared/claude-hook-events.ts +55 -0
  67. package/src/builtin-addons/coding-agents/shared/claude-status.ts +236 -0
  68. package/src/builtin-addons/coding-agents/shared/live-count.ts +39 -0
  69. package/src/builtin-addons/coding-agents/shared/notifier.ts +58 -0
  70. package/src/builtin-addons/coding-agents/shared/opencode-status.ts +82 -0
  71. package/src/builtin-addons/coding-agents/shared/snapshot.ts +66 -0
  72. package/src/builtin-addons/coding-agents/shared/state.ts +72 -0
  73. package/src/builtin-addons/coding-agents/sirenodeck.json +6 -0
  74. package/src/builtin-addons/coding-agents/types/types.ts +33 -0
  75. package/src/builtin-addons/core/README.md +89 -0
  76. package/src/builtin-addons/core/buttons/action/backend.ts +7 -0
  77. package/src/builtin-addons/core/buttons/action/config.ts +13 -0
  78. package/src/builtin-addons/core/buttons/action/frontend.tsx +16 -0
  79. package/src/builtin-addons/core/buttons/change-deck/backend.ts +18 -0
  80. package/src/builtin-addons/core/buttons/change-deck/config.ts +15 -0
  81. package/src/builtin-addons/core/buttons/change-deck/frontend.tsx +18 -0
  82. package/src/builtin-addons/core/buttons/media-sample/backend.ts +8 -0
  83. package/src/builtin-addons/core/buttons/media-sample/config.ts +7 -0
  84. package/src/builtin-addons/core/buttons/media-sample/frontend.tsx +2 -0
  85. package/src/builtin-addons/core/buttons/page-nav/backend.ts +28 -0
  86. package/src/builtin-addons/core/buttons/page-nav/config.ts +9 -0
  87. package/src/builtin-addons/core/buttons/page-nav/frontend.tsx +68 -0
  88. package/src/builtin-addons/core/buttons/toggle/backend.ts +119 -0
  89. package/src/builtin-addons/core/buttons/toggle/config.ts +57 -0
  90. package/src/builtin-addons/core/buttons/toggle/frontend.tsx +57 -0
  91. package/src/builtin-addons/core/buttons/toggle/global-service.ts +174 -0
  92. package/src/builtin-addons/core/index.ts +120 -0
  93. package/src/builtin-addons/core/sirenodeck.json +6 -0
  94. package/src/builtin-addons/date-time/README.md +73 -0
  95. package/src/builtin-addons/date-time/buttons/analog-clock/backend.ts +10 -0
  96. package/src/builtin-addons/date-time/buttons/analog-clock/config.ts +4 -0
  97. package/src/builtin-addons/date-time/buttons/analog-clock/frontend.tsx +88 -0
  98. package/src/builtin-addons/date-time/buttons/custom/backend.ts +10 -0
  99. package/src/builtin-addons/date-time/buttons/custom/config.ts +9 -0
  100. package/src/builtin-addons/date-time/buttons/custom/frontend.tsx +26 -0
  101. package/src/builtin-addons/date-time/buttons/date/backend.ts +10 -0
  102. package/src/builtin-addons/date-time/buttons/date/config.ts +10 -0
  103. package/src/builtin-addons/date-time/buttons/date/frontend.tsx +42 -0
  104. package/src/builtin-addons/date-time/buttons/time/backend.ts +10 -0
  105. package/src/builtin-addons/date-time/buttons/time/config.ts +9 -0
  106. package/src/builtin-addons/date-time/buttons/time/frontend.tsx +29 -0
  107. package/src/builtin-addons/date-time/index.d.ts +5 -0
  108. package/src/builtin-addons/date-time/index.ts +30 -0
  109. package/src/builtin-addons/date-time/poller.ts +11 -0
  110. package/src/builtin-addons/date-time/shared/format-date.ts +36 -0
  111. package/src/builtin-addons/date-time/shared/format.ts +105 -0
  112. package/src/builtin-addons/date-time/shared/use-now.ts +10 -0
  113. package/src/builtin-addons/date-time/sirenodeck.json +6 -0
  114. package/src/builtin-addons/emoji-selector/README.md +45 -0
  115. package/src/builtin-addons/emoji-selector/assets/launcher.svg +10 -0
  116. package/src/builtin-addons/emoji-selector/assets/smileys.svg +8 -0
  117. package/src/builtin-addons/emoji-selector/buttons/category/backend.ts +14 -0
  118. package/src/builtin-addons/emoji-selector/buttons/category/config.ts +8 -0
  119. package/src/builtin-addons/emoji-selector/buttons/category/frontend.tsx +19 -0
  120. package/src/builtin-addons/emoji-selector/buttons/emoji/backend.ts +7 -0
  121. package/src/builtin-addons/emoji-selector/buttons/emoji/config.ts +9 -0
  122. package/src/builtin-addons/emoji-selector/buttons/emoji/frontend.tsx +11 -0
  123. package/src/builtin-addons/emoji-selector/buttons/launcher/backend.ts +13 -0
  124. package/src/builtin-addons/emoji-selector/buttons/launcher/config.ts +6 -0
  125. package/src/builtin-addons/emoji-selector/buttons/launcher/frontend.tsx +31 -0
  126. package/src/builtin-addons/emoji-selector/data/categories.ts +1084 -0
  127. package/src/builtin-addons/emoji-selector/decks/index.ts +137 -0
  128. package/src/builtin-addons/emoji-selector/index.d.ts +5 -0
  129. package/src/builtin-addons/emoji-selector/index.ts +29 -0
  130. package/src/builtin-addons/emoji-selector/sirenodeck.json +6 -0
  131. package/src/builtin-addons/emoji-selector/support.ts +49 -0
  132. package/src/builtin-addons/index.ts +13 -0
  133. package/src/builtin-addons/internal-settings/README.md +26 -0
  134. package/src/builtin-addons/internal-settings/backend.ts +58 -0
  135. package/src/builtin-addons/internal-settings/buttons/app-info/backend.ts +7 -0
  136. package/src/builtin-addons/internal-settings/buttons/app-info/config.ts +13 -0
  137. package/src/builtin-addons/internal-settings/buttons/app-info/frontend.tsx +18 -0
  138. package/src/builtin-addons/internal-settings/buttons/brightness/common/frontend.tsx +50 -0
  139. package/src/builtin-addons/internal-settings/buttons/brightness-down/backend.ts +16 -0
  140. package/src/builtin-addons/internal-settings/buttons/brightness-down/config.ts +6 -0
  141. package/src/builtin-addons/internal-settings/buttons/brightness-down/frontend.tsx +8 -0
  142. package/src/builtin-addons/internal-settings/buttons/brightness-up/backend.ts +16 -0
  143. package/src/builtin-addons/internal-settings/buttons/brightness-up/config.ts +6 -0
  144. package/src/builtin-addons/internal-settings/buttons/brightness-up/frontend.tsx +8 -0
  145. package/src/builtin-addons/internal-settings/decks/settings.ts +25 -0
  146. package/src/builtin-addons/internal-settings/index.d.ts +4 -0
  147. package/src/builtin-addons/internal-settings/index.ts +53 -0
  148. package/src/builtin-addons/internal-settings/logo72x72.png +0 -0
  149. package/src/builtin-addons/internal-settings/sirenodeck.json +6 -0
  150. package/src/builtin-addons/media/README.md +45 -0
  151. package/src/builtin-addons/media/backend.ts +143 -0
  152. package/src/builtin-addons/media/buttons/media-player/backend.ts +19 -0
  153. package/src/builtin-addons/media/buttons/media-player/components/MediaStatus.tsx +25 -0
  154. package/src/builtin-addons/media/buttons/media-player/components/MediaSurface.tsx +52 -0
  155. package/src/builtin-addons/media/buttons/media-player/components/ProgressBar.tsx +21 -0
  156. package/src/builtin-addons/media/buttons/media-player/components/status-meta.ts +36 -0
  157. package/src/builtin-addons/media/buttons/media-player/config.ts +4 -0
  158. package/src/builtin-addons/media/buttons/media-player/frontend.tsx +29 -0
  159. package/src/builtin-addons/media/buttons/volume/common/frontend.tsx +52 -0
  160. package/src/builtin-addons/media/buttons/volume/mute/backend.ts +13 -0
  161. package/src/builtin-addons/media/buttons/volume/mute/config.ts +4 -0
  162. package/src/builtin-addons/media/buttons/volume/mute/frontend.tsx +26 -0
  163. package/src/builtin-addons/media/buttons/volume/volume-down/backend.ts +14 -0
  164. package/src/builtin-addons/media/buttons/volume/volume-down/config.ts +6 -0
  165. package/src/builtin-addons/media/buttons/volume/volume-down/frontend.tsx +10 -0
  166. package/src/builtin-addons/media/buttons/volume/volume-up/backend.ts +14 -0
  167. package/src/builtin-addons/media/buttons/volume/volume-up/config.ts +6 -0
  168. package/src/builtin-addons/media/buttons/volume/volume-up/frontend.tsx +8 -0
  169. package/src/builtin-addons/media/checks.ts +104 -0
  170. package/src/builtin-addons/media/executor.ts +36 -0
  171. package/src/builtin-addons/media/index.d.ts +5 -0
  172. package/src/builtin-addons/media/index.ts +53 -0
  173. package/src/builtin-addons/media/progress.ts +16 -0
  174. package/src/builtin-addons/media/providers/darwin.ts +391 -0
  175. package/src/builtin-addons/media/providers/index.ts +27 -0
  176. package/src/builtin-addons/media/providers/linux.ts +196 -0
  177. package/src/builtin-addons/media/providers/types.ts +48 -0
  178. package/src/builtin-addons/media/providers/windows.ts +121 -0
  179. package/src/builtin-addons/media/sirenodeck.json +6 -0
  180. package/src/builtin-addons/media/state.ts +19 -0
  181. package/src/builtin-addons/register-builtins.ts +65 -0
  182. package/src/builtin-addons/session/README.md +14 -0
  183. package/src/builtin-addons/session/buttons/session-info/backend.ts +8 -0
  184. package/src/builtin-addons/session/buttons/session-info/config.ts +4 -0
  185. package/src/builtin-addons/session/buttons/session-info/frontend.tsx +2 -0
  186. package/src/builtin-addons/session/buttons/time/backend.ts +7 -0
  187. package/src/builtin-addons/session/buttons/time/config.ts +6 -0
  188. package/src/builtin-addons/session/buttons/time/frontend.tsx +2 -0
  189. package/src/builtin-addons/session/index.d.ts +5 -0
  190. package/src/builtin-addons/session/index.ts +18 -0
  191. package/src/builtin-addons/session/sirenodeck.json +6 -0
  192. package/src/builtin-addons/system-status/README.md +169 -0
  193. package/src/builtin-addons/system-status/buttons/_shared.ts +131 -0
  194. package/src/builtin-addons/system-status/buttons/system-status/frontend.tsx +224 -0
  195. package/src/builtin-addons/system-status/buttons/system-status/schemas.ts +67 -0
  196. package/src/builtin-addons/system-status/domain/catalog.ts +53 -0
  197. package/src/builtin-addons/system-status/domain/chart-sampler.ts +58 -0
  198. package/src/builtin-addons/system-status/domain/display-metrics.ts +128 -0
  199. package/src/builtin-addons/system-status/domain/index.ts +10 -0
  200. package/src/builtin-addons/system-status/domain/live-metrics.ts +1021 -0
  201. package/src/builtin-addons/system-status/domain/metric-ids.ts +0 -0
  202. package/src/builtin-addons/system-status/index.ts +16 -0
  203. package/src/builtin-addons/system-status/manifest.ts +105 -0
  204. package/src/builtin-addons/system-status/shared/metrics-catalog.ts +316 -0
  205. package/src/builtin-addons/system-status/sirenodeck.json +6 -0
  206. package/src/builtin-addons/test-buildin/decks/action-buttons-test.ts +56 -0
  207. package/src/builtin-addons/test-buildin/index.d.ts +5 -0
  208. package/src/builtin-addons/test-buildin/index.ts +16 -0
  209. package/src/builtin-addons/value-display/README.md +59 -0
  210. package/src/builtin-addons/value-display/backend.ts +86 -0
  211. package/src/builtin-addons/value-display/buttons/value-display/backend.ts +17 -0
  212. package/src/builtin-addons/value-display/buttons/value-display/config.ts +36 -0
  213. package/src/builtin-addons/value-display/buttons/value-display/frontend.tsx +54 -0
  214. package/src/builtin-addons/value-display/index.d.ts +5 -0
  215. package/src/builtin-addons/value-display/index.ts +23 -0
  216. package/src/builtin-addons/value-display/sirenodeck.json +6 -0
  217. package/src/builtin-addons/weather/README.md +46 -0
  218. package/src/builtin-addons/weather/backend.ts +121 -0
  219. package/src/builtin-addons/weather/buttons/weather/backend.ts +27 -0
  220. package/src/builtin-addons/weather/buttons/weather/config.ts +29 -0
  221. package/src/builtin-addons/weather/buttons/weather/frontend.tsx +63 -0
  222. package/src/builtin-addons/weather/buttons/weather/icons.tsx +88 -0
  223. package/src/builtin-addons/weather/buttons/weather/pages.tsx +109 -0
  224. package/src/builtin-addons/weather/domain/codes.ts +35 -0
  225. package/src/builtin-addons/weather/domain/fetch.ts +47 -0
  226. package/src/builtin-addons/weather/index.d.ts +5 -0
  227. package/src/builtin-addons/weather/index.ts +23 -0
  228. package/src/builtin-addons/weather/poller.ts +70 -0
  229. package/src/builtin-addons/weather/provider/city-key.ts +9 -0
  230. package/src/builtin-addons/weather/provider/codes.ts +35 -0
  231. package/src/builtin-addons/weather/provider/fetch.ts +97 -0
  232. package/src/builtin-addons/weather/provider/geocode.ts +67 -0
  233. package/src/builtin-addons/weather/provider/types.ts +42 -0
  234. package/src/builtin-addons/weather/sirenodeck.json +6 -0
  235. package/src/cli/coding-agents-onboarding.ts +434 -0
  236. package/src/cli/commands/addon-decks.ts +441 -0
  237. package/src/cli/commands/addon-registry.ts +639 -0
  238. package/src/cli/commands/emulator-mode.ts +289 -0
  239. package/src/cli/commands/install.ts +128 -0
  240. package/src/cli/commands/logs.ts +74 -0
  241. package/src/cli/commands/network-bind.ts +111 -0
  242. package/src/cli/commands/pipeline/helpers.ts +75 -0
  243. package/src/cli/commands/pipeline/preflight.ts +20 -0
  244. package/src/cli/commands/port-identity.ts +152 -0
  245. package/src/cli/commands/reload.ts +37 -0
  246. package/src/cli/commands/restart.ts +139 -0
  247. package/src/cli/commands/run.ts +2270 -0
  248. package/src/cli/commands/service/install.ts +121 -0
  249. package/src/cli/commands/service/render-template.ts +158 -0
  250. package/src/cli/commands/service-manager.ts +184 -0
  251. package/src/cli/commands/spawn-daemon.ts +251 -0
  252. package/src/cli/commands/start.ts +1061 -0
  253. package/src/cli/commands/status.ts +110 -0
  254. package/src/cli/commands/stop.ts +121 -0
  255. package/src/cli/commands/subprocess-supervisor.ts +203 -0
  256. package/src/cli/commands/system-requirements.ts +699 -0
  257. package/src/cli/cwd.ts +28 -0
  258. package/src/cli/first-run.ts +149 -0
  259. package/src/cli/http-server.ts +244 -0
  260. package/src/cli/index.ts +355 -0
  261. package/src/cli/install-root.ts +26 -0
  262. package/src/cli/main.ts +194 -0
  263. package/src/cli/package-manager.ts +175 -0
  264. package/src/cli/probe-deps.ts +45 -0
  265. package/src/cli/prompt.ts +22 -0
  266. package/src/cli/startup-display.ts +452 -0
  267. package/src/config/builtin-icons.ts +57 -0
  268. package/src/config/config-diff.ts +54 -0
  269. package/src/config/discovery.ts +64 -0
  270. package/src/config/icon-resolver.ts +90 -0
  271. package/src/config/include-resolver.ts +202 -0
  272. package/src/config/index.ts +9 -0
  273. package/src/config/loader.ts +187 -0
  274. package/src/config/mutation.ts +514 -0
  275. package/src/config/reference-expander.ts +84 -0
  276. package/src/config/schemas.ts +223 -0
  277. package/src/config/validation.ts +254 -0
  278. package/src/core/asset-id.ts +21 -0
  279. package/src/core/gesture-state.ts +344 -0
  280. package/src/core/icon-asset-registry.ts +127 -0
  281. package/src/core/icon-source.ts +86 -0
  282. package/src/core/index.ts +30 -0
  283. package/src/core/mime.ts +16 -0
  284. package/src/core/pagination.ts +90 -0
  285. package/src/core/pub-sub.ts +126 -0
  286. package/src/core/settings.ts +153 -0
  287. package/src/core/store.ts +88 -0
  288. package/src/core/watcher.ts +73 -0
  289. package/src/deck/addon-handler-bridge.ts +516 -0
  290. package/src/deck/deck-config.ts +271 -0
  291. package/src/deck/editor-surfaces.ts +87 -0
  292. package/src/deck/host-context.ts +18 -0
  293. package/src/deck/index.ts +102 -0
  294. package/src/deck/macro-parse.ts +182 -0
  295. package/src/deck/methods.ts +348 -0
  296. package/src/deck/paginate-deck.ts +98 -0
  297. package/src/deck/position-buttons.ts +57 -0
  298. package/src/deck/runtime/index.ts +5 -0
  299. package/src/deck/runtime/runtime.ts +1060 -0
  300. package/src/deck/runtime-subscriptions.ts +45 -0
  301. package/src/deck/system-back-injection.ts +62 -0
  302. package/src/deck/system-buttons/registry.tsx +97 -0
  303. package/src/deck/system-buttons/types.ts +14 -0
  304. package/src/device/black-frame.ts +73 -0
  305. package/src/device/index.ts +13 -0
  306. package/src/device/linux-udev.ts +36 -0
  307. package/src/device/models.ts +89 -0
  308. package/src/device/reconnecting.ts +193 -0
  309. package/src/device/registry.ts +38 -0
  310. package/src/device/stream-deck.ts +155 -0
  311. package/src/index.ts +74 -0
  312. package/src/outputClient/emulator.ts +357 -0
  313. package/src/outputClient/index.ts +26 -0
  314. package/src/outputClient/open-browser.ts +26 -0
  315. package/src/outputClient/real.ts +393 -0
  316. package/src/outputClient/types.ts +83 -0
  317. package/src/render/browser-renderer.ts +240 -0
  318. package/src/render/buffer-hash.ts +20 -0
  319. package/src/render/editor-handler.ts +197 -0
  320. package/src/render/emulator-server.ts +128 -0
  321. package/src/render/icon-source-resolver.ts +69 -0
  322. package/src/render/index.ts +16 -0
  323. package/src/render/protocol.ts +61 -0
  324. package/src/render/push-raw-image.ts +100 -0
  325. package/src/render/screenshot-cadence.ts +87 -0
  326. package/src/render/state-publisher.ts +110 -0
  327. package/src/render/vite-server.ts +138 -0
  328. package/src/render/ws-bridge.ts +315 -0
  329. package/src/system/device-selection.ts +80 -0
  330. package/src/system/glob-match.ts +74 -0
  331. package/src/system/providers/active-app/darwin.ts +117 -0
  332. package/src/system/providers/active-app/linux.ts +245 -0
  333. package/src/system/providers/active-app/wayland-gnome.ts +303 -0
  334. package/src/system/providers/active-app/windows.ts +94 -0
  335. package/src/system/providers/active-app.ts +97 -0
  336. package/src/system/providers/clipboard/darwin.ts +65 -0
  337. package/src/system/providers/clipboard/linux.ts +163 -0
  338. package/src/system/providers/clipboard/windows.ts +94 -0
  339. package/src/system/providers/clipboard.ts +84 -0
  340. package/src/system/providers/error.ts +16 -0
  341. package/src/system/providers/key-macro/darwin.ts +221 -0
  342. package/src/system/providers/key-macro/linux.ts +651 -0
  343. package/src/system/providers/key-macro/parser.ts +215 -0
  344. package/src/system/providers/key-macro/windows.ts +476 -0
  345. package/src/system/providers/key-macro.ts +79 -0
  346. package/src/system/providers/notification/darwin.ts +36 -0
  347. package/src/system/providers/notification/linux.ts +115 -0
  348. package/src/system/providers/notification/windows.ts +72 -0
  349. package/src/system/providers/notification.ts +75 -0
  350. package/src/system/providers/session/darwin.ts +82 -0
  351. package/src/system/providers/session/linux.ts +175 -0
  352. package/src/system/providers/session/windows.ts +73 -0
  353. package/src/system/providers/session.ts +89 -0
  354. package/src/system/providers/shared.ts +65 -0
  355. package/src/system/requirements.ts +235 -0
  356. package/src/system/setup-wizard/config-seed.ts +30 -0
  357. package/src/system/setup-wizard/format.ts +275 -0
  358. package/src/system/setup-wizard/index.ts +45 -0
  359. package/src/system/setup-wizard/open-settings.ts +26 -0
  360. package/src/system/setup-wizard/plan.ts +230 -0
  361. package/src/system/setup-wizard/probe-cache.ts +34 -0
  362. package/src/system/setup-wizard/probe.ts +486 -0
  363. package/src/system/setup-wizard/runtime-features.ts +85 -0
  364. package/src/system/setup-wizard/sudo.ts +178 -0
  365. package/src/system/setup-wizard/types.ts +133 -0
  366. package/src/system/virtual-stream-deck.ts +67 -0
  367. package/src/themes/css.ts +210 -0
  368. package/src/themes/default/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  369. package/src/themes/default/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
  370. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
  371. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf +0 -0
  372. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf +0 -0
  373. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf +0 -0
  374. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Italic.ttf +0 -0
  375. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Light.ttf +0 -0
  376. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf +0 -0
  377. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
  378. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf +0 -0
  379. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
  380. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf +0 -0
  381. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf +0 -0
  382. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-Thin.ttf +0 -0
  383. package/src/themes/default/assets/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf +0 -0
  384. package/src/themes/default/assets/IBM_Plex_Mono/OFL.txt +93 -0
  385. package/src/themes/default/assets/OFL.txt +93 -0
  386. package/src/themes/default/assets/README.txt +106 -0
  387. package/src/themes/default/assets/static/IBMPlexSans-Bold.ttf +0 -0
  388. package/src/themes/default/assets/static/IBMPlexSans-BoldItalic.ttf +0 -0
  389. package/src/themes/default/assets/static/IBMPlexSans-ExtraLight.ttf +0 -0
  390. package/src/themes/default/assets/static/IBMPlexSans-ExtraLightItalic.ttf +0 -0
  391. package/src/themes/default/assets/static/IBMPlexSans-Italic.ttf +0 -0
  392. package/src/themes/default/assets/static/IBMPlexSans-Light.ttf +0 -0
  393. package/src/themes/default/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
  394. package/src/themes/default/assets/static/IBMPlexSans-Medium.ttf +0 -0
  395. package/src/themes/default/assets/static/IBMPlexSans-MediumItalic.ttf +0 -0
  396. package/src/themes/default/assets/static/IBMPlexSans-Regular.ttf +0 -0
  397. package/src/themes/default/assets/static/IBMPlexSans-SemiBold.ttf +0 -0
  398. package/src/themes/default/assets/static/IBMPlexSans-SemiBoldItalic.ttf +0 -0
  399. package/src/themes/default/assets/static/IBMPlexSans-Thin.ttf +0 -0
  400. package/src/themes/default/assets/static/IBMPlexSans-ThinItalic.ttf +0 -0
  401. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Bold.ttf +0 -0
  402. package/src/themes/default/assets/static/IBMPlexSans_Condensed-BoldItalic.ttf +0 -0
  403. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLight.ttf +0 -0
  404. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ExtraLightItalic.ttf +0 -0
  405. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Italic.ttf +0 -0
  406. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Light.ttf +0 -0
  407. package/src/themes/default/assets/static/IBMPlexSans_Condensed-LightItalic.ttf +0 -0
  408. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Medium.ttf +0 -0
  409. package/src/themes/default/assets/static/IBMPlexSans_Condensed-MediumItalic.ttf +0 -0
  410. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Regular.ttf +0 -0
  411. package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBold.ttf +0 -0
  412. package/src/themes/default/assets/static/IBMPlexSans_Condensed-SemiBoldItalic.ttf +0 -0
  413. package/src/themes/default/assets/static/IBMPlexSans_Condensed-Thin.ttf +0 -0
  414. package/src/themes/default/assets/static/IBMPlexSans_Condensed-ThinItalic.ttf +0 -0
  415. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Bold.ttf +0 -0
  416. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-BoldItalic.ttf +0 -0
  417. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLight.ttf +0 -0
  418. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ExtraLightItalic.ttf +0 -0
  419. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Italic.ttf +0 -0
  420. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Light.ttf +0 -0
  421. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-LightItalic.ttf +0 -0
  422. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Medium.ttf +0 -0
  423. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-MediumItalic.ttf +0 -0
  424. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Regular.ttf +0 -0
  425. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBold.ttf +0 -0
  426. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  427. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-Thin.ttf +0 -0
  428. package/src/themes/default/assets/static/IBMPlexSans_SemiCondensed-ThinItalic.ttf +0 -0
  429. package/src/themes/default/components.css +81 -0
  430. package/src/themes/default/sirenodeck.json +170 -0
  431. package/src/themes/index.ts +14 -0
  432. package/src/themes/light/assets/IBMPlexSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  433. package/src/themes/light/assets/IBMPlexSans-VariableFont_wdth,wght.ttf +0 -0
  434. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Bold.ttf +0 -0
  435. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Medium.ttf +0 -0
  436. package/src/themes/light/assets/IBM_Plex_Mono/IBMPlexMono-Regular.ttf +0 -0
  437. package/src/themes/light/assets/IBM_Plex_Mono/OFL.txt +93 -0
  438. package/src/themes/light/assets/OFL.txt +93 -0
  439. package/src/themes/light/assets/README.txt +106 -0
  440. package/src/themes/light/assets/static/IBMPlexSans-Light.ttf +0 -0
  441. package/src/themes/light/assets/static/IBMPlexSans-LightItalic.ttf +0 -0
  442. package/src/themes/light/assets/static/IBMPlexSans-Thin.ttf +0 -0
  443. package/src/themes/light/components.css +70 -0
  444. package/src/themes/light/sirenodeck.json +170 -0
  445. package/src/themes/loader.ts +343 -0
  446. package/src/themes/manifest.ts +164 -0
  447. package/src/themes/use-resolved-theme.tsx +32 -0
  448. package/src/ui/ButtonFrame.tsx +230 -0
  449. package/src/ui/contexts/AssetCacheContext.tsx +43 -0
  450. package/src/ui/index.ts +20 -0
  451. package/src/ui/primitives/Chip.tsx +53 -0
  452. package/src/ui/primitives/Icon.tsx +209 -0
  453. package/src/ui/primitives/Label.tsx +82 -0
  454. package/src/ui/primitives/ProgressBar.tsx +38 -0
  455. package/src/ui/primitives/TapIndicator.tsx +67 -0
  456. package/src/ui/primitives/Text.tsx +810 -0
  457. package/src/ui/primitives/index.ts +6 -0
  458. package/src/ui/surfaces/BarsSurface.tsx +211 -0
  459. package/src/ui/surfaces/IconLabelProgressSurface.tsx +133 -0
  460. package/src/ui/surfaces/IconLabelSurface.tsx +59 -0
  461. package/src/ui/surfaces/LabelValueListSurface.tsx +202 -0
  462. package/src/ui/surfaces/PaginatedSurface.tsx +164 -0
  463. package/src/ui/surfaces/SplitActionSurface.tsx +98 -0
  464. package/src/ui/surfaces/TemporaryErrorSurface.tsx +68 -0
  465. package/src/ui/surfaces/ValueChart.tsx +218 -0
  466. package/src/ui/surfaces/index.ts +32 -0
  467. package/src/ui/surfaces/paginated-schema.ts +22 -0
  468. package/src/ui/theme-presentation-builder.ts +184 -0
  469. package/src/ui/theme-presentation.tsx +146 -0
  470. package/src/ui/utils/cn.ts +12 -0
  471. package/src/util/cache-paths.ts +38 -0
  472. package/src/util/config-diff.ts +16 -0
  473. package/src/util/daemon-control.ts +85 -0
  474. package/src/util/daemon.ts +575 -0
  475. package/src/util/device-config.ts +56 -0
  476. package/src/util/errors.ts +13 -0
  477. package/src/util/log-reader.ts +114 -0
  478. package/src/util/log-tail.ts +103 -0
  479. package/src/util/logger.ts +417 -0
  480. package/src/util/process-title.ts +79 -0
  481. package/src/util/single-instance.ts +202 -0
  482. package/src/version.ts +10 -0
  483. package/src/vite/index.ts +6 -0
  484. package/src/vite/virtual-modules.ts +631 -0
  485. 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