@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,393 @@
1
+ import { fileURLToPath } from "node:url"
2
+
3
+ import type pino from "pino"
4
+
5
+ import { createGestureDetector } from "@/core/gesture-state"
6
+ import { pushBlackFrame } from "@/device/black-frame"
7
+ import type { DeviceDescriptor } from "@/device/registry"
8
+ import { createReconnectingDevice } from "@/device/reconnecting"
9
+ import { connectStreamDeck, type StreamDeckDevice } from "@/device/stream-deck"
10
+ import { BrowserRenderer } from "@/render/browser-renderer"
11
+ import { pushRawImage } from "@/render/push-raw-image"
12
+ import { NoStreamDeckFoundError, selectDevice } from "@/system/device-selection"
13
+ import { saveDeviceConfig } from "@/util/device-config"
14
+
15
+ import {
16
+ DEFAULT_FRONTEND_PORT,
17
+ killChild,
18
+ resolveConfigUiCwd,
19
+ resolveFrontendCwd,
20
+ spawnConfigUiVite,
21
+ spawnFrontendVite,
22
+ } from "../cli/commands/emulator-mode"
23
+ import {
24
+ DEFAULT_VITE_RETRY_SCHEDULE_MS,
25
+ supervise,
26
+ type SuperviseHandle,
27
+ } from "../cli/commands/subprocess-supervisor"
28
+
29
+ import type { InitOptions, OutputClient, OutputHandle } from "./types"
30
+ import { writeRuntimeState, type RuntimeState } from "@/util/daemon"
31
+
32
+ export interface RealOutputClientOptions {
33
+ readonly xdgConfigHome: string
34
+ }
35
+
36
+ export class RealOutputClient implements OutputClient {
37
+ readonly kind = "real" as const
38
+
39
+ private readonly xdgConfigHome: string
40
+ private device: StreamDeckDevice | null = null
41
+ // ponytail: the Stream Deck SDK exposes setBrightness but no getter, so we
42
+ // track the last value we set to skip redundant hardware writes.
43
+ private deviceBrightness: number | null = null
44
+ private descriptor: DeviceDescriptor | null = null
45
+
46
+ constructor(options: RealOutputClientOptions) {
47
+ this.xdgConfigHome = options.xdgConfigHome
48
+ }
49
+
50
+ async validateReady(): Promise<void> {
51
+ const devices = await this.listDevices()
52
+ if (devices.length === 0) {
53
+ throw new Error(
54
+ "No Stream Deck devices found. Connect a device and try again. On Linux, udev rules for vendor 0fd9 may be required — see packages/cli/src/device/linux-udev.ts for the rule file template.",
55
+ )
56
+ }
57
+ }
58
+
59
+ async listDevices(): Promise<ReadonlyArray<DeviceDescriptor>> {
60
+ return (await import("@/device/registry")).listDevices()
61
+ }
62
+
63
+ async selectDevice(
64
+ devices: ReadonlyArray<DeviceDescriptor>,
65
+ savedId: string | null,
66
+ logger: pino.Logger,
67
+ ): Promise<DeviceDescriptor> {
68
+ try {
69
+ const selection = await selectDevice({
70
+ devices,
71
+ ...(savedId !== null
72
+ ? {
73
+ current: {
74
+ serial: savedId,
75
+ path: "",
76
+ model: "",
77
+ },
78
+ }
79
+ : {}),
80
+ logger,
81
+ })
82
+ this.descriptor = selection.descriptor
83
+ return selection.descriptor
84
+ } catch (err) {
85
+ if (err instanceof NoStreamDeckFoundError) {
86
+ throw new Error(
87
+ "No Stream Deck devices found. Connect a device and try again. On Linux, udev rules may be required — see sireno install-udev.",
88
+ )
89
+ }
90
+ throw err
91
+ }
92
+ }
93
+
94
+ async storeSelection(descriptor: DeviceDescriptor): Promise<void> {
95
+ saveDeviceConfig({
96
+ xdgConfigHome: this.xdgConfigHome,
97
+ config: {
98
+ serial: descriptor.id,
99
+ path: descriptor.id,
100
+ model: descriptor.model,
101
+ },
102
+ })
103
+ }
104
+
105
+ async init(opts: InitOptions): Promise<OutputHandle> {
106
+ if (this.descriptor === null) {
107
+ throw new Error("RealOutputClient.init: selectDevice() must run first")
108
+ }
109
+ const descriptor = this.descriptor
110
+ const logger = opts.logger.child({ component: "real" })
111
+
112
+ let hardware: StreamDeckDevice
113
+ try {
114
+ hardware = await connectStreamDeck({ serial: descriptor.id })
115
+ } catch {
116
+ throw new Error(
117
+ `Saved device ${descriptor.id} is no longer connected. Re-run with --config to pick another.`,
118
+ )
119
+ }
120
+
121
+ // ponytail: from here on the pipeline talks to a wrapper, not the raw
122
+ // handle. A KVM switching hosts removes the USB device, the SDK emits
123
+ // `error`, and with nothing subscribed that unhandled event threw straight
124
+ // into the daemon's uncaughtException guard — the service died with no
125
+ // recovery. Nothing else in the pipeline cares about USB, so only the
126
+ // device handle is swapped; writes are dropped while the deck is away and
127
+ // the keys are repainted when it returns.
128
+ let renderer: BrowserRenderer | null = null
129
+ const device = createReconnectingDevice(hardware, {
130
+ serial: descriptor.id,
131
+ connect: connectStreamDeck,
132
+ logger,
133
+ onReconnect: async (fresh) => {
134
+ if (this.deviceBrightness !== null) {
135
+ await fresh.setBrightness(this.deviceBrightness)
136
+ }
137
+ renderer?.forceRedraw()
138
+ },
139
+ })
140
+ this.device = device
141
+
142
+ let shuttingDown = false
143
+ let frontendSupervisor: SuperviseHandle | null = null
144
+ let configUiSupervisor: SuperviseHandle | null = null
145
+
146
+ try {
147
+ // ponytail: hardware-only splash — push the logo immediately after the
148
+ // device is connected and before Playwright/Vite takes over. pushRawImage
149
+ // swallows errors (non-fatal). Skipped on emulator (no pushRawImage method).
150
+ const splashPath = fileURLToPath(
151
+ new URL("../assets/logoFull.png", import.meta.url),
152
+ )
153
+ try {
154
+ await pushRawImage({
155
+ imagePath: splashPath,
156
+ device,
157
+ logger,
158
+ })
159
+ } catch (err) {
160
+ logger.warn(
161
+ { err: (err as Error).message, splashPath },
162
+ "real: splash push failed (non-fatal)",
163
+ )
164
+ }
165
+
166
+ opts.bridge.setDevice(descriptor)
167
+
168
+ const unsubscribeBrightness = opts.pubSub.subscribe<{
169
+ deckId: string
170
+ position: number
171
+ durationMs: number
172
+ }>("runtime:buttonError", () => {})
173
+ void unsubscribeBrightness
174
+
175
+ const unsubAdjust = opts.pubSub.subscribe<{
176
+ direction: "up" | "down"
177
+ value: number
178
+ }>("methods:adjustBrightness", ({ value }) => {
179
+ if (this.device === null) return
180
+ const current = this.deviceBrightness
181
+ if (value === current) return
182
+ this.deviceBrightness = value
183
+ void this.device.setBrightness(value)
184
+ logger.info(
185
+ { from: current, to: value },
186
+ "real mode: hardware brightness adjusted",
187
+ )
188
+ opts.pubSub.publish("sireno:settings:brightness", { value })
189
+ })
190
+
191
+ const mainDeck = opts.decks.find((d) => d.isMain) ?? opts.decks[0]
192
+
193
+ const gestureDetector = createGestureDetector({
194
+ onGesture: (result) => {
195
+ const keyIndex = result.keyIndex ?? -1
196
+ const activeDeck = opts.runtime.getActiveDeck()
197
+ const button = activeDeck.buttons.find((b) => {
198
+ if (b.position === keyIndex) return true
199
+ const parsed = Number.parseInt(b.id, 10)
200
+ return Number.isFinite(parsed) && parsed === keyIndex
201
+ })
202
+ const position = button?.position ?? -1
203
+ logger.debug(
204
+ {
205
+ keyIndex,
206
+ gesture: result.kind,
207
+ position,
208
+ activeDeckId: activeDeck.id,
209
+ buttonId: button?.id ?? null,
210
+ },
211
+ "real mode: gesture detected",
212
+ )
213
+ if (button === undefined) {
214
+ logger.warn(
215
+ { keyIndex, activeDeckId: activeDeck.id },
216
+ "real mode: keyIndex not mapped to any button on active deck",
217
+ )
218
+ return
219
+ }
220
+ void opts.runtime.dispatchGesture(
221
+ `${activeDeck.id}:${button.id}`,
222
+ result.kind,
223
+ )
224
+ },
225
+ })
226
+
227
+ const gestureUnsubscribe = device.onKeyEvent((event) => {
228
+ logger.debug(
229
+ { keyIndex: event.keyIndex, type: event.type },
230
+ "real mode: key event received",
231
+ )
232
+ gestureDetector.detect({
233
+ type: event.type,
234
+ timestamp: event.timestamp,
235
+ keyIndex: event.keyIndex,
236
+ })
237
+ })
238
+ void mainDeck
239
+
240
+ let frontendUrl =
241
+ opts.frontendUrl ??
242
+ `http://127.0.0.1:${opts.port ?? DEFAULT_FRONTEND_PORT}`
243
+ if (opts.frontendUrl === undefined) {
244
+ frontendSupervisor = await supervise({
245
+ label: "frontend vite",
246
+ kill: killChild,
247
+ delayScheduleMs: DEFAULT_VITE_RETRY_SCHEDULE_MS,
248
+ spawn: async () => {
249
+ const r = await spawnFrontendVite({
250
+ port: opts.port ?? DEFAULT_FRONTEND_PORT,
251
+ cwd: resolveFrontendCwd(),
252
+ pnpmCommand: "pnpm",
253
+ readyTimeoutMs: 30_000,
254
+ wsUrl: `ws://127.0.0.1:${opts.bridge.port}`,
255
+ logger,
256
+ themeDir: opts.themeDir,
257
+ configPath: opts.configPath,
258
+ emulatorMode: false,
259
+ ...(opts.onChildPid !== undefined
260
+ ? { onPid: opts.onChildPid }
261
+ : {}),
262
+ })
263
+ frontendUrl = r.url
264
+ return r.process
265
+ },
266
+ onGiveUp: () => opts.onChildCrash?.(),
267
+ isShuttingDown: () => shuttingDown,
268
+ logger,
269
+ })
270
+ }
271
+
272
+ let configUiUrl = `http://127.0.0.1:${DEFAULT_FRONTEND_PORT + 1}`
273
+ configUiSupervisor = await supervise({
274
+ label: "config ui vite",
275
+ kill: killChild,
276
+ delayScheduleMs: DEFAULT_VITE_RETRY_SCHEDULE_MS,
277
+ spawn: async () => {
278
+ const r = await spawnConfigUiVite({
279
+ port: DEFAULT_FRONTEND_PORT + 1,
280
+ cwd: resolveConfigUiCwd(),
281
+ pnpmCommand: "pnpm",
282
+ readyTimeoutMs: 30_000,
283
+ logger,
284
+ wsUrl: `ws://127.0.0.1:${opts.bridge.port}`,
285
+ frontendUrl,
286
+ configPath: opts.configPath,
287
+ emulatorMode: false,
288
+ onPid: opts.onChildPid,
289
+ })
290
+ configUiUrl = r.url
291
+ return r.process
292
+ },
293
+ onGiveUp: () => opts.onChildCrash?.(),
294
+ isShuttingDown: () => shuttingDown,
295
+ logger,
296
+ })
297
+
298
+ logger.info({ frontendUrl }, "real mode: frontend URL")
299
+
300
+ renderer = new BrowserRenderer({
301
+ frontendUrl: `${frontendUrl}${frontendUrl.includes("?") ? "&" : "?"}compact=1`,
302
+ device,
303
+ logger,
304
+ ...(opts.intervalMs !== undefined
305
+ ? { intervalMs: opts.intervalMs }
306
+ : {}),
307
+ pubSub: opts.pubSub,
308
+ })
309
+ await renderer.start()
310
+
311
+ const frontendVitePid = frontendSupervisor?.process.pid ?? 0
312
+ const configUiPid = configUiSupervisor.process.pid ?? 0
313
+ const childPids = [frontendVitePid, configUiPid].filter((pid) => pid > 0)
314
+
315
+ const state: RuntimeState = {
316
+ configUiUrl,
317
+ wsUrl: opts.bridge.url,
318
+ frontendUrl,
319
+ lanHost: opts.lanHost ?? "127.0.0.1",
320
+ addresses: opts.lanAddresses ?? [],
321
+ emulatorMode: false,
322
+ remote: false,
323
+ startedAt: Date.now(),
324
+ theme: opts.theme.name,
325
+ }
326
+ writeRuntimeState(state)
327
+ logger.info({ frontendUrl }, "real mode: runtime state written")
328
+
329
+ return {
330
+ descriptor,
331
+ frontendUrl,
332
+ configUiUrl,
333
+ wsUrl: opts.bridge.url,
334
+ childPids,
335
+ async pushBlackFrame(): Promise<void> {
336
+ await pushBlackFrame(device, logger)
337
+ },
338
+ async pushRawImage(filePath: string): Promise<void> {
339
+ try {
340
+ await pushRawImage({
341
+ imagePath: filePath,
342
+ device,
343
+ logger,
344
+ })
345
+ } catch (err) {
346
+ logger.warn(
347
+ { err: (err as Error).message, filePath },
348
+ "real: pushRawImage failed (non-fatal)",
349
+ )
350
+ }
351
+ },
352
+ stop: async (): Promise<void> => {
353
+ gestureUnsubscribe()
354
+ try {
355
+ await renderer?.stop()
356
+ } finally {
357
+ try {
358
+ await device.close()
359
+ } catch {
360
+ void 0
361
+ } finally {
362
+ this.device = null
363
+ }
364
+ }
365
+ if (frontendSupervisor !== null) {
366
+ shuttingDown = true
367
+ // supervisor.stop() SIGTERMs the live child (respawn-aware) and
368
+ // falls back to SIGKILL. The previous killChild(handle.process)
369
+ // captured the initial child and leaked the respawned vite.
370
+ await frontendSupervisor.stop()
371
+ }
372
+ await configUiSupervisor?.stop()
373
+ },
374
+ }
375
+ } catch (err) {
376
+ shuttingDown = true
377
+ await Promise.allSettled([
378
+ renderer?.stop() ?? Promise.resolve(),
379
+ frontendSupervisor?.stop() ?? Promise.resolve(),
380
+ configUiSupervisor?.stop() ?? Promise.resolve(),
381
+ ])
382
+ await pushBlackFrame(device, logger)
383
+ try {
384
+ await device.close()
385
+ } catch {
386
+ void 0
387
+ } finally {
388
+ this.device = null
389
+ }
390
+ throw err
391
+ }
392
+ }
393
+ }
@@ -0,0 +1,83 @@
1
+ import type pino from "pino"
2
+
3
+ import type { PubSub, Runtime, RuntimeDeck, Store } from "@/deck"
4
+ import type { DeviceDescriptor } from "@/device/registry"
5
+ import type { WsBridge } from "@/render/ws-bridge"
6
+
7
+ export type OutputKind = "real" | "emulator"
8
+
9
+ export interface OutputClient {
10
+ readonly kind: OutputKind
11
+ validateReady(): Promise<void>
12
+ listDevices(): Promise<ReadonlyArray<DeviceDescriptor>>
13
+ selectDevice(
14
+ devices: ReadonlyArray<DeviceDescriptor>,
15
+ savedId: string | null,
16
+ logger: pino.Logger,
17
+ ): Promise<DeviceDescriptor>
18
+ storeSelection(descriptor: DeviceDescriptor): Promise<void>
19
+ init(opts: InitOptions): Promise<OutputHandle>
20
+ }
21
+
22
+ export interface InitOptions {
23
+ readonly bridge: WsBridge
24
+ readonly runtime: Runtime
25
+ readonly pubSub: PubSub
26
+ readonly store: Store
27
+ readonly decks: ReadonlyArray<RuntimeDeck>
28
+ readonly theme: { name: string; apiVersion: number }
29
+ readonly themeDir: string
30
+ readonly configPath?: string
31
+ readonly logger: pino.Logger
32
+ readonly frontendUrl?: string
33
+ readonly port?: number
34
+ readonly intervalMs?: number
35
+ /**
36
+ * --remote: bind WS bridge and Vite to 0.0.0.0 and token-gate the HTTP
37
+ * servers. The CLI prints a LAN-accessible QR code. Default false.
38
+ */
39
+ readonly remote?: boolean
40
+ /**
41
+ * --no-autoopen: do not launch the config UI URL in the system browser.
42
+ * Useful for capture tooling and headless runs. Default false.
43
+ */
44
+ readonly noAutoOpen?: boolean
45
+ /**
46
+ * The resolved LAN IP to use in the runtime-state.json. Only set when
47
+ * remote is true or --emulator is set.
48
+ */
49
+ readonly lanHost?: string
50
+ /**
51
+ * All discovered LAN addresses, used in runtime-state.json so the CLI can
52
+ * enumerate them in the QR banner.
53
+ */
54
+ readonly lanAddresses?: ReadonlyArray<string>
55
+ /**
56
+ * Rebuild the runtime deck set for a new device keyCount. The bridge then
57
+ * re-broadcasts a fresh deck-config so the frontend re-renders into the
58
+ * correct grid. Optional — emulator-only callers provide this; real
59
+ * devices have a fixed keyCount and skip the rebuild.
60
+ */
61
+ readonly rebuildDecksForKeyCount?: (
62
+ keyCount: number,
63
+ ) => ReadonlyArray<RuntimeDeck>
64
+ readonly onChildPid?: (pid: number) => void
65
+ /**
66
+ * Called when a supervised child (frontend/config UI vite) gives up after
67
+ * exhausting its retry budget. The pipeline uses this to resolve `done` and
68
+ * trigger the full shutdown sequence. ponytail: kept optional so existing
69
+ * tests that don't simulate subprocess crashes don't have to wire it.
70
+ */
71
+ readonly onChildCrash?: () => void
72
+ }
73
+
74
+ export interface OutputHandle {
75
+ readonly descriptor: DeviceDescriptor
76
+ readonly frontendUrl: string
77
+ readonly configUiUrl?: string
78
+ readonly wsUrl?: string
79
+ readonly childPids: ReadonlyArray<number>
80
+ pushBlackFrame?(): Promise<void>
81
+ pushRawImage?(filePath: string): Promise<void>
82
+ stop(): Promise<void>
83
+ }