@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,441 @@
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { resolve as resolvePath, join } from "node:path"
3
+ import { pathToFileURL } from "node:url"
4
+
5
+ import { addonNpmRoot } from "@/util/cache-paths"
6
+
7
+ import type {
8
+ AddonJsonManifest,
9
+ AddonLoadIssue,
10
+ AddonManifestV1,
11
+ LoadedTheme,
12
+ } from "./api"
13
+ import type { RawAddonEntry } from "@/config/schemas"
14
+ import {
15
+ addonRootExists,
16
+ isLocalAddonSpec,
17
+ isNpmAddonSpec,
18
+ normalizeAddonEntry,
19
+ resolveLocalAddonRoot,
20
+ } from "./spec"
21
+
22
+ export interface LoadAddonsOptions {
23
+ entries: RawAddonEntry[]
24
+ configDir: string
25
+ homeDir: string
26
+ currentApiVersion: number
27
+ cacheDir?: string
28
+ projectDir?: string
29
+ globalPackageRoots?: ReadonlyArray<string>
30
+ }
31
+
32
+ export interface ResolvedExternalAddon {
33
+ manifest: AddonManifestV1
34
+ source: { kind: "local" | "npm"; specifier: string; resolvedPath: string }
35
+ // ponytail: resolved module path the bridge should import to load this
36
+ // addon's frontend + globalService. Same file exports both; one path
37
+ // serves both import slots.
38
+ entryPath: string
39
+ // ponytail: when the manifest declares `frontendEntry`, the BROWSER loads
40
+ // this instead of `entryPath`. The Node bundle has the host's UI primitives
41
+ // stubbed out (Node cannot import the host's .tsx sources), so rendering it
42
+ // in the browser draws those components as nothing. Null when the addon
43
+ // ships a single bundle, in which case callers fall back to entryPath.
44
+ browserEntryPath: string | null
45
+ }
46
+
47
+ export interface LoadAddonsResult {
48
+ addons: ResolvedExternalAddon[]
49
+ themes: LoadedTheme[]
50
+ issues: AddonLoadIssue[]
51
+ }
52
+
53
+ const recordIssue = (issues: AddonLoadIssue[], issue: AddonLoadIssue): void => {
54
+ issues.push(issue)
55
+ }
56
+
57
+ const importAddonModule = async (resolvedPath: string): Promise<unknown> => {
58
+ const url = pathToFileURL(resolvedPath).href
59
+ return import(url)
60
+ }
61
+
62
+ const readJsonManifest = (root: string): AddonJsonManifest | null => {
63
+ const manifestPath = join(root, "sirenodeck.json")
64
+ if (!existsSync(manifestPath)) return null
65
+ let raw: unknown
66
+ try {
67
+ raw = JSON.parse(readFileSync(manifestPath, "utf8")) as unknown
68
+ } catch {
69
+ return null
70
+ }
71
+ if (raw === null || typeof raw !== "object") return null
72
+ const obj = raw as Record<string, unknown>
73
+ if (obj["apiVersion"] !== 1) return null
74
+ const kind = obj["kind"]
75
+ if (kind !== "addon" && kind !== "theme") return null
76
+ const name = obj["name"]
77
+ const entry = obj["entry"]
78
+ if (typeof name !== "string" || typeof entry !== "string") return null
79
+ // Optional; ignored entirely when absent, so older manifests are unaffected.
80
+ const frontendEntry = obj["frontendEntry"]
81
+ return {
82
+ kind,
83
+ apiVersion: 1,
84
+ name,
85
+ entry,
86
+ ...(typeof frontendEntry === "string" && frontendEntry.length > 0
87
+ ? { frontendEntry }
88
+ : {}),
89
+ }
90
+ }
91
+
92
+ // ponytail: resolve the manifest's optional `frontendEntry` against the addon
93
+ // root. Returns null when the field is absent OR the file is missing, so a
94
+ // stale manifest degrades to the single-bundle behaviour instead of handing
95
+ // the browser a path that 404s.
96
+ const resolveBrowserEntry = (
97
+ root: string,
98
+ json: AddonJsonManifest,
99
+ ): string | null => {
100
+ if (json.frontendEntry === undefined) return null
101
+ const candidate = join(root, json.frontendEntry)
102
+ return existsSync(candidate) ? candidate : null
103
+ }
104
+
105
+ const resolveEntryPaths = (
106
+ root: string,
107
+ json: AddonJsonManifest,
108
+ ): { ts: string; js: string } => {
109
+ const entryExt = json.entry
110
+ const extTs = entryExt.endsWith(".ts")
111
+ ? join(root, entryExt)
112
+ : join(root, `${entryExt}.ts`)
113
+ const extJs = join(root, entryExt)
114
+ return { ts: extTs, js: extJs }
115
+ }
116
+
117
+ // ponytail: ESM-CJS interop wraps CJS exports as `mod.default`, and some
118
+ // addons also wrap the manifest under a named key (e.g. chrome-overlay uses
119
+ // `module.exports = { manifest: {...} }`). Some combine both: the CJS wrapper
120
+ // becomes `mod.default`, then chrome-overlay's `.manifest` lives one level
121
+ // down. Walk at most two levels deep — never deeper, to avoid accidental
122
+ // infinite recursion on hostile modules.
123
+ const unwrapManifestExport = (mod: unknown): unknown => {
124
+ let cur: unknown = mod
125
+ for (let i = 0; i < 2; i += 1) {
126
+ if (cur === null || typeof cur !== "object") return cur
127
+ const rec = cur as Record<string, unknown>
128
+ const candidate = rec["manifest"] ?? rec["default"]
129
+ if (
130
+ candidate !== undefined &&
131
+ typeof candidate === "object" &&
132
+ candidate !== null &&
133
+ typeof (candidate as Record<string, unknown>)["apiVersion"] ===
134
+ "number" &&
135
+ typeof (candidate as Record<string, unknown>)["name"] === "string"
136
+ ) {
137
+ return candidate
138
+ }
139
+ if (candidate !== undefined) {
140
+ cur = candidate
141
+ continue
142
+ }
143
+ return cur
144
+ }
145
+ return cur
146
+ }
147
+
148
+ const validateAndLoadEntry = async (
149
+ source: string,
150
+ tsPath: string,
151
+ jsPath: string,
152
+ issues: AddonLoadIssue[],
153
+ ): Promise<{ manifest: AddonManifestV1; entryPath: string } | null> => {
154
+ const candidatePath = existsSync(tsPath) ? tsPath : jsPath
155
+ if (!existsSync(candidatePath)) {
156
+ recordIssue(issues, {
157
+ level: "error",
158
+ source,
159
+ message: `Entry file not found: tried ${tsPath} and ${jsPath}`,
160
+ })
161
+ return null
162
+ }
163
+ let mod: unknown
164
+ try {
165
+ mod = await importAddonModule(candidatePath)
166
+ } catch (err) {
167
+ recordIssue(issues, {
168
+ level: "error",
169
+ source,
170
+ message: `Failed to import addon entry: ${err instanceof Error ? err.message : String(err)}`,
171
+ })
172
+ return null
173
+ }
174
+ const exported = unwrapManifestExport(mod)
175
+ if (
176
+ exported === null ||
177
+ typeof exported !== "object" ||
178
+ typeof (exported as Record<string, unknown>)["apiVersion"] !== "number" ||
179
+ typeof (exported as Record<string, unknown>)["name"] !== "string"
180
+ ) {
181
+ const gotKeys =
182
+ exported !== null && typeof exported === "object"
183
+ ? Object.keys(exported as Record<string, unknown>).join(",")
184
+ : typeof exported
185
+ recordIssue(issues, {
186
+ level: "error",
187
+ source,
188
+ message: `Entry at ${candidatePath} did not export a valid AddonManifestV1 (apiVersion + name required). Got keys: [${gotKeys}]`,
189
+ })
190
+ return null
191
+ }
192
+ const manifest = exported as AddonManifestV1
193
+ if (Object.keys(manifest.buttonTypes ?? {}).length === 0 && !manifest.decks) {
194
+ recordIssue(issues, {
195
+ level: "error",
196
+ source,
197
+ message: `Addon ${source} defines no buttons or decks`,
198
+ })
199
+ return null
200
+ }
201
+ return { manifest, entryPath: candidatePath }
202
+ }
203
+
204
+ const loadLocalAddon = async (
205
+ source: string,
206
+ configDir: string,
207
+ homeDir: string,
208
+ issues: AddonLoadIssue[],
209
+ currentApi: number,
210
+ ): Promise<ResolvedExternalAddon | null> => {
211
+ const root = resolveLocalAddonRoot(source, configDir, homeDir)
212
+ if (!isLocalAddonSpec(source)) {
213
+ recordIssue(issues, {
214
+ level: "error",
215
+ source,
216
+ message: `Spec does not look like a local path: ${source}`,
217
+ })
218
+ return null
219
+ }
220
+ if (!addonRootExists(root)) {
221
+ recordIssue(issues, {
222
+ level: "error",
223
+ source,
224
+ message: `Local addon path does not exist: ${root}`,
225
+ })
226
+ return null
227
+ }
228
+ const json = readJsonManifest(root)
229
+ if (json === null) {
230
+ recordIssue(issues, {
231
+ level: "error",
232
+ source,
233
+ message: `Missing or invalid sirenodeck.json at ${root}`,
234
+ })
235
+ return null
236
+ }
237
+ if (json.kind === "theme") {
238
+ // ponytail: 3rd-party themes are loaded via the themes loader, not the
239
+ // addons loader. A theme author drops their package into
240
+ // `packages/cli/src/themes/<name>/` (or wires a custom themeDir via
241
+ // the CLI) and the discovery loop picks it up. Rejecting theme-kind
242
+ // entries from addons[] keeps the contracts clean and surfaces a
243
+ // clear error to anyone trying to put a theme in the wrong place.
244
+ recordIssue(issues, {
245
+ level: "warning",
246
+ source,
247
+ message: `Theme '${json.name}' declared via addons[] — drop themes into packages/cli/src/themes/<name>/ or pass --theme-dir; they aren't addons.`,
248
+ })
249
+ return null
250
+ }
251
+ if (json.apiVersion !== currentApi) {
252
+ recordIssue(issues, {
253
+ level: "warning",
254
+ source,
255
+ message: `Addon apiVersion mismatch: expected ${currentApi}, got ${json.apiVersion}`,
256
+ })
257
+ }
258
+ const { ts, js } = resolveEntryPaths(root, json)
259
+ const loaded = await validateAndLoadEntry(source, ts, js, issues)
260
+ if (!loaded) return null
261
+ return {
262
+ manifest: loaded.manifest,
263
+ source: { kind: "local", specifier: source, resolvedPath: root },
264
+ entryPath: loaded.entryPath,
265
+ browserEntryPath: resolveBrowserEntry(root, json),
266
+ }
267
+ }
268
+
269
+ interface ParsedSpecifier {
270
+ packageName: string
271
+ version: string | null
272
+ }
273
+
274
+ // npm package name spec — scoped names + optional @version
275
+ const NPM_SPECIFIER =
276
+ /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-z0-9][a-z0-9._-]*)?$/i
277
+ const NPM_VERSION = /^[a-z0-9][a-z0-9._-]*$/i
278
+
279
+ const parseNpmSpecifier = (spec: string): ParsedSpecifier | null => {
280
+ if (typeof spec !== "string" || !NPM_SPECIFIER.test(spec)) return null
281
+ const atIdx = spec.lastIndexOf("@")
282
+ if (atIdx <= 0) {
283
+ return { packageName: spec, version: null }
284
+ }
285
+ const packageName = spec.slice(0, atIdx)
286
+ const version = spec.slice(atIdx + 1)
287
+ if (version.length === 0 || !NPM_VERSION.test(version)) return null
288
+ return { packageName, version }
289
+ }
290
+
291
+ const readInstalledPackageJson = (
292
+ installPath: string,
293
+ ): {
294
+ name: string
295
+ version: string
296
+ apiVersion: number
297
+ } | null => {
298
+ const pkgPath = resolvePath(installPath, "package.json")
299
+ if (!existsSync(pkgPath)) return null
300
+ let raw: unknown
301
+ try {
302
+ raw = JSON.parse(readFileSync(pkgPath, "utf8")) as unknown
303
+ } catch {
304
+ return null
305
+ }
306
+ if (raw === null || typeof raw !== "object") return null
307
+ const obj = raw as Record<string, unknown>
308
+ const name = typeof obj["name"] === "string" ? (obj["name"] as string) : null
309
+ const version =
310
+ typeof obj["version"] === "string" ? (obj["version"] as string) : null
311
+ const apiVersion = obj["sirenoAddonApiVersion"]
312
+ if (name === null || version === null) return null
313
+ if (typeof apiVersion !== "number") return null
314
+ return { name, version, apiVersion }
315
+ }
316
+
317
+ const loadNpmAddon = async (
318
+ source: string,
319
+ cacheDir: string,
320
+ projectDir: string | undefined,
321
+ globalPackageRoots: ReadonlyArray<string>,
322
+ issues: AddonLoadIssue[],
323
+ currentApi: number,
324
+ ): Promise<ResolvedExternalAddon | null> => {
325
+ const parsed = parseNpmSpecifier(source)
326
+ if (parsed === null) {
327
+ recordIssue(issues, {
328
+ level: "error",
329
+ source,
330
+ message: "Invalid npm specifier",
331
+ })
332
+ return null
333
+ }
334
+
335
+ const roots = [
336
+ ...(projectDir !== undefined ? [join(projectDir, "node_modules")] : []),
337
+ ...globalPackageRoots,
338
+ addonNpmRoot(cacheDir),
339
+ ]
340
+ const installPath = roots
341
+ .map((root) => join(root, parsed.packageName))
342
+ .find((path) => {
343
+ const pkg = readInstalledPackageJson(path)
344
+ return (
345
+ pkg !== null &&
346
+ (parsed.version === null || pkg.version === parsed.version)
347
+ )
348
+ })
349
+ const pkg =
350
+ installPath === undefined ? null : readInstalledPackageJson(installPath)
351
+
352
+ if (installPath === undefined) {
353
+ recordIssue(issues, {
354
+ level: "error",
355
+ source,
356
+ message: `Package '${source}' is not installed. Run 'sirenodeck install ${source}'.`,
357
+ })
358
+ return null
359
+ }
360
+ if (pkg === null) return null
361
+ const json = readJsonManifest(installPath)
362
+ if (json === null) {
363
+ recordIssue(issues, {
364
+ level: "error",
365
+ source,
366
+ message: `Missing or invalid sirenodeck.json at ${installPath}`,
367
+ })
368
+ return null
369
+ }
370
+ if (json.apiVersion !== currentApi) {
371
+ recordIssue(issues, {
372
+ level: "warning",
373
+ source,
374
+ message: `Addon apiVersion mismatch: expected ${currentApi}, got ${json.apiVersion}`,
375
+ })
376
+ }
377
+
378
+ const { ts, js } = resolveEntryPaths(installPath, json)
379
+ const loaded = await validateAndLoadEntry(source, ts, js, issues)
380
+ if (!loaded) return null
381
+ return {
382
+ manifest: loaded.manifest,
383
+ source: { kind: "npm", specifier: source, resolvedPath: installPath },
384
+ entryPath: loaded.entryPath,
385
+ browserEntryPath: resolveBrowserEntry(installPath, json),
386
+ }
387
+ }
388
+
389
+ export const loadAddons = async ({
390
+ entries,
391
+ configDir,
392
+ homeDir,
393
+ currentApiVersion,
394
+ cacheDir,
395
+ projectDir,
396
+ globalPackageRoots = [],
397
+ }: LoadAddonsOptions): Promise<LoadAddonsResult> => {
398
+ const issues: AddonLoadIssue[] = []
399
+ const addons: ResolvedExternalAddon[] = []
400
+ const themes: LoadedTheme[] = []
401
+ for (const rawEntry of entries) {
402
+ const entry = normalizeAddonEntry(rawEntry)
403
+ if (!entry.enabled) {
404
+ recordIssue(issues, {
405
+ level: "info",
406
+ source: entry.source,
407
+ message: "Addon disabled in config",
408
+ })
409
+ continue
410
+ }
411
+ if (entry.isLocal || isLocalAddonSpec(entry.source)) {
412
+ const loaded = await loadLocalAddon(
413
+ entry.source,
414
+ configDir,
415
+ homeDir,
416
+ issues,
417
+ currentApiVersion,
418
+ )
419
+ if (loaded) addons.push(loaded)
420
+ continue
421
+ }
422
+ if (isNpmAddonSpec(entry.source) && cacheDir !== undefined) {
423
+ const loaded = await loadNpmAddon(
424
+ entry.source,
425
+ cacheDir,
426
+ projectDir,
427
+ entry.global ? globalPackageRoots : [],
428
+ issues,
429
+ currentApiVersion,
430
+ )
431
+ if (loaded) addons.push(loaded)
432
+ continue
433
+ }
434
+ recordIssue(issues, {
435
+ level: "error",
436
+ source: entry.source,
437
+ message: `Unknown addon spec: '${entry.source}'. Use a local path (./my-addon) or an npm specifier (my-addon, @scope/my-addon, my-addon@1.2.3).`,
438
+ })
439
+ }
440
+ return { addons, themes, issues }
441
+ }
@@ -0,0 +1,230 @@
1
+ import type {
2
+ AddonButtonTypeDef,
3
+ AddonDeckDefinition,
4
+ AddonManifestV1,
5
+ LoadedTheme,
6
+ } from "./api"
7
+ import type { GestureKind } from "@/core/gesture-state"
8
+
9
+ export class AddonRegistry {
10
+ private readonly addonsByName = new Map<string, AddonManifestV1>()
11
+ private readonly buttonsByType = new Map<
12
+ string,
13
+ { addonName: string; def: AddonButtonTypeDef }
14
+ >()
15
+ private readonly decksByType = new Map<
16
+ string,
17
+ { addonName: string; def: AddonDeckDefinition }
18
+ >()
19
+ private readonly themesByName = new Map<string, LoadedTheme>()
20
+
21
+ load(manifest: AddonManifestV1): void {
22
+ const name = manifest.name
23
+ if (this.addonsByName.has(name)) {
24
+ throw new Error(`Duplicate addon name: ${name}`)
25
+ }
26
+
27
+ // ponytail: hard cutover from legacy Record<key, {type, createDecks}>
28
+ // shape. Addons shipping the old JS shape (e.g. the 3 Phase-10 addons
29
+ // before phase-11 migration) hit this branch with a clear migration
30
+ // hint. TypeScript-shape manifests fail at type-check, never runtime.
31
+ if (
32
+ manifest.decks !== undefined &&
33
+ !Array.isArray(manifest.decks) &&
34
+ typeof manifest.decks === "object" &&
35
+ manifest.decks !== null
36
+ ) {
37
+ throw new Error(
38
+ `Addon '${name}' uses legacy decks format (Record keyed by '<addon>:<deck>'). ` +
39
+ `Migrate to an array: [{ id: '<addon>:<deck>', ...static fields }, ...]. ` +
40
+ `See phase-11 docs.`,
41
+ )
42
+ }
43
+
44
+ for (const buttonType of Object.keys(manifest.buttonTypes)) {
45
+ if (!buttonType.startsWith(`${name}:`)) {
46
+ throw new Error(
47
+ `Button type '${buttonType}' in addon '${name}' must be prefixed with '${name}:'`,
48
+ )
49
+ }
50
+ }
51
+
52
+ for (const [buttonType, def] of Object.entries(manifest.buttonTypes)) {
53
+ const service = def.service
54
+ if (!service) continue
55
+ const hasOnTap = typeof service.onTap === "function"
56
+ const hasOnDblTap = typeof service.onDblTap === "function"
57
+ const hasOnHold = typeof service.onHold === "function"
58
+ if (!hasOnTap && !hasOnDblTap && !hasOnHold) continue
59
+ const allowed = service.gestureHandlers ?? []
60
+ const toStrip: string[] = []
61
+ if (hasOnTap && !allowed.includes("tap" as GestureKind))
62
+ toStrip.push("onTap")
63
+ if (hasOnDblTap && !allowed.includes("dbl-tap" as GestureKind))
64
+ toStrip.push("onDblTap")
65
+ if (hasOnHold && !allowed.includes("hold" as GestureKind))
66
+ toStrip.push("onHold")
67
+ if (toStrip.length > 0) {
68
+ console.warn(
69
+ `[sirenodeck] addon "${name}" button "${buttonType}" declares [${toStrip.join(", ")}] ` +
70
+ `but not in gestureHandlers (default-deny). Stripping undeclared handlers. ` +
71
+ `Add gestureHandlers: [${toStrip.map((h) => `'${h.replace("on", "").toLowerCase()}'`).join(", ")}] to silence this.`,
72
+ )
73
+ }
74
+ }
75
+
76
+ this.addonsByName.set(name, manifest)
77
+ for (const [buttonType, def] of Object.entries(manifest.buttonTypes)) {
78
+ if (this.buttonsByType.has(buttonType)) {
79
+ throw new Error(
80
+ `Duplicate button type '${buttonType}' in addon ${name}`,
81
+ )
82
+ }
83
+ this.buttonsByType.set(buttonType, { addonName: name, def })
84
+ if (buttonType === `${name}:${name}`) {
85
+ this.buttonsByType.set(name, { addonName: name, def })
86
+ }
87
+ }
88
+
89
+ for (const entry of manifest.decks ?? []) {
90
+ // ponytail: three shapes — static, single-dynamic, multi-dynamic. The
91
+ // id (or each key returned from createDecks) becomes the deck-type key.
92
+ // Validation: ids must start with `<addon>:` prefix.
93
+ const prefix = `${name}:`
94
+ if (
95
+ typeof entry.createDeck === "function" &&
96
+ typeof entry.createDecks === "function"
97
+ ) {
98
+ throw new Error(
99
+ `Addon '${name}' deck entry has both createDeck and createDecks; choose one.`,
100
+ )
101
+ }
102
+ if (typeof entry.createDecks === "function") {
103
+ const def: AddonDeckDefinition = {
104
+ type: `${name}:__multi__`,
105
+ createDecks: entry.createDecks,
106
+ }
107
+ const syntheticKey = `${name}:__multi__`
108
+ if (this.decksByType.has(syntheticKey)) {
109
+ throw new Error(`Duplicate deck '${syntheticKey}' in addon ${name}`)
110
+ }
111
+ this.decksByType.set(syntheticKey, { addonName: name, def })
112
+ continue
113
+ }
114
+ if (typeof entry.createDeck === "function") {
115
+ if (entry.id === undefined || entry.id.length === 0) {
116
+ throw new Error(
117
+ `Addon '${name}' deck entry with createDeck must have an id.`,
118
+ )
119
+ }
120
+ if (!entry.id.startsWith(prefix)) {
121
+ throw new Error(
122
+ `Deck '${entry.id}' in addon '${name}' must be prefixed with '${prefix}'.`,
123
+ )
124
+ }
125
+ const def: AddonDeckDefinition = {
126
+ type: entry.id,
127
+ createDecks: (ctx) => ({ [entry.id]: entry.createDeck(ctx) }),
128
+ }
129
+ if (this.decksByType.has(entry.id)) {
130
+ throw new Error(`Duplicate deck '${entry.id}' in addon ${name}`)
131
+ }
132
+ this.decksByType.set(entry.id, { addonName: name, def })
133
+ continue
134
+ }
135
+ // ponytail: static — the entry IS the deck.
136
+ if (entry.id === undefined || entry.id.length === 0) {
137
+ throw new Error(`Addon '${name}' static deck entry must have an id.`)
138
+ }
139
+ if (!entry.id.startsWith(prefix)) {
140
+ throw new Error(
141
+ `Deck '${entry.id}' in addon '${name}' must be prefixed with '${prefix}'.`,
142
+ )
143
+ }
144
+ const def: AddonDeckDefinition = {
145
+ type: entry.id,
146
+ createDecks: () => ({ [entry.id]: entry }),
147
+ }
148
+ if (this.decksByType.has(entry.id)) {
149
+ throw new Error(`Duplicate deck '${entry.id}' in addon ${name}`)
150
+ }
151
+ this.decksByType.set(entry.id, { addonName: name, def })
152
+ }
153
+ }
154
+
155
+ getAddon(name: string): AddonManifestV1 | undefined {
156
+ return this.addonsByName.get(name)
157
+ }
158
+
159
+ listAddons(): AddonManifestV1[] {
160
+ return Array.from(this.addonsByName.values())
161
+ }
162
+
163
+ getButtonType(
164
+ type: string,
165
+ ): { addonName: string; def: AddonButtonTypeDef } | undefined {
166
+ return this.buttonsByType.get(type)
167
+ }
168
+
169
+ getDeckType(
170
+ type: string,
171
+ ): { addonName: string; def: AddonDeckDefinition } | undefined {
172
+ return this.decksByType.get(type)
173
+ }
174
+
175
+ hasButtonType(type: string): boolean {
176
+ return this.buttonsByType.has(type)
177
+ }
178
+
179
+ hasDeckType(type: string): boolean {
180
+ return this.decksByType.has(type)
181
+ }
182
+
183
+ listDeckTypes(): ReadonlyArray<{ id: string; addonName: string }> {
184
+ return [...this.decksByType.entries()].map(([id, v]) => ({
185
+ id,
186
+ addonName: v.addonName,
187
+ }))
188
+ }
189
+
190
+ loadTheme(theme: LoadedTheme): void {
191
+ if (this.themesByName.has(theme.name)) {
192
+ throw new Error(`Duplicate theme name: ${theme.name}`)
193
+ }
194
+ this.themesByName.set(theme.name, theme)
195
+ }
196
+
197
+ getTheme(name: string): LoadedTheme | undefined {
198
+ return this.themesByName.get(name)
199
+ }
200
+
201
+ listThemes(): LoadedTheme[] {
202
+ return Array.from(this.themesByName.values())
203
+ }
204
+
205
+ hasTheme(name: string): boolean {
206
+ return this.themesByName.has(name)
207
+ }
208
+
209
+ resolveActiveTheme(name: string | undefined): LoadedTheme {
210
+ const target = name ?? "default"
211
+ const theme = this.themesByName.get(target)
212
+ if (!theme) {
213
+ const available = this.listThemes()
214
+ .map((t) => t.name)
215
+ .sort()
216
+ .join(", ")
217
+ throw new Error(
218
+ `Theme '${target}' is not registered. Available themes: ${available || "(none)"}`,
219
+ )
220
+ }
221
+ return theme
222
+ }
223
+
224
+ reset(): void {
225
+ this.addonsByName.clear()
226
+ this.buttonsByType.clear()
227
+ this.decksByType.clear()
228
+ this.themesByName.clear()
229
+ }
230
+ }