@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,155 @@
1
+ import {
2
+ getStreamDeckModelName,
3
+ listStreamDecks,
4
+ openStreamDeck,
5
+ type StreamDeck as SdkDevice,
6
+ type StreamDeckDeviceInfo,
7
+ } from "@elgato-stream-deck/node"
8
+
9
+ export class StreamDeckSelectionError extends Error {
10
+ constructor(message: string) {
11
+ super(message)
12
+ this.name = "StreamDeckSelectionError"
13
+ }
14
+ }
15
+
16
+ export type StreamDeckKeyEventType = "down" | "up"
17
+
18
+ export interface StreamDeckKeyEvent {
19
+ readonly type: StreamDeckKeyEventType
20
+ readonly keyIndex: number
21
+ readonly timestamp: number
22
+ }
23
+
24
+ export interface StreamDeckDevice {
25
+ readonly serial: string
26
+ readonly path: string
27
+ readonly model: string
28
+ getKeyCount(): number
29
+ setBrightness(value: number): Promise<void>
30
+ fillKeyBuffer(keyIndex: number, buffer: Buffer): Promise<void>
31
+ onKeyEvent(handler: (event: StreamDeckKeyEvent) => void): () => void
32
+ /**
33
+ * Fires when the SDK reports a transport failure — most commonly the USB
34
+ * device going away (unplugged, or a KVM switching the host).
35
+ *
36
+ * ponytail: the SDK declares an `error` event and nothing subscribed to it.
37
+ * An unhandled `error` on an EventEmitter throws, which the daemon's
38
+ * uncaughtException guard turned into an immediate exit — so flipping a KVM
39
+ * killed the service outright instead of it noticing the deck had left.
40
+ */
41
+ onError(handler: (err: unknown) => void): () => void
42
+ close(): Promise<void>
43
+ }
44
+
45
+ export interface ConnectStreamDeckOptions {
46
+ readonly serial?: string
47
+ readonly path?: string
48
+ readonly model?: string
49
+ }
50
+
51
+ const buildDescriptor = (
52
+ info: StreamDeckDeviceInfo,
53
+ controls: ReadonlyArray<SdkDevice["CONTROLS"][number]>,
54
+ ) => ({
55
+ serial: info.serialNumber ?? "",
56
+ path: info.path,
57
+ model: getStreamDeckModelName(info.model),
58
+ keyCount: controls.filter((c) => c.type === "button").length,
59
+ })
60
+
61
+ export const connectStreamDeck = async (
62
+ options: ConnectStreamDeckOptions = {},
63
+ ): Promise<StreamDeckDevice> => {
64
+ const infos = await listStreamDecks()
65
+ let candidates: StreamDeckDeviceInfo[]
66
+ if (infos.length === 0) {
67
+ candidates = []
68
+ } else if (
69
+ options.serial !== undefined ||
70
+ options.path !== undefined ||
71
+ options.model !== undefined
72
+ ) {
73
+ candidates = infos.filter((info) => {
74
+ if (options.serial !== undefined && info.serialNumber !== options.serial)
75
+ return false
76
+ if (options.path !== undefined && info.path !== options.path) return false
77
+ if (options.model !== undefined && info.model !== options.model)
78
+ return false
79
+ return true
80
+ })
81
+ } else {
82
+ candidates = [...infos]
83
+ }
84
+
85
+ if (candidates.length === 0) {
86
+ throw new StreamDeckSelectionError(
87
+ options.serial !== undefined || options.path !== undefined
88
+ ? `No Stream Deck matches selector ${JSON.stringify(options)}`
89
+ : "No Stream Deck devices found",
90
+ )
91
+ }
92
+ if (
93
+ candidates.length > 1 &&
94
+ options.serial === undefined &&
95
+ options.path === undefined
96
+ ) {
97
+ throw new StreamDeckSelectionError(
98
+ `Multiple Stream Deck devices found (${candidates.length}); pass --serial or --path`,
99
+ )
100
+ }
101
+
102
+ const targetInfo = candidates[0]!
103
+ const handle = await openStreamDeck(targetInfo.path, {})
104
+ const descriptor = buildDescriptor(targetInfo, handle.CONTROLS)
105
+
106
+ // Baseline listener so the event always has a subscriber, even when the
107
+ // caller never registers one. Without it an `error` emitted between open and
108
+ // the caller's onError() call is still fatal.
109
+ const errorHandlers = new Set<(err: unknown) => void>()
110
+ handle.on("error", (err: unknown) => {
111
+ for (const h of errorHandlers) h(err)
112
+ })
113
+
114
+ return {
115
+ serial: descriptor.serial,
116
+ path: descriptor.path,
117
+ model: descriptor.model,
118
+ getKeyCount: () => descriptor.keyCount,
119
+ async setBrightness(value: number): Promise<void> {
120
+ await handle.setBrightness(value)
121
+ },
122
+ async fillKeyBuffer(keyIndex: number, buffer: Buffer): Promise<void> {
123
+ await handle.fillKeyBuffer(keyIndex, buffer)
124
+ },
125
+ onKeyEvent(handler: (event: StreamDeckKeyEvent) => void): () => void {
126
+ const onDown = (control: { type: string; index: number }): void => {
127
+ if (control.type !== "button") return
128
+ handler({
129
+ type: "down",
130
+ keyIndex: control.index,
131
+ timestamp: Date.now(),
132
+ })
133
+ }
134
+ const onUp = (control: { type: string; index: number }): void => {
135
+ if (control.type !== "button") return
136
+ handler({ type: "up", keyIndex: control.index, timestamp: Date.now() })
137
+ }
138
+ handle.on("down", onDown)
139
+ handle.on("up", onUp)
140
+ return () => {
141
+ handle.off("down", onDown)
142
+ handle.off("up", onUp)
143
+ }
144
+ },
145
+ onError(handler: (err: unknown) => void): () => void {
146
+ errorHandlers.add(handler)
147
+ return () => {
148
+ errorHandlers.delete(handler)
149
+ }
150
+ },
151
+ async close(): Promise<void> {
152
+ await handle.close()
153
+ },
154
+ }
155
+ }
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ export {
2
+ BUTTON_SIZE_PX,
3
+ DECK_GAP_PX,
4
+ DECK_PADDING_PX,
5
+ DEFAULT_DEVICE_MODEL_ID,
6
+ DEFAULT_KEY_COUNT,
7
+ DEVICE_MODELS,
8
+ deckDimensions,
9
+ getDeviceModel,
10
+ gridForKeyCount,
11
+ isKnownDeviceModel,
12
+ resolveKeyCount,
13
+ resolveDeckGap,
14
+ type DeviceModelSpec,
15
+ } from "./device/models"
16
+ export {
17
+ PACKAGE_NAME,
18
+ PROTOCOL_VERSION,
19
+ SIRENO_ADDON_API_VERSION,
20
+ } from "./version"
21
+
22
+ export {
23
+ DOUBLE_TAP_DELAY_MS,
24
+ HOLD_ACTION_DELAY_MS,
25
+ SPA_DOUBLE_TAP_DELAY_MS,
26
+ SPA_HOLD_DELAY_MS,
27
+ nextGesture,
28
+ type GestureEvent,
29
+ type GestureKind,
30
+ type GestureResult,
31
+ type GestureType,
32
+ createGestureDetector,
33
+ type GestureDetector,
34
+ } from "./core/gesture-state"
35
+
36
+ export type { AddonGestureEvent } from "./addon/api"
37
+
38
+ export { ChannelRegistry } from "./api/react/registry"
39
+ export {
40
+ useAddonChannel,
41
+ type UseAddonChannelReturn,
42
+ } from "./api/react/use-addon-channel"
43
+ export {
44
+ ThemeContext,
45
+ ThemeProvider,
46
+ useTheme,
47
+ type ThemeContextValue,
48
+ } from "./themes/use-resolved-theme"
49
+
50
+ export {
51
+ AssetCacheProvider,
52
+ useAssetCache,
53
+ useAssetCacheMutations,
54
+ type AssetCache,
55
+ buildPresentation,
56
+ ButtonFrame,
57
+ Chip,
58
+ Icon,
59
+ IconLabelSurface,
60
+ Label,
61
+ SplitActionSurface,
62
+ TapIndicator,
63
+ TemporaryErrorSurface,
64
+ Text,
65
+ ThemeUiPresentationProvider,
66
+ type ButtonFrameProps,
67
+ type IconLabelSurfaceProps,
68
+ type SplitActionSurfaceProps,
69
+ type TapIndicatorProps,
70
+ type TemporaryErrorSurfaceProps,
71
+ type ThemeUiPresentation,
72
+ } from "./ui"
73
+
74
+ export const cliVersion = "0.1.0"
@@ -0,0 +1,357 @@
1
+ import { execFile } from "node:child_process"
2
+ import { platform } from "node:os"
3
+
4
+ import type pino from "pino"
5
+
6
+ import type {
7
+ ButtonActionMessage,
8
+ SelectDeckMessage,
9
+ SetDeviceMessage,
10
+ WsMessage,
11
+ } from "@/api/protocol-internal"
12
+ import { resolveKeyCount } from "@/device/models"
13
+ import type { DeviceDescriptor } from "@/device/registry"
14
+ import { writeRuntimeState, type RuntimeState } from "@/util/daemon"
15
+
16
+ import {
17
+ DEFAULT_CONFIG_UI_PORT,
18
+ DEFAULT_FRONTEND_PORT,
19
+ killChild,
20
+ resolveConfigUiCwd,
21
+ resolveFrontendCwd,
22
+ spawnConfigUiVite,
23
+ spawnFrontendVite,
24
+ } from "../cli/commands/emulator-mode"
25
+ import {
26
+ DEFAULT_VITE_RETRY_SCHEDULE_MS,
27
+ supervise,
28
+ type SuperviseHandle,
29
+ } from "../cli/commands/subprocess-supervisor"
30
+
31
+ import type { InitOptions, OutputClient, OutputHandle } from "./types"
32
+
33
+ const DEFAULT_TIMEOUT_MS = 30_000
34
+
35
+ const VIRTUAL_MODELS = ["mk2", "mini", "xl"] as const
36
+
37
+ const isButtonAction = (m: WsMessage): m is ButtonActionMessage =>
38
+ m.type === "button-action"
39
+
40
+ const isSetDevice = (m: WsMessage): m is SetDeviceMessage =>
41
+ m.type === "set-device"
42
+
43
+ const isSelectDeck = (m: WsMessage): m is SelectDeckMessage =>
44
+ m.type === "select-deck"
45
+
46
+ const openBrowser = (
47
+ url: string,
48
+ logger: pino.Logger,
49
+ noOpen = false,
50
+ ): void => {
51
+ if (noOpen) {
52
+ logger.debug("browser auto-open disabled")
53
+ return
54
+ }
55
+ const os = platform()
56
+ const cmd = os === "win32" ? "cmd" : os === "darwin" ? "open" : "xdg-open"
57
+ const args = os === "win32" ? ["/c", "start", "", url] : [url]
58
+ execFile(cmd, args, (err) => {
59
+ if (err !== null) {
60
+ logger.debug(
61
+ { err: err.message },
62
+ "browser auto-open unavailable, open the URL manually",
63
+ )
64
+ }
65
+ })
66
+ }
67
+
68
+ export class EmulatorOutputClient implements OutputClient {
69
+ readonly kind = "emulator" as const
70
+
71
+ private descriptor: DeviceDescriptor | null = null
72
+
73
+ async validateReady(): Promise<void> {
74
+ void this.kind
75
+ }
76
+
77
+ async listDevices(): Promise<ReadonlyArray<DeviceDescriptor>> {
78
+ return VIRTUAL_MODELS.map((model) => buildVirtualDescriptor(model))
79
+ }
80
+
81
+ async selectDevice(
82
+ devices: ReadonlyArray<DeviceDescriptor>,
83
+ savedId: string | null,
84
+ logger: pino.Logger,
85
+ ): Promise<DeviceDescriptor> {
86
+ void logger
87
+ const match =
88
+ savedId !== null ? devices.find((d) => d.id === savedId) : undefined
89
+ if (match !== undefined) {
90
+ this.descriptor = match
91
+ return match
92
+ }
93
+ const fallback = devices.find((d) => d.model === "mk2") ?? devices[0]
94
+ if (fallback === undefined) {
95
+ throw new Error("EmulatorOutputClient: no virtual devices available")
96
+ }
97
+ this.descriptor = fallback
98
+ return fallback
99
+ }
100
+
101
+ async storeSelection(_descriptor: DeviceDescriptor): Promise<void> {
102
+ void _descriptor
103
+ }
104
+
105
+ async init(opts: InitOptions): Promise<OutputHandle> {
106
+ if (this.descriptor === null) {
107
+ throw new Error(
108
+ "EmulatorOutputClient.init: selectDevice() must run first",
109
+ )
110
+ }
111
+ const descriptor = this.descriptor
112
+ const logger = opts.logger.child({ component: "emulator" })
113
+ let shuttingDown = false
114
+
115
+ const remote = opts.remote === true
116
+ const host: "127.0.0.1" | "0.0.0.0" = remote ? "0.0.0.0" : "127.0.0.1"
117
+ const token = process.env["SIRENO_TOKEN"] ?? ""
118
+ const requireToken = remote ? token : undefined
119
+
120
+ // ponytail: supervise each vite child independently — frontend crash
121
+ // respawns only the frontend, config UI crash respawns only the config UI.
122
+ // Both share the same `onChildCrash` so the pipeline exits cleanly when
123
+ // either exhausts its retry budget. The supervisor spawn closure captures
124
+ // the resolved URL into a hoisted `frontendUrl`/`configUiUrl` so callers
125
+ // see it after the initial await; subsequent respawns bind the same port
126
+ // so the URL stays accurate.
127
+ let frontendUrl = ""
128
+ let configUiUrl = ""
129
+
130
+ const frontendSupervisor: SuperviseHandle | null =
131
+ opts.frontendUrl !== undefined
132
+ ? (() => {
133
+ frontendUrl = opts.frontendUrl
134
+ return null
135
+ })()
136
+ : await supervise({
137
+ label: "frontend vite",
138
+ kill: killChild,
139
+ delayScheduleMs: DEFAULT_VITE_RETRY_SCHEDULE_MS,
140
+ spawn: async () => {
141
+ const r = await spawnFrontendVite({
142
+ port: DEFAULT_FRONTEND_PORT,
143
+ cwd: resolveFrontendCwd(),
144
+ pnpmCommand: "pnpm",
145
+ readyTimeoutMs: DEFAULT_TIMEOUT_MS,
146
+ logger,
147
+ wsUrl: opts.bridge.url,
148
+ configPath: opts.configPath,
149
+ emulatorMode: true,
150
+ host,
151
+ ...(requireToken !== undefined ? { requireToken } : {}),
152
+ ...(opts.onChildPid !== undefined
153
+ ? { onPid: opts.onChildPid }
154
+ : {}),
155
+ })
156
+ frontendUrl = r.url
157
+ return r.process
158
+ },
159
+ onGiveUp: () => opts.onChildCrash?.(),
160
+ isShuttingDown: () => shuttingDown,
161
+ logger,
162
+ })
163
+
164
+ const emulatorSupervisor = await supervise({
165
+ label: "config ui vite",
166
+ kill: killChild,
167
+ delayScheduleMs: DEFAULT_VITE_RETRY_SCHEDULE_MS,
168
+ spawn: async () => {
169
+ const r = await spawnConfigUiVite({
170
+ port: DEFAULT_CONFIG_UI_PORT,
171
+ cwd: resolveConfigUiCwd(),
172
+ pnpmCommand: "pnpm",
173
+ readyTimeoutMs: DEFAULT_TIMEOUT_MS,
174
+ logger,
175
+ wsUrl: opts.bridge.url,
176
+ configPath: opts.configPath,
177
+ emulatorMode: true,
178
+ host,
179
+ frontendUrl:
180
+ opts.frontendUrl ?? `http://127.0.0.1:${DEFAULT_FRONTEND_PORT}`,
181
+ ...(requireToken !== undefined ? { requireToken } : {}),
182
+ ...(opts.onChildPid !== undefined ? { onPid: opts.onChildPid } : {}),
183
+ })
184
+ configUiUrl = r.url
185
+ return r.process
186
+ },
187
+ onGiveUp: () => opts.onChildCrash?.(),
188
+ isShuttingDown: () => shuttingDown,
189
+ logger,
190
+ })
191
+
192
+ opts.bridge.setDevice(descriptor)
193
+
194
+ opts.bridge.onMessage((message) => {
195
+ if (isSelectDeck(message)) {
196
+ if (opts.runtime.deckExists(message.deckId))
197
+ opts.runtime.navigateToDeck(message.deckId, { addToHistory: false })
198
+ else
199
+ logger.warn(
200
+ { deckId: message.deckId },
201
+ "emulator: select-deck targets unknown deck",
202
+ )
203
+ return
204
+ }
205
+ if (isSetDevice(message)) {
206
+ if (
207
+ !VIRTUAL_MODELS.includes(
208
+ message.deviceId as (typeof VIRTUAL_MODELS)[number],
209
+ )
210
+ ) {
211
+ logger.warn(
212
+ { deviceId: message.deviceId },
213
+ "emulator: ignoring unsupported virtual device",
214
+ )
215
+ return
216
+ }
217
+ const descriptor = buildVirtualDescriptor(message.deviceId)
218
+ this.descriptor = descriptor
219
+ opts.bridge.setDevice(descriptor)
220
+ // The runtime was built once at startup with the original device's
221
+ // keyCount, so system-button injection + pagination baked in the
222
+ // wrong n-1 / n-2 positions. Rebuild with the new keyCount and
223
+ // swap the runtime's deck set so the next deck-config broadcast
224
+ // carries the correct layout. The rebuild path runs
225
+ // positionButtons(runtimeDeck.config, keyCount) on every deck so
226
+ // positions always come from the original config snapshot, never
227
+ // from prior in-memory state.
228
+ if (opts.rebuildDecksForKeyCount !== undefined) {
229
+ const rebuilt = opts.rebuildDecksForKeyCount(descriptor.keyCount)
230
+ opts.runtime.setDecks(rebuilt)
231
+ }
232
+ logger.info(
233
+ { deviceId: message.deviceId },
234
+ "emulator: virtual device changed",
235
+ )
236
+ return
237
+ }
238
+ if (!isButtonAction(message)) return
239
+ logger.debug(
240
+ {
241
+ deckId: message.deckId,
242
+ position: message.position,
243
+ gesture: message.gesture,
244
+ },
245
+ "emulator: button-action received",
246
+ )
247
+ // The runtime is the authority on the active deck. The client sends
248
+ // message.deckId for routing on the frontend (so React knows which
249
+ // cell fired), but the emulator backend uses runtime.getActiveDeck()
250
+ // to resolve the actual deck — this avoids relying on the client's
251
+ // sometimes-stale local view of the active deck.
252
+ const activeDeck = opts.runtime.getActiveDeck()
253
+ const button = activeDeck.buttons.find((b) => {
254
+ if (b.position === message.position) return true
255
+ const parsed = Number.parseInt(b.id, 10)
256
+ return Number.isFinite(parsed) && parsed === message.position
257
+ })
258
+ logger.debug(
259
+ {
260
+ activeDeckId: activeDeck.id,
261
+ position: message.position,
262
+ gesture: message.gesture,
263
+ },
264
+ "[emulator] button lookup",
265
+ )
266
+ if (button === undefined) {
267
+ logger.warn(
268
+ { activeDeckId: activeDeck.id, position: message.position },
269
+ "emulator: button-action targets unknown button",
270
+ )
271
+ return
272
+ }
273
+ void opts.runtime.dispatchGesture(
274
+ `${activeDeck.id}:${button.id}`,
275
+ message.gesture,
276
+ )
277
+ })
278
+
279
+ const frontendPid = frontendSupervisor?.process.pid ?? 0
280
+ const emulatorPid = emulatorSupervisor.process.pid ?? 0
281
+ const childPids = [frontendPid, emulatorPid].filter((p) => p > 0)
282
+
283
+ logger.info(
284
+ {
285
+ configUiUrl,
286
+ frontendUrl,
287
+ wsUrl: opts.bridge.url,
288
+ },
289
+ "emulator mode ready",
290
+ )
291
+ if (remote) {
292
+ process.stdout.write(`\n Config UI: ${configUiUrl}\n\n`)
293
+ const lanHost = opts.lanHost ?? "127.0.0.1"
294
+ const state: RuntimeState = {
295
+ configUiUrl,
296
+ wsUrl: opts.bridge.url,
297
+ frontendUrl,
298
+ lanHost,
299
+ addresses: opts.lanAddresses ?? [],
300
+ emulatorMode: true,
301
+ remote: true,
302
+ startedAt: Date.now(),
303
+ theme: opts.theme.name,
304
+ }
305
+ writeRuntimeState(state)
306
+ } else {
307
+ const frontendLine =
308
+ process.env.SIRENO_DEV_MODE === "true"
309
+ ? `\n Frontend: ${frontendUrl}`
310
+ : ""
311
+ process.stdout.write(`\n Config UI: ${configUiUrl}${frontendLine}\n\n`)
312
+ openBrowser(configUiUrl, logger, opts.noAutoOpen === true)
313
+ const lanHost = opts.lanHost ?? "127.0.0.1"
314
+ const state: RuntimeState = {
315
+ configUiUrl,
316
+ wsUrl: opts.bridge.url,
317
+ frontendUrl,
318
+ lanHost,
319
+ addresses: opts.lanAddresses ?? [],
320
+ emulatorMode: true,
321
+ remote: false,
322
+ startedAt: Date.now(),
323
+ theme: opts.theme.name,
324
+ }
325
+ writeRuntimeState(state)
326
+ }
327
+
328
+ opts.runtime.setBrightness(opts.runtime.getBrightness())
329
+
330
+ return {
331
+ descriptor,
332
+ frontendUrl,
333
+ configUiUrl,
334
+ wsUrl: opts.bridge.url,
335
+ childPids,
336
+ async stop(): Promise<void> {
337
+ shuttingDown = true
338
+ // The supervisor's stop() now SIGTERMs the current mutable child and
339
+ // falls back to SIGKILL after a grace period. After a respawn the
340
+ // live vite is the one killed — the previous stale-.process bug
341
+ // leaked the respawned child and bound the port.
342
+ await Promise.allSettled([
343
+ emulatorSupervisor.stop(),
344
+ frontendSupervisor?.stop() ?? Promise.resolve(),
345
+ ])
346
+ },
347
+ }
348
+ }
349
+ }
350
+
351
+ const buildVirtualDescriptor = (model: string): DeviceDescriptor => ({
352
+ id: `emulator:${model}`,
353
+ model,
354
+ keyCount: resolveKeyCount(model),
355
+ label: `Emulator ${model.toUpperCase()}`,
356
+ transport: "emulated",
357
+ })
@@ -0,0 +1,26 @@
1
+ import { EmulatorOutputClient } from "./emulator"
2
+ import { RealOutputClient } from "./real"
3
+ import type { OutputClient } from "./types"
4
+
5
+ export interface SelectOutputClientOptions {
6
+ readonly emulator: boolean
7
+ readonly xdgConfigHome: string
8
+ }
9
+
10
+ export const selectOutputClient = (
11
+ options: SelectOutputClientOptions,
12
+ ): OutputClient => {
13
+ if (options.emulator) {
14
+ return new EmulatorOutputClient()
15
+ }
16
+ return new RealOutputClient({ xdgConfigHome: options.xdgConfigHome })
17
+ }
18
+
19
+ export type {
20
+ InitOptions,
21
+ OutputClient,
22
+ OutputHandle,
23
+ OutputKind,
24
+ } from "./types"
25
+ export { EmulatorOutputClient } from "./emulator"
26
+ export { RealOutputClient } from "./real"
@@ -0,0 +1,26 @@
1
+ import { execFile } from "node:child_process"
2
+ import { platform } from "node:os"
3
+
4
+ import type pino from "pino"
5
+
6
+ export const openBrowser = (
7
+ url: string,
8
+ logger: pino.Logger,
9
+ noOpen = false,
10
+ ): void => {
11
+ if (noOpen) {
12
+ logger.info("browser auto-open disabled")
13
+ return
14
+ }
15
+ const os = platform()
16
+ const cmd = os === "win32" ? "cmd" : os === "darwin" ? "open" : "xdg-open"
17
+ const args = os === "win32" ? ["/c", "start", "", url] : [url]
18
+ execFile(cmd, args, (err) => {
19
+ if (err !== null) {
20
+ logger.warn(
21
+ { err: err.message },
22
+ "browser auto-open unavailable, open the URL manually",
23
+ )
24
+ }
25
+ })
26
+ }