@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,289 @@
1
+ import { spawn, type ChildProcess } from "node:child_process"
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
3
+ import { dirname, join, resolve as resolvePath } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ import type pino from "pino"
7
+
8
+ import {
9
+ BUILT_IN_THEMES,
10
+ buildThemeCssFromManifest,
11
+ copyThemeAssets,
12
+ readAndValidateManifest,
13
+ } from "@/themes/loader"
14
+
15
+ export const DEFAULT_FRONTEND_PORT = 5180
16
+ export const DEFAULT_CONFIG_UI_PORT = 52938
17
+ const DEFAULT_TIMEOUT_MS = 30_000
18
+ // eslint-disable-next-line no-control-regex
19
+ const ANSI_REGEX = /\u001b\[[0-9;]*m/g
20
+ // Matches Vite's Local: line — localhost binding
21
+ const LOCAL_URL_REGEX =
22
+ /(?:Local|➜\s*Local)[^\n]*?(https?):\/\/([0-9.]+|[^:\s]+)(?::(\d+))?/
23
+ // Matches Vite's Network: line — 0.0.0.0 binding
24
+ const NETWORK_URL_REGEX =
25
+ /(?:Network)[^\n]*?(https?):\/\/([0-9.]+|[^:\s]+)(?::(\d+))?/
26
+
27
+ export const findWorkspaceRoot = (): string => {
28
+ const here = dirname(fileURLToPath(import.meta.url))
29
+ let dir = here
30
+ for (let i = 0; i < 8; i += 1) {
31
+ if (existsSync(resolvePath(dir, "pnpm-workspace.yaml"))) return dir
32
+ const parent = dirname(dir)
33
+ if (parent === dir) break
34
+ dir = parent
35
+ }
36
+ return here
37
+ }
38
+
39
+ export const resolveConfigUiCwd = (override?: string): string => {
40
+ if (override !== undefined) return override
41
+ return resolvePath(findWorkspaceRoot(), "packages", "cli", "config-ui")
42
+ }
43
+
44
+ export const resolveFrontendCwd = (): string =>
45
+ resolvePath(findWorkspaceRoot(), "packages", "cli", "frontend")
46
+
47
+ interface ViteSpawnOptions {
48
+ port: number
49
+ cwd: string
50
+ pnpmCommand: string
51
+ readyTimeoutMs: number
52
+ logger: pino.Logger
53
+ wsUrl?: string
54
+ frontendUrl?: string
55
+ themeDir?: string
56
+ configPath?: string
57
+ host?: string
58
+ requireToken?: string
59
+ emulatorMode?: boolean
60
+ childLabel: "frontend vite" | "config ui vite"
61
+ exitFatalMessage: string
62
+ onPid?: (pid: number) => void
63
+ }
64
+
65
+ const spawnViteAndWaitForReady = (
66
+ options: ViteSpawnOptions,
67
+ ): Promise<{ process: ChildProcess; url: string }> => {
68
+ const {
69
+ port,
70
+ cwd,
71
+ readyTimeoutMs,
72
+ logger,
73
+ wsUrl,
74
+ frontendUrl,
75
+ themeDir,
76
+ configPath,
77
+ host,
78
+ requireToken,
79
+ emulatorMode,
80
+ childLabel,
81
+ exitFatalMessage,
82
+ onPid,
83
+ } = options
84
+
85
+ return new Promise((resolve, reject) => {
86
+ if (!existsSync(cwd)) {
87
+ reject(new Error(`${childLabel} workspace not found at ${cwd}`))
88
+ return
89
+ }
90
+ const env: Record<string, string> = { ...process.env, FORCE_COLOR: "0" }
91
+ if (wsUrl !== undefined) {
92
+ env["SIRENO_WS_URL"] = wsUrl
93
+ }
94
+ if (frontendUrl !== undefined) {
95
+ env["SIRENO_FRONTEND_URL"] = frontendUrl
96
+ }
97
+ if (themeDir !== undefined) {
98
+ env["SIRENO_THEME_DIR"] = themeDir
99
+ }
100
+ if (configPath !== undefined) {
101
+ env["SIRENO_CONFIG_PATH"] = configPath
102
+ }
103
+ if (host !== undefined) {
104
+ env["SIRENO_VITE_HOST"] = host
105
+ }
106
+ if (requireToken !== undefined) {
107
+ env["SIRENO_REQUIRE_TOKEN"] = requireToken
108
+ }
109
+ if (emulatorMode !== undefined) {
110
+ env["SIRENO_EMULATOR_MODE"] = String(emulatorMode)
111
+ }
112
+ const viteBin = findWorkspaceRoot() + "/node_modules/.bin/vite"
113
+ const viteArgs = [
114
+ "--config",
115
+ resolvePath(cwd, "vite.config.ts"),
116
+ "--port",
117
+ String(port),
118
+ ]
119
+ if (host !== undefined) {
120
+ viteArgs.push("--host", host)
121
+ }
122
+ const child = spawn(viteBin, viteArgs, {
123
+ cwd,
124
+ env,
125
+ stdio: ["ignore", "pipe", "pipe"],
126
+ })
127
+ const spawnedPid = child.pid
128
+ if (spawnedPid !== undefined) onPid?.(spawnedPid)
129
+
130
+ const stdoutChunks: string[] = []
131
+ const stderrChunks: string[] = []
132
+ let settled = false
133
+ let settledUrl: string | null = null
134
+
135
+ const collectOutput = (text: string, label: "stdout" | "stderr"): void => {
136
+ const stripped = text.replace(ANSI_REGEX, "").trimEnd()
137
+ if (stripped.length === 0) return
138
+ const formatted = `${label}:\n${stripped}`
139
+ if (label === "stdout") stdoutChunks.push(formatted)
140
+ else stderrChunks.push(formatted)
141
+ if (label === "stderr") logger.warn(formatted)
142
+ else logger.info(formatted)
143
+ }
144
+
145
+ const timer = setTimeout(() => {
146
+ child.kill("SIGTERM")
147
+ const output = stdoutChunks.join("") + stderrChunks.join("")
148
+ const detail = output.length > 0 ? `\n output:\n${output}` : ""
149
+ reject(
150
+ new Error(
151
+ `${childLabel} did not become ready within ${readyTimeoutMs}ms${detail}`,
152
+ ),
153
+ )
154
+ }, readyTimeoutMs)
155
+
156
+ const onData = (chunk: Buffer): void => {
157
+ const text = chunk.toString()
158
+ collectOutput(text, "stdout")
159
+ const stripped = text.replace(ANSI_REGEX, "")
160
+ const regex = host === "0.0.0.0" ? NETWORK_URL_REGEX : LOCAL_URL_REGEX
161
+ const match = stripped.match(regex)
162
+ if (match && match[1] && match[2] && match[3]) {
163
+ clearTimeout(timer)
164
+ const resolvedHost = host ?? "127.0.0.1"
165
+ const url = `${match[1]}://${resolvedHost}:${match[3]}`
166
+ if (childLabel === "frontend vite") {
167
+ setTimeout(() => {
168
+ settled = true
169
+ settledUrl = url
170
+ resolve({ process: child, url })
171
+ }, 1000)
172
+ } else {
173
+ settled = true
174
+ settledUrl = url
175
+ resolve({ process: child, url })
176
+ }
177
+ }
178
+ }
179
+
180
+ child.stdout?.on("data", onData)
181
+ child.stderr?.on("data", (chunk: Buffer) => {
182
+ collectOutput(chunk.toString(), "stderr")
183
+ })
184
+ child.on("exit", (code) => {
185
+ clearTimeout(timer)
186
+ if (settled) {
187
+ // ponytail: this logs as a warn, not a fatal. The supervisor (which
188
+ // owns this child) already decides whether to respawn or give up
189
+ // and emits its own warn + fatal as appropriate. Logging fatal here
190
+ // is redundant and clutters `p dev restart`, which legitimately
191
+ // kills the previous vite as part of the stop→start cycle.
192
+ logger.warn({ code, url: settledUrl }, exitFatalMessage)
193
+ return
194
+ }
195
+ const output = stdoutChunks.join("") + stderrChunks.join("")
196
+ const detail = output.length > 0 ? `\n output:\n${output}` : ""
197
+ reject(
198
+ new Error(
199
+ `${childLabel} exited (code=${code}) before becoming ready${detail}`,
200
+ ),
201
+ )
202
+ })
203
+ child.on("error", (err) => {
204
+ clearTimeout(timer)
205
+ reject(err)
206
+ })
207
+ })
208
+ }
209
+
210
+ export const spawnFrontendVite = (options: {
211
+ port: number
212
+ cwd: string
213
+ pnpmCommand: string
214
+ readyTimeoutMs: number
215
+ logger: pino.Logger
216
+ wsUrl?: string
217
+ themeDir?: string
218
+ configPath?: string
219
+ host?: string
220
+ requireToken?: string
221
+ emulatorMode?: boolean
222
+ onPid?: (pid: number) => void
223
+ }): Promise<{ process: ChildProcess; url: string }> =>
224
+ spawnViteAndWaitForReady({
225
+ ...options,
226
+ emulatorMode: options.emulatorMode ?? false,
227
+ childLabel: "frontend vite",
228
+ exitFatalMessage:
229
+ "frontend vite exited after becoming ready — emulator will show a blank deck until the frontend is restarted",
230
+ })
231
+
232
+ export const spawnConfigUiVite = (options: {
233
+ port: number
234
+ cwd: string
235
+ pnpmCommand: string
236
+ readyTimeoutMs: number
237
+ logger: pino.Logger
238
+ wsUrl?: string
239
+ frontendUrl?: string
240
+ configPath?: string
241
+ host?: string
242
+ requireToken?: string
243
+ emulatorMode?: boolean
244
+ onPid?: (pid: number) => void
245
+ }): Promise<{ process: ChildProcess; url: string }> =>
246
+ spawnViteAndWaitForReady({
247
+ ...options,
248
+ emulatorMode: options.emulatorMode ?? true,
249
+ childLabel: "config ui vite",
250
+ exitFatalMessage:
251
+ "config ui vite exited after becoming ready — commands from buttons will no longer be received",
252
+ })
253
+
254
+ export const killChild = (child: ChildProcess): Promise<void> =>
255
+ new Promise<void>((resolve) => {
256
+ if (child.exitCode !== null) {
257
+ resolve()
258
+ return
259
+ }
260
+ child.once("exit", () => resolve())
261
+ child.kill("SIGTERM")
262
+ setTimeout(() => {
263
+ if (child.exitCode === null) child.kill("SIGKILL")
264
+ }, 2_000)
265
+ })
266
+
267
+ export const ensureDefaultThemeEnv = (frontendCwd: string): void => {
268
+ if (
269
+ process.env["SIRENO_THEME"] !== undefined &&
270
+ process.env["SIRENO_THEME"].length > 0
271
+ ) {
272
+ return
273
+ }
274
+ const defaultSpec = BUILT_IN_THEMES[0]
275
+ if (defaultSpec === undefined) return
276
+ const manifestPath = resolvePath(defaultSpec.dir, "sirenodeck.json")
277
+ process.env["SIRENO_THEME"] = JSON.stringify({
278
+ name: defaultSpec.name,
279
+ manifestPath,
280
+ uiOverridesPath: null,
281
+ })
282
+ process.env["SIRENO_THEME_DIR"] = frontendCwd
283
+ const cssDir = join(frontendCwd, ".sirenodeck")
284
+ if (!existsSync(cssDir)) mkdirSync(cssDir, { recursive: true })
285
+ const manifest = readAndValidateManifest(manifestPath, defaultSpec.name)
286
+ const cssContent = buildThemeCssFromManifest(manifest, defaultSpec.dir)
287
+ writeFileSync(join(cssDir, "theme.css"), cssContent, "utf8")
288
+ copyThemeAssets(defaultSpec.dir, cssDir)
289
+ }
@@ -0,0 +1,128 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs"
2
+ import { dirname, join, resolve as resolvePath } from "node:path"
3
+
4
+ import { execa } from "execa"
5
+ import { parse, stringify } from "yaml"
6
+
7
+ import { resolveConfigPath, type ConfigSource } from "./pipeline/helpers"
8
+ import { loadConfig } from "@/config/loader"
9
+ import {
10
+ assertPackageManager,
11
+ detectPackageManager,
12
+ globalPackageRoot,
13
+ packageManagerArgs,
14
+ type PackageManager,
15
+ } from "../package-manager"
16
+
17
+ export interface InstallOptions {
18
+ readonly packageName: string
19
+ readonly config?: string
20
+ readonly global?: boolean
21
+ readonly packageManager?: PackageManager
22
+ readonly homeDir?: string
23
+ readonly xdgConfigHome?: string
24
+ readonly configure?: boolean
25
+ }
26
+
27
+ const configPathFor = (
28
+ options: InstallOptions,
29
+ ): { path: string; source: ConfigSource } =>
30
+ resolveConfigPath({
31
+ config: options.config,
32
+ homeDir: options.homeDir,
33
+ xdgConfigHome: options.xdgConfigHome,
34
+ logger: console as never,
35
+ })
36
+
37
+ export const installPackage = async (
38
+ options: InstallOptions,
39
+ ): Promise<void> => {
40
+ const resolved = configPathFor(options)
41
+ const configPath = resolvePath(resolved.path)
42
+ const configDir = dirname(configPath)
43
+ loadConfig({ configPath })
44
+ const manager = options.packageManager ?? detectPackageManager(configDir)
45
+ assertPackageManager(manager)
46
+ await execa(
47
+ manager,
48
+ packageManagerArgs(manager, options.packageName, options.global === true),
49
+ {
50
+ cwd: options.global === true ? undefined : configDir,
51
+ stdio: "inherit",
52
+ },
53
+ )
54
+
55
+ const packageRoot = options.global === true ? undefined : configDir
56
+ const manifestPath = await resolveManifest(
57
+ options.packageName,
58
+ packageRoot,
59
+ options.global === true
60
+ ? (globalPackageRoot(manager) ?? undefined)
61
+ : undefined,
62
+ )
63
+ if (manifestPath === null)
64
+ throw new Error(
65
+ `Installed package '${options.packageName}' has no valid sirenodeck.json manifest.`,
66
+ )
67
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as {
68
+ kind?: string
69
+ }
70
+ const raw = parse(readFileSync(configPath, "utf8")) as Record<string, unknown>
71
+ if (options.configure === false) return
72
+ if (manifest.kind === "theme") {
73
+ raw.theme =
74
+ options.global === true
75
+ ? { src: options.packageName, global: true }
76
+ : options.packageName
77
+ } else if (manifest.kind === "addon") {
78
+ const addons = Array.isArray(raw.addons) ? raw.addons : []
79
+ if (
80
+ !addons.some((entry) =>
81
+ typeof entry === "string"
82
+ ? entry === options.packageName
83
+ : entry !== null &&
84
+ typeof entry === "object" &&
85
+ (entry as { src?: string }).src === options.packageName,
86
+ )
87
+ ) {
88
+ addons.push(
89
+ options.global === true
90
+ ? { src: options.packageName, global: true }
91
+ : options.packageName,
92
+ )
93
+ }
94
+ raw.addons = addons
95
+ } else {
96
+ throw new Error(
97
+ `Package '${options.packageName}' is neither an addon nor a theme.`,
98
+ )
99
+ }
100
+ writeFileSync(configPath, stringify(raw), "utf8")
101
+ }
102
+
103
+ const resolveManifest = async (
104
+ name: string,
105
+ projectDir?: string,
106
+ globalRoot?: string,
107
+ ): Promise<string | null> => {
108
+ const packageName = name.startsWith("@")
109
+ ? name.slice(
110
+ 0,
111
+ name.indexOf("@", 1) > 0 ? name.indexOf("@", 1) : name.length,
112
+ )
113
+ : name.split("@")[0]!
114
+ if (globalRoot !== undefined) {
115
+ const path = join(globalRoot, packageName, "sirenodeck.json")
116
+ return existsSync(path) ? path : null
117
+ }
118
+ const { createRequire } = await import("node:module")
119
+ const require = createRequire(
120
+ resolvePath(projectDir ?? process.cwd(), "package.json"),
121
+ )
122
+ try {
123
+ const packageJson = require.resolve(`${packageName}/package.json`)
124
+ return resolvePath(dirname(packageJson), "sirenodeck.json")
125
+ } catch {
126
+ return null
127
+ }
128
+ }
@@ -0,0 +1,74 @@
1
+ import { existsSync } from "node:fs"
2
+ import type { CommandModule } from "yargs"
3
+
4
+ import type { Logger } from "pino"
5
+
6
+ import { resolveDaemonPaths } from "@/util/daemon"
7
+ import { tailLogs } from "@/util/log-tail"
8
+
9
+ export interface LogsOptions {
10
+ readonly follow?: boolean
11
+ readonly lines?: number
12
+ readonly timestamps?: boolean
13
+ readonly logger: Logger
14
+ }
15
+
16
+ export const logs = async (options: LogsOptions): Promise<void> => {
17
+ const { logger, follow = true, lines = 50, timestamps = true } = options
18
+ const paths = resolveDaemonPaths()
19
+ const logPath = `${paths.runtimeDir}/service.log`
20
+
21
+ if (!existsSync(logPath)) {
22
+ logger.error(
23
+ { logPath },
24
+ "logs: file does not exist (is the daemon running?)",
25
+ )
26
+ process.exitCode = 1
27
+ return
28
+ }
29
+
30
+ logger.info({ logPath, follow, lines }, "logs: tailing")
31
+ await tailLogs({ logPath, follow, lines, timestamps })
32
+ }
33
+
34
+ interface LogsArgs {
35
+ follow?: boolean
36
+ lines?: number
37
+ timestamps?: boolean
38
+ }
39
+
40
+ export const logsCommand: CommandModule<object, LogsArgs> = {
41
+ command: "logs",
42
+ aliases: ["log"],
43
+ describe: "Tail the daemon service log (Ctrl+C to exit)",
44
+ builder: (yargs) =>
45
+ yargs
46
+ .option("follow", {
47
+ alias: "f",
48
+ type: "boolean",
49
+ default: true,
50
+ description: "Follow log output (default: true)",
51
+ })
52
+ .option("lines", {
53
+ alias: "n",
54
+ type: "number",
55
+ default: 50,
56
+ description: "Initial lines to print when following",
57
+ })
58
+ .option("timestamps", {
59
+ alias: "t",
60
+ type: "boolean",
61
+ default: true,
62
+ description: "Show when each entry was logged",
63
+ }),
64
+ handler: async (argv) => {
65
+ const { createLogger } = await import("@/util/logger")
66
+ const logger = createLogger({ verbose: false })
67
+ await logs({
68
+ logger,
69
+ follow: argv.follow !== false,
70
+ timestamps: argv.timestamps !== false,
71
+ ...(argv.lines !== undefined ? { lines: argv.lines } : {}),
72
+ })
73
+ },
74
+ }
@@ -0,0 +1,111 @@
1
+ import { networkInterfaces, type NetworkInterfaceInfo } from "node:os"
2
+
3
+ export interface LanAddress {
4
+ readonly address: string
5
+ readonly interfaceName: string
6
+ }
7
+
8
+ export interface NetworkInterfacesMap {
9
+ readonly [interfaceName: string]:
10
+ | ReadonlyArray<NetworkInterfaceInfo>
11
+ | undefined
12
+ }
13
+
14
+ export interface SelectLanAddressesOptions {
15
+ readonly interfaces?: NetworkInterfacesMap
16
+ readonly networkInterfaces?: typeof networkInterfaces
17
+ }
18
+
19
+ export const TUNNEL_INTERFACE_PATTERN = /^(utun|tun|tap|vpn|tailscale|wg)/i
20
+ export const PHYSICAL_ETHERNET_PATTERN = /^en/i
21
+ export const PHYSICAL_LINUX_ETHERNET_PATTERN = /^eth/i
22
+ export const NON_TUNNEL_WIRELESS_PATTERN = /^wlan/i
23
+
24
+ const LAN_INTERFACE_PRIORITY: ReadonlyArray<{
25
+ readonly pattern: RegExp
26
+ readonly score: number
27
+ }> = [
28
+ { pattern: PHYSICAL_ETHERNET_PATTERN, score: 0 },
29
+ { pattern: PHYSICAL_LINUX_ETHERNET_PATTERN, score: 0 },
30
+ { pattern: NON_TUNNEL_WIRELESS_PATTERN, score: 1 },
31
+ { pattern: TUNNEL_INTERFACE_PATTERN, score: 3 },
32
+ ]
33
+
34
+ const computePriority = (interfaceName: string): number => {
35
+ for (const { pattern, score } of LAN_INTERFACE_PRIORITY) {
36
+ if (pattern.test(interfaceName)) return score
37
+ }
38
+ return 2
39
+ }
40
+
41
+ const isValidIPv4LanAddress = (info: NetworkInterfaceInfo): boolean => {
42
+ if (info.family !== "IPv4") return false
43
+ if (info.internal) return false
44
+ if (info.address.startsWith("127.")) return false
45
+ if (info.address.startsWith("169.254.")) return false
46
+ return true
47
+ }
48
+
49
+ export const selectLanAddresses = (
50
+ options: SelectLanAddressesOptions = {},
51
+ ): ReadonlyArray<LanAddress> => {
52
+ const fetchInterfaces = options.networkInterfaces ?? networkInterfaces
53
+ const interfaces =
54
+ options.interfaces ?? (fetchInterfaces() as unknown as NetworkInterfacesMap)
55
+
56
+ const candidates: LanAddress[] = []
57
+ for (const interfaceName of Object.keys(interfaces)) {
58
+ const list = interfaces[interfaceName]
59
+ if (list === undefined) continue
60
+ const first = list[0]
61
+ if (first === undefined) continue
62
+ if (!isValidIPv4LanAddress(first)) continue
63
+ candidates.push({ address: first.address, interfaceName })
64
+ }
65
+
66
+ return candidates.sort(
67
+ (a, b) =>
68
+ computePriority(a.interfaceName) - computePriority(b.interfaceName),
69
+ )
70
+ }
71
+
72
+ export interface PrintConfigUiBannerOptions {
73
+ readonly configUiUrlFn: (lanAddress: string) => string
74
+ readonly lanAddresses: ReadonlyArray<LanAddress>
75
+ readonly securityWarning: string
76
+ readonly output: (text: string) => void
77
+ readonly qrGenerate?: (text: string) => string | Promise<string>
78
+ }
79
+
80
+ const formatInterfaceLabel = (name: string): string => name
81
+
82
+ export async function printConfigUiBanner(
83
+ options: PrintConfigUiBannerOptions,
84
+ ): Promise<void> {
85
+ const { configUiUrlFn, lanAddresses, securityWarning, output, qrGenerate } =
86
+ options
87
+
88
+ if (lanAddresses.length === 0) {
89
+ output("\n Config UI: http://127.0.0.1:52938\n")
90
+ output(
91
+ "\x1b[33m warning: no LAN interfaces detected — QR may not reach your phone.\x1b[0m\n\n",
92
+ )
93
+ output(`\x1b[33m ${securityWarning}\x1b[0m\n\n`)
94
+ return
95
+ }
96
+
97
+ output("\n Config UI (LAN):\n")
98
+ for (const entry of lanAddresses) {
99
+ const url = configUiUrlFn(entry.address)
100
+ if (qrGenerate !== undefined) {
101
+ output("\n")
102
+ const qr = await qrGenerate(url)
103
+ output(qr)
104
+ output(` ${url} ← ${formatInterfaceLabel(entry.interfaceName)}\n`)
105
+ } else {
106
+ output(` ${url} ← ${formatInterfaceLabel(entry.interfaceName)}\n`)
107
+ }
108
+ }
109
+ output("\n")
110
+ output(`\x1b[33m ${securityWarning}\x1b[0m\n\n`)
111
+ }
@@ -0,0 +1,75 @@
1
+ import { existsSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import { resolve as resolvePath } from "node:path"
4
+
5
+ import { findConfigPath, XDG_CONFIG_SUBDIR } from "@/config/discovery"
6
+ import { getOriginalCwd, resolveUserPath } from "@/cli/cwd"
7
+
8
+ import type { RunOptions } from "../run"
9
+
10
+ export const resolveXdgConfigHome = (options: RunOptions): string =>
11
+ options.xdgConfigHome ??
12
+ process.env["XDG_CONFIG_HOME"] ??
13
+ `${options.homeDir ?? homedir()}/.config`
14
+
15
+ export const resolveXdgConfigPath = (options: RunOptions): string =>
16
+ resolvePath(resolveXdgConfigHome(options), XDG_CONFIG_SUBDIR, "config.yml")
17
+
18
+ export const resolveRunFolderConfigPath = (): string =>
19
+ resolvePath(getOriginalCwd(), "config.yml")
20
+
21
+ export type ConfigSource = "cli" | "xdg" | "run-folder"
22
+
23
+ export interface ResolveConfigPathResult {
24
+ readonly path: string
25
+ readonly source: ConfigSource
26
+ }
27
+
28
+ // ponytail: precedence is cli arg → ~/.config/sirenodeck/config.yml →
29
+ // ./config.yml (run folder). The cli arg is validated eagerly so the
30
+ // failure mode is obvious at startup; the other two come from
31
+ // findConfigPath which walks XDG first, then the run folder.
32
+ export const resolveConfigPath = (
33
+ options: RunOptions,
34
+ ): ResolveConfigPathResult => {
35
+ if (options.config !== undefined) {
36
+ const explicit = resolveUserPath(options.config)
37
+ if (!existsSync(explicit)) {
38
+ throw new Error(
39
+ `Config file not found: ${explicit}\n` +
40
+ ` Fix: pass a valid --config path, or remove --config to let sirenodeck auto-discover config.yml.`,
41
+ )
42
+ }
43
+ return { path: explicit, source: "cli" }
44
+ }
45
+ const cwd = getOriginalCwd()
46
+ const home = options.homeDir ?? homedir()
47
+ const xdgPath = resolveXdgConfigPath(options)
48
+ if (existsSync(xdgPath)) {
49
+ return { path: xdgPath, source: "xdg" }
50
+ }
51
+ const cwdPath = resolvePath(cwd, "config.yml")
52
+ if (existsSync(cwdPath)) {
53
+ return { path: cwdPath, source: "run-folder" }
54
+ }
55
+ // ponytail: re-use findConfigPath to keep the search algorithm in one
56
+ // place — this branch is hit when neither the XDG nor the cwd file
57
+ // exists but a discovery-side option (envVar) is set or the user has
58
+ // a non-default xdgConfigHome. The result is the same XDG-first lookup.
59
+ const found = findConfigPath({
60
+ cwd,
61
+ homeDir: home,
62
+ ...(options.xdgConfigHome !== undefined
63
+ ? { xdgConfigHome: options.xdgConfigHome }
64
+ : {}),
65
+ })
66
+ if (found === null) {
67
+ throw new Error(
68
+ `Could not find config.yml.\n` +
69
+ ` Fix: pass --config <path>, create ./config.yml in the current directory, or create ~/.config/sirenodeck/config.yml.\n` +
70
+ ` Looked in: ${xdgPath} and ${cwdPath}.`,
71
+ )
72
+ }
73
+ const source: ConfigSource = found === xdgPath ? "xdg" : "run-folder"
74
+ return { path: found, source }
75
+ }
@@ -0,0 +1,20 @@
1
+ import { selectOutputClient } from "@/outputClient"
2
+ import { resolveXdgConfigHome } from "./helpers"
3
+ import { validateAndLoadConfig, type RunOptions } from "../run"
4
+
5
+ export const preflight = async (options: RunOptions): Promise<void> => {
6
+ await validateAndLoadConfig(options)
7
+ const outputClient = selectOutputClient({
8
+ emulator: options.emulator === true,
9
+ xdgConfigHome: resolveXdgConfigHome(options),
10
+ })
11
+ if (outputClient.kind === "real") {
12
+ const devices = await outputClient.listDevices()
13
+ if (devices.length === 0) {
14
+ throw new Error(
15
+ "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.",
16
+ )
17
+ }
18
+ }
19
+ await outputClient.validateReady()
20
+ }