@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,143 @@
1
+ import {
2
+ AddonServiceContext,
3
+ AddonServiceMethod,
4
+ AddonGlobalService,
5
+ } from "@/addon"
6
+ import { computeProgress } from "./progress"
7
+ import { createMediaProvider, MediaStatusProvider } from "./providers"
8
+ import type { MediaPlayerState } from "./state"
9
+
10
+ const shellQuote = (arg: string): string => `'${arg.replace(/'/g, "'\\''")}'`
11
+
12
+ const FALLBACK_STATE: MediaPlayerState = {
13
+ title: null,
14
+ artist: null,
15
+ source: null,
16
+ status: "notAvailable",
17
+ isPlaying: false,
18
+ volume: 0,
19
+ progress: 0,
20
+ currentTime: 0,
21
+ totalTime: 0,
22
+ muted: false,
23
+ }
24
+
25
+ let provider: MediaStatusProvider | null = null
26
+ let ctxRef: AddonServiceContext | null = null
27
+
28
+ const mapStatus = (
29
+ playStatus: Awaited<
30
+ ReturnType<MediaStatusProvider["getStatus"]>
31
+ >["playStatus"],
32
+ ): MediaPlayerState["status"] => {
33
+ if (
34
+ playStatus === "play" ||
35
+ playStatus === "pause" ||
36
+ playStatus === "stop"
37
+ ) {
38
+ return playStatus
39
+ }
40
+ // 'unavailable' (provider reachable but no data) → 'notAvailable'.
41
+ return "notAvailable"
42
+ }
43
+
44
+ const buildState = (
45
+ status: Awaited<ReturnType<MediaStatusProvider["getStatus"]>>,
46
+ ): MediaPlayerState => ({
47
+ title: status.track?.name ?? null,
48
+ artist: status.track?.artist ?? null,
49
+ source: status.track?.album ?? null,
50
+ status: mapStatus(status.playStatus),
51
+ isPlaying: status.playStatus === "play",
52
+ volume: Math.round(status.volume * 100),
53
+ progress: computeProgress(status.currentTime, status.totalTime),
54
+ currentTime: status.currentTime,
55
+ totalTime: status.totalTime,
56
+ muted: status.muted,
57
+ })
58
+
59
+ const wrap = (
60
+ action: (...args: readonly unknown[]) => Promise<void>,
61
+ ): AddonServiceMethod => {
62
+ return async (...args) => {
63
+ try {
64
+ await action(...args)
65
+ } catch (err) {
66
+ console.error("[media] action failed:", err)
67
+ }
68
+ await ctxRef?.poll("state")
69
+ }
70
+ }
71
+
72
+ export const globalService: AddonGlobalService = {
73
+ pollers: [
74
+ {
75
+ id: "state",
76
+ channel: "media:state",
77
+ intervalMs: 1_000,
78
+ poll: async (_ctx: AddonServiceContext): Promise<MediaPlayerState> => {
79
+ if (provider === null) return FALLBACK_STATE
80
+ try {
81
+ return buildState(await provider.getStatus())
82
+ } catch {
83
+ return FALLBACK_STATE
84
+ }
85
+ },
86
+ },
87
+ ],
88
+
89
+ methods: {
90
+ play: wrap(async () => {
91
+ await provider?.play()
92
+ }),
93
+ pause: wrap(async () => {
94
+ await provider?.pause()
95
+ }),
96
+ toggle: wrap(async () => {
97
+ await provider?.toggle()
98
+ }),
99
+ next: wrap(async () => {
100
+ await provider?.next()
101
+ }),
102
+ previous: wrap(async () => {
103
+ await provider?.previous()
104
+ }),
105
+ setVolume: wrap(async (...args: readonly unknown[]) => {
106
+ const value = typeof args[0] === "number" ? args[0] : 1
107
+ await provider?.setVolume(value)
108
+ }),
109
+ volumeUp: wrap(async (...args: readonly unknown[]) => {
110
+ const step = typeof args[0] === "number" ? args[0] : 0.05
111
+ await provider?.volumeUp(step)
112
+ }),
113
+ volumeDown: wrap(async (...args: readonly unknown[]) => {
114
+ const step = typeof args[0] === "number" ? args[0] : 0.05
115
+ await provider?.volumeDown(step)
116
+ }),
117
+ toggleMute: wrap(async () => {
118
+ await provider?.toggleMute()
119
+ }),
120
+ },
121
+
122
+ onLoad: (ctx: AddonServiceContext) => {
123
+ ctxRef = ctx
124
+ provider = createMediaProvider(process.platform, {
125
+ async run(
126
+ command: string,
127
+ args: readonly string[],
128
+ _options?: { timeoutMs?: number },
129
+ ) {
130
+ const shellCmd =
131
+ args.length === 0
132
+ ? command
133
+ : `${command} ${args.map(shellQuote).join(" ")}`
134
+ return ctx.executor.run(shellCmd)
135
+ },
136
+ })
137
+ },
138
+
139
+ onUnload: () => {
140
+ ctxRef = null
141
+ provider = null
142
+ },
143
+ }
@@ -0,0 +1,19 @@
1
+ import type { AddonButtonTypeService } from "@/addon/api"
2
+ import { configSchema } from "./config"
3
+
4
+ export default {
5
+ configSchema,
6
+ onTap: async ({
7
+ methods,
8
+ }: {
9
+ methods: Record<string, (...args: unknown[]) => unknown>
10
+ }) => {
11
+ await methods["media:toggle"]?.()
12
+ },
13
+ onDblTap: async ({ methods }) => {
14
+ await methods["media:previous"]?.()
15
+ },
16
+ onHold: async ({ methods }) => {
17
+ await methods["media:next"]?.()
18
+ },
19
+ } satisfies AddonButtonTypeService
@@ -0,0 +1,25 @@
1
+ import { cn, Icon } from "@/ui"
2
+ import { ReactElement } from "react"
3
+ import { MediaButtonStatus, statusesMeta } from "./status-meta"
4
+
5
+ export function MediaStatusIcon(props: {
6
+ status: MediaButtonStatus
7
+ }): ReactElement {
8
+ const currentStatus =
9
+ statusesMeta[props.status] || statusesMeta["unsupported"]
10
+
11
+ const icon = currentStatus.icon
12
+ const bgColor = currentStatus.bgColor
13
+ const iconColor = currentStatus.iconColor
14
+
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "w-6 h-6 rounded-full flex items-center justify-center overflow-hidden",
19
+ bgColor,
20
+ )}
21
+ >
22
+ <Icon source={`icon://${icon}`} size={14} fill tone={iconColor} />
23
+ </div>
24
+ )
25
+ }
@@ -0,0 +1,52 @@
1
+ import { formatTime } from "@/builtin-addons/media/progress"
2
+ import { Text } from "@/ui/index"
3
+ import type { ReactElement } from "react"
4
+ import { MediaStatusIcon } from "./MediaStatus"
5
+ import { ProgressBar } from "./ProgressBar"
6
+ import { type MediaButtonStatus } from "./status-meta"
7
+
8
+ interface MediaSurfaceProps {
9
+ title: string
10
+ artist: string
11
+ source: string
12
+ progress: number
13
+ status: MediaButtonStatus
14
+ currentTime: number
15
+ totalTime: number
16
+ }
17
+
18
+ export const MediaSurface = ({
19
+ title,
20
+ artist,
21
+ source: _source,
22
+ progress,
23
+ status,
24
+ currentTime,
25
+ }: MediaSurfaceProps): ReactElement => {
26
+ return (
27
+ <div className="flex h-full w-full flex-col relative gap-0.5">
28
+ <div className="flex items-center justify-between gap-2">
29
+ <MediaStatusIcon status={status} />
30
+ <Text
31
+ align="right"
32
+ size="lg"
33
+ tone="primary"
34
+ weight="bold"
35
+ text={formatTime(currentTime)}
36
+ />
37
+ </div>
38
+
39
+ <Text
40
+ align="left"
41
+ fit="ellipsis"
42
+ size="md"
43
+ tone="fg"
44
+ weight="bold"
45
+ text={title}
46
+ />
47
+ <Text align="left" fit="ellipsis" size="sm" tone="muted" text={artist} />
48
+ <div className="flex-1"></div>
49
+ <ProgressBar status={status} value={progress} className="mb-1" />
50
+ </div>
51
+ )
52
+ }
@@ -0,0 +1,21 @@
1
+ import { cn } from "@/ui"
2
+ import type { ReactElement } from "react"
3
+ import type { MediaButtonStatus } from "./status-meta"
4
+ import { statusesMeta } from "./status-meta"
5
+
6
+ export const ProgressBar = (props: {
7
+ className?: string
8
+ status: MediaButtonStatus
9
+ value: number
10
+ }): ReactElement => {
11
+ const bgColor = statusesMeta[props.status]?.bgColor || "bg-gray-500"
12
+ const bgColorAlt = statusesMeta[props.status]?.bgColorAlt || "bg-gray-300"
13
+ return (
14
+ <div className={cn("h-2 w-full rounded-4xl", bgColorAlt, props.className)}>
15
+ <div
16
+ className={cn(bgColor, "h-2 rounded-4xl")}
17
+ style={{ width: `${Math.min(100, Math.max(0, props.value))}%` }}
18
+ />
19
+ </div>
20
+ )
21
+ }
@@ -0,0 +1,36 @@
1
+ import { IconTone } from "@/ui"
2
+ import type { MediaButtonStatus } from "../../../state"
3
+
4
+ export const statusesMeta: Record<
5
+ MediaButtonStatus,
6
+ { icon: string; bgColor: string; bgColorAlt: string; iconColor?: IconTone }
7
+ > = {
8
+ pause: {
9
+ icon: "pause",
10
+ bgColor: "bg-muted/30",
11
+ bgColorAlt: "bg-muted/10",
12
+ },
13
+ play: {
14
+ icon: "play",
15
+ bgColor: "bg-primary",
16
+ bgColorAlt: "bg-primary/30",
17
+ iconColor: "foreground-contrast",
18
+ },
19
+ stop: {
20
+ icon: "square",
21
+ bgColor: "bg-danger/30",
22
+ bgColorAlt: "bg-danger/10",
23
+ },
24
+ unsupported: {
25
+ icon: "slash",
26
+ bgColor: "bg-danger/30",
27
+ bgColorAlt: "bg-danger/10",
28
+ },
29
+ notAvailable: {
30
+ icon: "slash",
31
+ bgColor: "bg-background/30",
32
+ bgColorAlt: "bg-background/10",
33
+ },
34
+ }
35
+
36
+ export type { MediaButtonStatus }
@@ -0,0 +1,4 @@
1
+ import { z } from "zod"
2
+
3
+ export const configSchema = z.object({})
4
+ export type ConfigSchema = z.infer<typeof configSchema>
@@ -0,0 +1,29 @@
1
+ import type { AddonFrontendButton } from "@/addon/api"
2
+ import { useAddonChannel } from "@/api/react"
3
+
4
+ import { Icon } from "@/ui"
5
+ import type { MediaPlayerState } from "../../state"
6
+ import { MediaSurface } from "./components/MediaSurface"
7
+ import { ConfigSchema } from "./config"
8
+
9
+ const MediaPlayerButtonFrontend: AddonFrontendButton<ConfigSchema> = () => {
10
+ const { data } = useAddonChannel<MediaPlayerState>("media:state")
11
+
12
+ if (!data) {
13
+ return <Icon source="icon://hourglass" />
14
+ }
15
+
16
+ return (
17
+ <MediaSurface
18
+ title={data?.title ?? ""}
19
+ artist={data?.artist ?? ""}
20
+ source={data?.source ?? ""}
21
+ progress={data?.progress ?? 0}
22
+ currentTime={data?.currentTime ?? 0}
23
+ totalTime={data?.totalTime ?? 0}
24
+ status={data?.status ?? "notAvailable"}
25
+ />
26
+ )
27
+ }
28
+
29
+ export default MediaPlayerButtonFrontend
@@ -0,0 +1,52 @@
1
+ import type { AddonGestureEvent } from "@/addon/api"
2
+ import { useAddonChannel } from "@/api/react"
3
+ import { IconLabelProgressSurface } from "@/ui/index"
4
+ import { useEffect, useRef, useState } from "react"
5
+ import type { MediaPlayerState } from "../../../state"
6
+
7
+ const PROGRESS_VISIBLE_MS = 2000
8
+
9
+ export type VolumeVariant = "down" | "up"
10
+
11
+ interface VolumeButtonFrontendProps {
12
+ readonly variant: VolumeVariant
13
+ readonly gesture?: AddonGestureEvent | null
14
+ }
15
+
16
+ const VolumeButtonFrontend = ({
17
+ variant,
18
+ gesture,
19
+ }: VolumeButtonFrontendProps) => {
20
+ const { data } = useAddonChannel<MediaPlayerState>("media:state")
21
+ const volume = data?.volume ?? 0
22
+ const [showProgress, setShowProgress] = useState(false)
23
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
24
+
25
+ useEffect(() => {
26
+ if (gesture?.gesture !== "tap") return
27
+ setShowProgress(true)
28
+ if (timerRef.current) clearTimeout(timerRef.current)
29
+ timerRef.current = setTimeout(
30
+ () => setShowProgress(false),
31
+ PROGRESS_VISIBLE_MS,
32
+ )
33
+ }, [gesture?.at])
34
+
35
+ useEffect(
36
+ () => () => {
37
+ if (timerRef.current) clearTimeout(timerRef.current)
38
+ },
39
+ [],
40
+ )
41
+
42
+ return (
43
+ <IconLabelProgressSurface
44
+ source={`icon://${variant === "down" ? "volume" : "volume-2"}`}
45
+ label={variant === "down" ? "Vol -" : "Vol +"}
46
+ progress={volume}
47
+ visible={showProgress}
48
+ />
49
+ )
50
+ }
51
+
52
+ export default VolumeButtonFrontend
@@ -0,0 +1,13 @@
1
+ import type { AddonButtonTypeService } from "@/addon/api"
2
+ import { configSchema } from "./config"
3
+
4
+ export default {
5
+ configSchema,
6
+ onTap: async ({
7
+ methods,
8
+ }: {
9
+ methods: Record<string, (...args: unknown[]) => unknown>
10
+ }) => {
11
+ await methods["media:toggleMute"]?.()
12
+ },
13
+ } satisfies AddonButtonTypeService
@@ -0,0 +1,4 @@
1
+ import { z } from "zod"
2
+
3
+ export const configSchema = z.object({})
4
+ export type ConfigSchema = z.infer<typeof configSchema>
@@ -0,0 +1,26 @@
1
+ import type { AddonFrontendButton } from "@/addon/api"
2
+ import { useAddonChannel } from "@/api/react"
3
+ import { IconLabelSurface } from "@/ui/index"
4
+ import type { MediaPlayerState } from "../../../state"
5
+
6
+ const MuteButtonFrontend: AddonFrontendButton<Record<string, never>> = () => {
7
+ const { data } = useAddonChannel<MediaPlayerState>("media:state")
8
+ const volume = data?.volume ?? 0
9
+ const muted = data?.muted ?? false
10
+
11
+ const isMuted = muted || volume === 0
12
+
13
+ const iconName = isMuted ? "volume-off" : "volume-2"
14
+
15
+ return (
16
+ <div className="flex h-full w-full flex-col items-center justify-center gap-1 relative">
17
+ <IconLabelSurface
18
+ key={iconName}
19
+ label="Mute"
20
+ source={`icon://${iconName}`}
21
+ />
22
+ </div>
23
+ )
24
+ }
25
+
26
+ export default MuteButtonFrontend
@@ -0,0 +1,14 @@
1
+ import { z } from "zod"
2
+ import type { AddonButtonTypeService } from "@/addon/api"
3
+
4
+ import { configSchema } from "./config"
5
+
6
+ type Config = z.infer<typeof configSchema>
7
+
8
+ export default {
9
+ configSchema,
10
+ onTap: async ({ config, methods }) => {
11
+ const step = (config.step ?? 5) / 100
12
+ await (methods["media:volumeDown"] as (s: number) => Promise<void>)?.(step)
13
+ },
14
+ } satisfies AddonButtonTypeService<Config>
@@ -0,0 +1,6 @@
1
+ import { z } from "zod"
2
+
3
+ export const configSchema = z.object({
4
+ step: z.number().positive().default(5),
5
+ })
6
+ export type ConfigSchema = z.infer<typeof configSchema>
@@ -0,0 +1,10 @@
1
+ import type { AddonFrontendButton } from "@/addon/api"
2
+ import VolumeButtonFrontend from "../common/frontend"
3
+
4
+ const VolumeDownButtonFrontend: AddonFrontendButton<unknown> = ({
5
+ gesture,
6
+ }) => {
7
+ return <VolumeButtonFrontend variant="down" gesture={gesture ?? null} />
8
+ }
9
+
10
+ export default VolumeDownButtonFrontend
@@ -0,0 +1,14 @@
1
+ import { z } from "zod"
2
+ import type { AddonButtonTypeService } from "@/addon/api"
3
+
4
+ import { configSchema } from "./config"
5
+
6
+ type Config = z.infer<typeof configSchema>
7
+
8
+ export default {
9
+ configSchema,
10
+ onTap: async ({ config, methods }) => {
11
+ const step = (config.step ?? 5) / 100
12
+ await (methods["media:volumeUp"] as (s: number) => Promise<void>)?.(step)
13
+ },
14
+ } satisfies AddonButtonTypeService<Config>
@@ -0,0 +1,6 @@
1
+ import { z } from "zod"
2
+
3
+ export const configSchema = z.object({
4
+ step: z.number().positive().default(5),
5
+ })
6
+ export type ConfigSchema = z.infer<typeof configSchema>
@@ -0,0 +1,8 @@
1
+ import type { AddonFrontendButton } from "@/addon/api"
2
+ import VolumeButtonFrontend from "../common/frontend"
3
+
4
+ const VolumeUpButtonFrontend: AddonFrontendButton<unknown> = ({ gesture }) => {
5
+ return <VolumeButtonFrontend variant="up" gesture={gesture ?? null} />
6
+ }
7
+
8
+ export default VolumeUpButtonFrontend
@@ -0,0 +1,104 @@
1
+ import type { AddonCheck, AddonCheckResult } from "@/addon/api"
2
+ import type { CommandExecutor } from "@/system/providers/shared"
3
+
4
+ interface MediaChecksDeps {
5
+ readonly platform: NodeJS.Platform
6
+ readonly executor: CommandExecutor
7
+ }
8
+
9
+ const commandExists = async (
10
+ executor: CommandExecutor,
11
+ command: string,
12
+ ): Promise<boolean> => {
13
+ // `command` is a shell builtin, not an executable that execFile can spawn.
14
+ const quoted = `'${command.replaceAll(`'`, `'\\''`)}'`
15
+ const result = await executor.run("sh", ["-c", `command -v ${quoted}`])
16
+ return result.exitCode === 0 && result.stdout.trim().length > 0
17
+ }
18
+
19
+ // ponytail: each OS exposes a different media control surface. The
20
+ // check should match the binary the provider actually invokes — if the
21
+ // provider shells out to `playerctl`, the check looks for `playerctl`.
22
+ // No need to instantiate the full provider; that would spawn real
23
+ // subprocesses during preflight.
24
+ const checkLinux = async ({
25
+ executor,
26
+ }: MediaChecksDeps): Promise<AddonCheckResult> => {
27
+ const found: string[] = []
28
+ const missing: string[] = []
29
+ if (await commandExists(executor, "playerctl")) found.push("playerctl")
30
+ else missing.push("playerctl")
31
+ if (await commandExists(executor, "wpctl")) found.push("wpctl")
32
+ else missing.push("wpctl")
33
+ if (missing.length === 0) return { available: true }
34
+ return {
35
+ available: false,
36
+ reason: `install ${missing.join(", ")} (volume control needs wpctl on PipeWire/WirePlumber; transport needs playerctl)`,
37
+ }
38
+ }
39
+
40
+ const checkDarwin = async ({
41
+ executor,
42
+ }: MediaChecksDeps): Promise<AddonCheckResult> => {
43
+ // ponytail: mirrors the Linux check's shape — there, playerctl is the
44
+ // system-wide MPRIS bridge and its absence is worth reporting. On macOS the
45
+ // equivalent is `media-control`: without it the provider can only talk to
46
+ // scriptable desktop players (Spotify, Music, TV) via AppleScript and will
47
+ // never see a browser, which is where most people actually play audio.
48
+ if (!(await commandExists(executor, "osascript"))) {
49
+ return {
50
+ available: false,
51
+ reason:
52
+ "install osascript (ships with macOS; needed for system volume control)",
53
+ }
54
+ }
55
+ if (await commandExists(executor, "media-control")) {
56
+ return { available: true }
57
+ }
58
+ return {
59
+ available: false,
60
+ reason:
61
+ "install media-control (`brew install media-control`) to detect playback from any app, browsers included; without it only Spotify/Music/TV are visible",
62
+ }
63
+ }
64
+
65
+ const checkWindows = async ({
66
+ executor,
67
+ }: MediaChecksDeps): Promise<AddonCheckResult> => {
68
+ if (await commandExists(executor, "powershell")) {
69
+ return { available: true }
70
+ }
71
+ return {
72
+ available: false,
73
+ reason:
74
+ "install powershell (ships with Windows; needed for SMTC media control)",
75
+ }
76
+ }
77
+
78
+ const checkUnknown = async (): Promise<AddonCheckResult> => ({
79
+ available: false,
80
+ reason: "unsupported platform; media controls unavailable",
81
+ })
82
+
83
+ export const buildMediaAddonChecks = (
84
+ deps: MediaChecksDeps,
85
+ ): ReadonlyArray<AddonCheck> => {
86
+ const check = async (): Promise<AddonCheckResult> => {
87
+ switch (deps.platform) {
88
+ case "linux":
89
+ return checkLinux(deps)
90
+ case "darwin":
91
+ return checkDarwin(deps)
92
+ case "win32":
93
+ return checkWindows(deps)
94
+ default:
95
+ return checkUnknown()
96
+ }
97
+ }
98
+ return [
99
+ {
100
+ name: "media-control",
101
+ check,
102
+ },
103
+ ]
104
+ }
@@ -0,0 +1,36 @@
1
+ import { execFile } from "node:child_process"
2
+
3
+ import type { CommandExecutor } from "@/system/providers/shared"
4
+
5
+ const execFileAsync = (
6
+ command: string,
7
+ args: ReadonlyArray<string>,
8
+ timeoutMs: number,
9
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> =>
10
+ new Promise((resolve) => {
11
+ execFile(
12
+ command,
13
+ [...args],
14
+ { timeout: timeoutMs },
15
+ (err, stdout, stderr) => {
16
+ const stdoutStr = typeof stdout === "string" ? stdout : ""
17
+ const stderrStr = typeof stderr === "string" ? stderr : ""
18
+ if (err !== null) {
19
+ const code = (err as NodeJS.ErrnoException).code
20
+ resolve({
21
+ exitCode: code === "ENOENT" ? 127 : 1,
22
+ stdout: stdoutStr,
23
+ stderr: stderrStr || err.message,
24
+ })
25
+ return
26
+ }
27
+ resolve({ exitCode: 0, stdout: stdoutStr, stderr: stderrStr })
28
+ },
29
+ )
30
+ })
31
+
32
+ export const createChildProcessExecutor = (): CommandExecutor => ({
33
+ async run(command, args, options) {
34
+ return execFileAsync(command, args, options?.timeoutMs ?? 2_000)
35
+ },
36
+ })
@@ -0,0 +1,5 @@
1
+ import type { NewAddonManifest } from "@/addon/api"
2
+
3
+ declare const manifest: NewAddonManifest
4
+ export default manifest
5
+ export { manifest }