@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,344 @@
1
+ export const HOLD_ACTION_DELAY_MS = 200
2
+ export const DOUBLE_TAP_DELAY_MS = 200
3
+
4
+ export const SPA_HOLD_DELAY_MS = 500
5
+ export const SPA_DOUBLE_TAP_DELAY_MS = 300
6
+
7
+ export type GestureType = "down" | "up"
8
+
9
+ export interface GestureEvent {
10
+ readonly type: GestureType
11
+ readonly timestamp: number
12
+ readonly keyIndex?: number
13
+ }
14
+
15
+ export type GestureKind = "tap" | "dbl-tap" | "hold"
16
+
17
+ export interface GestureResult {
18
+ readonly kind: GestureKind
19
+ readonly keyIndex?: number
20
+ readonly timestamp: number
21
+ readonly durationMs?: number
22
+ readonly timestamps?: ReadonlyArray<number>
23
+ }
24
+
25
+ type KeyState =
26
+ | { name: "idle"; keyIndex?: number }
27
+ | {
28
+ name: "holding"
29
+ downAt: number
30
+ timer?: ReturnType<typeof setTimeout>
31
+ keyIndex?: number
32
+ emittedHold?: boolean
33
+ }
34
+ | {
35
+ name: "waiting-second"
36
+ firstUpAt: number
37
+ firstDownAt: number
38
+ timer?: ReturnType<typeof setTimeout>
39
+ keyIndex?: number
40
+ }
41
+ | {
42
+ name: "second-down"
43
+ secondDownAt: number
44
+ firstUpAt: number
45
+ firstDownAt: number
46
+ keyIndex?: number
47
+ }
48
+
49
+ const tap = (
50
+ upEvent: GestureEvent,
51
+ downAt: number,
52
+ keyIndex?: number,
53
+ ): GestureResult =>
54
+ Object.freeze({
55
+ kind: "tap",
56
+ timestamp: upEvent.timestamp,
57
+ durationMs: upEvent.timestamp - downAt,
58
+ timestamps: Object.freeze([downAt, upEvent.timestamp]),
59
+ ...(keyIndex !== undefined ? { keyIndex } : {}),
60
+ })
61
+
62
+ const dblTap = (
63
+ upEvent: GestureEvent,
64
+ firstDownAt: number,
65
+ firstUpAt: number,
66
+ secondDownAt: number,
67
+ keyIndex?: number,
68
+ ): GestureResult =>
69
+ Object.freeze({
70
+ kind: "dbl-tap",
71
+ timestamp: upEvent.timestamp,
72
+ durationMs: upEvent.timestamp - firstDownAt,
73
+ timestamps: Object.freeze([
74
+ firstDownAt,
75
+ firstUpAt,
76
+ secondDownAt,
77
+ upEvent.timestamp,
78
+ ]),
79
+ ...(keyIndex !== undefined ? { keyIndex } : {}),
80
+ })
81
+
82
+ const hold = (
83
+ timestamp: number,
84
+ downAt: number,
85
+ keyIndex?: number,
86
+ ): GestureResult =>
87
+ Object.freeze({
88
+ kind: "hold",
89
+ timestamp,
90
+ durationMs: timestamp - downAt,
91
+ timestamps: Object.freeze([downAt, timestamp]),
92
+ ...(keyIndex !== undefined ? { keyIndex } : {}),
93
+ })
94
+
95
+ const clearTimer = (timer: ReturnType<typeof setTimeout> | undefined): void => {
96
+ if (timer !== undefined) clearTimeout(timer)
97
+ }
98
+
99
+ export interface GestureDetectorOptions {
100
+ readonly onGesture?: (result: GestureResult) => void
101
+ }
102
+
103
+ export interface GestureDetector {
104
+ detect(event: GestureEvent): GestureResult | null
105
+ reset(): void
106
+ }
107
+
108
+ const createKeyState = (keyIndex: number | undefined): KeyState => ({
109
+ name: "idle",
110
+ keyIndex,
111
+ })
112
+
113
+ export const createGestureDetector = (
114
+ options: GestureDetectorOptions = {},
115
+ ): GestureDetector => {
116
+ const states = new Map<number, KeyState>()
117
+ const { onGesture } = options
118
+
119
+ const getOrCreateState = (keyIndex: number | undefined): KeyState => {
120
+ const k = keyIndex ?? -1
121
+ const existing = states.get(k)
122
+ if (existing !== undefined) return existing
123
+ const s = createKeyState(keyIndex)
124
+ if (keyIndex !== undefined) states.set(k, s)
125
+ return s
126
+ }
127
+
128
+ const setState = (keyIndex: number | undefined, next: KeyState): void => {
129
+ const k = keyIndex ?? -1
130
+ if (k !== undefined) states.set(k, next)
131
+ }
132
+
133
+ const detect = (event: GestureEvent): GestureResult | null => {
134
+ const key = event.keyIndex
135
+ const state = getOrCreateState(key)
136
+ const k = key ?? -1
137
+
138
+ switch (state.name) {
139
+ case "idle":
140
+ if (event.type === "down") {
141
+ for (const [otherK, otherState] of states) {
142
+ if (otherState.name === "waiting-second") {
143
+ clearTimer(otherState.timer)
144
+ states.set(otherK, {
145
+ name: "idle",
146
+ keyIndex: otherState.keyIndex,
147
+ })
148
+ }
149
+ }
150
+ const downAt = event.timestamp
151
+ const newState: KeyState = {
152
+ name: "holding",
153
+ downAt,
154
+ timer: setTimeout(() => {
155
+ const s = states.get(k)
156
+ if (s?.name === "holding") {
157
+ s.emittedHold = true
158
+ onGesture?.(hold(downAt + HOLD_ACTION_DELAY_MS, downAt, key))
159
+ }
160
+ }, HOLD_ACTION_DELAY_MS),
161
+ keyIndex: key,
162
+ }
163
+ setState(key, newState)
164
+ return null
165
+ }
166
+ break
167
+
168
+ case "holding": {
169
+ if (event.type === "up") {
170
+ const duration = event.timestamp - state.downAt
171
+ clearTimer(state.timer)
172
+ if (duration >= HOLD_ACTION_DELAY_MS) {
173
+ setState(key, { name: "idle", keyIndex: key })
174
+ return hold(event.timestamp, state.downAt, key)
175
+ }
176
+ const newState: KeyState = {
177
+ name: "waiting-second",
178
+ firstUpAt: event.timestamp,
179
+ firstDownAt: state.downAt,
180
+ timer: setTimeout(() => {
181
+ const s = states.get(k)
182
+ if (s?.name === "waiting-second") {
183
+ onGesture?.(
184
+ tap(
185
+ { type: "up", timestamp: s.firstUpAt, keyIndex: key },
186
+ s.firstDownAt,
187
+ key,
188
+ ),
189
+ )
190
+ states.delete(k)
191
+ }
192
+ }, DOUBLE_TAP_DELAY_MS),
193
+ keyIndex: key,
194
+ }
195
+ setState(key, newState)
196
+ return null
197
+ }
198
+ break
199
+ }
200
+
201
+ case "waiting-second": {
202
+ clearTimer(state.timer)
203
+ if (event.type === "down") {
204
+ if (event.keyIndex !== key) {
205
+ const t = tap(
206
+ { type: "up", timestamp: state.firstUpAt, keyIndex: key },
207
+ state.firstDownAt,
208
+ key,
209
+ )
210
+ onGesture?.(t)
211
+ setState(key, { name: "idle", keyIndex: key })
212
+ return null
213
+ }
214
+ const newState: KeyState = {
215
+ name: "second-down",
216
+ firstUpAt: state.firstUpAt,
217
+ firstDownAt: state.firstDownAt,
218
+ secondDownAt: event.timestamp,
219
+ keyIndex: key,
220
+ }
221
+ setState(key, newState)
222
+ return null
223
+ }
224
+ if (event.type === "up") {
225
+ const t = tap(event, state.firstDownAt, key)
226
+ setState(key, { name: "idle", keyIndex: key })
227
+ return t
228
+ }
229
+ break
230
+ }
231
+
232
+ case "second-down":
233
+ if (event.type === "up") {
234
+ const d = dblTap(
235
+ event,
236
+ state.firstDownAt,
237
+ state.firstUpAt,
238
+ state.secondDownAt,
239
+ key,
240
+ )
241
+ setState(key, { name: "idle", keyIndex: key })
242
+ onGesture?.(d)
243
+ return d
244
+ }
245
+ break
246
+ }
247
+
248
+ return null
249
+ }
250
+
251
+ const reset = (): void => {
252
+ for (const s of states.values()) {
253
+ if (s.name === "holding") clearTimer(s.timer)
254
+ if (s.name === "waiting-second") clearTimer(s.timer)
255
+ }
256
+ states.clear()
257
+ }
258
+
259
+ return Object.freeze({ detect, reset })
260
+ }
261
+
262
+ export const nextGesture = (
263
+ events: ReadonlyArray<GestureEvent>,
264
+ ): GestureResult | null => {
265
+ let state: KeyState = { name: "idle" }
266
+
267
+ for (const event of events) {
268
+ switch (state.name) {
269
+ case "idle":
270
+ if (event.type === "down") {
271
+ state = {
272
+ name: "holding",
273
+ downAt: event.timestamp,
274
+ timer: undefined,
275
+ keyIndex: event.keyIndex,
276
+ }
277
+ }
278
+ break
279
+
280
+ case "holding":
281
+ if (event.type === "up") {
282
+ const duration = event.timestamp - state.downAt
283
+ if (duration >= HOLD_ACTION_DELAY_MS) {
284
+ return hold(event.timestamp, state.downAt, state.keyIndex)
285
+ }
286
+ state = {
287
+ name: "waiting-second",
288
+ firstUpAt: event.timestamp,
289
+ firstDownAt: state.downAt,
290
+ timer: undefined,
291
+ keyIndex: state.keyIndex,
292
+ }
293
+ }
294
+ break
295
+
296
+ case "waiting-second":
297
+ if (event.type === "down") {
298
+ if (event.keyIndex !== state.keyIndex) {
299
+ return tap(
300
+ {
301
+ type: "up",
302
+ timestamp: state.firstUpAt,
303
+ keyIndex: state.keyIndex,
304
+ },
305
+ state.firstDownAt,
306
+ state.keyIndex,
307
+ )
308
+ }
309
+ state = {
310
+ name: "second-down",
311
+ firstUpAt: state.firstUpAt,
312
+ firstDownAt: state.firstDownAt,
313
+ secondDownAt: event.timestamp,
314
+ keyIndex: state.keyIndex,
315
+ }
316
+ } else if (event.type === "up") {
317
+ return tap(event, state.firstDownAt, state.keyIndex)
318
+ }
319
+ break
320
+
321
+ case "second-down":
322
+ if (event.type === "up") {
323
+ return dblTap(
324
+ event,
325
+ state.firstDownAt,
326
+ state.firstUpAt,
327
+ state.secondDownAt,
328
+ state.keyIndex,
329
+ )
330
+ }
331
+ break
332
+ }
333
+ }
334
+
335
+ if (state.name === "waiting-second") {
336
+ return tap(
337
+ { type: "up", timestamp: state.firstUpAt, keyIndex: state.keyIndex },
338
+ state.firstDownAt,
339
+ state.keyIndex,
340
+ )
341
+ }
342
+
343
+ return null
344
+ }
@@ -0,0 +1,127 @@
1
+ import { readFileSync, statSync } from "node:fs"
2
+
3
+ import { makeAssetId } from "./asset-id"
4
+ import { inferMimeFromPath } from "./mime"
5
+ import { resolveIconSource } from "../render/icon-source-resolver"
6
+ import type { ResolveIconPathOptions } from "../render/icon-source-resolver"
7
+ import type { RuntimeButton, RuntimeDeck } from "@/deck"
8
+ import type pino from "pino"
9
+
10
+ export const MAX_ASSET_SIZE = 200 * 1024
11
+
12
+ export interface Asset {
13
+ readonly id: string
14
+ readonly fullPath: string
15
+ readonly mime: string
16
+ readonly src: string
17
+ readonly filesize: number
18
+ readonly mtime: number
19
+ }
20
+
21
+ const assets = new Map<string, Asset>()
22
+
23
+ export const clearAssets = (): void => {
24
+ assets.clear()
25
+ }
26
+
27
+ const registerOneIcon = (
28
+ icon: string,
29
+ resolverOptions: ResolveIconPathOptions,
30
+ logger?: pino.Logger,
31
+ ): void => {
32
+ let resolved
33
+ try {
34
+ resolved = resolveIconSource(icon, resolverOptions)
35
+ } catch (err) {
36
+ logger?.warn(
37
+ { icon, err: (err as Error).message },
38
+ "skipping unresolvable icon",
39
+ )
40
+ return
41
+ }
42
+ if (resolved.kind !== "asset") return
43
+ if (assets.has(resolved.fullPath)) return
44
+
45
+ let stats
46
+ try {
47
+ stats = statSync(resolved.fullPath)
48
+ } catch (err) {
49
+ logger?.warn(
50
+ {
51
+ icon,
52
+ fullPath: resolved.fullPath,
53
+ err: (err as Error).message,
54
+ },
55
+ `icon file missing or unreadable: '${icon}' resolved to '${resolved.fullPath}' — ${(err as Error).message}`,
56
+ )
57
+ return
58
+ }
59
+ const filesize = stats.size
60
+ if (filesize > MAX_ASSET_SIZE) {
61
+ logger?.warn(
62
+ { fullPath: resolved.fullPath, filesize, limit: MAX_ASSET_SIZE },
63
+ "icon exceeds MAX_ASSET_SIZE; skipping",
64
+ )
65
+ return
66
+ }
67
+ let raw: Buffer
68
+ try {
69
+ raw = readFileSync(resolved.fullPath)
70
+ } catch (err) {
71
+ logger?.warn(
72
+ { fullPath: resolved.fullPath, err: (err as Error).message },
73
+ "icon read failed",
74
+ )
75
+ return
76
+ }
77
+ const mime = inferMimeFromPath(resolved.fullPath)
78
+ const id = makeAssetId(resolved.fullPath, filesize, stats.mtimeMs)
79
+ assets.set(resolved.fullPath, {
80
+ id,
81
+ fullPath: resolved.fullPath,
82
+ mime,
83
+ src: `data:${mime};base64,${raw.toString("base64")}`,
84
+ filesize,
85
+ mtime: stats.mtimeMs,
86
+ })
87
+ }
88
+
89
+ const readButtonIcon = (button: RuntimeButton): string | undefined => {
90
+ const cfg = button.config
91
+ if (typeof cfg !== "object" || cfg === null) return undefined
92
+ const icon = (cfg as Record<string, unknown>).icon
93
+ return typeof icon === "string" && icon !== "" ? icon : undefined
94
+ }
95
+
96
+ export const registerIconForDeck = (
97
+ buttons: ReadonlyArray<RuntimeButton>,
98
+ resolverOptions: ResolveIconPathOptions,
99
+ logger?: pino.Logger,
100
+ ): void => {
101
+ for (const button of buttons) {
102
+ const icon = readButtonIcon(button)
103
+ if (icon !== undefined) registerOneIcon(icon, resolverOptions, logger)
104
+ }
105
+ }
106
+
107
+ export const registerDeckIcon = (
108
+ deck: Pick<RuntimeDeck, "icon">,
109
+ resolverOptions: ResolveIconPathOptions,
110
+ logger?: pino.Logger,
111
+ ): void => {
112
+ if (typeof deck.icon !== "string" || deck.icon === "") return
113
+ registerOneIcon(deck.icon, resolverOptions, logger)
114
+ }
115
+
116
+ export const getUnsentAssets = (
117
+ sentIds: ReadonlySet<string>,
118
+ ): ReadonlyArray<Asset> => {
119
+ const out: Asset[] = []
120
+ for (const asset of assets.values()) {
121
+ if (!sentIds.has(asset.id)) out.push(asset)
122
+ }
123
+ return out
124
+ }
125
+
126
+ export const getAssetByPath = (fullPath: string): Asset | undefined =>
127
+ assets.get(fullPath)
@@ -0,0 +1,86 @@
1
+ // Single-emoji source matcher. Accepts:
2
+ // - one extended-pictographic char (with optional VS16 after each
3
+ // segment; ZWJ chains extend it — covers 🏳️‍🌈, 🧑‍🦽, 👨‍👩‍👧, etc.),
4
+ // - OR two regional-indicator chars (a country flag like 🇪🇸).
5
+ // Two regional indicators don't have `Emoji_Presentation` individually
6
+ // (the flag is only "complete" as a pair), so the old single-codepoint
7
+ // regex rejected every country flag.
8
+ export const EMOJI_RE =
9
+ /^(?:[\p{Extended_Pictographic}\p{Emoji_Component}]\uFE0F?(?:\u200D[\p{Extended_Pictographic}\p{Emoji_Component}]\uFE0F?)*|\p{Regional_Indicator}{2})$/u
10
+
11
+ export const ICON_FALLBACK = "icon://alert-circle"
12
+
13
+ // Runtime-safe absolute path detection (POSIX: leading '/'; Windows:
14
+ // leading '\' or drive letter like 'C:\' or 'C:/'). Implemented as a
15
+ // regex so this module is safe to import from both the Node runtime and
16
+ // the browser bundle (which can't use `node:path.isAbsolute`).
17
+ const ABSOLUTE_PATH_RE = /^(?:\/|\\|[A-Za-z]:[\\/])/
18
+
19
+ /**
20
+ * Validate that a runtime icon source is one of:
21
+ * - `icon://<name>` — Lucide icon by name (resolved by the runtime
22
+ * `resolveIconSource`)
23
+ * - `asset://<id>` — pre-resolved asset id from the registry
24
+ * - `addon://<a>/<p>` — asset inside an addon's frontendEntry dir
25
+ * - `builtin://<p>` — asset inside the builtin addon's dir
26
+ * - absolute path — asset resolved directly
27
+ * - relative path — asset resolved against the config's baseDirs
28
+ * (e.g. `./assets/chrome.svg`, `../shared/icon.svg`,
29
+ * `~/Pictures/x.png`)
30
+ * - a single emoji (Presentation, or base+VS16 like ✈️; also
31
+ * regional-indicator pair flags like 🇪🇸, and ZWJ sequences
32
+ * like 🏳️‍🌈 or 👨‍👩‍👧)
33
+ *
34
+ * Rejected: empty string, "%", "abc", multi-char (e.g. "abc🔥"),
35
+ * inline URLs (data:, http://, https://, file://) — these must be
36
+ * pre-resolved to asset:// before reaching the runtime.
37
+ *
38
+ * Anything rejected here triggers a runtime fallback (alert-circle
39
+ * Lucide icon) AND, at config-load, a zod validation error.
40
+ */
41
+ export const isIconSource = (s: unknown): s is string => {
42
+ if (typeof s !== "string" || s.length === 0) return false
43
+ if (EMOJI_RE.test(s)) return true
44
+ if (s.startsWith("icon://") && s.length > "icon://".length) return true
45
+ if (s.startsWith("asset://") && s.length > "asset://".length) return true
46
+ if (s.startsWith("addon://")) {
47
+ // addon://<addonName>/<subPath> — must have both segments
48
+ const rest = s.slice("addon://".length)
49
+ const slash = rest.indexOf("/")
50
+ if (slash > 0 && slash < rest.length - 1) return true
51
+ return false
52
+ }
53
+ if (s.startsWith("builtin://") && s.length > "builtin://".length) return true
54
+ // Inline URLs are explicitly rejected by the runtime resolver.
55
+ if (
56
+ s.startsWith("data:") ||
57
+ s.startsWith("http://") ||
58
+ s.startsWith("https://") ||
59
+ s.startsWith("file://")
60
+ ) {
61
+ return false
62
+ }
63
+ // Absolute path (POSIX '/' or Windows '\' or 'C:\').
64
+ if (ABSOLUTE_PATH_RE.test(s)) return true
65
+ // Reject anything that looks like a scheme-prefixed URL the runtime
66
+ // doesn't understand (e.g. "abc://x"). The runtime would treat it as a
67
+ // path and produce a garbage absolute path. Recognized schemes
68
+ // (icon://, asset://, addon://, builtin://) are handled above.
69
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return false
70
+ // Relative paths: must contain a path separator OR start with one of
71
+ // the recognized path prefixes ("./", "../", "~/", "~\\"). Single-char
72
+ // paths like "." are also valid.
73
+ if (
74
+ s === "." ||
75
+ s === "~" ||
76
+ s.startsWith("./") ||
77
+ s.startsWith("../") ||
78
+ s.startsWith("~/") ||
79
+ s.startsWith("~\\") ||
80
+ /[\\/]/.test(s) ||
81
+ s.startsWith("/")
82
+ ) {
83
+ return true
84
+ }
85
+ return false
86
+ }
@@ -0,0 +1,30 @@
1
+ export {
2
+ createPubSub,
3
+ type CreatePubSubOptions,
4
+ type FlushCallback,
5
+ type Payload,
6
+ type PubSub,
7
+ type Unsubscribe,
8
+ } from "./pub-sub"
9
+
10
+ export {
11
+ DOUBLE_TAP_DELAY_MS,
12
+ HOLD_ACTION_DELAY_MS,
13
+ type GestureEvent,
14
+ type GestureKind,
15
+ type GestureResult,
16
+ type GestureType,
17
+ nextGesture,
18
+ } from "./gesture-state"
19
+
20
+ export { createStore, type Scope, type ScopeKind, type Store } from "./store"
21
+
22
+ export {
23
+ NEXT_PAGE_MARKER,
24
+ type NextPageMarker,
25
+ type Page,
26
+ type PaginateOptions,
27
+ type PaginatedItem,
28
+ type PaginationResult,
29
+ paginate,
30
+ } from "./pagination"
@@ -0,0 +1,16 @@
1
+ const EXT_TO_MIME: Readonly<Record<string, string>> = {
2
+ svg: "image/svg+xml",
3
+ png: "image/png",
4
+ jpg: "image/jpeg",
5
+ jpeg: "image/jpeg",
6
+ webp: "image/webp",
7
+ gif: "image/gif",
8
+ ico: "image/x-icon",
9
+ }
10
+
11
+ export const inferMimeFromPath = (path: string): string => {
12
+ const dot = path.lastIndexOf(".")
13
+ if (dot === -1 || dot === path.length - 1) return "application/octet-stream"
14
+ const ext = path.slice(dot + 1).toLowerCase()
15
+ return EXT_TO_MIME[ext] ?? "application/octet-stream"
16
+ }
@@ -0,0 +1,90 @@
1
+ export const NEXT_PAGE_MARKER = "__nextPageMarker" as const
2
+
3
+ export type NextPageMarker = typeof NEXT_PAGE_MARKER
4
+
5
+ export interface PaginatedItem<T> {
6
+ readonly id: string
7
+ readonly value: T
8
+ }
9
+
10
+ export interface Page<T> {
11
+ readonly items: ReadonlyArray<PaginatedItem<T> | null>
12
+ readonly pageIndex: number
13
+ readonly totalPages: number
14
+ readonly hasNext: boolean
15
+ readonly hasPrev: boolean
16
+ }
17
+
18
+ export interface PaginationResult<T> {
19
+ readonly pages: ReadonlyArray<Page<T>>
20
+ readonly totalItems: number
21
+ }
22
+
23
+ export interface PaginateOptions {
24
+ readonly keyCount: number
25
+ readonly pageSize?: number
26
+ }
27
+
28
+ export const paginationReservedPositions = (
29
+ keyCount: number,
30
+ paginated: boolean,
31
+ ): number[] => (paginated && keyCount > 1 ? [keyCount - 2] : [])
32
+
33
+ const buildPageItem = <T>(id: string, value: T): PaginatedItem<T> => ({
34
+ id,
35
+ value,
36
+ })
37
+
38
+ export const paginate = <T>(
39
+ items: ReadonlyArray<T>,
40
+ opts: PaginateOptions,
41
+ ): PaginationResult<T> => {
42
+ if (opts.keyCount <= 0) {
43
+ throw new Error("paginate: keyCount must be > 0")
44
+ }
45
+ const pageSize = opts.pageSize ?? opts.keyCount - 2
46
+ if (pageSize <= 0) {
47
+ throw new Error("paginate: pageSize must be > 0")
48
+ }
49
+
50
+ const totalItems = items.length
51
+ const totalPages = totalItems === 0 ? 0 : Math.ceil(totalItems / pageSize)
52
+ if (totalPages === 0) {
53
+ return { pages: [], totalItems: 0 }
54
+ }
55
+
56
+ const pages: Array<Page<T>> = []
57
+ for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
58
+ const start = pageIndex * pageSize
59
+ const end = Math.min(start + pageSize, totalItems)
60
+ const slice = items.slice(start, end)
61
+
62
+ const pageItems: Array<PaginatedItem<T> | null> = slice.map((value, i) =>
63
+ buildPageItem(`${pageIndex}-${i}`, value),
64
+ )
65
+
66
+ const hasMultiplePages = totalPages > 1
67
+ if (hasMultiplePages) {
68
+ const isFullPage = pageItems.length >= pageSize
69
+ const markerIndex = isFullPage ? pageSize - 1 : pageItems.length
70
+ pageItems.splice(markerIndex, 0, {
71
+ id: NEXT_PAGE_MARKER,
72
+ value: NEXT_PAGE_MARKER,
73
+ } as PaginatedItem<T>)
74
+ }
75
+
76
+ while (pageItems.length < pageSize) {
77
+ pageItems.push(null)
78
+ }
79
+
80
+ pages.push({
81
+ items: pageItems,
82
+ pageIndex,
83
+ totalPages,
84
+ hasNext: pageIndex < totalPages - 1,
85
+ hasPrev: pageIndex > 0,
86
+ })
87
+ }
88
+
89
+ return { pages, totalItems }
90
+ }