@ohmykreee/dsh-remote-control 0.0.0-stage → 1.0.3

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 (373) hide show
  1. package/AGENTS.md +556 -0
  2. package/INSTALLATION.md +310 -0
  3. package/LICENSE +21 -0
  4. package/README.md +195 -2
  5. package/client.js +3 -0
  6. package/components.json +20 -0
  7. package/cordis.patch.yml +3 -0
  8. package/docs/README.en.md +196 -0
  9. package/docs/screenshots/en/overview.png +0 -0
  10. package/docs/screenshots/en/question.png +0 -0
  11. package/docs/screenshots/zh/overview.png +0 -0
  12. package/docs/screenshots/zh/question.png +0 -0
  13. package/icon.svg +24 -0
  14. package/index.js +2 -0
  15. package/lib/account.js +1 -0
  16. package/lib/app-mapping.js +1 -0
  17. package/lib/i18n.js +1 -0
  18. package/lib/media-enrich.js +1 -0
  19. package/lib/notifications.js +1 -0
  20. package/lib/performance.js +1 -0
  21. package/lib/platform/index.js +1 -0
  22. package/lib/probe-adapter.js +1 -0
  23. package/lib/probe-daemon.js +1 -0
  24. package/lib/probe.js +1 -0
  25. package/lib/qr.js +1 -0
  26. package/lib/runtime.js +1 -0
  27. package/lib/service.js +1 -0
  28. package/lib/sessions.js +1 -0
  29. package/lib/settings.js +1 -0
  30. package/lib/types.js +1 -0
  31. package/lib/util.js +1 -0
  32. package/lib/weather.js +1 -0
  33. package/lib/ws.js +1 -0
  34. package/locale/en.json +6 -0
  35. package/locale/zh.json +6 -0
  36. package/package.json +118 -4
  37. package/scripts/browser-shot.mjs +217 -0
  38. package/scripts/build-dsh.mjs +228 -0
  39. package/scripts/build-rust.mjs +319 -0
  40. package/scripts/build-web.mjs +17 -0
  41. package/scripts/check-npm-package.mjs +322 -0
  42. package/scripts/mock-preview.py +574 -0
  43. package/scripts/screenshot-compose.py +89 -0
  44. package/scripts/screenshot-device.sh +75 -0
  45. package/src/dsh/client/api.ts +68 -0
  46. package/src/dsh/client/i18n.ts +573 -0
  47. package/src/dsh/client/icons.ts +56 -0
  48. package/src/dsh/client/index.ts +95 -0
  49. package/src/dsh/client/logic.ts +310 -0
  50. package/src/dsh/client/panel.ts +483 -0
  51. package/src/dsh/client/sections/apps.ts +40 -0
  52. package/src/dsh/client/sections/balance.ts +38 -0
  53. package/src/dsh/client/sections/connection.ts +97 -0
  54. package/src/dsh/client/sections/notifications.ts +43 -0
  55. package/src/dsh/client/sections/pomodoro.ts +184 -0
  56. package/src/dsh/client/sections/preferences.ts +138 -0
  57. package/src/dsh/client/sections/system.ts +120 -0
  58. package/src/dsh/client/sections/weather.ts +276 -0
  59. package/src/dsh/client/styles.ts +1119 -0
  60. package/src/dsh/client/types.ts +366 -0
  61. package/src/dsh/client/ui.ts +142 -0
  62. package/src/dsh/index.ts +108 -0
  63. package/src/dsh/lib/account.ts +520 -0
  64. package/src/dsh/lib/app-mapping.ts +366 -0
  65. package/src/dsh/lib/i18n.ts +82 -0
  66. package/src/dsh/lib/media-enrich.ts +202 -0
  67. package/src/dsh/lib/notifications.ts +322 -0
  68. package/src/dsh/lib/performance.ts +279 -0
  69. package/src/dsh/lib/platform/index.ts +43 -0
  70. package/src/dsh/lib/probe-adapter.ts +275 -0
  71. package/src/dsh/lib/probe-daemon.ts +359 -0
  72. package/src/dsh/lib/probe.ts +395 -0
  73. package/src/dsh/lib/qr.ts +42 -0
  74. package/src/dsh/lib/runtime.ts +126 -0
  75. package/src/dsh/lib/service.ts +1645 -0
  76. package/src/dsh/lib/sessions.ts +907 -0
  77. package/src/dsh/lib/settings.ts +468 -0
  78. package/src/dsh/lib/types.ts +231 -0
  79. package/src/dsh/lib/util.ts +115 -0
  80. package/src/dsh/lib/weather.ts +532 -0
  81. package/src/dsh/lib/ws.ts +328 -0
  82. package/src/dsh/types/qrcode-generator.d.ts +16 -0
  83. package/src/rust/Cargo.lock +1431 -0
  84. package/src/rust/Cargo.toml +68 -0
  85. package/src/rust/src/cli.rs +233 -0
  86. package/src/rust/src/daemon.rs +140 -0
  87. package/src/rust/src/location/linux.rs +118 -0
  88. package/src/rust/src/location/macos.rs +26 -0
  89. package/src/rust/src/location/mod.rs +170 -0
  90. package/src/rust/src/location/windows.rs +205 -0
  91. package/src/rust/src/main.rs +42 -0
  92. package/src/rust/src/media/catalog.rs +454 -0
  93. package/src/rust/src/media/linux.rs +350 -0
  94. package/src/rust/src/media/macos/accessibility.rs +59 -0
  95. package/src/rust/src/media/macos.rs +366 -0
  96. package/src/rust/src/media/mod.rs +298 -0
  97. package/src/rust/src/media/vendor/apple_music.rs +90 -0
  98. package/src/rust/src/media/vendor/common.rs +186 -0
  99. package/src/rust/src/media/vendor/kugou.rs +52 -0
  100. package/src/rust/src/media/vendor/kuwo.rs +49 -0
  101. package/src/rust/src/media/vendor/mod.rs +75 -0
  102. package/src/rust/src/media/vendor/netease.rs +1715 -0
  103. package/src/rust/src/media/vendor/qishui.rs +36 -0
  104. package/src/rust/src/media/vendor/qqmusic.rs +33 -0
  105. package/src/rust/src/media/vendor/spotify.rs +46 -0
  106. package/src/rust/src/media/vendor/wesing.rs +39 -0
  107. package/src/rust/src/media/vendor/youtube_music.rs +49 -0
  108. package/src/rust/src/media/windows/accessibility.rs +85 -0
  109. package/src/rust/src/media/windows/audio.rs +116 -0
  110. package/src/rust/src/media/windows/private.rs +291 -0
  111. package/src/rust/src/media/windows.rs +775 -0
  112. package/src/rust/src/notifications/linux.rs +276 -0
  113. package/src/rust/src/notifications/macos.rs +382 -0
  114. package/src/rust/src/notifications/mod.rs +244 -0
  115. package/src/rust/src/notifications/windows.rs +565 -0
  116. package/src/rust/src/perf/common.rs +109 -0
  117. package/src/rust/src/perf/linux.rs +282 -0
  118. package/src/rust/src/perf/macos.rs +28 -0
  119. package/src/rust/src/perf/mod.rs +157 -0
  120. package/src/rust/src/perf/windows.rs +981 -0
  121. package/src/rust/src/util.rs +360 -0
  122. package/src/rust/src/volume/linux.rs +195 -0
  123. package/src/rust/src/volume/macos.rs +54 -0
  124. package/src/rust/src/volume/mod.rs +116 -0
  125. package/src/rust/src/volume/windows.rs +75 -0
  126. package/src/rust/tests/cli.rs +79 -0
  127. package/src/rust/tests/daemon.rs +107 -0
  128. package/src/svelte/App.svelte +385 -0
  129. package/src/svelte/index.html +18 -0
  130. package/src/svelte/lib/billing.svelte.ts +228 -0
  131. package/src/svelte/lib/components/BalanceCard.svelte +173 -0
  132. package/src/svelte/lib/components/ClockCard.svelte +183 -0
  133. package/src/svelte/lib/components/CountdownCard.svelte +261 -0
  134. package/src/svelte/lib/components/CpuMemoryCard.svelte +238 -0
  135. package/src/svelte/lib/components/EmptyState.svelte +34 -0
  136. package/src/svelte/lib/components/GpuCard.svelte +245 -0
  137. package/src/svelte/lib/components/HomePage.svelte +34 -0
  138. package/src/svelte/lib/components/MusicControls.svelte +256 -0
  139. package/src/svelte/lib/components/MusicCover.svelte +88 -0
  140. package/src/svelte/lib/components/MusicPage.svelte +116 -0
  141. package/src/svelte/lib/components/NotificationsCard.svelte +109 -0
  142. package/src/svelte/lib/components/Panel.svelte +22 -0
  143. package/src/svelte/lib/components/PerformancePage.svelte +55 -0
  144. package/src/svelte/lib/components/PomodoroCard.svelte +250 -0
  145. package/src/svelte/lib/components/QuestionSheet.svelte +354 -0
  146. package/src/svelte/lib/components/RadialGauge.svelte +99 -0
  147. package/src/svelte/lib/components/Rail.svelte +217 -0
  148. package/src/svelte/lib/components/SectionHeader.svelte +40 -0
  149. package/src/svelte/lib/components/SeekBar.svelte +421 -0
  150. package/src/svelte/lib/components/SessionList.svelte +364 -0
  151. package/src/svelte/lib/components/SessionStats.svelte +98 -0
  152. package/src/svelte/lib/components/SessionsPage.svelte +61 -0
  153. package/src/svelte/lib/components/SpendCard.svelte +105 -0
  154. package/src/svelte/lib/components/ThermalCard.svelte +249 -0
  155. package/src/svelte/lib/components/Toast.svelte +32 -0
  156. package/src/svelte/lib/components/WeatherIcon.svelte +40 -0
  157. package/src/svelte/lib/components/WeatherPanel.svelte +420 -0
  158. package/src/svelte/lib/components/ui/alert/alert-action.svelte +20 -0
  159. package/src/svelte/lib/components/ui/alert/alert-description.svelte +23 -0
  160. package/src/svelte/lib/components/ui/alert/alert-title.svelte +23 -0
  161. package/src/svelte/lib/components/ui/alert/alert.svelte +43 -0
  162. package/src/svelte/lib/components/ui/alert/index.ts +17 -0
  163. package/src/svelte/lib/components/ui/avatar/avatar-badge.svelte +26 -0
  164. package/src/svelte/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
  165. package/src/svelte/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
  166. package/src/svelte/lib/components/ui/avatar/avatar-group.svelte +23 -0
  167. package/src/svelte/lib/components/ui/avatar/avatar-image.svelte +17 -0
  168. package/src/svelte/lib/components/ui/avatar/avatar.svelte +26 -0
  169. package/src/svelte/lib/components/ui/avatar/index.ts +22 -0
  170. package/src/svelte/lib/components/ui/badge/badge.svelte +49 -0
  171. package/src/svelte/lib/components/ui/badge/index.ts +2 -0
  172. package/src/svelte/lib/components/ui/button/button.svelte +82 -0
  173. package/src/svelte/lib/components/ui/button/index.ts +17 -0
  174. package/src/svelte/lib/components/ui/card/card-action.svelte +23 -0
  175. package/src/svelte/lib/components/ui/card/card-content.svelte +20 -0
  176. package/src/svelte/lib/components/ui/card/card-description.svelte +20 -0
  177. package/src/svelte/lib/components/ui/card/card-footer.svelte +20 -0
  178. package/src/svelte/lib/components/ui/card/card-header.svelte +23 -0
  179. package/src/svelte/lib/components/ui/card/card-title.svelte +20 -0
  180. package/src/svelte/lib/components/ui/card/card.svelte +22 -0
  181. package/src/svelte/lib/components/ui/card/index.ts +25 -0
  182. package/src/svelte/lib/components/ui/dialog/dialog-close.svelte +11 -0
  183. package/src/svelte/lib/components/ui/dialog/dialog-content.svelte +48 -0
  184. package/src/svelte/lib/components/ui/dialog/dialog-description.svelte +17 -0
  185. package/src/svelte/lib/components/ui/dialog/dialog-footer.svelte +32 -0
  186. package/src/svelte/lib/components/ui/dialog/dialog-header.svelte +20 -0
  187. package/src/svelte/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
  188. package/src/svelte/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  189. package/src/svelte/lib/components/ui/dialog/dialog-title.svelte +17 -0
  190. package/src/svelte/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  191. package/src/svelte/lib/components/ui/dialog/dialog.svelte +7 -0
  192. package/src/svelte/lib/components/ui/dialog/index.ts +34 -0
  193. package/src/svelte/lib/components/ui/empty/empty-content.svelte +23 -0
  194. package/src/svelte/lib/components/ui/empty/empty-description.svelte +23 -0
  195. package/src/svelte/lib/components/ui/empty/empty-header.svelte +20 -0
  196. package/src/svelte/lib/components/ui/empty/empty-media.svelte +41 -0
  197. package/src/svelte/lib/components/ui/empty/empty-title.svelte +20 -0
  198. package/src/svelte/lib/components/ui/empty/empty.svelte +23 -0
  199. package/src/svelte/lib/components/ui/empty/index.ts +22 -0
  200. package/src/svelte/lib/components/ui/input/index.ts +7 -0
  201. package/src/svelte/lib/components/ui/input/input.svelte +48 -0
  202. package/src/svelte/lib/components/ui/progress/index.ts +7 -0
  203. package/src/svelte/lib/components/ui/progress/progress.svelte +27 -0
  204. package/src/svelte/lib/components/ui/scroll-area/index.ts +10 -0
  205. package/src/svelte/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte +30 -0
  206. package/src/svelte/lib/components/ui/scroll-area/scroll-area.svelte +43 -0
  207. package/src/svelte/lib/components/ui/separator/index.ts +7 -0
  208. package/src/svelte/lib/components/ui/separator/separator.svelte +21 -0
  209. package/src/svelte/lib/components/ui/sheet/index.ts +34 -0
  210. package/src/svelte/lib/components/ui/sheet/sheet-close.svelte +7 -0
  211. package/src/svelte/lib/components/ui/sheet/sheet-content.svelte +55 -0
  212. package/src/svelte/lib/components/ui/sheet/sheet-description.svelte +17 -0
  213. package/src/svelte/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  214. package/src/svelte/lib/components/ui/sheet/sheet-header.svelte +20 -0
  215. package/src/svelte/lib/components/ui/sheet/sheet-overlay.svelte +20 -0
  216. package/src/svelte/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  217. package/src/svelte/lib/components/ui/sheet/sheet-title.svelte +17 -0
  218. package/src/svelte/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  219. package/src/svelte/lib/components/ui/sheet/sheet.svelte +7 -0
  220. package/src/svelte/lib/components/ui/skeleton/index.ts +7 -0
  221. package/src/svelte/lib/components/ui/skeleton/skeleton.svelte +17 -0
  222. package/src/svelte/lib/components/ui/slider/index.ts +7 -0
  223. package/src/svelte/lib/components/ui/slider/slider.svelte +52 -0
  224. package/src/svelte/lib/components/ui/tooltip/index.ts +19 -0
  225. package/src/svelte/lib/components/ui/tooltip/tooltip-content.svelte +51 -0
  226. package/src/svelte/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  227. package/src/svelte/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  228. package/src/svelte/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  229. package/src/svelte/lib/components/ui/tooltip/tooltip.svelte +7 -0
  230. package/src/svelte/lib/countdown.svelte.ts +252 -0
  231. package/src/svelte/lib/format.ts +199 -0
  232. package/src/svelte/lib/i18n.svelte.ts +583 -0
  233. package/src/svelte/lib/media.ts +32 -0
  234. package/src/svelte/lib/pages.ts +18 -0
  235. package/src/svelte/lib/pomodoro.svelte.ts +223 -0
  236. package/src/svelte/lib/session-tree.ts +80 -0
  237. package/src/svelte/lib/state.svelte.ts +359 -0
  238. package/src/svelte/lib/timer-alarm.svelte.ts +319 -0
  239. package/src/svelte/lib/types.ts +306 -0
  240. package/src/svelte/lib/utils.ts +8 -0
  241. package/src/svelte/lib/weather.ts +84 -0
  242. package/src/svelte/lib/ws.ts +104 -0
  243. package/src/svelte/main.ts +13 -0
  244. package/src/svelte/styles/global.css +315 -0
  245. package/src/svelte/vite-env.d.ts +12 -0
  246. package/static/fonts/LICENSE.txt +287 -0
  247. package/static/icon.svg +24 -0
  248. package/static/manifest.webmanifest +18 -0
  249. package/svelte.config.js +5 -0
  250. package/tsconfig.dsh-client.json +18 -0
  251. package/tsconfig.dsh.json +21 -0
  252. package/tsconfig.json +20 -0
  253. package/vite.config.js +31 -0
  254. package/web/assets/index-DLdSfnJ3.css +1 -0
  255. package/web/assets/index-YNE8X0Uy.js +6 -0
  256. package/web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  257. package/web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  258. package/web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  259. package/web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  260. package/web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  261. package/web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  262. package/web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  263. package/web/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
  264. package/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  265. package/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  266. package/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  267. package/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  268. package/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  269. package/web/assets/noto-sans-sc-100-wght-normal-DluiXYzi.woff2 +0 -0
  270. package/web/assets/noto-sans-sc-101-wght-normal-CPkZHnw0.woff2 +0 -0
  271. package/web/assets/noto-sans-sc-102-wght-normal-gzIulUH6.woff2 +0 -0
  272. package/web/assets/noto-sans-sc-103-wght-normal-CJfSBxFo.woff2 +0 -0
  273. package/web/assets/noto-sans-sc-104-wght-normal-B3QMglgE.woff2 +0 -0
  274. package/web/assets/noto-sans-sc-105-wght-normal-xXh_OMfk.woff2 +0 -0
  275. package/web/assets/noto-sans-sc-106-wght-normal-B9n3jMjP.woff2 +0 -0
  276. package/web/assets/noto-sans-sc-107-wght-normal-D7fvVprN.woff2 +0 -0
  277. package/web/assets/noto-sans-sc-108-wght-normal-J0_LhxVo.woff2 +0 -0
  278. package/web/assets/noto-sans-sc-109-wght-normal-Dsp-De_0.woff2 +0 -0
  279. package/web/assets/noto-sans-sc-110-wght-normal-DE_6NUrb.woff2 +0 -0
  280. package/web/assets/noto-sans-sc-111-wght-normal-C4S-TlTn.woff2 +0 -0
  281. package/web/assets/noto-sans-sc-112-wght-normal-BcFuP3w6.woff2 +0 -0
  282. package/web/assets/noto-sans-sc-113-wght-normal-Dk932kmj.woff2 +0 -0
  283. package/web/assets/noto-sans-sc-114-wght-normal-CO_5Xo-i.woff2 +0 -0
  284. package/web/assets/noto-sans-sc-115-wght-normal-BWy07H14.woff2 +0 -0
  285. package/web/assets/noto-sans-sc-116-wght-normal-B4HVAfHo.woff2 +0 -0
  286. package/web/assets/noto-sans-sc-117-wght-normal-siZzFqgK.woff2 +0 -0
  287. package/web/assets/noto-sans-sc-118-wght-normal-xhBB7f6V.woff2 +0 -0
  288. package/web/assets/noto-sans-sc-119-wght-normal-Ck_g2FGz.woff2 +0 -0
  289. package/web/assets/noto-sans-sc-21-wght-normal-D0x9-yBt.woff2 +0 -0
  290. package/web/assets/noto-sans-sc-22-wght-normal-BBUVTShg.woff2 +0 -0
  291. package/web/assets/noto-sans-sc-23-wght-normal-Du1oYWkE.woff2 +0 -0
  292. package/web/assets/noto-sans-sc-24-wght-normal-CmWjJ0gt.woff2 +0 -0
  293. package/web/assets/noto-sans-sc-25-wght-normal-BFHiqilg.woff2 +0 -0
  294. package/web/assets/noto-sans-sc-26-wght-normal-C0sF2Z2f.woff2 +0 -0
  295. package/web/assets/noto-sans-sc-27-wght-normal-COFdrquJ.woff2 +0 -0
  296. package/web/assets/noto-sans-sc-28-wght-normal-DMgdNu1o.woff2 +0 -0
  297. package/web/assets/noto-sans-sc-29-wght-normal-Cmq8em30.woff2 +0 -0
  298. package/web/assets/noto-sans-sc-30-wght-normal-CZWqZ3yU.woff2 +0 -0
  299. package/web/assets/noto-sans-sc-31-wght-normal-Wfi_lutW.woff2 +0 -0
  300. package/web/assets/noto-sans-sc-32-wght-normal-BRJQ4PY5.woff2 +0 -0
  301. package/web/assets/noto-sans-sc-33-wght-normal-CUVQXNAJ.woff2 +0 -0
  302. package/web/assets/noto-sans-sc-34-wght-normal-Bspba0wC.woff2 +0 -0
  303. package/web/assets/noto-sans-sc-35-wght-normal-K8fX_YPG.woff2 +0 -0
  304. package/web/assets/noto-sans-sc-36-wght-normal-CkITyjoT.woff2 +0 -0
  305. package/web/assets/noto-sans-sc-37-wght-normal-WCZ2zfEe.woff2 +0 -0
  306. package/web/assets/noto-sans-sc-38-wght-normal-DeM7CUce.woff2 +0 -0
  307. package/web/assets/noto-sans-sc-39-wght-normal-BMZNLWQU.woff2 +0 -0
  308. package/web/assets/noto-sans-sc-4-wght-normal-AR5bdDjz.woff2 +0 -0
  309. package/web/assets/noto-sans-sc-40-wght-normal-BQ5xpS5j.woff2 +0 -0
  310. package/web/assets/noto-sans-sc-41-wght-normal-Hthm7FFi.woff2 +0 -0
  311. package/web/assets/noto-sans-sc-42-wght-normal-eaS2gKds.woff2 +0 -0
  312. package/web/assets/noto-sans-sc-43-wght-normal-o-T53XXV.woff2 +0 -0
  313. package/web/assets/noto-sans-sc-44-wght-normal-BJH5J4Ez.woff2 +0 -0
  314. package/web/assets/noto-sans-sc-45-wght-normal-BKuxSCOJ.woff2 +0 -0
  315. package/web/assets/noto-sans-sc-46-wght-normal-BR_5Mu6e.woff2 +0 -0
  316. package/web/assets/noto-sans-sc-47-wght-normal-CyY7gKNX.woff2 +0 -0
  317. package/web/assets/noto-sans-sc-48-wght-normal-BNMfTR6k.woff2 +0 -0
  318. package/web/assets/noto-sans-sc-49-wght-normal-DX-0y7wg.woff2 +0 -0
  319. package/web/assets/noto-sans-sc-5-wght-normal-CfWugllB.woff2 +0 -0
  320. package/web/assets/noto-sans-sc-50-wght-normal-DAOGW-Dr.woff2 +0 -0
  321. package/web/assets/noto-sans-sc-51-wght-normal-BlFLOjcP.woff2 +0 -0
  322. package/web/assets/noto-sans-sc-52-wght-normal-OZjrybnw.woff2 +0 -0
  323. package/web/assets/noto-sans-sc-53-wght-normal-CwyWNT6x.woff2 +0 -0
  324. package/web/assets/noto-sans-sc-54-wght-normal-DM0nRe18.woff2 +0 -0
  325. package/web/assets/noto-sans-sc-55-wght-normal-DBK2wUOR.woff2 +0 -0
  326. package/web/assets/noto-sans-sc-56-wght-normal-C-VKK1LS.woff2 +0 -0
  327. package/web/assets/noto-sans-sc-57-wght-normal-Cdbfh5kq.woff2 +0 -0
  328. package/web/assets/noto-sans-sc-58-wght-normal-CflMKARZ.woff2 +0 -0
  329. package/web/assets/noto-sans-sc-59-wght-normal-BS4Kwzxx.woff2 +0 -0
  330. package/web/assets/noto-sans-sc-6-wght-normal-ChnZIQfQ.woff2 +0 -0
  331. package/web/assets/noto-sans-sc-60-wght-normal-B7QOYsXJ.woff2 +0 -0
  332. package/web/assets/noto-sans-sc-61-wght-normal-C8JjQS7w.woff2 +0 -0
  333. package/web/assets/noto-sans-sc-62-wght-normal-bS4wq1Gg.woff2 +0 -0
  334. package/web/assets/noto-sans-sc-63-wght-normal-BJr8P-OB.woff2 +0 -0
  335. package/web/assets/noto-sans-sc-64-wght-normal-DVB11t7j.woff2 +0 -0
  336. package/web/assets/noto-sans-sc-65-wght-normal-DMPCXBvK.woff2 +0 -0
  337. package/web/assets/noto-sans-sc-66-wght-normal-BobaG10V.woff2 +0 -0
  338. package/web/assets/noto-sans-sc-67-wght-normal-Dzk7WhCR.woff2 +0 -0
  339. package/web/assets/noto-sans-sc-68-wght-normal-DGbCD9xu.woff2 +0 -0
  340. package/web/assets/noto-sans-sc-69-wght-normal-Ctrv1EqA.woff2 +0 -0
  341. package/web/assets/noto-sans-sc-70-wght-normal-RgTO941D.woff2 +0 -0
  342. package/web/assets/noto-sans-sc-71-wght-normal-D9sG-CWv.woff2 +0 -0
  343. package/web/assets/noto-sans-sc-72-wght-normal-C4oWy3ul.woff2 +0 -0
  344. package/web/assets/noto-sans-sc-73-wght-normal-B1ZoOTgM.woff2 +0 -0
  345. package/web/assets/noto-sans-sc-74-wght-normal-F7dH_rZJ.woff2 +0 -0
  346. package/web/assets/noto-sans-sc-75-wght-normal-Cp2wp4si.woff2 +0 -0
  347. package/web/assets/noto-sans-sc-76-wght-normal-B494D7xS.woff2 +0 -0
  348. package/web/assets/noto-sans-sc-77-wght-normal-B3MVY9Ax.woff2 +0 -0
  349. package/web/assets/noto-sans-sc-78-wght-normal-5jX7iNMC.woff2 +0 -0
  350. package/web/assets/noto-sans-sc-79-wght-normal-BIoZy8fr.woff2 +0 -0
  351. package/web/assets/noto-sans-sc-80-wght-normal-Bje1Fxy1.woff2 +0 -0
  352. package/web/assets/noto-sans-sc-81-wght-normal-B2NN7Qi3.woff2 +0 -0
  353. package/web/assets/noto-sans-sc-82-wght-normal-CUggNZi4.woff2 +0 -0
  354. package/web/assets/noto-sans-sc-83-wght-normal-99rD8YO-.woff2 +0 -0
  355. package/web/assets/noto-sans-sc-84-wght-normal-zMBmUocm.woff2 +0 -0
  356. package/web/assets/noto-sans-sc-85-wght-normal-CjI3qdxG.woff2 +0 -0
  357. package/web/assets/noto-sans-sc-86-wght-normal-BHY8TVEH.woff2 +0 -0
  358. package/web/assets/noto-sans-sc-87-wght-normal-DioZSHnU.woff2 +0 -0
  359. package/web/assets/noto-sans-sc-88-wght-normal-BXYxwf2t.woff2 +0 -0
  360. package/web/assets/noto-sans-sc-89-wght-normal-ClMFrFaL.woff2 +0 -0
  361. package/web/assets/noto-sans-sc-90-wght-normal-BnOlPreP.woff2 +0 -0
  362. package/web/assets/noto-sans-sc-91-wght-normal-DTr9RQcS.woff2 +0 -0
  363. package/web/assets/noto-sans-sc-97-wght-normal-9u6Aiav-.woff2 +0 -0
  364. package/web/assets/noto-sans-sc-98-wght-normal-BR-H2C0V.woff2 +0 -0
  365. package/web/assets/noto-sans-sc-99-wght-normal-BI6XPoxp.woff2 +0 -0
  366. package/web/assets/noto-sans-sc-cyrillic-wght-normal-BGmsQlw-.woff2 +0 -0
  367. package/web/assets/noto-sans-sc-latin-ext-wght-normal-DuJTpTg_.woff2 +0 -0
  368. package/web/assets/noto-sans-sc-latin-wght-normal-DDW8mKAR.woff2 +0 -0
  369. package/web/assets/noto-sans-sc-vietnamese-wght-normal-Bi3ATJ76.woff2 +0 -0
  370. package/web/fonts/LICENSE.txt +287 -0
  371. package/web/icon.svg +24 -0
  372. package/web/index.html +19 -0
  373. package/web/manifest.webmanifest +18 -0
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Bridge to the native `dsh-probe` sidecar.
3
+ *
4
+ * The sidecar is built per platform/arch into `native/bin/<platform>-<arch>/`
5
+ * by `scripts/build-rust.mjs` (its Rust source lives in `src/rust`). It is the
6
+ * only OS probe layer: there are no PowerShell / playerctl / osascript
7
+ * fallbacks. When the binary is missing this module reports it as unavailable
8
+ * and callers return a clear error state.
9
+ *
10
+ * Environment overrides:
11
+ * DSH_RC_PROBE=/absolute/path/to/dsh-probe use a custom binary
12
+ * DSH_RC_DISABLE_RUST_PROBE=1 force "probe unavailable" (tests)
13
+ * DSH_RC_TMPDIR=/absolute/path writable scratch dir for SQLite copies
14
+ */
15
+
16
+ import { execFile } from 'node:child_process';
17
+ import type { ExecFileOptionsWithStringEncoding } from 'node:child_process';
18
+ import { existsSync, mkdirSync, statSync } from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { serializeError } from './util.js';
22
+ import type { JsonRecord, ProbeMediaResult } from './types.js';
23
+ import { isRecord } from './types.js';
24
+ import { ProbeDaemonClient, ProbeDaemonStartError } from './probe-daemon.js';
25
+
26
+ const ARCH = process.arch;
27
+ const DIRECTORY = `${process.platform}-${ARCH}`;
28
+ const BINARY = process.platform === 'win32' ? 'dsh-probe.exe' : 'dsh-probe';
29
+ const DEFAULT_BINARY = fileURLToPath(new URL(`../native/bin/${DIRECTORY}/${BINARY}`, import.meta.url));
30
+ const SCRATCH_DIRECTORY = fileURLToPath(new URL('../.probe-tmp', import.meta.url));
31
+ const FAILURE_COOLDOWN_MS = 15_000;
32
+ const DAEMON_RETRY_MS = 60_000;
33
+ const MAX_BUFFER = 24 * 1024 * 1024;
34
+
35
+ export interface ProbeInfo {
36
+ available: boolean;
37
+ binary: string;
38
+ platform: NodeJS.Platform;
39
+ arch: string;
40
+ directory: string;
41
+ disabled: boolean;
42
+ error: string | null;
43
+ }
44
+
45
+ export interface ProbeStatus {
46
+ available: boolean;
47
+ binary: string;
48
+ platform: NodeJS.Platform;
49
+ arch: string;
50
+ directory: string;
51
+ error: string | null;
52
+ }
53
+
54
+ /** Platform backends reported by `dsh-probe version`. */
55
+ export interface ProbeVersionInfo {
56
+ version: string;
57
+ os: string;
58
+ arch: string;
59
+ media: string;
60
+ volume: string;
61
+ performance: string;
62
+ notifications: string;
63
+ location: string;
64
+ }
65
+
66
+ export interface RunProbeOptions {
67
+ timeout?: number;
68
+ }
69
+
70
+ interface ExecFileResult {
71
+ stdout: string;
72
+ stderr: string;
73
+ }
74
+
75
+ /** `execFile` with an explicit promise shape so callers get string stdout. */
76
+ function execFileAsync(
77
+ file: string,
78
+ args: readonly string[],
79
+ options: ExecFileOptionsWithStringEncoding,
80
+ ): Promise<ExecFileResult> {
81
+ return new Promise((resolve, reject) => {
82
+ execFile(file, args, options, (error, stdout, stderr) => {
83
+ if (error) {
84
+ reject(Object.assign(error, { stdout, stderr }));
85
+ return;
86
+ }
87
+ resolve({ stdout, stderr });
88
+ });
89
+ });
90
+ }
91
+
92
+ /**
93
+ * The DSH sandbox can deny the sidecar writes to the system temp directory when
94
+ * it runs from the plugin workspace. The host creates a workspace-local scratch
95
+ * directory and passes it to the probe for its read-only SQLite copies.
96
+ */
97
+ function scratchDirectory(): string | null {
98
+ try {
99
+ mkdirSync(SCRATCH_DIRECTORY, { recursive: true });
100
+ return SCRATCH_DIRECTORY;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ let cachedInfo: ProbeInfo | null = null;
107
+ let failureUntil = 0;
108
+ let lastFailure: string | null = null;
109
+ let daemonClient: ProbeDaemonClient | null = null;
110
+ let daemonDisabledUntil = 0;
111
+ let exitHookInstalled = false;
112
+
113
+ /** Resolve (and cache) the sidecar location. Never throws. */
114
+ export function probeInfo(): ProbeInfo {
115
+ if (cachedInfo) return cachedInfo;
116
+ const disabled = /^(1|true|yes|on)$/i.test(process.env.DSH_RC_DISABLE_RUST_PROBE || '');
117
+ const override = String(process.env.DSH_RC_PROBE || '').trim();
118
+ const binary = override ? path.resolve(override) : DEFAULT_BINARY;
119
+ let available = false;
120
+ let error: string | null = null;
121
+ if (disabled) {
122
+ error = 'native probe disabled by DSH_RC_DISABLE_RUST_PROBE';
123
+ } else {
124
+ try {
125
+ if (!existsSync(binary)) error = `native probe not built: ${binary}`;
126
+ else if (!statSync(binary).isFile()) error = `native probe path is not a file: ${binary}`;
127
+ else available = true;
128
+ } catch (failure) {
129
+ error = serializeError(failure);
130
+ }
131
+ }
132
+ cachedInfo = {
133
+ available,
134
+ binary,
135
+ platform: process.platform,
136
+ arch: ARCH,
137
+ directory: DIRECTORY,
138
+ disabled,
139
+ error,
140
+ };
141
+ return cachedInfo;
142
+ }
143
+
144
+ /** Availability plus the most recent failure, for diagnostics payloads. */
145
+ export function probeStatus(): ProbeStatus {
146
+ const info = probeInfo();
147
+ const coolingDown = Date.now() < failureUntil;
148
+ return {
149
+ available: info.available && !coolingDown,
150
+ binary: info.binary,
151
+ platform: info.platform,
152
+ arch: info.arch,
153
+ directory: info.directory,
154
+ error: info.error || (coolingDown ? lastFailure : null),
155
+ };
156
+ }
157
+
158
+ let versionCache: { until: number; value: ProbeVersionInfo | null } | null = null;
159
+
160
+ /**
161
+ * Read the probe's build identity and platform backends.
162
+ *
163
+ * This is a diagnostics read, so it deliberately bypasses the shared failure
164
+ * cooldown: a broken `version` response must not make media/performance look
165
+ * unavailable. Results are cached briefly because the settings page may be
166
+ * opened repeatedly.
167
+ */
168
+ export async function probeVersion(): Promise<ProbeVersionInfo | null> {
169
+ if (versionCache && Date.now() < versionCache.until) return versionCache.value;
170
+ const info = probeInfo();
171
+ if (!info.available) {
172
+ versionCache = { until: Date.now() + 30_000, value: null };
173
+ return null;
174
+ }
175
+ try {
176
+ const scratch = scratchDirectory();
177
+ const { stdout } = await execFileAsync(info.binary, ['version'], {
178
+ timeout: 2_500,
179
+ windowsHide: true,
180
+ maxBuffer: 1024 * 1024,
181
+ encoding: 'utf8',
182
+ env: scratch ? { ...process.env, DSH_RC_TMPDIR: scratch } : process.env,
183
+ });
184
+ const payload = extractJson(stdout);
185
+ if (!payload || payload.ok === false) throw new Error('dsh-probe version returned no payload');
186
+ const value: ProbeVersionInfo = {
187
+ version: String(payload.version || ''),
188
+ os: String(payload.os || ''),
189
+ arch: String(payload.arch || ''),
190
+ media: String(payload.media || ''),
191
+ volume: String(payload.volume || ''),
192
+ performance: String(payload.performance || ''),
193
+ notifications: String(payload.notifications || ''),
194
+ location: String(payload.location || ''),
195
+ };
196
+ versionCache = { until: Date.now() + 30_000, value };
197
+ return value;
198
+ } catch {
199
+ versionCache = { until: Date.now() + 15_000, value: null };
200
+ return null;
201
+ }
202
+ }
203
+
204
+ function extractJson(stdout: unknown): JsonRecord | null {
205
+ const lines = String(stdout || '').split(/\r?\n/);
206
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
207
+ const line = lines[index].trim();
208
+ if (!line.startsWith('{')) continue;
209
+ try {
210
+ const parsed: unknown = JSON.parse(line);
211
+ if (isRecord(parsed)) return parsed;
212
+ } catch {
213
+ /* keep looking upwards */
214
+ }
215
+ }
216
+ return null;
217
+ }
218
+
219
+ function failureMessage(error: unknown): string {
220
+ let stderrValue: unknown;
221
+ if (error instanceof Error) stderrValue = (error as Error & { stderr?: unknown }).stderr;
222
+ else if (isRecord(error)) stderrValue = error.stderr;
223
+ const stderr = stderrValue ? String(stderrValue).trim() : '';
224
+ if (stderr) return stderr.slice(0, 800);
225
+ return serializeError(error);
226
+ }
227
+
228
+ /** Commands that only read state and are safe to replay after a daemon crash. */
229
+ function isReadOnlyCommand(args: string[]): boolean {
230
+ const command = args[0];
231
+ if (command === 'version' || command === 'performance' || command === 'perf') return true;
232
+ if (command === 'notifications' || command === 'notification') return args[1] !== 'watch';
233
+ if (command === 'location') return args[1] !== 'request';
234
+ if (command === 'media') {
235
+ const action = args[1] || 'read';
236
+ return action === 'read';
237
+ }
238
+ if (command === 'volume') {
239
+ const action = args[1] || 'read';
240
+ return action === 'read';
241
+ }
242
+ return false;
243
+ }
244
+
245
+ function daemonAllowed(): boolean {
246
+ if (Date.now() < daemonDisabledUntil) return false;
247
+ return !/^(0|false|no|off)$/i.test(process.env.DSH_RC_DISABLE_PROBE_DAEMON || '');
248
+ }
249
+
250
+ function installExitHook(): void {
251
+ if (exitHookInstalled) return;
252
+ exitHookInstalled = true;
253
+ process.once('exit', () => {
254
+ daemonClient?.kill();
255
+ });
256
+ }
257
+
258
+ function ensureDaemon(info: ProbeInfo): ProbeDaemonClient {
259
+ if (!daemonClient) {
260
+ const scratch = scratchDirectory();
261
+ const env = scratch ? { ...process.env, DSH_RC_TMPDIR: scratch } : process.env;
262
+ daemonClient = new ProbeDaemonClient(info.binary, env);
263
+ installExitHook();
264
+ }
265
+ return daemonClient;
266
+ }
267
+
268
+ function dropDaemon(): void {
269
+ const client = daemonClient;
270
+ daemonClient = null;
271
+ client?.shutdown();
272
+ }
273
+
274
+ /** Stop the daemon on plugin unload; the next probe call starts a fresh one. */
275
+ export function shutdownProbeDaemon(): void {
276
+ dropDaemon();
277
+ }
278
+
279
+ /**
280
+ * Test seam: forget the cached sidecar path and daemon state so one process can
281
+ * exercise the missing, one-shot and daemon paths against different binaries.
282
+ */
283
+ export function resetProbeCache(): void {
284
+ dropDaemon();
285
+ cachedInfo = null;
286
+ failureUntil = 0;
287
+ lastFailure = null;
288
+ daemonDisabledUntil = 0;
289
+ }
290
+
291
+ /** One-shot fallback used when the daemon is disabled or unsupported. */
292
+ async function runProbeOnce(args: string[], timeout: number, info: ProbeInfo): Promise<JsonRecord> {
293
+ const scratch = scratchDirectory();
294
+ try {
295
+ const { stdout, stderr } = await execFileAsync(info.binary, args, {
296
+ timeout,
297
+ windowsHide: true,
298
+ maxBuffer: MAX_BUFFER,
299
+ encoding: 'utf8',
300
+ env: scratch ? { ...process.env, DSH_RC_TMPDIR: scratch } : process.env,
301
+ });
302
+ const parsed = extractJson(stdout);
303
+ if (!parsed) {
304
+ throw new Error(`dsh-probe ${args.join(' ')} returned no JSON${stderr ? `: ${String(stderr).trim().slice(0, 500)}` : ''}`);
305
+ }
306
+ lastFailure = null;
307
+ failureUntil = 0;
308
+ return parsed;
309
+ } catch (error) {
310
+ // A usage error still prints JSON and exits 2; surface that payload.
311
+ const parsed = extractJson((error as { stdout?: unknown } | null)?.stdout);
312
+ if (parsed) return parsed;
313
+ lastFailure = failureMessage(error);
314
+ failureUntil = Date.now() + FAILURE_COOLDOWN_MS;
315
+ const wrapped = new Error(`dsh-probe ${args.join(' ')} failed: ${lastFailure}`);
316
+ wrapped.cause = error;
317
+ throw wrapped;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Run a sidecar subcommand and return its JSON payload.
323
+ *
324
+ * Uses the persistent daemon when available; falls back to a one-shot process
325
+ * when the daemon cannot start (older binary, spawn failure) or the environment
326
+ * disables it. A request that already reached a running daemon is not replayed
327
+ * unless it is read-only, so media actions cannot be executed twice.
328
+ *
329
+ * Throws only when the process cannot run or produces no JSON; a well-formed
330
+ * `{ ok: false }` payload is returned so callers can show its error.
331
+ */
332
+ export async function runProbe(args: string[], options: RunProbeOptions = {}): Promise<JsonRecord> {
333
+ const info = probeInfo();
334
+ if (!info.available) throw new Error(info.error || 'native probe unavailable');
335
+ if (Date.now() < failureUntil) {
336
+ throw new Error(`native probe temporarily unavailable: ${lastFailure || 'previous failure'}`);
337
+ }
338
+ const timeout = Number.isFinite(Number(options.timeout)) ? Number(options.timeout) : 15_000;
339
+
340
+ if (daemonAllowed()) {
341
+ try {
342
+ const result = await ensureDaemon(info).request(args, timeout);
343
+ lastFailure = null;
344
+ failureUntil = 0;
345
+ return result;
346
+ } catch (error) {
347
+ const startFailure = error instanceof ProbeDaemonStartError;
348
+ if (startFailure) {
349
+ // Nothing was executed yet, so every command may retry one-shot.
350
+ daemonDisabledUntil = Date.now() + DAEMON_RETRY_MS;
351
+ dropDaemon();
352
+ return runProbeOnce(args, timeout, info);
353
+ }
354
+ daemonDisabledUntil = Date.now() + DAEMON_RETRY_MS;
355
+ dropDaemon();
356
+ if (isReadOnlyCommand(args)) return runProbeOnce(args, timeout, info);
357
+ lastFailure = failureMessage(error);
358
+ const wrapped = new Error(`dsh-probe ${args.join(' ')} failed: ${lastFailure}`);
359
+ wrapped.cause = error;
360
+ throw wrapped;
361
+ }
362
+ }
363
+
364
+ return runProbeOnce(args, timeout, info);
365
+ }
366
+
367
+ /**
368
+ * MPRIS can return an HTTP(S) artwork URL. The sidecar has no HTTP client, so
369
+ * download the image here and convert it to a data URL.
370
+ */
371
+ export async function materializeThumbnail(result: ProbeMediaResult): Promise<ProbeMediaResult> {
372
+ const media = result?.media;
373
+ if (!media || media.thumbnail || !media.thumbnailUrl) return result;
374
+ // CDP covers are plain image URLs; let the control-end browser fetch and
375
+ // cache them directly instead of downloading the image on every poll.
376
+ if (String(media.providerDetail || '').toLowerCase().includes('cdp')) return result;
377
+ if (!/^https?:\/\//i.test(media.thumbnailUrl)) return result;
378
+ const controller = new AbortController();
379
+ const timer = setTimeout(() => controller.abort(), 3500);
380
+ try {
381
+ const response = await fetch(media.thumbnailUrl, { signal: controller.signal });
382
+ if (!response.ok) return result;
383
+ const mime = String(response.headers.get('content-type') || 'image/jpeg').split(';')[0].trim();
384
+ if (!mime.startsWith('image/')) return result;
385
+ const bytes = Buffer.from(await response.arrayBuffer());
386
+ if (bytes.length === 0 || bytes.length > 4 * 1024 * 1024) return result;
387
+ media.thumbnail = `data:${mime};base64,${bytes.toString('base64')}`;
388
+ media.thumbnailType = mime;
389
+ } catch {
390
+ /* fall back to whatever image the adapter already had */
391
+ } finally {
392
+ clearTimeout(timer);
393
+ }
394
+ return result;
395
+ }
@@ -0,0 +1,42 @@
1
+ /** Tiny SVG renderer around the qrcode-generator dependency. */
2
+
3
+ import qrcode from 'qrcode-generator';
4
+ import { escapeHtml } from './util.js';
5
+
6
+ export interface QrSvgOptions {
7
+ cellSize?: number;
8
+ margin?: number;
9
+ label?: string;
10
+ }
11
+
12
+ const cache = new Map<string, string>();
13
+
14
+ export function qrSvg(text: string, options: QrSvgOptions = {}): string {
15
+ const key = `${text}|${options.cellSize ?? 8}|${options.margin ?? 4}`;
16
+ const hit = cache.get(key);
17
+ if (hit) return hit;
18
+
19
+ const qr = qrcode(0, 'M');
20
+ qr.addData(String(text));
21
+ qr.make();
22
+ const count = qr.getModuleCount();
23
+ const cell = Math.max(2, Math.min(16, Number(options.cellSize) || 8));
24
+ const margin = Math.max(0, Math.min(12, Number(options.margin) || 4));
25
+ const size = (count + margin * 2) * cell;
26
+ let path = '';
27
+ for (let row = 0; row < count; row += 1) {
28
+ for (let col = 0; col < count; col += 1) {
29
+ if (!qr.isDark(row, col)) continue;
30
+ const x = (col + margin) * cell;
31
+ const y = (row + margin) * cell;
32
+ path += `M${x} ${y}h${cell}v${cell}h-${cell}z`;
33
+ }
34
+ }
35
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" role="img" aria-label="${escapeHtml(options.label || 'Pairing QR code')}"><rect width="${size}" height="${size}" rx="${Math.round(cell * 1.5)}" fill="#ffffff"/><path d="${path}" fill="#0a0d14" shape-rendering="crispEdges"/></svg>`;
36
+ cache.set(key, svg);
37
+ if (cache.size > 20) {
38
+ const oldest = cache.keys().next().value;
39
+ if (oldest !== undefined) cache.delete(oldest);
40
+ }
41
+ return svg;
42
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Runtime capability payloads for the sidebar's system-information page.
3
+ *
4
+ * `dsh-probe` and Node.js are reported separately from the media/notification
5
+ * adapters: the adapters describe features, while these describe the two
6
+ * runtimes those features depend on.
7
+ */
8
+
9
+ import path from 'node:path';
10
+ import { probeInfo, probeStatus, probeVersion } from './probe.js';
11
+
12
+ export interface ProbeBackends {
13
+ media: string;
14
+ volume: string;
15
+ performance: string;
16
+ notifications: string;
17
+ location: string;
18
+ }
19
+
20
+ export interface ProbeCapability {
21
+ ok: boolean;
22
+ available: boolean;
23
+ binary: string;
24
+ platform: string;
25
+ arch: string;
26
+ directory: string;
27
+ disabled: boolean;
28
+ error: string | null;
29
+ version: string | null;
30
+ backends: ProbeBackends | null;
31
+ }
32
+
33
+ export interface NodeCapability {
34
+ ok: boolean;
35
+ version: string;
36
+ v8: string;
37
+ platform: string;
38
+ arch: string;
39
+ pid: number;
40
+ uptimeSeconds: number;
41
+ memory: {
42
+ rss: number;
43
+ heapUsed: number;
44
+ heapTotal: number;
45
+ };
46
+ }
47
+
48
+ /** Probe availability plus its build identity and platform backends. */
49
+ export async function readProbeCapability(): Promise<ProbeCapability> {
50
+ const status = probeStatus();
51
+ const info = probeInfo();
52
+ const version = status.available ? await probeVersion() : null;
53
+ return {
54
+ ok: status.available,
55
+ available: status.available,
56
+ binary: status.binary,
57
+ platform: status.platform,
58
+ arch: status.arch,
59
+ directory: status.directory,
60
+ disabled: info.disabled,
61
+ error: status.error,
62
+ version: version?.version || null,
63
+ backends: version
64
+ ? {
65
+ media: version.media,
66
+ volume: version.volume,
67
+ performance: version.performance,
68
+ notifications: version.notifications,
69
+ location: version.location,
70
+ }
71
+ : null,
72
+ };
73
+ }
74
+
75
+ /** The Host process's Node.js runtime, sampled at request time. */
76
+ export function readNodeCapability(): NodeCapability {
77
+ const memory = process.memoryUsage();
78
+ return {
79
+ ok: true,
80
+ version: process.version,
81
+ v8: process.versions.v8 || '',
82
+ platform: process.platform,
83
+ arch: process.arch,
84
+ pid: process.pid,
85
+ uptimeSeconds: Math.round(process.uptime()),
86
+ memory: {
87
+ rss: memory.rss,
88
+ heapUsed: memory.heapUsed,
89
+ heapTotal: memory.heapTotal,
90
+ },
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Current DSH profile name (`web`, `desktop`, `cli`, or a user-defined name).
96
+ *
97
+ * The Desktop host receives the profile directory as a positional argument and
98
+ * does not always export `DSH_PROFILE`, so read the environment first and then
99
+ * fall back to the `profiles/<name>` argument or the profile directory's
100
+ * basename. An empty string means this Host was started without a profile.
101
+ */
102
+ export function readProfileName(): string {
103
+ const fromEnv = String(process.env.DSH_PROFILE || '').trim();
104
+ if (fromEnv) return fromEnv;
105
+ const argv = process.argv;
106
+ for (let index = 0; index < argv.length; index += 1) {
107
+ const arg = String(argv[index] || '');
108
+ const match = /(?:^|[\\/])profiles[\\/]([^\\/]+)(?:[\\/]|$)/.exec(arg);
109
+ if (match?.[1]) return match[1];
110
+ if (arg === '--profile' || arg === '-p') {
111
+ const next = String(argv[index + 1] || '').trim();
112
+ if (next) return next;
113
+ }
114
+ if (arg.startsWith('--profile=')) {
115
+ const next = arg.slice('--profile='.length).trim();
116
+ if (next) return next;
117
+ }
118
+ }
119
+ const profileDir = String(process.env.DSH_PROFILE_DIR || '').trim();
120
+ if (profileDir) {
121
+ const base = path.basename(profileDir);
122
+ if (base && base !== '.' && base !== path.sep) return base;
123
+ }
124
+ return '';
125
+ }
126
+