@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,223 @@
1
+ import { z } from "zod"
2
+
3
+ import { isIconSource } from "@/core/icon-source"
4
+
5
+ export const TriggerSchema = z
6
+ .object({
7
+ process_name: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
8
+ window_name: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
9
+ })
10
+ .strict()
11
+ .refine((t) => t.process_name !== undefined || t.window_name !== undefined, {
12
+ message: "trigger requires at least one of `process_name` or `window_name`",
13
+ })
14
+
15
+ export const ButtonActionsSchema = z
16
+ .object({
17
+ tap: z.string().min(1).optional(),
18
+ dbltap: z.string().min(1).optional(),
19
+ hold: z.string().min(1).optional(),
20
+ })
21
+ .strict()
22
+
23
+ export type ButtonActions = z.infer<typeof ButtonActionsSchema>
24
+
25
+ /**
26
+ * Runtime icon source. Accepted shapes:
27
+ * - `icon://<name>` — Lucide icon by name (resolved at render time)
28
+ * - `asset://<id>` — pre-resolved asset id from the registry
29
+ * - a single emoji (Presentation or base+VS16 like ✈️)
30
+ *
31
+ * Anything else is rejected at config-load time so typos surface before
32
+ * the daemon starts. The runtime also has a fallback path (alert-circle
33
+ * Lucide icon) so an invalid source that's slipped through can't crash
34
+ * the render tree.
35
+ */
36
+ export const IconSourceSchema = z.string().min(1).refine(isIconSource, {
37
+ message:
38
+ "icon must be icon://<name>, asset://<id>, or a single emoji (one Presentation character or one base+VS16 like ✈️)",
39
+ })
40
+
41
+ export const ButtonConfigSchema = z.record(z.string(), z.unknown()).optional()
42
+
43
+ /**
44
+ * Closed core set of per-button hue names. Themes declare which of these
45
+ * (or which additional names like riptide's `neon-pink`) they support
46
+ * by adding entries to `variants.<name>` in their manifest. Themes are
47
+ * authoritative — this list is a convention hint, not a constraint on
48
+ * the theme. Names not declared in the active theme fall back to
49
+ * "default" with a console.warn at render time (ButtonFrame).
50
+ */
51
+ export const ButtonColorSchema = z.enum([
52
+ "blue",
53
+ "green",
54
+ "purple",
55
+ "cyan",
56
+ "magenta",
57
+ "amber",
58
+ "lime",
59
+ ])
60
+
61
+ export const ButtonDefSchema = z
62
+ .object({
63
+ position: z.number().int().nonnegative().optional(),
64
+ type: z.string().min(1),
65
+ config: ButtonConfigSchema,
66
+ accent: z.string().optional(),
67
+ background: z.string().optional(),
68
+ actions: ButtonActionsSchema.optional(),
69
+ /**
70
+ * Variant token name. Resolved by ButtonFrame against the active
71
+ * theme's CSS vars at render time. Themes may declare extra
72
+ * variants beyond the 5-required set; users pick from whatever the
73
+ * active theme supports. Unknown names fall back to "default" with
74
+ * a console.warn.
75
+ */
76
+ variant: z.string().min(1).optional(),
77
+ /**
78
+ * Per-button hue customisation. Themes declare a `variants.<name>`
79
+ * entry for each buttonColor they support. See ButtonColorSchema
80
+ * for the closed core set; themes may add their own names
81
+ * (e.g. riptide's `neon-pink`) and the runtime accepts them — but
82
+ * this Zod schema restricts to the core set so typos fail-fast at
83
+ * config load.
84
+ */
85
+ buttonColor: ButtonColorSchema.optional(),
86
+ label: z.string().optional(),
87
+ icon: IconSourceSchema.optional(),
88
+ iconUrl: z.string().min(1).optional(),
89
+ size: z.string().optional(),
90
+ textSize: z.string().optional(),
91
+ borderRadius: z.string().optional(),
92
+ addon: z.string().optional(),
93
+ action: z.string().optional(),
94
+ })
95
+ .strict()
96
+
97
+ export const ButtonEntrySchema = z.union([ButtonDefSchema, z.string()])
98
+
99
+ export const DeckDefSchema = z
100
+ .object({
101
+ name: z.string().min(1).optional(),
102
+ label: z.string().min(1).optional(),
103
+ columns: z.number().int().positive().optional(),
104
+ rows: z.number().int().positive().optional(),
105
+ icon: IconSourceSchema.optional(),
106
+ background: z.string().min(1).optional(),
107
+ paginated: z.boolean().optional(),
108
+ trigger: TriggerSchema.optional(),
109
+ autoShow: z.boolean().optional(),
110
+ buttons: z.array(ButtonEntrySchema),
111
+ })
112
+ .strict()
113
+
114
+ export const UserDeckCreateSchema = z
115
+ .object({
116
+ id: z.string().min(1),
117
+ name: z.string().min(1).optional(),
118
+ icon: IconSourceSchema.optional(),
119
+ background: z.string().min(1).optional(),
120
+ paginated: z.boolean().optional(),
121
+ })
122
+ .strict()
123
+
124
+ export const UserDeckUpdateSchema = z
125
+ .object({
126
+ name: z.string().min(1).optional(),
127
+ icon: IconSourceSchema.nullable().optional(),
128
+ background: z.string().min(1).nullable().optional(),
129
+ paginated: z.boolean().optional(),
130
+ trigger: TriggerSchema.nullable().optional(),
131
+ autoShow: z.boolean().optional(),
132
+ })
133
+ .strict()
134
+
135
+ /**
136
+ * Phase 11: per-deck overrides for an addon-deck generated by `createDeck(s)`.
137
+ * The deck id is the key in the parent record. Field-level overrides
138
+ * (`autoShow`/`name`/`icon`/`trigger`) apply on top of the generated
139
+ * deck; `config` is forwarded to `createDeck(s)({config})` as an
140
+ * additive merge over the addon's default config.
141
+ */
142
+ export const AddonDeckOverrideSchema = z
143
+ .object({
144
+ autoShow: z.boolean().optional(),
145
+ name: z.string().min(1).optional(),
146
+ icon: IconSourceSchema.optional(),
147
+ trigger: TriggerSchema.optional(),
148
+ config: z.record(z.string(), z.unknown()).optional(),
149
+ })
150
+ .strict()
151
+
152
+ export const AddonDeckDefaultsSchema = z
153
+ .object({
154
+ autoShow: z.boolean().optional(),
155
+ })
156
+ .strict()
157
+
158
+ /**
159
+ * Phase 11: addon-config block under `addons[i]`. Hosts three shape variants:
160
+ * - `defaults:` addon-wide deck defaults (applied before per-deck overrides)
161
+ * - `decks:` record keyed by addon deck id, with per-deck overrides
162
+ * - addon-wide opaque config (anything else; merged into `createDeck(s)({config})`)
163
+ */
164
+ export const AddonConfigSchema = z
165
+ .object({
166
+ defaults: AddonDeckDefaultsSchema.optional(),
167
+ decks: z.record(z.string(), AddonDeckOverrideSchema).optional(),
168
+ })
169
+ .strict()
170
+
171
+ export const AddonEntrySchema = z.union([
172
+ z.string().min(1),
173
+ z
174
+ .object({
175
+ src: z.string().min(1),
176
+ enabled: z.boolean().optional(),
177
+ global: z.boolean().optional(),
178
+ config: AddonConfigSchema.optional(),
179
+ })
180
+ .strict(),
181
+ ])
182
+
183
+ export const LoggingSchema = z
184
+ .object({
185
+ level: z
186
+ .enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"])
187
+ .optional(),
188
+ verbose: z.boolean().optional(),
189
+ })
190
+ .strict()
191
+ .optional()
192
+
193
+ export const LockSchema = z
194
+ .object({
195
+ buttons: z.array(ButtonDefSchema).optional(),
196
+ })
197
+ .strict()
198
+ .optional()
199
+
200
+ export const ThemeEntrySchema = z.union([
201
+ z.string().min(1),
202
+ z.object({ src: z.string().min(1), global: z.boolean().optional() }).strict(),
203
+ ])
204
+
205
+ export const RawConfigSchema = z
206
+ .object({
207
+ theme: ThemeEntrySchema.optional(),
208
+ logging: LoggingSchema,
209
+ decks: z.record(z.string(), DeckDefSchema),
210
+ addons: z.array(AddonEntrySchema).optional(),
211
+ lock: LockSchema,
212
+ })
213
+ .strict()
214
+
215
+ export type RawButtonDef = z.infer<typeof ButtonDefSchema>
216
+ export type RawButtonEntry = z.infer<typeof ButtonEntrySchema>
217
+ export type RawDeckDef = z.infer<typeof DeckDefSchema>
218
+ export type UserDeckCreate = z.infer<typeof UserDeckCreateSchema>
219
+ export type UserDeckUpdate = z.infer<typeof UserDeckUpdateSchema>
220
+ export type RawAddonEntry = z.infer<typeof AddonEntrySchema>
221
+ export type RawConfig = z.infer<typeof RawConfigSchema>
222
+ export type TriggerDef = z.infer<typeof TriggerSchema>
223
+ export type ThemeEntry = z.infer<typeof ThemeEntrySchema>
@@ -0,0 +1,254 @@
1
+ import type { AddonRegistry } from "@/addon/registry"
2
+ import { z } from "zod"
3
+ import { isSystemButtonType } from "@/deck/system-buttons/types"
4
+ import type { RawButtonDef, RawConfig } from "./schemas"
5
+
6
+ export interface BootstrapIssue {
7
+ level: "error" | "warning"
8
+ path: string
9
+ message: string
10
+ }
11
+
12
+ export interface BootstrapResult {
13
+ issues: BootstrapIssue[]
14
+ }
15
+
16
+ export type ValidationReason =
17
+ | "unknown-type"
18
+ | "internal-type"
19
+ | "malformed-config"
20
+ | "duplicate-position"
21
+ | "missing-main-deck"
22
+
23
+ export interface FullValidationIssue {
24
+ level: "error" | "warning"
25
+ path: string
26
+ message: string
27
+ reason?: ValidationReason
28
+ deckId?: string
29
+ position?: number
30
+ }
31
+
32
+ export interface FullValidationResult {
33
+ issues: FullValidationIssue[]
34
+ }
35
+
36
+ const reportDuplicatePositions = (
37
+ deckId: string,
38
+ deck: RawConfig["decks"][string],
39
+ ): BootstrapIssue[] => {
40
+ const seen = new Map<number, number>()
41
+ const issues: BootstrapIssue[] = []
42
+ deck.buttons.forEach((btn, index) => {
43
+ if (typeof btn === "string") return
44
+ if (btn.position === undefined) return
45
+ const prev = seen.get(btn.position)
46
+ if (prev !== undefined) {
47
+ issues.push({
48
+ level: "warning",
49
+ path: `decks.${deckId}.buttons[${index}]`,
50
+ message: `Duplicate position ${btn.position} (also at index ${prev})`,
51
+ })
52
+ } else {
53
+ seen.set(btn.position, index)
54
+ }
55
+ })
56
+ return issues
57
+ }
58
+
59
+ export const validateBootstrap = (config: RawConfig): BootstrapResult => {
60
+ const issues: BootstrapIssue[] = []
61
+ if (!("main" in config.decks)) {
62
+ issues.push({
63
+ level: "warning",
64
+ path: "decks",
65
+ message:
66
+ "Missing required `main` deck — synthetic main deck will be created",
67
+ })
68
+ }
69
+ for (const [id, deck] of Object.entries(config.decks)) {
70
+ issues.push(...reportDuplicatePositions(id, deck))
71
+ }
72
+ return { issues }
73
+ }
74
+
75
+ export const isBootstrapValid = (result: BootstrapResult): boolean =>
76
+ result.issues.every((i) => i.level !== "error")
77
+
78
+ export const formatBootstrapIssues = (issues: BootstrapIssue[]): string =>
79
+ issues
80
+ .map((issue) => {
81
+ const tag = issue.level === "error" ? "error" : "warning"
82
+ return `[${tag}] ${issue.path}: ${issue.message}`
83
+ })
84
+ .join("\n")
85
+
86
+ export interface ConfigSchemaIssue {
87
+ readonly path: ReadonlyArray<string | number>
88
+ readonly message: string
89
+ }
90
+
91
+ export interface ButtonValidationResult {
92
+ readonly issues: FullValidationIssue[]
93
+ readonly schemaIssues: ConfigSchemaIssue[]
94
+ }
95
+
96
+ export const validateButton = (
97
+ btn: RawButtonDef,
98
+ registry: AddonRegistry,
99
+ path: string,
100
+ deckId?: string,
101
+ position?: number,
102
+ ): ButtonValidationResult => {
103
+ const base = { deckId, position }
104
+ const issues: FullValidationIssue[] = []
105
+ if (!registry.hasButtonType(btn.type)) {
106
+ issues.push({
107
+ level: "error",
108
+ path: `${path}.type`,
109
+ message: `Unknown button type: ${btn.type}`,
110
+ reason: "unknown-type",
111
+ ...base,
112
+ })
113
+ return { issues, schemaIssues: [] }
114
+ }
115
+ const def = registry.getButtonType(btn.type)!
116
+ if (isSystemButtonType(btn.type) || def.def.service.internal === true) {
117
+ issues.push({
118
+ level: "error",
119
+ path: `${path}.type`,
120
+ message: `Internal button type ${btn.type} cannot be used in user config`,
121
+ reason: "internal-type",
122
+ ...base,
123
+ })
124
+ return { issues, schemaIssues: [] }
125
+ }
126
+ const schema = def.def.service.configSchema as
127
+ | {
128
+ safeParse: (input: unknown) => {
129
+ success: boolean
130
+ error?: {
131
+ issues: Array<{ path: Array<string | number>; message: string }>
132
+ }
133
+ }
134
+ }
135
+ | undefined
136
+ if (schema === undefined) return { issues, schemaIssues: [] }
137
+ const parseResult = schema.safeParse(btn.config ?? {})
138
+ if (!parseResult.success) {
139
+ const schemaIssues = parseResult.error?.issues ?? []
140
+ for (const issue of schemaIssues) {
141
+ issues.push({
142
+ level: "error",
143
+ path: `${path}.config.${[...issue.path].join(".")}`,
144
+ message: issue.message,
145
+ reason: "malformed-config",
146
+ ...base,
147
+ })
148
+ }
149
+ return { issues, schemaIssues }
150
+ }
151
+ return { issues, schemaIssues: [] }
152
+ }
153
+
154
+ export const serializeButtonSchemas = (
155
+ registry: AddonRegistry,
156
+ ): Record<string, Record<string, unknown>> => {
157
+ const schemas: Record<string, Record<string, unknown>> = {}
158
+ for (const addon of registry.listAddons()) {
159
+ for (const [type, definition] of Object.entries(addon.buttonTypes)) {
160
+ const schema = definition.service?.configSchema
161
+ if (schema === undefined) continue
162
+ try {
163
+ schemas[type] = z.toJSONSchema(schema as never, {
164
+ io: "input",
165
+ unrepresentable: "any",
166
+ }) as Record<string, unknown>
167
+ } catch {
168
+ // Some external addons expose only a safeParse-compatible schema.
169
+ }
170
+ }
171
+ }
172
+ return schemas
173
+ }
174
+
175
+ // ponytail: deckId/position lets the runtime replace the button in-place
176
+ // without a second validation pass or path-string parsing.
177
+
178
+ export const validateFull = (
179
+ config: RawConfig,
180
+ registry: AddonRegistry,
181
+ ): FullValidationResult => {
182
+ const issues: FullValidationIssue[] = []
183
+ for (const [deckId, deck] of Object.entries(config.decks)) {
184
+ deck.buttons.forEach((btn, index) => {
185
+ if (typeof btn === "string") return
186
+ const path = `decks.${deckId}.buttons[${index}]`
187
+ issues.push(
188
+ ...validateButton(btn, registry, path, deckId, btn.position).issues,
189
+ )
190
+ })
191
+ }
192
+ return { issues }
193
+ }
194
+
195
+ export interface PerButtonValidationV2 {
196
+ deckId: string
197
+ buttonIndex: number
198
+ button: RawButtonDef
199
+ buttonId: string
200
+ position: number | undefined
201
+ path: string
202
+ issues: FullValidationIssue[]
203
+ schemaIssues: ConfigSchemaIssue[]
204
+ }
205
+
206
+ export interface PerDeckValidationV2 {
207
+ issues: FullValidationIssue[]
208
+ perButton: PerButtonValidationV2[]
209
+ }
210
+
211
+ export const validatePerDeck = (
212
+ config: RawConfig,
213
+ registry: AddonRegistry,
214
+ ): PerDeckValidationV2 => {
215
+ const issues: FullValidationIssue[] = []
216
+ const perButton: PerButtonValidationV2[] = []
217
+ for (const [deckId, deck] of Object.entries(config.decks)) {
218
+ deck.buttons.forEach((btn, index) => {
219
+ if (typeof btn === "string") return
220
+ const path = `decks.${deckId}.buttons[${index}]`
221
+ const { issues: buttonIssues, schemaIssues } = validateButton(
222
+ btn,
223
+ registry,
224
+ path,
225
+ deckId,
226
+ btn.position,
227
+ )
228
+ const buttonId = btn.position?.toString() ?? `b${index}`
229
+ perButton.push({
230
+ deckId,
231
+ buttonIndex: index,
232
+ button: btn,
233
+ buttonId,
234
+ position: btn.position,
235
+ path,
236
+ issues: buttonIssues,
237
+ schemaIssues,
238
+ })
239
+ issues.push(...buttonIssues)
240
+ })
241
+ }
242
+ return { issues, perButton }
243
+ }
244
+
245
+ export const isFullValid = (result: FullValidationResult): boolean =>
246
+ result.issues.every((i) => i.level !== "error")
247
+
248
+ export const formatFullIssues = (issues: FullValidationIssue[]): string =>
249
+ issues
250
+ .map((issue) => {
251
+ const tag = issue.level === "error" ? "error" : "warning"
252
+ return `[${tag}] ${issue.path}: ${issue.message}`
253
+ })
254
+ .join("\n")
@@ -0,0 +1,21 @@
1
+ import { basename } from "node:path"
2
+
3
+ import { createHash } from "node:crypto"
4
+
5
+ const HASH_BYTES = 8
6
+
7
+ export const makeAssetId = (
8
+ fullPath: string,
9
+ filesize: number,
10
+ mtime: number,
11
+ ): string => {
12
+ const hash = createHash("sha256")
13
+ .update(fullPath)
14
+ .update("\0")
15
+ .update(String(filesize))
16
+ .update("\0")
17
+ .update(String(mtime))
18
+ .digest("hex")
19
+ .slice(0, HASH_BYTES * 2)
20
+ return `${basename(fullPath)}-${hash}`
21
+ }