@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,121 @@
1
+ import { execSync } from "node:child_process"
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
3
+ import { homedir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import { currentOS, renderTemplate, type TemplateVars } from "./render-template"
7
+
8
+ export interface InstallOptions {
9
+ readonly logger: import("pino").Logger
10
+ readonly system?: boolean
11
+ }
12
+
13
+ const DAEMON_NAME = "sirenodeck"
14
+ const SERVICE_NAME = `${DAEMON_NAME}.service`
15
+
16
+ // ponytail: ExecStart calls `start` — no flags. The systemd-started process
17
+ // detects `INVOCATION_ID` and runs in-process, reading config + flags from
18
+ // runtimeDir. Keeps the unit stable; config changes don't require reinstall.
19
+ //
20
+ // Spawn through the CURRENT node binary rather than letting the supervisor exec
21
+ // the .js directly. bin/sirenodeck.js starts `#!/usr/bin/env node`, and
22
+ // launchd's PATH is only /usr/bin:/bin:/usr/sbin:/sbin — a Homebrew, nvm or
23
+ // Volta node isn't on it, so the agent died with "env: node: No such file or
24
+ // directory" and KeepAlive respawned it forever. process.execPath is absolute
25
+ // and always correct. Both paths are quoted because splitExec (and systemd)
26
+ // honour quotes, and macOS install locations routinely contain spaces.
27
+ const getExecStart = (): string => {
28
+ const script = process.argv[1]
29
+ if (script === undefined) return "sirenodeck start"
30
+ return `"${process.execPath}" "${script}" start`
31
+ }
32
+
33
+ const getTemplateVars = (): TemplateVars => {
34
+ const home = homedir()
35
+ return {
36
+ name: DAEMON_NAME,
37
+ displayName: "Sireno Deck",
38
+ description: "Sireno Deck daemon service",
39
+ execStart: getExecStart(),
40
+ restartPolicy: "always",
41
+ workingDirectory: home,
42
+ logPath: join(home, "Library", "Logs", `${DAEMON_NAME}.log`),
43
+ }
44
+ }
45
+
46
+ const ensureDir = (dir: string, mode: number): void => {
47
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode })
48
+ }
49
+
50
+ export const installService = async (
51
+ options: InstallOptions,
52
+ ): Promise<void> => {
53
+ const { logger, system = false } = options
54
+ const os = currentOS()
55
+ const vars = getTemplateVars()
56
+ const userLevel = !system
57
+
58
+ if (os === "linux") {
59
+ const unitDir = userLevel
60
+ ? join(homedir(), ".config", "systemd", "user")
61
+ : "/etc/systemd/system"
62
+ const unitPath = join(unitDir, SERVICE_NAME)
63
+ const content = renderTemplate(
64
+ os,
65
+ {
66
+ ...vars,
67
+ ...(userLevel ? {} : { user: "root", group: "root" }),
68
+ },
69
+ { userLevel },
70
+ )
71
+
72
+ try {
73
+ if (userLevel) ensureDir(unitDir, 0o755)
74
+ writeFileSync(unitPath, content, { mode: 0o644 })
75
+ logger.info({ path: unitPath }, "install: systemd unit installed")
76
+
77
+ const systemctl = userLevel ? "systemctl --user" : "systemctl"
78
+ execSync(`${systemctl} daemon-reload`, { stdio: "ignore" })
79
+ logger.info({ userLevel }, "install: systemd daemon reloaded")
80
+ execSync(`${systemctl} enable ${SERVICE_NAME}`, { stdio: "ignore" })
81
+ logger.info({ service: SERVICE_NAME, userLevel }, "install: enabled")
82
+ } catch (err) {
83
+ logger.error(
84
+ { err, userLevel },
85
+ userLevel
86
+ ? "install: failed to install user-level systemd service"
87
+ : "install: failed to install system systemd service (needs root)",
88
+ )
89
+ process.exitCode = 1
90
+ }
91
+ } else if (os === "darwin") {
92
+ const plistDir = join(homedir(), "Library", "LaunchAgents")
93
+ const plistPath = join(plistDir, `${DAEMON_NAME}.plist`)
94
+ const logDir = join(homedir(), "Library", "Logs")
95
+ try {
96
+ ensureDir(plistDir, 0o755)
97
+ ensureDir(logDir, 0o755)
98
+ const content = renderTemplate(os, vars, { userLevel })
99
+ writeFileSync(plistPath, content, { mode: 0o644 })
100
+ logger.info({ path: plistPath }, "install: launchd plist installed")
101
+ // ponytail: install used the legacy `launchctl load` while
102
+ // invokeManager drives the modern bootstrap/kickstart API. Mixing the two
103
+ // made the very next `start` fail — bootstrap returns EALREADY (5: Input/
104
+ // output error) against a service the legacy call had already loaded.
105
+ // Writing the plist is enough; invokeManager bootstraps it. Leaving the
106
+ // service un-loaded here is intentional and keeps one API in play.
107
+ logger.info(
108
+ { service: SERVICE_NAME },
109
+ "install: plist written — bootstrap happens on start",
110
+ )
111
+ } catch (err) {
112
+ logger.error({ err }, "install: failed to install launchd service")
113
+ process.exitCode = 1
114
+ }
115
+ } else {
116
+ logger.error(
117
+ "install: native service installation not supported on this platform",
118
+ )
119
+ process.exitCode = 1
120
+ }
121
+ }
@@ -0,0 +1,158 @@
1
+ import { dirname } from "node:path"
2
+ import { platform } from "node:process"
3
+
4
+ export type OS = "linux" | "darwin" | "win32"
5
+
6
+ export const currentOS = (): OS => {
7
+ const p = platform
8
+ if (p === "darwin") return "darwin"
9
+ if (p === "win32") return "win32"
10
+ return "linux"
11
+ }
12
+
13
+ export interface TemplateVars {
14
+ name: string
15
+ displayName: string
16
+ description: string
17
+ execStart: string
18
+ restartPolicy: string
19
+ workingDirectory: string
20
+ user?: string
21
+ group?: string
22
+ logPath: string
23
+ }
24
+
25
+ export interface SystemdOptions {
26
+ userLevel: boolean
27
+ }
28
+
29
+ export const renderSystemd = (
30
+ vars: TemplateVars,
31
+ options: SystemdOptions,
32
+ ): string => {
33
+ const lines: string[] = [
34
+ "[Unit]",
35
+ `Description=${vars.description}`,
36
+ "After=network-online.target",
37
+ "Wants=network-online.target",
38
+ "",
39
+ "[Service]",
40
+ "Type=simple",
41
+ `ExecStart=${vars.execStart}`,
42
+ `Restart=${vars.restartPolicy}`,
43
+ "RestartSec=5",
44
+ `WorkingDirectory=${vars.workingDirectory}`,
45
+ ]
46
+ if (vars.user !== undefined) lines.push(`User=${vars.user}`)
47
+ if (vars.group !== undefined) lines.push(`Group=${vars.group}`)
48
+ lines.push("Environment=NODE_ENV=production")
49
+ lines.push("")
50
+ lines.push("[Install]")
51
+ lines.push(
52
+ `WantedBy=${options.userLevel ? "default.target" : "multi-user.target"}`,
53
+ )
54
+ return `${lines.join("\n")}\n`
55
+ }
56
+
57
+ const splitExec = (execStart: string): readonly string[] => {
58
+ const tokens: string[] = []
59
+ let buf = ""
60
+ let quote: string | null = null
61
+ for (const ch of execStart) {
62
+ if (quote !== null) {
63
+ if (ch === quote) {
64
+ quote = null
65
+ } else {
66
+ buf += ch
67
+ }
68
+ } else if (ch === '"' || ch === "'") {
69
+ quote = ch
70
+ } else if (ch === " ") {
71
+ if (buf.length > 0) {
72
+ tokens.push(buf)
73
+ buf = ""
74
+ }
75
+ } else {
76
+ buf += ch
77
+ }
78
+ }
79
+ if (buf.length > 0) tokens.push(buf)
80
+ return tokens
81
+ }
82
+
83
+ // ponytail: launchd hands an agent a bare PATH (/usr/bin:/bin:/usr/sbin:/sbin).
84
+ // The daemon spawns node-based children (vite) and shells out to Homebrew tools,
85
+ // none of which live there. Prepend the usual Homebrew prefixes and the
86
+ // directory of the node binary launching us, so children resolve the same
87
+ // binaries an interactive shell would.
88
+ const darwinPathEntries = (program: string | undefined): string => {
89
+ const entries = [
90
+ ...(program !== undefined ? [dirname(program)] : []),
91
+ "/opt/homebrew/bin",
92
+ "/usr/local/bin",
93
+ "/usr/bin",
94
+ "/bin",
95
+ "/usr/sbin",
96
+ "/sbin",
97
+ ]
98
+ return [...new Set(entries)].join(":")
99
+ }
100
+
101
+ export const renderDarwinPlist = (vars: TemplateVars): string => {
102
+ const [program, ...restArgs] = splitExec(vars.execStart)
103
+ const lines: string[] = [
104
+ '<?xml version="1.0" encoding="UTF-8"?>',
105
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
106
+ '<plist version="1.0">',
107
+ "<dict>",
108
+ " <key>Label</key>",
109
+ ` <string>${vars.name}</string>`,
110
+ " <key>ProgramArguments</key>",
111
+ " <array>",
112
+ ...(program !== undefined ? [` <string>${program}</string>`] : []),
113
+ ...restArgs.map((arg) => ` <string>${arg}</string>`),
114
+ " </array>",
115
+ " <key>RunAtLoad</key>",
116
+ " <true/>",
117
+ " <key>KeepAlive</key>",
118
+ ` <${vars.restartPolicy === "always" ? "true" : "false"}/>`,
119
+ " <key>EnvironmentVariables</key>",
120
+ " <dict>",
121
+ " <key>PATH</key>",
122
+ ` <string>${darwinPathEntries(program)}</string>`,
123
+ " </dict>",
124
+ " <key>WorkingDirectory</key>",
125
+ ` <string>${vars.workingDirectory}</string>`,
126
+ " <key>StandardOutPath</key>",
127
+ ` <string>${vars.logPath}</string>`,
128
+ " <key>StandardErrorPath</key>",
129
+ ` <string>${vars.logPath}</string>`,
130
+ "</dict>",
131
+ "</plist>",
132
+ ]
133
+ return `${lines.join("\n")}\n`
134
+ }
135
+
136
+ export const renderWindowsSvc = (vars: TemplateVars): string => {
137
+ return `@echo off
138
+ REM ${vars.description}
139
+ sc create ${vars.name} binPath= "${vars.execStart}" start= auto
140
+ sc description ${vars.name} "${vars.description}"
141
+ sc failure "${vars.name}" reset= 86400 actions= restart/5000/restart/10000/restart/30000
142
+ `
143
+ }
144
+
145
+ export const renderTemplate = (
146
+ os: OS,
147
+ vars: TemplateVars,
148
+ options: SystemdOptions,
149
+ ): string => {
150
+ switch (os) {
151
+ case "linux":
152
+ return renderSystemd(vars, options)
153
+ case "darwin":
154
+ return renderDarwinPlist(vars)
155
+ case "win32":
156
+ return renderWindowsSvc(vars)
157
+ }
158
+ }
@@ -0,0 +1,184 @@
1
+ import { execFileSync } from "node:child_process"
2
+ import { existsSync } from "node:fs"
3
+ import { homedir } from "node:os"
4
+ import { join } from "node:path"
5
+ import { platform } from "node:process"
6
+
7
+ import type { Logger } from "pino"
8
+
9
+ export type ServiceAction = "start" | "stop" | "restart" | "reload"
10
+
11
+ export interface EnsureInstalledOptions {
12
+ readonly logger: Logger
13
+ readonly system?: boolean
14
+ }
15
+
16
+ export interface InvokeManagerOptions {
17
+ readonly action: ServiceAction
18
+ readonly logger: Logger
19
+ }
20
+
21
+ const DAEMON_NAME = "sirenodeck"
22
+
23
+ const systemctl = (userLevel: boolean, args: ReadonlyArray<string>): void => {
24
+ const cmd = userLevel ? "systemctl" : "systemctl"
25
+ const fullArgs = userLevel ? ["--user", ...args] : [...args]
26
+ execFileSync(cmd, fullArgs, { stdio: "ignore" })
27
+ }
28
+
29
+ const launchctl = (sub: string, args: ReadonlyArray<string>): void => {
30
+ execFileSync("launchctl", [sub, ...args], { stdio: "ignore" })
31
+ }
32
+
33
+ const sleep = (ms: number): Promise<void> =>
34
+ new Promise((resolve) => setTimeout(resolve, ms))
35
+
36
+ /**
37
+ * Is launchd still holding this job? `print` fails for a label it does not
38
+ * know, which is the only reliable "is it gone yet" signal available.
39
+ */
40
+ const isJobLoaded = (uid: string, label: string): boolean => {
41
+ try {
42
+ execFileSync("launchctl", ["print", `gui/${uid}/${label}`], {
43
+ stdio: "ignore",
44
+ timeout: 5_000,
45
+ })
46
+ return true
47
+ } catch {
48
+ return false
49
+ }
50
+ }
51
+
52
+ /**
53
+ * ponytail: `launchctl bootout` returns before launchd has finished tearing
54
+ * the job down. Bootstrapping straight afterwards fails with a bare I/O error,
55
+ * and because that threw out of `invokeManager`, a restart could end with the
56
+ * agent booted out and never brought back — no daemon at all, which is a worse
57
+ * place than it started. Wait for the label to actually disappear, then
58
+ * bootstrap, retrying while launchd settles. If the job turns out to be loaded
59
+ * already, a kickstart is the right move rather than another bootstrap.
60
+ */
61
+ const bootstrapWithRetry = async (
62
+ uid: string,
63
+ plist: string,
64
+ label: string,
65
+ timeoutMs = 15_000,
66
+ ): Promise<void> => {
67
+ const deadline = Date.now() + timeoutMs
68
+ let lastError: unknown = null
69
+ while (Date.now() < deadline) {
70
+ if (isJobLoaded(uid, label)) {
71
+ try {
72
+ launchctl("enable", [`gui/${uid}/${label}`])
73
+ } catch {
74
+ // already enabled
75
+ }
76
+ launchctl("kickstart", [`gui/${uid}/${label}`])
77
+ return
78
+ }
79
+ try {
80
+ launchctl("bootstrap", [`gui/${uid}`, plist])
81
+ try {
82
+ launchctl("enable", [`gui/${uid}/${label}`])
83
+ } catch {
84
+ // already enabled
85
+ }
86
+ launchctl("kickstart", [`gui/${uid}/${label}`])
87
+ return
88
+ } catch (err) {
89
+ lastError = err
90
+ await sleep(300)
91
+ }
92
+ }
93
+ throw lastError instanceof Error
94
+ ? lastError
95
+ : new Error(`launchctl: could not bootstrap ${label} within ${timeoutMs}ms`)
96
+ }
97
+
98
+ const currentOS = (): "linux" | "darwin" | "win32" => {
99
+ if (platform === "darwin") return "darwin"
100
+ if (platform === "win32") return "win32"
101
+ return "linux"
102
+ }
103
+
104
+ export const isUnitInstalled = (userLevel = true): boolean => {
105
+ const os = currentOS()
106
+ if (os === "linux") {
107
+ const unitPath = userLevel
108
+ ? join(homedir(), ".config", "systemd", "user", `${DAEMON_NAME}.service`)
109
+ : `/etc/systemd/system/${DAEMON_NAME}.service`
110
+ return existsSync(unitPath)
111
+ }
112
+ if (os === "darwin") {
113
+ return existsSync(
114
+ join(homedir(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`),
115
+ )
116
+ }
117
+ return false
118
+ }
119
+
120
+ export const ensureInstalled = async (
121
+ options: EnsureInstalledOptions,
122
+ ): Promise<void> => {
123
+ const { logger, system = false } = options
124
+ const userLevel = !system
125
+ if (isUnitInstalled(userLevel)) {
126
+ logger.debug({ userLevel }, "ensureInstalled: unit already present")
127
+ return
128
+ }
129
+ const { installService } = await import("./service/install")
130
+ await installService({ logger, system })
131
+ }
132
+
133
+ export const invokeManager = async (
134
+ options: InvokeManagerOptions,
135
+ ): Promise<void> => {
136
+ const { logger, action } = options
137
+ const os = currentOS()
138
+
139
+ if (os === "linux") {
140
+ const verb = action === "reload" ? "reload-or-restart" : action
141
+ systemctl(true, [verb, `${DAEMON_NAME}.service`])
142
+ logger.info(
143
+ { action, userLevel: true },
144
+ "ensureInstalled: invoked systemctl",
145
+ )
146
+ return
147
+ }
148
+ if (os === "darwin") {
149
+ const plist = join(
150
+ homedir(),
151
+ "Library",
152
+ "LaunchAgents",
153
+ `${DAEMON_NAME}.plist`,
154
+ )
155
+ const uid = String(process.getuid?.() ?? 0)
156
+ if (action === "start") {
157
+ // Same settling problem as restart: bootstrapping a label launchd is
158
+ // still holding throws, so go through the retry.
159
+ await bootstrapWithRetry(uid, plist, DAEMON_NAME)
160
+ } else if (action === "stop") {
161
+ launchctl("bootout", [`gui/${uid}/${DAEMON_NAME}`])
162
+ } else if (action === "restart") {
163
+ try {
164
+ launchctl("bootout", [`gui/${uid}/${DAEMON_NAME}`])
165
+ } catch {
166
+ // not bootstrapped — ignore
167
+ }
168
+ const goneBy = Date.now() + 10_000
169
+ while (Date.now() < goneBy && isJobLoaded(uid, DAEMON_NAME)) {
170
+ await sleep(200)
171
+ }
172
+ await bootstrapWithRetry(uid, plist, DAEMON_NAME)
173
+ } else {
174
+ launchctl("kill", ["-SIGUSR1", `gui/${uid}/${DAEMON_NAME}`])
175
+ }
176
+ logger.info({ action }, "ensureInstalled: invoked launchctl")
177
+ return
178
+ }
179
+ logger.error(
180
+ { os, action },
181
+ "ensureInstalled: native service management not supported on this platform",
182
+ )
183
+ process.exitCode = 1
184
+ }