@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,79 @@
1
+ import type pino from "pino"
2
+
3
+ import { ProviderError } from "./error"
4
+ import { type CommandExecutor } from "./shared"
5
+
6
+ export interface KeyMacroProvider {
7
+ sendKey(comboOrText: string): Promise<void>
8
+ stop(): Promise<void>
9
+ }
10
+
11
+ export { isValidKey, knownKeys, parseCombo } from "./key-macro/parser"
12
+ export type { ParsedCombo } from "./key-macro/parser"
13
+
14
+ export const createNullKeyMacroProvider = (
15
+ logger?: pino.Logger,
16
+ ): KeyMacroProvider => {
17
+ if (logger) {
18
+ logger.warn(
19
+ { provider: "key-macro" },
20
+ "OS key-macro provider unavailable, sendKey will throw ProviderError",
21
+ )
22
+ }
23
+ return {
24
+ async sendKey(_comboOrText: string): Promise<void> {
25
+ throw new ProviderError(
26
+ "NOT_AVAILABLE",
27
+ "Key-macro provider not available on this platform",
28
+ )
29
+ },
30
+ async stop() {
31
+ return
32
+ },
33
+ }
34
+ }
35
+
36
+ export interface CreateKeyMacroProviderOptions {
37
+ readonly platform?: NodeJS.Platform
38
+ readonly executor: CommandExecutor
39
+ readonly env: Readonly<Record<string, string>>
40
+ readonly logger: pino.Logger
41
+ readonly extraFsProbe?: (tool: string) => boolean
42
+ }
43
+
44
+ export const createKeyMacroProvider = async (
45
+ options: CreateKeyMacroProviderOptions,
46
+ ): Promise<KeyMacroProvider> => {
47
+ const platform = options.platform ?? process.platform
48
+ if (platform === "linux") {
49
+ const { createLinuxKeyMacroProvider } = await import("./key-macro/linux")
50
+ return createLinuxKeyMacroProvider({
51
+ executor: options.executor,
52
+ env: options.env,
53
+ logger: options.logger,
54
+ ...(options.extraFsProbe !== undefined
55
+ ? { extraFsProbe: options.extraFsProbe }
56
+ : {}),
57
+ })
58
+ }
59
+ if (platform === "darwin") {
60
+ const { createDarwinKeyMacroProvider } = await import("./key-macro/darwin")
61
+ return createDarwinKeyMacroProvider({
62
+ executor: options.executor,
63
+ logger: options.logger,
64
+ })
65
+ }
66
+ if (platform === "win32") {
67
+ const { createWindowsKeyMacroProvider } =
68
+ await import("./key-macro/windows")
69
+ return createWindowsKeyMacroProvider({
70
+ executor: options.executor,
71
+ logger: options.logger,
72
+ })
73
+ }
74
+ void createNullKeyMacroProvider(options.logger)
75
+ throw new ProviderError(
76
+ "UNSUPPORTED_PLATFORM",
77
+ `Key-macro provider not implemented for platform '${platform}' in this build`,
78
+ )
79
+ }
@@ -0,0 +1,36 @@
1
+ import { type CommandExecutor } from "../shared"
2
+ import type { NotificationProvider } from "../notification"
3
+
4
+ export interface DarwinNotificationDeps {
5
+ readonly executor: CommandExecutor
6
+ readonly soundPath?: string
7
+ }
8
+
9
+ const escapeForAppleScript = (value: string): string =>
10
+ value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
11
+
12
+ export const createDarwinNotificationProvider = async (
13
+ deps: DarwinNotificationDeps,
14
+ ): Promise<NotificationProvider> => {
15
+ return {
16
+ async notify(args) {
17
+ const script = `display notification "${escapeForAppleScript(args.body)}" with title "${escapeForAppleScript(args.title)}"`
18
+ try {
19
+ const result = await deps.executor.run("osascript", ["-e", script])
20
+ if (result.exitCode !== 0) {
21
+ // swallow — osascript may fail in headless contexts; notification is best-effort
22
+ }
23
+ } catch {
24
+ // best-effort; never throw
25
+ }
26
+
27
+ if (args.sound === true && deps.soundPath !== undefined) {
28
+ try {
29
+ await deps.executor.run("afplay", [deps.soundPath])
30
+ } catch {
31
+ // best-effort
32
+ }
33
+ }
34
+ },
35
+ }
36
+ }
@@ -0,0 +1,115 @@
1
+ import type pino from "pino"
2
+
3
+ import { type CommandExecutor, withTimeout } from "../shared"
4
+ import type { NotificationProvider } from "../notification"
5
+
6
+ export interface LinuxNotificationDeps {
7
+ readonly executor: CommandExecutor
8
+ readonly logger: pino.Logger
9
+ readonly timeoutMs?: number
10
+ readonly extraFsProbe?: (tool: string) => boolean
11
+ readonly soundPath?: string
12
+ }
13
+
14
+ const NOTIFY_SEND_TOOL = "notify-send"
15
+ const FFPLAY_TOOL = "ffplay"
16
+ const PAPLAY_TOOL = "paplay"
17
+ const DEFAULT_TIMEOUT_MS = 2_000
18
+ const SOUND_TIMEOUT_MS = 5_000
19
+
20
+ const probeTool = async (
21
+ executor: CommandExecutor,
22
+ tool: string,
23
+ extraFsProbe?: (tool: string) => boolean,
24
+ ): Promise<boolean> => {
25
+ const result = await executor.run("which", [tool])
26
+ if (result.exitCode === 0 && result.stdout.trim().length > 0) return true
27
+ return extraFsProbe?.(tool) === true
28
+ }
29
+
30
+ const shellQuote = (value: string): string =>
31
+ `'${value.replace(/'/g, "'\\''")}'`
32
+
33
+ export const createLinuxNotificationProvider = async (
34
+ deps: LinuxNotificationDeps,
35
+ ): Promise<NotificationProvider> => {
36
+ const notifySendOk = await probeTool(
37
+ deps.executor,
38
+ NOTIFY_SEND_TOOL,
39
+ deps.extraFsProbe,
40
+ )
41
+ if (!notifySendOk) {
42
+ deps.logger.warn(
43
+ {},
44
+ "linux notification: notify-send not found; notify() will be a no-op",
45
+ )
46
+ return {
47
+ async notify() {
48
+ return
49
+ },
50
+ }
51
+ }
52
+
53
+ const ffplayOk = await probeTool(
54
+ deps.executor,
55
+ FFPLAY_TOOL,
56
+ deps.extraFsProbe,
57
+ )
58
+ const paplayOk =
59
+ !ffplayOk &&
60
+ (await probeTool(deps.executor, PAPLAY_TOOL, deps.extraFsProbe))
61
+ const soundTool: string | null = ffplayOk
62
+ ? FFPLAY_TOOL
63
+ : paplayOk
64
+ ? PAPLAY_TOOL
65
+ : null
66
+
67
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS
68
+
69
+ return {
70
+ async notify(args) {
71
+ const sendArgs = [
72
+ "-t",
73
+ "5000",
74
+ shellQuote(args.title),
75
+ shellQuote(args.body),
76
+ ]
77
+ try {
78
+ const result = await withTimeout(
79
+ deps.executor.run(NOTIFY_SEND_TOOL, sendArgs),
80
+ timeoutMs,
81
+ )
82
+ if (result.exitCode !== 0) {
83
+ deps.logger.warn(
84
+ { stderr: result.stderr.trim() },
85
+ "linux notification: notify-send returned non-zero",
86
+ )
87
+ }
88
+ } catch (err) {
89
+ deps.logger.warn({ err }, "linux notification: notify-send failed")
90
+ }
91
+
92
+ if (
93
+ args.sound === true &&
94
+ deps.soundPath !== undefined &&
95
+ soundTool !== null
96
+ ) {
97
+ try {
98
+ await withTimeout(
99
+ deps.executor.run(soundTool, [
100
+ "-nodisp",
101
+ "-autoexit",
102
+ deps.soundPath,
103
+ ]),
104
+ SOUND_TIMEOUT_MS,
105
+ )
106
+ } catch (err) {
107
+ deps.logger.warn(
108
+ { err, tool: soundTool },
109
+ "linux notification: sound playback failed",
110
+ )
111
+ }
112
+ }
113
+ },
114
+ }
115
+ }
@@ -0,0 +1,72 @@
1
+ import type pino from "pino"
2
+
3
+ import { type CommandExecutor, withTimeout } from "../shared"
4
+ import type { NotificationProvider } from "../notification"
5
+
6
+ export interface WindowsNotificationDeps {
7
+ readonly executor: CommandExecutor
8
+ readonly logger: pino.Logger
9
+ readonly timeoutMs?: number
10
+ readonly soundPath?: string
11
+ }
12
+
13
+ const DEFAULT_TIMEOUT_MS = 5_000
14
+
15
+ const shellQuote = (value: string): string =>
16
+ `'${value.replace(/'/g, "''").replace(/'/g, "'\\''")}'`
17
+
18
+ // ponytail: raw toast template via PowerShell BurntToast-free path. Sufficient
19
+ // for our "Pomodoro done" use case; richer XML templates are out of scope.
20
+ const buildToastScript = (title: string, body: string): string => {
21
+ const safeTitle = title.replace(/[`$"\\]/g, "\\$&")
22
+ const safeBody = body.replace(/[`$"\\]/g, "\\$&")
23
+ return [
24
+ "Add-Type -AssemblyName System.Windows.Forms | Out-Null",
25
+ `$balloon = New-Object System.Windows.Forms.NotifyIcon`,
26
+ `$balloon.Icon = [System.Drawing.SystemIcons]::Information`,
27
+ `$balloon.BalloonTipTitle = "${safeTitle}"`,
28
+ `$balloon.BalloonTipText = "${safeBody}"`,
29
+ `$balloon.Visible = $true`,
30
+ `$balloon.ShowBalloonTip(5000)`,
31
+ "Start-Sleep -Milliseconds 5500",
32
+ "$balloon.Dispose()",
33
+ ].join("; ")
34
+ }
35
+
36
+ export const createWindowsNotificationProvider = async (
37
+ deps: WindowsNotificationDeps,
38
+ ): Promise<NotificationProvider> => {
39
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS
40
+
41
+ return {
42
+ async notify(args) {
43
+ const script = buildToastScript(args.title, args.body)
44
+ try {
45
+ await withTimeout(
46
+ deps.executor.run("powershell", ["-NoProfile", "-Command", script]),
47
+ timeoutMs,
48
+ )
49
+ } catch (err) {
50
+ deps.logger.warn({ err }, "windows notification: toast failed")
51
+ }
52
+
53
+ if (args.sound === true && deps.soundPath !== undefined) {
54
+ try {
55
+ await withTimeout(
56
+ deps.executor.run("powershell", [
57
+ "-NoProfile",
58
+ "-Command",
59
+ `(New-Object Media.SoundPlayer '${shellQuote(deps.soundPath)}').PlaySync()`,
60
+ ]),
61
+ timeoutMs,
62
+ )
63
+ } catch (err) {
64
+ deps.logger.warn(
65
+ { err },
66
+ "windows notification: sound playback failed",
67
+ )
68
+ }
69
+ }
70
+ },
71
+ }
72
+ }
@@ -0,0 +1,75 @@
1
+ import type pino from "pino"
2
+
3
+ import { logNull, type CommandExecutor } from "./shared"
4
+
5
+ export interface NotificationArgs {
6
+ readonly title: string
7
+ readonly body: string
8
+ readonly sound?: boolean
9
+ }
10
+
11
+ export interface NotificationProvider {
12
+ notify(args: NotificationArgs): Promise<void>
13
+ }
14
+
15
+ export interface CreateNotificationProviderOptions {
16
+ readonly platform?: NodeJS.Platform
17
+ readonly executor: CommandExecutor
18
+ readonly env: Readonly<Record<string, string>>
19
+ readonly logger: pino.Logger
20
+ readonly extraFsProbe?: (tool: string) => boolean
21
+ readonly soundPath?: string
22
+ }
23
+
24
+ export const createNullNotificationProvider = (
25
+ logger?: pino.Logger,
26
+ ): NotificationProvider => {
27
+ logNull(logger, "notification", "platform unsupported, notify() is a no-op")
28
+ return {
29
+ async notify() {
30
+ return
31
+ },
32
+ }
33
+ }
34
+
35
+ export const createNotificationProvider = async (
36
+ options: CreateNotificationProviderOptions,
37
+ ): Promise<NotificationProvider> => {
38
+ const platform = options.platform ?? process.platform
39
+ if (platform === "linux") {
40
+ const { createLinuxNotificationProvider } =
41
+ await import("./notification/linux")
42
+ return createLinuxNotificationProvider({
43
+ executor: options.executor,
44
+ logger: options.logger,
45
+ ...(options.extraFsProbe !== undefined
46
+ ? { extraFsProbe: options.extraFsProbe }
47
+ : {}),
48
+ ...(options.soundPath !== undefined
49
+ ? { soundPath: options.soundPath }
50
+ : {}),
51
+ })
52
+ }
53
+ if (platform === "darwin") {
54
+ const { createDarwinNotificationProvider } =
55
+ await import("./notification/darwin")
56
+ return createDarwinNotificationProvider({
57
+ executor: options.executor,
58
+ ...(options.soundPath !== undefined
59
+ ? { soundPath: options.soundPath }
60
+ : {}),
61
+ })
62
+ }
63
+ if (platform === "win32") {
64
+ const { createWindowsNotificationProvider } =
65
+ await import("./notification/windows")
66
+ return createWindowsNotificationProvider({
67
+ executor: options.executor,
68
+ logger: options.logger,
69
+ ...(options.soundPath !== undefined
70
+ ? { soundPath: options.soundPath }
71
+ : {}),
72
+ })
73
+ }
74
+ return createNullNotificationProvider(options.logger)
75
+ }
@@ -0,0 +1,82 @@
1
+ import type pino from "pino"
2
+
3
+ import type { CommandExecutor } from "../shared"
4
+ import { type SessionProvider, type SessionState } from "../session"
5
+
6
+ export interface DarwinSessionDeps {
7
+ readonly executor: CommandExecutor
8
+ readonly logger: pino.Logger
9
+ readonly pollIntervalMs?: number
10
+ }
11
+
12
+ const toState = (locked: boolean): SessionState =>
13
+ locked ? "locked" : "unlocked"
14
+
15
+ // ponytail: this used to ask System Events whether the `loginwindow` process
16
+ // was running, which was wrong twice over. The script was a syntax error
17
+ // (`loginwindow process` instead of `process "loginwindow"`), so every tick
18
+ // exited non-zero and the state stayed "unknown" forever; and had it parsed,
19
+ // loginwindow runs for the whole login session whether or not the screen is
20
+ // locked, so it would have reported "locked" permanently.
21
+ //
22
+ // The real signal is CGSSessionScreenIsLocked in the console session
23
+ // dictionary, which `ioreg` exposes without any Accessibility grant:
24
+ // the key is present and true only while the screen is locked, and absent
25
+ // entirely when unlocked.
26
+ const IOREG_ARGS = ["-n", "Root", "-d1", "-a"] as const
27
+
28
+ const parseScreenLocked = (raw: string): boolean =>
29
+ /<key>CGSSessionScreenIsLocked<\/key>\s*<true\/>/.test(raw)
30
+
31
+ export const createDarwinSessionProvider = async (
32
+ deps: DarwinSessionDeps,
33
+ ): Promise<SessionProvider> => {
34
+ const listeners = new Set<(s: SessionState) => void>()
35
+ let state: SessionState = "unknown"
36
+ let stopped = false
37
+ const pollMs = deps.pollIntervalMs ?? 5_000
38
+
39
+ const tick = async (): Promise<SessionState> => {
40
+ try {
41
+ const result = await deps.executor.run("ioreg", [...IOREG_ARGS], {
42
+ timeoutMs: 2_000,
43
+ })
44
+ if (result.exitCode !== 0) return state
45
+ return toState(parseScreenLocked(result.stdout))
46
+ } catch (err) {
47
+ deps.logger.debug({ err }, "session: ioreg failed")
48
+ return state
49
+ }
50
+ }
51
+
52
+ state = await tick()
53
+ let interval: ReturnType<typeof setInterval> | null = setInterval(() => {
54
+ if (stopped) return
55
+ void tick().then((s) => {
56
+ if (stopped) return
57
+ if (s !== state) {
58
+ state = s
59
+ for (const l of listeners) l(state)
60
+ }
61
+ })
62
+ }, pollMs)
63
+
64
+ return {
65
+ getState() {
66
+ return state
67
+ },
68
+ subscribe(handler) {
69
+ listeners.add(handler)
70
+ return () => {
71
+ listeners.delete(handler)
72
+ }
73
+ },
74
+ async stop() {
75
+ stopped = true
76
+ if (interval !== null) {
77
+ clearInterval(interval)
78
+ interval = null
79
+ }
80
+ },
81
+ }
82
+ }
@@ -0,0 +1,175 @@
1
+ import { sessionBus } from "dbus-next"
2
+
3
+ import type pino from "pino"
4
+
5
+ import {
6
+ createNullSessionProvider,
7
+ type SessionProvider,
8
+ type SessionState,
9
+ } from "../session"
10
+ import type { LinuxDbusBus } from "../shared"
11
+
12
+ export interface LinuxSessionDeps {
13
+ readonly dbus?: LinuxDbusBus
14
+ readonly logger: pino.Logger
15
+ readonly idleMs?: number
16
+ }
17
+
18
+ const SCREENSAVER_SERVICE = "org.gnome.ScreenSaver"
19
+ const SCREENSAVER_PATH = "/org/gnome/ScreenSaver"
20
+ const SCREENSAVER_IFACE = "org.gnome.ScreenSaver"
21
+ const IDLE_MONITOR_SERVICE = "org.gnome.Mutter.IdleMonitor"
22
+ const IDLE_MONITOR_PATH = "/org/gnome/Mutter/IdleMonitor/Core"
23
+ const IDLE_MONITOR_IFACE = "org.gnome.Mutter.IdleMonitor"
24
+
25
+ const toState = (locked: boolean): SessionState =>
26
+ locked ? "locked" : "unlocked"
27
+
28
+ export const createLinuxSessionProvider = async (
29
+ deps: LinuxSessionDeps,
30
+ ): Promise<SessionProvider> => {
31
+ const idleMs = deps.idleMs ?? 5 * 60 * 1000
32
+ const listeners = new Set<(s: SessionState) => void>()
33
+ let state: SessionState = "unknown"
34
+ let stopped = false
35
+ let idleSupported = false
36
+ let bus: LinuxDbusBus | null = deps.dbus ?? null
37
+ if (bus === null) {
38
+ try {
39
+ bus = (await sessionBus()) as unknown as LinuxDbusBus
40
+ } catch (err) {
41
+ deps.logger.debug({ err }, "session: dbus sessionBus unavailable")
42
+ return createNullSessionProvider(deps.logger)
43
+ }
44
+ }
45
+
46
+ let screensaverOk = false
47
+ try {
48
+ const proxy = await bus.getProxyObject(
49
+ SCREENSAVER_SERVICE,
50
+ SCREENSAVER_PATH,
51
+ )
52
+ const saver = proxy.getInterface(SCREENSAVER_IFACE)
53
+ const initial = (await saver.GetActive?.()) ?? false
54
+ state = toState(initial)
55
+ saver.on?.("ActiveChanged", (locked: unknown) => {
56
+ if (typeof locked === "boolean") {
57
+ const next = toState(locked)
58
+ if (next !== state) {
59
+ state = next
60
+ for (const l of listeners) l(state)
61
+ }
62
+ }
63
+ })
64
+ screensaverOk = true
65
+ } catch (err) {
66
+ // ponytail: ScreenSaver can be unavailable on non-GNOME sessions; keep
67
+ // the provider alive so the idle-monitor fallback can still fire.
68
+ deps.logger.debug({ err }, "session: ScreenSaver init failed")
69
+ }
70
+
71
+ try {
72
+ const idleProxy = await bus.getProxyObject(
73
+ IDLE_MONITOR_SERVICE,
74
+ IDLE_MONITOR_PATH,
75
+ )
76
+ const idleIface = idleProxy.getInterface(IDLE_MONITOR_IFACE)
77
+ await idleIface.GetIdletime?.()
78
+ idleSupported = true
79
+ } catch (err) {
80
+ deps.logger.debug(
81
+ { err },
82
+ "session: IdleMonitor init failed; idle polling disabled",
83
+ )
84
+ }
85
+
86
+ if (!screensaverOk && !idleSupported) {
87
+ deps.logger.debug("session: no lock source available, returning null")
88
+ return createNullSessionProvider(deps.logger)
89
+ }
90
+
91
+ void screensaverOk
92
+
93
+ let interval: ReturnType<typeof setInterval> | null = null
94
+ if (idleSupported) {
95
+ let cachedIdleIface: {
96
+ GetIdletime?: () => Promise<unknown>
97
+ } | null = null
98
+ const getIdleIface = async (): Promise<{
99
+ GetIdletime?: () => Promise<unknown>
100
+ } | null> => {
101
+ if (cachedIdleIface !== null) return cachedIdleIface
102
+ const p = await bus!.getProxyObject(
103
+ IDLE_MONITOR_SERVICE,
104
+ IDLE_MONITOR_PATH,
105
+ )
106
+ cachedIdleIface = p.getInterface(IDLE_MONITOR_IFACE) as {
107
+ GetIdletime?: () => Promise<unknown>
108
+ }
109
+ return cachedIdleIface
110
+ }
111
+ interval = setInterval(() => {
112
+ if (stopped) return
113
+ void getIdleIface()
114
+ .then((iface) => iface?.GetIdletime?.())
115
+ .then((idleMsRaw) => {
116
+ deps.logger.debug(
117
+ { idleMsRaw, idleMs, currentState: state },
118
+ "session: idle monitor tick",
119
+ )
120
+ if (typeof idleMsRaw === "number") {
121
+ if (idleMsRaw > idleMs && state === "unlocked") {
122
+ state = "locked"
123
+ deps.logger.info(
124
+ { idleMsRaw, idleMs },
125
+ "session: locked via idle monitor",
126
+ )
127
+ for (const l of listeners) l(state)
128
+ } else if (idleMsRaw <= idleMs && state === "locked") {
129
+ state = "unlocked"
130
+ deps.logger.info(
131
+ { idleMsRaw, idleMs },
132
+ "session: unlocked via idle monitor",
133
+ )
134
+ for (const l of listeners) l(state)
135
+ }
136
+ }
137
+ })
138
+ .catch(() => undefined)
139
+ }, 5_000)
140
+ }
141
+
142
+ return {
143
+ getState() {
144
+ return state
145
+ },
146
+ subscribe(handler) {
147
+ let initialFired = false
148
+ const wrappedHandler = (s: SessionState): void => {
149
+ initialFired = true
150
+ handler(s)
151
+ }
152
+ listeners.add(wrappedHandler)
153
+ if (!initialFired && state !== "unknown") {
154
+ queueMicrotask(() => {
155
+ if (!initialFired) handler(state)
156
+ })
157
+ }
158
+ return () => {
159
+ listeners.delete(wrappedHandler)
160
+ }
161
+ },
162
+ async stop() {
163
+ stopped = true
164
+ if (interval !== null) {
165
+ clearInterval(interval)
166
+ interval = null
167
+ }
168
+ try {
169
+ bus?.disconnect?.()
170
+ } catch {
171
+ // ignore
172
+ }
173
+ },
174
+ }
175
+ }