@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,178 @@
1
+ import { execFile, spawn } from "node:child_process"
2
+ import { promisify } from "node:util"
3
+
4
+ import type pino from "pino"
5
+
6
+ const execFileAsync = promisify(execFile)
7
+
8
+ export interface SudoRunOptions {
9
+ readonly command: string
10
+ readonly args: ReadonlyArray<string>
11
+ readonly logger?: pino.Logger
12
+ readonly stdinInput?: string
13
+ readonly timeoutMs?: number
14
+ }
15
+
16
+ export interface SudoRunResult {
17
+ readonly exitCode: number
18
+ readonly stdout: string
19
+ readonly stderr: string
20
+ readonly neededPassword: boolean
21
+ readonly succeeded: boolean
22
+ }
23
+
24
+ const PROBE_TIMEOUT_MS = 3_000
25
+
26
+ export const isSudoNopasswd = async (): Promise<boolean> => {
27
+ try {
28
+ await execFileAsync("sudo", ["-n", "true"], {
29
+ timeout: PROBE_TIMEOUT_MS,
30
+ })
31
+ return true
32
+ } catch {
33
+ return false
34
+ }
35
+ }
36
+
37
+ // ponytail: match the real-world sudo failure patterns. The last pattern
38
+ // catches the PAM-style prompt sudo emits when it has eaten the password
39
+ // from stdin and decides auth failed — `[sudo: authenticate] Password:` is
40
+ // what appears when -S doesn't read from a TTY and the user didn't type
41
+ // anything. Without this match the wizard reports a bare "exit 1" with no
42
+ // hint that the password was the cause.
43
+ const NEEDS_PASSWORD = [
44
+ /password is required/i,
45
+ /sorry,? try again/i,
46
+ /incorrect password attempt/i,
47
+ /authentication required but not attempted/i,
48
+ /^\[sudo:?\s*\w*\]?\s*Password:?$/m,
49
+ ]
50
+
51
+ const stderrNeedsPassword = (stderr: string): boolean =>
52
+ NEEDS_PASSWORD.some((re) => re.test(stderr))
53
+
54
+ // ponytail: spawn (not execFile) so we control the stdin lifecycle. The
55
+ // previous execFile + `input` race lost the password to the pipe close —
56
+ // `child.stdin.write(input)` is queued, but sudo reads EOF before Node.js
57
+ // flushes it. spawn gives us an explicit write → drain → close sequence.
58
+ export const runWithSudo = async (
59
+ options: SudoRunOptions,
60
+ ): Promise<SudoRunResult> => {
61
+ const args = ["-S", "--", options.command, ...options.args]
62
+ const timeoutMs = options.timeoutMs ?? 60_000
63
+ const logger = options.logger
64
+
65
+ return new Promise((resolve) => {
66
+ const child = spawn("sudo", args, { stdio: ["pipe", "pipe", "pipe"] })
67
+ let stdout = ""
68
+ let stderr = ""
69
+ let settled = false
70
+
71
+ const settle = (result: SudoRunResult): void => {
72
+ if (settled) return
73
+ settled = true
74
+ resolve(result)
75
+ }
76
+
77
+ child.stdout.on("data", (c: Buffer) => {
78
+ stdout += c.toString()
79
+ })
80
+ child.stderr.on("data", (c: Buffer) => {
81
+ stderr += c.toString()
82
+ })
83
+ child.stdin.on("error", () => {
84
+ // ignore EPIPE if sudo exits before we finish writing
85
+ })
86
+
87
+ const timeout = setTimeout(() => {
88
+ child.kill("SIGKILL")
89
+ settle({
90
+ exitCode: 124,
91
+ stdout,
92
+ stderr: stderr + "\n[sirenodeck] sudo timed out",
93
+ neededPassword: stderrNeedsPassword(stderr),
94
+ succeeded: false,
95
+ })
96
+ }, timeoutMs)
97
+
98
+ child.on("error", (err) => {
99
+ clearTimeout(timeout)
100
+ settle({
101
+ exitCode: 1,
102
+ stdout,
103
+ stderr: stderr + (stderr.length > 0 ? "\n" : "") + err.message,
104
+ neededPassword: false,
105
+ succeeded: false,
106
+ })
107
+ })
108
+
109
+ child.on("close", (code) => {
110
+ clearTimeout(timeout)
111
+ const exitCode = code ?? 1
112
+ const neededPassword = stderrNeedsPassword(stderr)
113
+ if (exitCode !== 0) {
114
+ logger?.warn(
115
+ { exitCode, stderr: stderr.slice(0, 500) },
116
+ "sudo run failed",
117
+ )
118
+ }
119
+ settle({
120
+ exitCode,
121
+ stdout,
122
+ stderr,
123
+ neededPassword,
124
+ succeeded: code === 0,
125
+ })
126
+ })
127
+
128
+ if (options.stdinInput !== undefined && options.stdinInput.length > 0) {
129
+ child.stdin.write(options.stdinInput, (err) => {
130
+ if (err) return
131
+ child.stdin.end()
132
+ })
133
+ } else {
134
+ child.stdin.end()
135
+ }
136
+ })
137
+ }
138
+
139
+ export const capturePassword = (prompt: string): Promise<string> =>
140
+ new Promise((resolve, reject) => {
141
+ if (!process.stdin.isTTY) {
142
+ reject(new Error("cannot read sudo password: no TTY"))
143
+ return
144
+ }
145
+ const stdin = process.stdin
146
+ const oldRaw = (stdin as { isRaw?: boolean }).isRaw
147
+ stdin.setRawMode?.(true)
148
+ stdin.resume()
149
+ stdin.write("")
150
+ let password = ""
151
+ const onData = (chunk: Buffer | string): void => {
152
+ const str = chunk.toString()
153
+ for (const ch of str) {
154
+ if (ch === "\n" || ch === "\r" || ch === "\u0004") {
155
+ stdin.removeListener("data", onData)
156
+ stdin.setRawMode?.(oldRaw ?? false)
157
+ stdin.pause()
158
+ process.stdout.write("\n")
159
+ resolve(password)
160
+ return
161
+ }
162
+ if (ch === "\u0003") {
163
+ stdin.removeListener("data", onData)
164
+ stdin.setRawMode?.(oldRaw ?? false)
165
+ stdin.pause()
166
+ reject(new Error("user cancelled password prompt"))
167
+ return
168
+ }
169
+ if (ch === "\u007f" || ch === "\b") {
170
+ password = password.slice(0, -1)
171
+ continue
172
+ }
173
+ password += ch
174
+ }
175
+ }
176
+ stdin.on("data", onData)
177
+ process.stdout.write(prompt)
178
+ })
@@ -0,0 +1,133 @@
1
+ import type { CommandExecutor } from "../providers/shared"
2
+
3
+ export type CapabilityName =
4
+ | "keyMacro"
5
+ | "clipboard"
6
+ | "notification"
7
+ | "activeApp"
8
+
9
+ export type PackageManager =
10
+ | "apt"
11
+ | "dnf"
12
+ | "pacman"
13
+ | "zypper"
14
+ | "brew"
15
+ | "none"
16
+
17
+ export type DesktopSession = "wayland" | "x11" | "unknown"
18
+
19
+ /**
20
+ * An OS permission a capability needs on top of its binary. `null` on
21
+ * platforms where the capability needs no grant.
22
+ *
23
+ * ponytail: without this, a denied grant was squeezed into `missing` as a fake
24
+ * command name, so the panel rendered "osascript: Not installed" for a binary
25
+ * that ships with macOS and is very much installed. Installed-ness and
26
+ * permission are two independent facts and the report now carries both.
27
+ */
28
+ export interface CapabilityPermission {
29
+ /** Human name of the grant, e.g. "Accessibility". */
30
+ readonly label: string
31
+ readonly granted: boolean
32
+ /** One sentence telling the user how to grant it. */
33
+ readonly hint: string
34
+ /** Deep link we can hand to `open`, when the OS offers one. */
35
+ readonly settingsUrl: string | null
36
+ }
37
+
38
+ export interface CapabilityProbe {
39
+ readonly name: CapabilityName
40
+ /** Binary present AND any required permission granted. */
41
+ readonly available: boolean
42
+ /** Binary present, regardless of permissions. */
43
+ readonly toolInstalled: boolean
44
+ readonly permission: CapabilityPermission | null
45
+ readonly missing: ReadonlyArray<string>
46
+ readonly preferred: string
47
+ readonly reason: string
48
+ }
49
+
50
+ export interface UdevProbe {
51
+ readonly rulesInstalled: boolean
52
+ readonly rulesPath: string
53
+ readonly streamDeckConnected: boolean
54
+ readonly matchedProductIds: ReadonlyArray<string>
55
+ }
56
+
57
+ export interface ConfigProbe {
58
+ readonly exists: boolean
59
+ readonly path: string
60
+ }
61
+
62
+ export interface SystemReport {
63
+ readonly platform: string
64
+ readonly homeDir: string
65
+ readonly xdgConfigHome: string
66
+ readonly session: DesktopSession
67
+ readonly packageManager: PackageManager
68
+ readonly capabilities: Readonly<Record<CapabilityName, CapabilityProbe>>
69
+ readonly udev: UdevProbe
70
+ readonly config: ConfigProbe
71
+ }
72
+
73
+ export interface InstallStep {
74
+ readonly id: string
75
+ readonly capability: CapabilityName | "udev" | "config"
76
+ readonly title: string
77
+ readonly description: string
78
+ readonly packageManager: PackageManager
79
+ readonly packages: ReadonlyArray<string>
80
+ readonly sudo: boolean
81
+ readonly manualOnly: boolean
82
+ readonly manualInstructions: string
83
+ readonly verifyCommand?: string
84
+ }
85
+
86
+ export type InstallStepResult = "installed" | "skipped" | "failed" | "manual"
87
+
88
+ export interface WizardOutcome {
89
+ readonly report: SystemReport
90
+ readonly steps: ReadonlyArray<InstallStep>
91
+ readonly results: Readonly<Record<string, InstallStepResult>>
92
+ readonly configSeeded: boolean
93
+ readonly configPath: string
94
+ }
95
+
96
+ export interface ProbeDeps {
97
+ readonly platform: string
98
+ readonly homeDir: string
99
+ readonly xdgConfigHome: string
100
+ readonly env: NodeJS.ProcessEnv
101
+ readonly executor: CommandExecutor
102
+ readonly extraFsProbe?: (command: string) => boolean
103
+ readonly fileExists: (path: string) => boolean
104
+ readonly readFile: (path: string) => string | null
105
+ // ponytail: the config the caller actually intends to run with. Without it
106
+ // the probe only ever stats $XDG_CONFIG_HOME/sirenodeck/config.yml, so a
107
+ // valid `--config ./my.yml` still reported "Config: missing" and the
108
+ // first-run gate refused to start the daemon.
109
+ readonly configPath?: string
110
+ }
111
+
112
+ export interface SystemReportSummary {
113
+ readonly ok: boolean
114
+ readonly session: DesktopSession
115
+ readonly packageManager: PackageManager
116
+ readonly missingCapabilities: ReadonlyArray<CapabilityName>
117
+ readonly udevMissing: boolean
118
+ readonly configMissing: boolean
119
+ readonly configPath: string
120
+ readonly streamDeckConnected: boolean
121
+ readonly lines: ReadonlyArray<string>
122
+ }
123
+
124
+ export const UDEV_RULES_PATH = "/etc/udev/rules.d/70-sirenodeck.rules"
125
+
126
+ /**
127
+ * Deep link that opens System Settings straight at the Accessibility pane, so
128
+ * the user does not have to hunt through Privacy & Security by hand. This URL
129
+ * scheme is the documented way to target a settings pane and has survived the
130
+ * System Preferences → System Settings rename.
131
+ */
132
+ export const ACCESSIBILITY_SETTINGS_URL =
133
+ "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
@@ -0,0 +1,67 @@
1
+ import { EventEmitter } from "node:events"
2
+
3
+ export type StreamDeckKeyEventType = "down" | "up"
4
+
5
+ export interface StreamDeckKeyEvent {
6
+ readonly type: StreamDeckKeyEventType
7
+ readonly keyIndex: number
8
+ readonly timestamp: number
9
+ }
10
+
11
+ export interface VirtualStreamDeckOptions {
12
+ readonly keyCount: number
13
+ readonly autoTimestamp?: boolean
14
+ }
15
+
16
+ export interface VirtualStreamDeckLifecycle {
17
+ getKeyCount(): number
18
+ injectKeyEvent(event: StreamDeckKeyEvent): void
19
+ injectKey(type: StreamDeckKeyEventType, keyIndex: number): void
20
+ onKeyEvent(handler: (event: StreamDeckKeyEvent) => void): () => void
21
+ clear(): void
22
+ }
23
+
24
+ export const createVirtualStreamDeckLifecycle = (
25
+ options: VirtualStreamDeckOptions,
26
+ ): VirtualStreamDeckLifecycle => {
27
+ const emitter = new EventEmitter()
28
+ const { autoTimestamp = true } = options
29
+
30
+ const getKeyCount = (): number => options.keyCount
31
+
32
+ const injectKeyEvent = (event: StreamDeckKeyEvent): void => {
33
+ emitter.emit("key", event)
34
+ }
35
+
36
+ const injectKey = (type: StreamDeckKeyEventType, keyIndex: number): void => {
37
+ if (keyIndex < 0 || keyIndex >= options.keyCount) {
38
+ throw new Error(
39
+ `injectKey: keyIndex ${keyIndex} out of range (0..${options.keyCount - 1})`,
40
+ )
41
+ }
42
+ injectKeyEvent({
43
+ type,
44
+ keyIndex,
45
+ timestamp: autoTimestamp ? Date.now() : 0,
46
+ })
47
+ }
48
+
49
+ const onKeyEvent = (
50
+ handler: (event: StreamDeckKeyEvent) => void,
51
+ ): (() => void) => {
52
+ emitter.on("key", handler)
53
+ return () => emitter.off("key", handler)
54
+ }
55
+
56
+ const clear = (): void => {
57
+ emitter.removeAllListeners("key")
58
+ }
59
+
60
+ return {
61
+ getKeyCount,
62
+ injectKeyEvent,
63
+ injectKey,
64
+ onKeyEvent,
65
+ clear,
66
+ }
67
+ }
@@ -0,0 +1,210 @@
1
+ import type { ThemeJsonManifest } from "./manifest"
2
+
3
+ function toFontFamilyValue(fontFamily: string): string {
4
+ return fontFamily.includes(",")
5
+ ? fontFamily
6
+ : `'${fontFamily.replace(/'/g, "\\'")}'`
7
+ }
8
+
9
+ function formatRoleName(name: string): string {
10
+ if (name === "main_text") return "main"
11
+ if (name === "auxiliary_text") return "aux"
12
+ if (name === "monospace") return "mono"
13
+ return name
14
+ }
15
+
16
+ function formatTypographyRoleVariables(
17
+ roleName: string,
18
+ role: {
19
+ fontFamily: string
20
+ fontSize: number
21
+ fontWeight: number
22
+ letterSpacing?: number
23
+ },
24
+ ): string[] {
25
+ const cssRoleName = formatRoleName(roleName)
26
+ return [
27
+ ` --sireno-font-${cssRoleName}-family: ${toFontFamilyValue(role.fontFamily)};`,
28
+ ` --sireno-font-${cssRoleName}-size: ${role.fontSize}px;`,
29
+ ` --sireno-font-${cssRoleName}-weight: ${role.fontWeight};`,
30
+ ` --sireno-font-${cssRoleName}-tracking: ${role.letterSpacing ?? 0}px;`,
31
+ ]
32
+ }
33
+
34
+ const FORMAT_BY_EXT: Record<string, string> = {
35
+ ".woff2": "woff2",
36
+ ".woff": "woff",
37
+ ".ttf": "truetype",
38
+ ".otf": "opentype",
39
+ }
40
+
41
+ /**
42
+ * Derive the CSS `format()` token from the font src extension. Falls back to
43
+ * truetype for unknown / extensionless paths so legacy manifests keep working.
44
+ */
45
+ function fontFormatForSrc(src: string): string {
46
+ const match = src.match(/\.[a-z0-9]+(?:\?.*)?$/i)
47
+ if (match === null) return "truetype"
48
+ return FORMAT_BY_EXT[match[0].toLowerCase()] ?? "truetype"
49
+ }
50
+
51
+ const MANIFEST_TO_CSS_TOKEN: Record<string, string> = {
52
+ background: "bg",
53
+ foreground: "fg",
54
+ "foreground-contrast": "foreground-contrast",
55
+ frame: "frame",
56
+ primary: "primary",
57
+ accent: "accent",
58
+ success: "success",
59
+ danger: "danger",
60
+ }
61
+
62
+ export function buildThemeCss(
63
+ manifest: ThemeJsonManifest,
64
+ stylesheetContents: ReadonlyArray<string>,
65
+ ): string {
66
+ const { colorTokens, typography, fonts } = manifest
67
+
68
+ const parts: string[] = []
69
+
70
+ // Tailwind @theme block — emitted last so the @theme block is the
71
+ // final block the Tailwind v4 PostCSS plugin sees. Empirically the
72
+ // vite plugin truncates a leading @theme block to only the first
73
+ // few entries; emitting it after @font-face / :root makes the full
74
+ // set of variables land in @layer theme and produce utilities like
75
+ // bg-frame, text-accent, font-mono, etc.
76
+ const themeLines = ["@theme {"]
77
+ for (const [token, value] of Object.entries(colorTokens)) {
78
+ const cssToken =
79
+ MANIFEST_TO_CSS_TOKEN[token] ??
80
+ token.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
81
+ themeLines.push(` --color-${cssToken}: ${value};`)
82
+ }
83
+ for (const [roleName, role] of Object.entries(typography)) {
84
+ const cssRoleName = formatRoleName(roleName)
85
+ themeLines.push(
86
+ ` --font-${cssRoleName}: ${toFontFamilyValue(role.fontFamily)}, ui-sans-serif, system-ui, sans-serif;`,
87
+ )
88
+ }
89
+ themeLines.push("}")
90
+
91
+ // @font-face declarations
92
+ if (fonts.length > 0) {
93
+ const fontFaces = fonts.map((f) => {
94
+ // Variable fonts — emit a single @font-face with from..to range when
95
+ // the manifest declares axes. Otherwise preserve the single weight.
96
+ const axes = f.axes
97
+ const weightRange = axes?.weight
98
+ const weight =
99
+ weightRange !== undefined
100
+ ? `${weightRange[0]} ${weightRange[1]}`
101
+ : f.fontStyle === "italic"
102
+ ? `${f.fontWeight} italic`
103
+ : String(f.fontWeight)
104
+ const format = fontFormatForSrc(f.src)
105
+ return `@font-face {\n font-family: '${f.fontFamily}';\n font-style: ${f.fontStyle};\n font-weight: ${weight};\n src: url('${f.src}') format('${format}');\n}`
106
+ })
107
+ parts.push(fontFaces.join("\n\n"))
108
+ }
109
+
110
+ // :root CSS variables (runtime) — emit only the --sireno-color-* prefixed
111
+ // runtime values. We deliberately avoid also setting the plain --color-*
112
+ // names here, because Tailwind v4 will only keep a @theme variable in
113
+ // @layer theme when it is the unique declaration of that name. If we
114
+ // duplicate --color-frame in :root, Tailwind considers the @theme entry
115
+ // redundant and silently drops it, leaving bg-frame / text-frame / etc.
116
+ // utilities un-generated.
117
+ const rootLines = [":root {"]
118
+ for (const [token, value] of Object.entries(colorTokens)) {
119
+ const cssToken =
120
+ MANIFEST_TO_CSS_TOKEN[token] ??
121
+ token.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
122
+ rootLines.push(` --sireno-color-${cssToken}: ${value};`)
123
+ }
124
+ for (const [roleName, role] of Object.entries(typography)) {
125
+ rootLines.push(...formatTypographyRoleVariables(roleName, role))
126
+ }
127
+ // Variant tokens — every theme must declare the 5 required keys; themes
128
+ // may add extras. ButtonFrame reads --sireno-variant-<name>-{bg,border,fg,glow}
129
+ // directly, so addons and themes both go through one surface.
130
+ //
131
+ // buttonColor variants may also declare a `tokens` block to override
132
+ // primary/accent/foreground/foreground-contrast/success/danger per-variant.
133
+ // For 4-slot variants (no tokens block), fall back to theme colorTokens so
134
+ // primitives reading --sireno-color-* via cascade get the right value.
135
+ const VARIANT_TOKEN_KEYS = [
136
+ "primary",
137
+ "accent",
138
+ "foreground",
139
+ "foreground-contrast",
140
+ "success",
141
+ "danger",
142
+ "muted",
143
+ ] as const
144
+ for (const [variantName, styles] of Object.entries(manifest.variants)) {
145
+ rootLines.push(
146
+ ` --sireno-variant-${variantName}-bg: ${styles.background};`,
147
+ )
148
+ rootLines.push(
149
+ ` --sireno-variant-${variantName}-border: ${styles.border};`,
150
+ )
151
+ rootLines.push(
152
+ ` --sireno-variant-${variantName}-fg: ${styles.foreground};`,
153
+ )
154
+ rootLines.push(
155
+ ` --sireno-variant-${variantName}-glow: ${styles.glow ?? "0 0 0 transparent"};`,
156
+ )
157
+ const tokens = styles.tokens
158
+ for (const tokenKey of VARIANT_TOKEN_KEYS) {
159
+ const cssToken =
160
+ MANIFEST_TO_CSS_TOKEN[tokenKey] ??
161
+ tokenKey.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
162
+ const themeValue =
163
+ colorTokens[tokenKey as keyof typeof colorTokens] ?? "#000000"
164
+ const variantValue =
165
+ tokens?.[tokenKey as keyof typeof tokens] ?? themeValue
166
+ rootLines.push(
167
+ ` --sireno-variant-${variantName}-${cssToken}: ${variantValue};`,
168
+ )
169
+ }
170
+ }
171
+ // Effect tokens — glow / shadow / blur, optional. Themes without effects
172
+ // still get sane defaults so component CSS never reads `undefined` for a
173
+ // token.
174
+ const effects = manifest.effects
175
+ rootLines.push(
176
+ ` --sireno-glow-sm: ${effects?.glow?.sm ?? "0 0 4px transparent"};`,
177
+ )
178
+ rootLines.push(
179
+ ` --sireno-glow-md: ${effects?.glow?.md ?? "0 0 12px transparent"};`,
180
+ )
181
+ rootLines.push(
182
+ ` --sireno-glow-lg: ${effects?.glow?.lg ?? "0 0 24px transparent"};`,
183
+ )
184
+ rootLines.push(
185
+ ` --sireno-shadow-soft: ${effects?.shadow?.soft ?? "0 1px 2px transparent"};`,
186
+ )
187
+ rootLines.push(
188
+ ` --sireno-shadow-hard: ${effects?.shadow?.hard ?? "0 4px 12px transparent"};`,
189
+ )
190
+ rootLines.push(` --sireno-blur-sm: ${effects?.blur?.sm ?? "4px"};`)
191
+ rootLines.push(` --sireno-blur-md: ${effects?.blur?.md ?? "10px"};`)
192
+ // Default pressed / holding glow hooks — every theme gets these for free
193
+ // (Gaps 10). Themes can override in their own stylesheet under @layer.
194
+ rootLines.push(` --sireno-pressed-glow: var(--sireno-glow-md);`)
195
+ rootLines.push(` --sireno-held-glow: var(--sireno-glow-lg);`)
196
+ rootLines.push("}")
197
+ parts.push(rootLines.join("\n"))
198
+
199
+ // Concatenated stylesheets — wrap in @layer so theme styles override
200
+ // built-in styles predictably (Tailwind v4 layer order is fixed:
201
+ // theme < base < components < utilities < user-overrides).
202
+ if (stylesheetContents.length > 0) {
203
+ parts.push(`@layer theme-override {\n${stylesheetContents.join("\n\n")}\n}`)
204
+ }
205
+
206
+ // @theme block emitted last — see comment above the @theme block.
207
+ parts.push(themeLines.join("\n"))
208
+
209
+ return parts.join("\n\n")
210
+ }