@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,639 @@
1
+ import { readFileSync, readdirSync } from "node:fs"
2
+ import { existsSync } from "node:fs"
3
+ import { dirname, join, resolve as resolvePath } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ import type { AddonPoller, AddonPollerChannel } from "@/addon/api-types"
7
+
8
+ export interface ScannedDeck {
9
+ readonly id: string
10
+ readonly paginated: boolean
11
+ readonly buttons: number
12
+ readonly internal: boolean
13
+ readonly hasTrigger: boolean
14
+ readonly generated?: boolean
15
+ readonly isOverlay?: boolean
16
+ }
17
+
18
+ export interface ScannedAddon {
19
+ readonly name: string
20
+ readonly types: ReadonlyArray<string>
21
+ /**
22
+ * The module the DAEMON imports (Node). Its host-UI specifiers are stubbed
23
+ * at build time so plain Node can load it for the manifest and global
24
+ * service.
25
+ */
26
+ readonly frontendEntry: string | null
27
+ /**
28
+ * The module the BROWSER loads, when the addon ships a separate bundle.
29
+ *
30
+ * ponytail: these two were one field, set to `browserEntryPath ?? entryPath`.
31
+ * The browser bundle deliberately leaves `@sirenodeck/sirenodeck/ui/*`
32
+ * external, which Node cannot resolve — so the daemon's `import()` threw
33
+ * into a silent catch, no button handler was registered, and every tap on
34
+ * an addon button did nothing at all. They must stay separate.
35
+ */
36
+ readonly browserEntry?: string | null
37
+ readonly publishIntervalMs: number | null
38
+ readonly pollerEntry: string | null
39
+ readonly buttonTypes: Readonly<Record<string, ScannedButtonType>>
40
+ readonly deckTypes: Readonly<Record<string, string>>
41
+ readonly source: "json" | "regex"
42
+ readonly globalServiceEntry: string | null
43
+ readonly decks: ReadonlyArray<ScannedDeck>
44
+ readonly path?: string
45
+ readonly internal?: boolean
46
+ readonly defaultButton?: string | null
47
+ }
48
+
49
+ export interface ScannedButtonType {
50
+ readonly exportName: string
51
+ readonly internal: boolean
52
+ readonly defaultConfig?: unknown
53
+ }
54
+
55
+ export interface AddonFrontendRef {
56
+ readonly name: string
57
+ readonly frontendEntry: string | null
58
+ }
59
+
60
+ const buildAddonByType = (
61
+ scanned: ReadonlyArray<ScannedAddon>,
62
+ ): Map<string, AddonFrontendRef> => {
63
+ const map = new Map<string, AddonFrontendRef>()
64
+ for (const addon of scanned) {
65
+ for (const type of addon.types) {
66
+ if (!map.has(type)) {
67
+ map.set(type, { name: addon.name, frontendEntry: addon.frontendEntry })
68
+ }
69
+ if (type === `${addon.name}:${addon.name}` && !map.has(addon.name)) {
70
+ map.set(addon.name, {
71
+ name: addon.name,
72
+ frontendEntry: addon.frontendEntry,
73
+ })
74
+ }
75
+ }
76
+ }
77
+ return map
78
+ }
79
+
80
+ export const collectBuiltinAddonRegistry = async (): Promise<{
81
+ scanned: ReadonlyArray<ScannedAddon>
82
+ byType: Map<string, AddonFrontendRef>
83
+ }> => {
84
+ const scanned = await scanBuiltinAddons()
85
+ return { scanned, byType: buildAddonByType(scanned) }
86
+ }
87
+
88
+ const here = dirname(fileURLToPath(import.meta.url))
89
+
90
+ /**
91
+ * Where the builtin addons live on disk, from wherever this module is running.
92
+ *
93
+ * ponytail: this used to be a single hard-coded hop — `../../builtin-addons` —
94
+ * which is right only when the module runs from source at `src/cli/commands/`.
95
+ * The published CLI runs the bundle at `dist/main.mjs`, where the same hop
96
+ * lands on `<pkg>/../builtin-addons`: a path that does not exist. The scan
97
+ * below then returned an empty array without a word, and the consequences were
98
+ * entirely silent and entirely fatal to the deck — every builtin lost its
99
+ * frontend entry, so it was absent from `SIRENO_ADDONS`, absent from the
100
+ * frontend's `virtual:sireno/addons/registry`, and `ButtonSurface` rendered
101
+ * `null` for it. The deck came up with blank keys, no error anywhere, and the
102
+ * only buttons that survived were the hardcoded system ones and addons loaded
103
+ * from an absolute path outside the package.
104
+ *
105
+ * The package ships `src/` beside `dist/` (see the `files` field in
106
+ * package.json), so the sources are present in both layouts; only the number
107
+ * of hops differs. Try each layout and take the one that is actually there.
108
+ */
109
+ export const resolveBuiltinDir = (
110
+ moduleDir: string,
111
+ exists: (path: string) => boolean = existsSync,
112
+ ): string => {
113
+ const candidates = [
114
+ // running from source: src/cli/commands → src/builtin-addons
115
+ resolvePath(moduleDir, "..", "..", "builtin-addons"),
116
+ // running from the bundle: dist → src/builtin-addons
117
+ resolvePath(moduleDir, "..", "src", "builtin-addons"),
118
+ // bundled alongside, if a future build ever emits them next to the entry
119
+ resolvePath(moduleDir, "builtin-addons"),
120
+ ]
121
+ return candidates.find((candidate) => exists(candidate)) ?? candidates[0]!
122
+ }
123
+
124
+ export const builtinDir = resolveBuiltinDir(here)
125
+
126
+ /**
127
+ * The text of the modules an entry file re-exports, one hop deep.
128
+ *
129
+ * ponytail: the regex scan used to read only the entry file itself, which is
130
+ * empty of button types whenever the entry is a barrel — `system-status` and
131
+ * `coding-agents` both just re-export `./manifest`. Those two addons have a
132
+ * `sirenodeck.json`, so the JSON path was meant to cover them by importing the
133
+ * entry and reading `buttonTypes` off the module. That works under tsx and
134
+ * fails under the published bundle, because `await import()` on a `.ts` file
135
+ * is not something plain node can do — and the failure is swallowed by a catch
136
+ * that leaves the type list empty. The result was two addons whose buttons
137
+ * rendered blank only in a real install, which is the worst place to find out.
138
+ *
139
+ * Reading the re-exported file's text needs no loader, so it works in both.
140
+ * One hop is enough for a barrel and keeps this from walking a module graph.
141
+ */
142
+ const reexportedSources = (addonDir: string, entrySource: string): string[] => {
143
+ const out: string[] = []
144
+ const seen = new Set<string>()
145
+ for (const match of entrySource.matchAll(
146
+ /(?:^|\n)\s*(?:export|import)\b[^"']*from\s*["'](\.[^"']+)["']/g,
147
+ )) {
148
+ const spec = match[1]
149
+ if (spec === undefined || seen.has(spec)) continue
150
+ seen.add(spec)
151
+ const base = resolvePath(addonDir, spec)
152
+ // TypeScript's ESM imports name the emitted `.js`, so `./manifest.js` is
153
+ // `manifest.ts` on disk — `coding-agents` imports exactly that way, and
154
+ // without the swap its barrel looked like it re-exported nothing.
155
+ const withoutJs = base.replace(/\.(js|jsx|mjs|cjs)$/, "")
156
+ for (const candidate of [
157
+ base,
158
+ `${base}.ts`,
159
+ `${base}.tsx`,
160
+ `${withoutJs}.ts`,
161
+ `${withoutJs}.tsx`,
162
+ join(base, "index.ts"),
163
+ join(base, "index.tsx"),
164
+ ]) {
165
+ if (!existsSync(candidate)) continue
166
+ try {
167
+ out.push(readFileSync(candidate, "utf8"))
168
+ } catch {
169
+ // unreadable sibling — the entry's own text still stands
170
+ }
171
+ break
172
+ }
173
+ }
174
+ return out
175
+ }
176
+
177
+ const scanAddonDir = async (
178
+ addonDir: string,
179
+ addonName: string,
180
+ ): Promise<ScannedAddon | null> => {
181
+ const jsonScanned = await scanAddonJsonManifest(addonDir, addonName)
182
+ if (jsonScanned !== null && jsonScanned.types.length > 0) return jsonScanned
183
+ const indexPath = join(addonDir, "index.ts")
184
+ const indexTsxPath = join(addonDir, "index.tsx")
185
+ // ponytail: the text fallback used to insist on an `index.ts`, so an addon
186
+ // whose manifest names a different entry had no fallback at all — it simply
187
+ // returned the empty JSON scan. `coding-agents` has no index.ts and declares
188
+ // `manifest.ts`, which is why its two buttons were missing from the registry
189
+ // in a published install while every other builtin recovered. When the
190
+ // manifest names an entry, that is the file to read.
191
+ const manifestEntry = jsonScanned?.frontendEntry ?? null
192
+ const indexFile = existsSync(indexPath)
193
+ ? indexPath
194
+ : existsSync(indexTsxPath)
195
+ ? indexTsxPath
196
+ : manifestEntry !== null && existsSync(manifestEntry)
197
+ ? manifestEntry
198
+ : null
199
+ if (indexFile === null) return jsonScanned
200
+ let raw: string
201
+ try {
202
+ raw = readFileSync(indexFile, "utf8")
203
+ } catch {
204
+ return jsonScanned
205
+ }
206
+ const allSources = [raw, ...reexportedSources(dirname(indexFile), raw)]
207
+ const scanFrom = (source: string): Set<string> => {
208
+ const out = new Set<string>()
209
+ for (const m of source.matchAll(
210
+ /type:\s*["']([a-z0-9-]+:[a-z0-9-]+)["']/gi,
211
+ )) {
212
+ if (m[1] !== undefined) out.add(m[1])
213
+ }
214
+ for (const m of source.matchAll(
215
+ /["']([a-z0-9-]+:[a-z0-9-]+)["']\s*:\s*\{/g,
216
+ )) {
217
+ if (m[1] !== undefined) out.add(m[1])
218
+ }
219
+ return out
220
+ }
221
+ // Scan the re-exported modules too: a barrel entry declares no types of its
222
+ // own, which is exactly how `system-status` and `coding-agents` came up empty.
223
+ const types = new Set<string>()
224
+ for (const source of allSources)
225
+ for (const type of scanFrom(source)) types.add(type)
226
+ let publishIntervalMs: number | null = null
227
+ for (const src of allSources) {
228
+ const match = src.match(/publishIntervalMs:\s*(\d+)/)
229
+ if (match !== null) {
230
+ publishIntervalMs = Number.parseInt(match[1]!, 10)
231
+ break
232
+ }
233
+ }
234
+ const pollerEntry = existsSync(join(addonDir, "poller.ts"))
235
+ ? join(addonDir, "poller.ts")
236
+ : null
237
+ // Across every source, for the same reason as the type scan: a barrel entry
238
+ // declares nothing itself, and `system-status` keeps its globalService in
239
+ // `manifest.ts`. Testing only the entry text left it with no backend at all,
240
+ // so its buttons rendered but never received a metric to show.
241
+ const hasGlobalService = allSources.some((source) =>
242
+ /\bglobalService\b/.test(source),
243
+ )
244
+ if (types.size === 0 && pollerEntry === null && !hasGlobalService)
245
+ return jsonScanned
246
+ const buttonTypes: Record<string, ScannedButtonType> = {}
247
+ const entryMapRegex =
248
+ /["']([a-z0-9-]+:[a-z0-9-]+)["']\s*:\s*\{\s*(?=[^}]*frontend\s*:\s*([A-Za-z_$][A-Za-z0-9_$]*))/g
249
+ for (const src of allSources) {
250
+ for (const m of src.matchAll(entryMapRegex)) {
251
+ const type = m[1]
252
+ const exportName = m[2]
253
+ if (
254
+ type !== undefined &&
255
+ exportName !== undefined &&
256
+ !buttonTypes[type]
257
+ ) {
258
+ buttonTypes[type] = { exportName, internal: false }
259
+ }
260
+ }
261
+ }
262
+ return {
263
+ name: addonName,
264
+ types: [...types],
265
+ frontendEntry: indexFile,
266
+ publishIntervalMs,
267
+ pollerEntry,
268
+ buttonTypes,
269
+ deckTypes: {},
270
+ source: "regex",
271
+ // ponytail: prefer a dedicated global-entry.ts — index.ts is imported by
272
+ // the frontend's virtual addons/registry, so a global backend reachable
273
+ // from it puts node builtins in the browser graph and crashes the app.
274
+ // A global-entry.ts next to index.ts keeps the node side out of the
275
+ // browser bundle entirely.
276
+ globalServiceEntry: existsSync(join(addonDir, "global-entry.ts"))
277
+ ? join(addonDir, "global-entry.ts")
278
+ : hasGlobalService && indexFile !== null
279
+ ? indexFile
280
+ : null,
281
+ decks: [],
282
+ internal: true,
283
+ path: addonDir,
284
+ }
285
+ }
286
+
287
+ const scanAddonJsonManifest = async (
288
+ addonDir: string,
289
+ addonName: string,
290
+ ): Promise<ScannedAddon | null> => {
291
+ const jsonPath = join(addonDir, "sirenodeck.json")
292
+ if (!existsSync(jsonPath)) return null
293
+ let raw: unknown
294
+ try {
295
+ raw = JSON.parse(readFileSync(jsonPath, "utf8")) as unknown
296
+ } catch {
297
+ return null
298
+ }
299
+ if (raw === null || typeof raw !== "object") return null
300
+ const obj = raw as Record<string, unknown>
301
+ if (obj["kind"] !== "addon") return null
302
+ if (obj["apiVersion"] !== 1) return null
303
+ const name = obj["name"]
304
+ const entry = obj["entry"]
305
+ if (typeof name !== "string" || typeof entry !== "string") return null
306
+ if (name !== addonName) return null
307
+
308
+ const entryPath = resolvePath(addonDir, entry)
309
+ const types = new Set<string>()
310
+ const buttonTypes: Record<string, ScannedButtonType> = {}
311
+ let publishIntervalMs: number | null = null
312
+ let hasGlobalService = false
313
+ const decks: Array<{
314
+ id: string
315
+ hasTrigger: boolean
316
+ paginated: boolean
317
+ buttons: number
318
+ internal: boolean
319
+ }> = []
320
+ try {
321
+ const mod = (await import(entryPath)) as {
322
+ default?: {
323
+ buttonTypes?: Record<string, unknown>
324
+ decks?: unknown
325
+ publishIntervalMs?: number
326
+ globalService?: unknown
327
+ }
328
+ }
329
+ const exported =
330
+ mod !== null &&
331
+ typeof mod === "object" &&
332
+ "default" in mod &&
333
+ mod.default !== undefined
334
+ ? mod.default
335
+ : mod
336
+ const candidate =
337
+ exported !== null && typeof exported === "object"
338
+ ? (exported as {
339
+ buttonTypes?: Record<string, unknown>
340
+ decks?: unknown
341
+ publishIntervalMs?: number
342
+ globalService?: unknown
343
+ })
344
+ : null
345
+ if (candidate !== null) {
346
+ for (const [type, def] of Object.entries(candidate.buttonTypes ?? {})) {
347
+ types.add(type)
348
+ const d = def as Record<string, unknown> | null
349
+ const service =
350
+ d !== null && typeof d === "object"
351
+ ? (d["service"] as Record<string, unknown> | undefined)
352
+ : undefined
353
+ const internal =
354
+ service?.["internal"] === true || d?.["internal"] === true
355
+ buttonTypes[type] = { exportName: "default", internal }
356
+ }
357
+ if (typeof candidate.publishIntervalMs === "number") {
358
+ publishIntervalMs = candidate.publishIntervalMs
359
+ }
360
+ if (candidate.globalService !== undefined) {
361
+ hasGlobalService = true
362
+ }
363
+ if (candidate.decks !== null && typeof candidate.decks === "object") {
364
+ const isArray = Array.isArray(candidate.decks)
365
+ if (isArray) {
366
+ for (const entry of candidate.decks as Array<
367
+ Record<string, unknown>
368
+ >) {
369
+ if (entry === null || typeof entry !== "object") continue
370
+ // Static entry: { id: string, buttons?: [], trigger?: {...}, ... }
371
+ if (
372
+ typeof entry["id"] === "string" &&
373
+ entry["createDeck"] === undefined &&
374
+ entry["createDecks"] === undefined
375
+ ) {
376
+ const trigger = entry["trigger"] as
377
+ | Record<string, unknown>
378
+ | undefined
379
+ const hasTrigger =
380
+ trigger !== undefined &&
381
+ (trigger["process_name"] !== undefined ||
382
+ trigger["window_name"] !== undefined)
383
+ decks.push({
384
+ id: entry["id"] as string,
385
+ hasTrigger,
386
+ paginated: entry["paginated"] === true,
387
+ buttons: Array.isArray(entry["buttons"])
388
+ ? (entry["buttons"] as unknown[]).length
389
+ : 0,
390
+ internal: entry["internal"] === true,
391
+ })
392
+ continue
393
+ }
394
+ // Single-dynamic: { createDeck: fn } — id is the entry's id field
395
+ if (
396
+ typeof entry["createDeck"] === "function" &&
397
+ typeof entry["id"] === "string"
398
+ ) {
399
+ const trigger = entry["trigger"] as
400
+ | Record<string, unknown>
401
+ | undefined
402
+ const hasTrigger =
403
+ trigger !== undefined &&
404
+ (trigger["process_name"] !== undefined ||
405
+ trigger["window_name"] !== undefined)
406
+ decks.push({
407
+ id: entry["id"] as string,
408
+ hasTrigger,
409
+ paginated: entry["paginated"] === true,
410
+ buttons: 0,
411
+ internal: entry["internal"] === true,
412
+ })
413
+ continue
414
+ }
415
+ // Multi-dynamic: { createDecks: fn } — id unknown without calling it; skip
416
+ }
417
+ } else {
418
+ // Legacy Record<string, AddonDeckDefinition> form
419
+ for (const [deckId, deckDef] of Object.entries(
420
+ candidate.decks as Record<string, unknown>,
421
+ )) {
422
+ if (deckDef === null || typeof deckDef !== "object") continue
423
+ const d = deckDef as Record<string, unknown>
424
+ const trigger = d["trigger"] as Record<string, unknown> | undefined
425
+ const hasTrigger =
426
+ trigger !== undefined &&
427
+ (trigger["process_name"] !== undefined ||
428
+ trigger["window_name"] !== undefined)
429
+ const paginated = d["paginated"] === true
430
+ const buttonCount = Array.isArray(d["buttons"])
431
+ ? d["buttons"].length
432
+ : 0
433
+ const deckInternal = d["internal"] === true
434
+ decks.push({
435
+ id: deckId,
436
+ hasTrigger,
437
+ paginated,
438
+ buttons: buttonCount,
439
+ internal: deckInternal,
440
+ })
441
+ }
442
+ }
443
+ }
444
+ }
445
+ } catch {
446
+ // Fall through with empty types — caller will fall back to regex scan.
447
+ }
448
+
449
+ return {
450
+ name: addonName,
451
+ types: [...types],
452
+ frontendEntry: entryPath,
453
+ publishIntervalMs,
454
+ pollerEntry: null,
455
+ buttonTypes,
456
+ deckTypes: {},
457
+ source: "json",
458
+ // ponytail: prefer a dedicated global-entry.ts when present (mirrors the
459
+ // regex path's rule at line 153). A browser-safe manifest can't export a
460
+ // node-only globalService, so this entry file is where it lives.
461
+ globalServiceEntry: existsSync(join(addonDir, "global-entry.ts"))
462
+ ? join(addonDir, "global-entry.ts")
463
+ : hasGlobalService
464
+ ? entryPath
465
+ : null,
466
+ decks,
467
+ internal: true,
468
+ path: addonDir,
469
+ }
470
+ }
471
+
472
+ const scanBuiltinAddons = async (): Promise<ReadonlyArray<ScannedAddon>> => {
473
+ if (!existsSync(builtinDir)) {
474
+ // Never normal: without these the deck renders blank keys. Say so loudly
475
+ // rather than returning an empty list and letting the UI explain it.
476
+ process.emitWarning(
477
+ `sirenodeck: builtin addons not found at ${builtinDir} — every builtin button will render empty`,
478
+ "SirenoDeckBuiltinsMissing",
479
+ )
480
+ return []
481
+ }
482
+ const entries = readdirSync(builtinDir, { withFileTypes: true })
483
+ const out: ScannedAddon[] = []
484
+ for (const entry of entries) {
485
+ if (!entry.isDirectory()) continue
486
+ const scanned = await scanAddonDir(join(builtinDir, entry.name), entry.name)
487
+ if (scanned !== null) out.push(scanned)
488
+ }
489
+ return out
490
+ }
491
+
492
+ export interface ButtonValidationIssue {
493
+ readonly addon: string
494
+ readonly button: string
495
+ readonly message: string
496
+ }
497
+
498
+ const validateButtonConfigExport = (
499
+ addonName: string,
500
+ buttonDir: string,
501
+ buttonName: string,
502
+ ): ButtonValidationIssue | null => {
503
+ const backendPath = join(buttonDir, "backend.ts")
504
+ const configPath = join(buttonDir, "config.ts")
505
+
506
+ if (!existsSync(backendPath)) {
507
+ return {
508
+ addon: addonName,
509
+ button: buttonName,
510
+ message: "missing backend.ts",
511
+ }
512
+ }
513
+ if (!existsSync(configPath)) {
514
+ return {
515
+ addon: addonName,
516
+ button: buttonName,
517
+ message: "missing config.ts",
518
+ }
519
+ }
520
+
521
+ let backendSrc: string
522
+ try {
523
+ backendSrc = readFileSync(backendPath, "utf8")
524
+ } catch {
525
+ return {
526
+ addon: addonName,
527
+ button: buttonName,
528
+ message: "could not read backend.ts",
529
+ }
530
+ }
531
+
532
+ const usesAddonButtonService =
533
+ /import\s+type\s+\{[^}]*AddonButtonService[^}]*\}\s+from\s+["']@\/addon\/api["']/.test(
534
+ backendSrc,
535
+ )
536
+ if (usesAddonButtonService) return null
537
+
538
+ if (
539
+ !/import\s*\{[^}]*\bconfigSchema\b[^}]*\}\s*from\s*["']\.\/config(\.js)?["']/.test(
540
+ backendSrc,
541
+ )
542
+ ) {
543
+ return {
544
+ addon: addonName,
545
+ button: buttonName,
546
+ message: 'backend.ts does not import { configSchema } from "./config"',
547
+ }
548
+ }
549
+
550
+ let configSrc: string
551
+ try {
552
+ configSrc = readFileSync(configPath, "utf8")
553
+ } catch {
554
+ return {
555
+ addon: addonName,
556
+ button: buttonName,
557
+ message: "could not read config.ts",
558
+ }
559
+ }
560
+
561
+ if (!/export\s+const\s+configSchema\b/.test(configSrc)) {
562
+ return {
563
+ addon: addonName,
564
+ button: buttonName,
565
+ message: 'config.ts does not export "configSchema" as a named const',
566
+ }
567
+ }
568
+
569
+ return null
570
+ }
571
+
572
+ export const validateBuiltinButtonConfigs =
573
+ (): ReadonlyArray<ButtonValidationIssue> => {
574
+ if (!existsSync(builtinDir)) return []
575
+ const issues: ButtonValidationIssue[] = []
576
+ for (const addonEntry of readdirSync(builtinDir, { withFileTypes: true })) {
577
+ if (!addonEntry.isDirectory()) continue
578
+ const addonName = addonEntry.name
579
+ const addonDir = join(builtinDir, addonName)
580
+ const buttonsDir = join(addonDir, "buttons")
581
+ if (!existsSync(buttonsDir)) continue
582
+ for (const btnEntry of readdirSync(buttonsDir, { withFileTypes: true })) {
583
+ if (!btnEntry.isDirectory()) continue
584
+ const btnDir = join(buttonsDir, btnEntry.name)
585
+ if (!existsSync(join(btnDir, "backend.ts"))) continue
586
+ const issue = validateButtonConfigExport(
587
+ addonName,
588
+ btnDir,
589
+ btnEntry.name,
590
+ )
591
+ if (issue !== null) issues.push(issue)
592
+ }
593
+ }
594
+ return issues
595
+ }
596
+
597
+ const matchesManifest = (
598
+ publishIntervalMs: number | null,
599
+ addonName: string,
600
+ ): boolean => {
601
+ if (publishIntervalMs === null) return false
602
+ return Boolean(addonName)
603
+ }
604
+
605
+ export interface DiscoveredAddonPoller {
606
+ readonly addonName: string
607
+ readonly channels: ReadonlyArray<AddonPollerChannel>
608
+ }
609
+
610
+ export interface AddonPollerDependencies {
611
+ readonly executor?: unknown
612
+ readonly mediaProvider?: unknown
613
+ readonly brightnessProvider?: unknown
614
+ }
615
+
616
+ export const discoverAddonPollers = async (
617
+ deps: AddonPollerDependencies,
618
+ scanned: ReadonlyArray<ScannedAddon>,
619
+ ): Promise<ReadonlyArray<DiscoveredAddonPoller>> => {
620
+ const out: DiscoveredAddonPoller[] = []
621
+ for (const addon of scanned) {
622
+ if (addon.pollerEntry === null) continue
623
+ if (!matchesManifest(addon.publishIntervalMs, addon.name)) continue
624
+ try {
625
+ const mod = (await import(addon.pollerEntry)) as {
626
+ createPoller?: (deps: AddonPollerDependencies) => AddonPoller
627
+ }
628
+ if (typeof mod.createPoller !== "function") continue
629
+ const poller = mod.createPoller(deps)
630
+ if (poller.channels.length === 0) continue
631
+ out.push({ addonName: addon.name, channels: poller.channels })
632
+ } catch {
633
+ // Poller failed to load; skip.
634
+ }
635
+ }
636
+ return out
637
+ }
638
+
639
+ export { scanBuiltinAddons }