@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 @@
1
+ import e from"node:os";import{probeInfo as r,runProbe as o}from"./probe.js";import{isRecord as t}from"./types.js";import{clamp as n,finiteNumber as l,serializeError as u}from"./util.js";function s(e,r=1){const o=10**r;return Math.round(e*o)/o}function a(){let r=0,o=0;for(const t of e.cpus())r+=t.times.idle,o+=t.times.user+t.times.nice+t.times.sys+t.times.idle+t.times.irq;return{idle:r,total:o}}export function createPerformanceService(){let c=a(),m=0,i=null,p=0,d=null;return{sample:async function(f){const y=await async function(){return r().available?i&&Date.now()-p<8e3?null:d||(d=(async()=>{try{const e=await o(["performance"],{timeout:12e3}),r=t(e)?e:null;if(r&&!1===r.ok){const e="string"==typeof r.error&&r.error?r.error:"dsh-probe performance failed";throw new Error(e)}return i=null,r}catch(e){return i=u(e),p=Date.now(),null}finally{d=null}})(),d):null}(),h=y&&t(y.cpu)?y.cpu:null,g=y&&t(y.memory)?y.memory:null,C=h?{usage:l(h.usage,0)??0,model:"string"==typeof h.model&&h.model?h.model:"CPU",cores:l(h.cores,null)||e.cpus().length,physicalCores:l(h.physicalCores,null),speed:l(h.speedMhz,null),baseSpeed:l(h.baseMhz,null),uptime:l(h.uptime,e.uptime())??e.uptime(),source:"rust"}:function(){const r=a(),o=r.idle-c.idle,t=r.total-c.total;c=r,t>0&&(m=n(100*(1-o/t),0,100));const u=e.cpus(),i=l(u[0]?.speed,null);return{usage:s(m),model:u[0]?.model||"CPU",cores:u.length,physicalCores:null,speed:i,baseSpeed:i,uptime:e.uptime(),source:"node"}}();let w,M;if(g){const e=l(g.total,0)??0,r=l(g.used,0)??0;w={total:e,used:r,free:l(g.free,0)??0,percent:l(g.percent,null)??(e?s(r/e*100):0),source:"rust"}}else w=function(){const r=e.totalmem(),o=e.freemem();return{total:r,used:r-o,free:o,percent:r?s((r-o)/r*100):0,source:"node"}}();const b=y&&t(y.gpu)?y.gpu:null;M=b?function(e){const r=l(e.memoryTotal,null),o=l(e.memoryUsed,null),t=Array.isArray(e.adapters)?e.adapters:[];return{name:"string"==typeof e.name&&e.name?e.name:"GPU",driver:"string"==typeof e.driver&&e.driver?e.driver:"",adapters:t,utilization:l(e.utilization,null),memoryUsed:o,memoryTotal:r,memoryPercent:l(e.memoryPercent,null),coreClockMhz:l(e.coreClockMhz,null),coreClockMaxMhz:l(e.coreClockMaxMhz,null),memoryClockMhz:l(e.memoryClockMhz,null),powerPercent:l(e.powerPercent,null),fanRpm:l(e.fanRpm,null),ok:!1!==e.ok,error:"string"==typeof e.error&&e.error?e.error:null,source:"string"==typeof e.source&&e.source?e.source:"rust"}}(b):{name:"GPU",ok:!1,error:y?"当前平台没有可用的 GPU 读数":r().error||i||"native probe unavailable",source:"none"};const k=[],z=new Set,P=y&&Array.isArray(y.temperatures)?y.temperatures:[];for(const e of P){const r=t(e)?e:null,o=l(r?.celsius,null);if(null===o||o<5||o>125)continue;const n=r&&r.name?String(r.name):"设备",u=n.toLowerCase();z.has(u)||(z.add(u),k.push({name:n,celsius:s(o)}))}return{cpu:C,memory:w,gpu:M,temperatures:k,probe:{used:Boolean(y),error:y?null:r().error||i||null},sampledAt:Date.now()}},status:()=>({cpu:m,probeError:i})}}
@@ -0,0 +1 @@
1
+ import{createNotificationService as i}from"../notifications.js";import{createProbeMediaAdapter as o}from"../probe-adapter.js";const t={win32:"Windows",darwin:"macOS",linux:"Linux"};export function createPlatformAdapters(r={}){const a=process.platform,n=t[a]||a,s=i({limit:r.limit}),e=o({label:n});return{platform:a,label:n,notifications:s,media:e,dispose(){s.dispose?.()}}}
@@ -0,0 +1 @@
1
+ import{probeInfo as e,materializeThumbnail as a,runProbe as t}from"./probe.js";import{createMediaEnrichment as r,mediaKey as o}from"./media-enrich.js";import{clamp as n,finiteNumber as l,serializeError as u}from"./util.js";const i=["playpause","next","previous","seek","like","volume","mute"];export function createProbeMediaAdapter({label:d="平台"}={}){let c=null,s=0,m=null,k=0;const p=r();function b(){return c?.media?.appId||""}async function v(...r){if(!e().available)throw function(){const a=e();return new Error(a.error||"native probe unavailable")}();const o=await a(await t(r));if(!1===o?.ok){const e=new Error(String(o.actionError||o.error||`dsh-probe ${r.join(" ")} failed`));throw e.probeResult=o,e}return o}async function w(e=!1){const a=Date.now();if(!e&&c&&a-s<(c?.ok?1400:5e3))return{...c,cached:!0};try{const e=["media","read"],t=b();t&&e.push("--app-id",t);const r=await v(...e);c={ok:!0,available:!1!==r.available,media:await p.enrich(r),error:null,backend:"rust",updatedAt:a}}catch(e){c={ok:!1,available:!1,media:null,error:u(e),backend:"rust",updatedAt:a}}return s=Date.now(),c}async function f(e=!1){const a=Date.now();if(!e&&m&&a-k<(m?.ok?3e3:5e3))return{...m,cached:!0};try{const e=await v("volume","read");m={ok:!0,available:!0,volume:n(l(e.volume,.5)??.5,0,1),muted:Boolean(e.muted),error:null,backend:"rust",updatedAt:a}}catch(e){m={ok:!1,available:!1,volume:.5,muted:!1,error:u(e),backend:"rust",updatedAt:a}}return k=Date.now(),m}return{getState:async function(){const[e,a]=await Promise.all([w(),f()]);return{ok:e.ok||a.ok,media:e,volume:a,label:d,sampledAt:Date.now()}},getMedia:w,getVolume:f,control:async function(e,a=null){const t=String(e||"").toLowerCase();if(!i.includes(t))throw new Error("Unsupported media action");if("volume"===t||"mute"===t)return async function(e,a){const t=n(l(a,.5)??.5,0,1),r="mute"===e?await v("volume","toggle"):await v("volume","set","--value",String(t));return m={ok:!0,available:!0,volume:n(l(r.volume,t)??t,0,1),muted:Boolean(r.muted),error:null,backend:"rust",updatedAt:Date.now()},k=Date.now(),{ok:!0,action:e,volume:m}}(t,Number(a));if("like"===t&&!c?.media?.canSetLiked)throw new Error("当前播放器不支持修改红心");let r=0;if("seek"===t){if(!c?.media?.canSeek)throw new Error("当前播放器只上报进度,不支持拖动");r=n(l(a,0)??0,0,86400)}"seek"===t&&p.markSeek(o(c?.media),r);const u=["media",t];"seek"===t&&u.push("--position",String(r)),"like"===t&&"boolean"==typeof a&&u.push("--value",a?"true":"false");const d=b();d&&u.push("--app-id",d);const w=await v(...u);return c={ok:!0,available:!1!==w.available,media:await p.enrich(w),error:null,backend:"rust",updatedAt:Date.now()},s=Date.now(),{ok:!0,action:t,media:c}},status:function(){return{media:c?{ok:c.ok,available:c.available,error:c.error,backend:c.backend,updatedAt:c.updatedAt}:{ok:null,available:null,error:null,backend:null,updatedAt:null},volume:m?{ok:m.ok,available:m.available,error:m.error,backend:m.backend,updatedAt:m.updatedAt}:{ok:null,available:null,error:null,backend:null,updatedAt:null}}}}}
@@ -0,0 +1 @@
1
+ import{spawn as e}from"node:child_process";import{createInterface as t}from"node:readline";import{isRecord as r}from"./types.js";export const DAEMON_PROTOCOL_VERSION=1;export class ProbeDaemonStartError extends Error{constructor(e){super(e),this.name="ProbeDaemonStartError"}}export class ProbeDaemonClient{binary;env;child=null;reader=null;readyGate=null;ready=!1;starting=null;pending=new Map;nextId=1;stderr="";idleTimer=null;stopped=!1;constructor(e,t=process.env){this.binary=e,this.env=t}isRunning(){return this.ready&&null!==this.child}async request(e,t){this.ready&&this.child||await this.ensureStarted();const r=this.child;if(!r||!this.ready)throw new ProbeDaemonStartError("probe daemon is not running");if(r.stdin.destroyed)throw new ProbeDaemonStartError("probe daemon stdin is closed");const i=this.nextId++,s=`${JSON.stringify({id:i,argv:e})}\n`,n=Math.max(1e3,Math.min(Number(t)||15e3,6e4));return new Promise((t,o)=>{const l=setTimeout(()=>{this.pending.delete(i),this.fail(new Error(`dsh-probe daemon ${e.join(" ")} timed out after ${n} ms`))},n);l.unref?.(),this.pending.set(i,{resolve:t,reject:o,timer:l}),this.armIdle(),r.stdin.write(s,e=>{if(!e)return;const t=this.pending.get(i);t&&(this.pending.delete(i),clearTimeout(t.timer),t.reject(e))})})}shutdown(){this.stopped=!0,this.clearIdle();const e=new Error("probe daemon shut down");this.rejectAll(e);const t=this.readyGate;this.readyGate=null,t&&(clearTimeout(t.timer),t.reject(new ProbeDaemonStartError(e.message)));const r=this.child;if(this.child=null,this.ready=!1,this.reader?.close(),this.reader=null,r){try{r.stdin.end()}catch{}try{r.kill()}catch{}}}kill(){const e=this.child;if(this.child=null,this.ready=!1,e)try{e.kill()}catch{}}ensureStarted(){return this.ready&&this.child?Promise.resolve():(this.starting||(this.starting=this.start().finally(()=>{this.starting=null})),this.starting)}start(){return new Promise((r,i)=>{let s;try{s=e(this.binary,["daemon"],{stdio:["pipe","pipe","pipe"],windowsHide:!0,env:this.env})}catch(e){return void i(new ProbeDaemonStartError(`failed to start dsh-probe daemon: ${String(e)}`))}this.child=s,this.ready=!1,this.stderr="",this.stopped=!1;const n=setTimeout(()=>{this.readyGate=null,this.stopChild(s),i(new ProbeDaemonStartError("dsh-probe daemon did not become ready within 10000 ms"))},1e4);n.unref?.(),this.readyGate={resolve:r,reject:i,timer:n},s.stderr.setEncoding("utf8"),s.stderr.on("data",e=>{this.stderr=(this.stderr+e).slice(-4e3)}),s.once("error",e=>{this.child===s&&this.failStart(s,new ProbeDaemonStartError(`failed to start dsh-probe daemon: ${e.message}`))}),s.once("exit",(e,t)=>{this.handleExit(s,e,t)});const o=t({input:s.stdout,crlfDelay:1/0});this.reader=o,o.on("line",e=>{this.handleLine(e)}),o.on("close",()=>{})})}handleLine(e){const t=e.trim();if(!t)return;let i;try{i=JSON.parse(t)}catch{return}if(!r(i))return;if("ready"===i.event){if(this.ready)return;if(1!==Number(i.protocol))return void this.failStart(this.child,new ProbeDaemonStartError(`dsh-probe daemon protocol ${String(i.protocol)} is not supported (expected 1)`));this.ready=!0;const e=this.readyGate;return this.readyGate=null,e&&(clearTimeout(e.timer),e.resolve()),void this.armIdle()}const s="number"==typeof i.id?i.id:null;if(null===s)return;const n=this.pending.get(s);n&&(this.pending.delete(s),clearTimeout(n.timer),this.armIdle(),!0===i.ok?r(i.result)?n.resolve(i.result):n.reject(new Error("dsh-probe daemon returned no result payload")):n.reject(new Error("string"==typeof i.error?i.error:"dsh-probe daemon request failed")))}handleExit(e,t,r){if(this.child!==e)return;this.child=null,this.ready=!1,this.reader?.close(),this.reader=null,this.clearIdle();const i=[null===t?"":`code ${t}`,r?`signal ${r}`:"",this.stderr.trim()].filter(Boolean).join(", "),s="dsh-probe daemon exited"+(i?` (${i})`:""),n=this.readyGate;this.readyGate=null,n&&(clearTimeout(n.timer),n.reject(new ProbeDaemonStartError(s))),this.rejectAll(new Error(s))}failStart(e,t){const r=this.readyGate;this.readyGate=null,r&&(clearTimeout(r.timer),r.reject(t)),this.rejectAll(t),e&&this.stopChild(e)}fail(e){this.rejectAll(e);const t=this.child;this.child=null,this.ready=!1,t&&this.stopChild(t)}stopChild(e){this.child===e&&(this.child=null,this.ready=!1),this.reader?.close(),this.reader=null;try{e.stdin.end()}catch{}try{e.kill()}catch{}}rejectAll(e){for(const t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}armIdle(){this.clearIdle(),!this.stopped&&this.ready&&(this.idleTimer=setTimeout(()=>{this.idleTimer=null,this.pending.size>0?this.armIdle():this.shutdown()},function(){const e=Number(process.env.DSH_RC_PROBE_DAEMON_IDLE_MS);return!Number.isFinite(e)||e<5e3?6e4:Math.min(e,18e5)}()),this.idleTimer.unref?.())}clearIdle(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}}
package/lib/probe.js ADDED
@@ -0,0 +1 @@
1
+ import{execFile as r}from"node:child_process";import{existsSync as t,mkdirSync as e,statSync as n}from"node:fs";import o from"node:path";import{fileURLToPath as i}from"node:url";import{serializeError as a}from"./util.js";import{isRecord as s}from"./types.js";import{ProbeDaemonClient as l,ProbeDaemonStartError as u}from"./probe-daemon.js";const c=process.arch,f=`${process.platform}-${c}`,p="win32"===process.platform?"dsh-probe.exe":"dsh-probe",m=i(new URL(`../native/bin/${f}/${p}`,import.meta.url)),b=i(new URL("../.probe-tmp",import.meta.url));function d(t,e,n){return new Promise((o,i)=>{r(t,e,n,(r,t,e)=>{r?i(Object.assign(r,{stdout:t,stderr:e})):o({stdout:t,stderr:e})})})}function h(){try{return e(b,{recursive:!0}),b}catch{return null}}let w=null,v=0,S=null,y=null,g=0,D=!1;export function probeInfo(){if(w)return w;const r=/^(1|true|yes|on)$/i.test(process.env.DSH_RC_DISABLE_RUST_PROBE||""),e=String(process.env.DSH_RC_PROBE||"").trim(),i=e?o.resolve(e):m;let s=!1,l=null;if(r)l="native probe disabled by DSH_RC_DISABLE_RUST_PROBE";else try{t(i)?n(i).isFile()?s=!0:l=`native probe path is not a file: ${i}`:l=`native probe not built: ${i}`}catch(r){l=a(r)}return w={available:s,binary:i,platform:process.platform,arch:c,directory:f,disabled:r,error:l},w}export function probeStatus(){const r=probeInfo(),t=Date.now()<v;return{available:r.available&&!t,binary:r.binary,platform:r.platform,arch:r.arch,directory:r.directory,error:r.error||(t?S:null)}}let _=null;export async function probeVersion(){if(_&&Date.now()<_.until)return _.value;const r=probeInfo();if(!r.available)return _={until:Date.now()+3e4,value:null},null;try{const t=h(),{stdout:e}=await d(r.binary,["version"],{timeout:2500,windowsHide:!0,maxBuffer:1048576,encoding:"utf8",env:t?{...process.env,DSH_RC_TMPDIR:t}:process.env}),n=R(e);if(!n||!1===n.ok)throw new Error("dsh-probe version returned no payload");const o={version:String(n.version||""),os:String(n.os||""),arch:String(n.arch||""),media:String(n.media||""),volume:String(n.volume||""),performance:String(n.performance||""),notifications:String(n.notifications||""),location:String(n.location||"")};return _={until:Date.now()+3e4,value:o},o}catch{return _={until:Date.now()+15e3,value:null},null}}function R(r){const t=String(r||"").split(/\r?\n/);for(let r=t.length-1;r>=0;r-=1){const e=t[r].trim();if(e.startsWith("{"))try{const r=JSON.parse(e);if(s(r))return r}catch{}}return null}function $(r){let t;(r instanceof Error||s(r))&&(t=r.stderr);const e=t?String(t).trim():"";return e?e.slice(0,800):a(r)}function E(){const r=y;y=null,r?.shutdown()}export function shutdownProbeDaemon(){E()}export function resetProbeCache(){E(),w=null,v=0,S=null,g=0}async function x(r,t,e){const n=h();try{const{stdout:o,stderr:i}=await d(e.binary,r,{timeout:t,windowsHide:!0,maxBuffer:25165824,encoding:"utf8",env:n?{...process.env,DSH_RC_TMPDIR:n}:process.env}),a=R(o);if(!a)throw new Error(`dsh-probe ${r.join(" ")} returned no JSON${i?`: ${String(i).trim().slice(0,500)}`:""}`);return S=null,v=0,a}catch(t){const e=R(t?.stdout);if(e)return e;S=$(t),v=Date.now()+15e3;const n=new Error(`dsh-probe ${r.join(" ")} failed: ${S}`);throw n.cause=t,n}}export async function runProbe(r,t={}){const e=probeInfo();if(!e.available)throw new Error(e.error||"native probe unavailable");if(Date.now()<v)throw new Error(`native probe temporarily unavailable: ${S||"previous failure"}`);const n=Number.isFinite(Number(t.timeout))?Number(t.timeout):15e3;if(!(Date.now()<g||/^(0|false|no|off)$/i.test(process.env.DSH_RC_DISABLE_PROBE_DAEMON||"")))try{const t=await function(r){if(!y){const t=h(),e=t?{...process.env,DSH_RC_TMPDIR:t}:process.env;y=new l(r.binary,e),D||(D=!0,process.once("exit",()=>{y?.kill()}))}return y}(e).request(r,n);return S=null,v=0,t}catch(t){if(t instanceof u)return g=Date.now()+6e4,E(),x(r,n,e);if(g=Date.now()+6e4,E(),function(r){const t=r[0];return"version"===t||"performance"===t||"perf"===t||("notifications"===t||"notification"===t?"watch"!==r[1]:"location"===t?"request"!==r[1]:("media"===t||"volume"===t)&&"read"===(r[1]||"read"))}(r))return x(r,n,e);S=$(t);const o=new Error(`dsh-probe ${r.join(" ")} failed: ${S}`);throw o.cause=t,o}return x(r,n,e)}export async function materializeThumbnail(r){const t=r?.media;if(!t||t.thumbnail||!t.thumbnailUrl)return r;if(String(t.providerDetail||"").toLowerCase().includes("cdp"))return r;if(!/^https?:\/\//i.test(t.thumbnailUrl))return r;const e=new AbortController,n=setTimeout(()=>e.abort(),3500);try{const n=await fetch(t.thumbnailUrl,{signal:e.signal});if(!n.ok)return r;const o=String(n.headers.get("content-type")||"image/jpeg").split(";")[0].trim();if(!o.startsWith("image/"))return r;const i=Buffer.from(await n.arrayBuffer());if(0===i.length||i.length>4194304)return r;t.thumbnail=`data:${o};base64,${i.toString("base64")}`,t.thumbnailType=o}catch{}finally{clearTimeout(n)}return r}
package/lib/qr.js ADDED
@@ -0,0 +1 @@
1
+ import e from"qrcode-generator";import{escapeHtml as t}from"./util.js";const r=new Map;export function qrSvg(i,a={}){const n=`${i}|${a.cellSize??8}|${a.margin??4}`,o=r.get(n);if(o)return o;const l=e(0,"M");l.addData(String(i)),l.make();const s=l.getModuleCount(),g=Math.max(2,Math.min(16,Number(a.cellSize)||8)),$=Math.max(0,Math.min(12,Number(a.margin)||4)),h=(s+2*$)*g;let f="";for(let e=0;e<s;e+=1)for(let t=0;t<s;t+=1)l.isDark(e,t)&&(f+=`M${(t+$)*g} ${(e+$)*g}h${g}v${g}h-${g}z`);const m=`<svg xmlns="http://www.w3.org/2000/svg" width="${h}" height="${h}" viewBox="0 0 ${h} ${h}" role="img" aria-label="${t(a.label||"Pairing QR code")}"><rect width="${h}" height="${h}" rx="${Math.round(1.5*g)}" fill="#ffffff"/><path d="${f}" fill="#0a0d14" shape-rendering="crispEdges"/></svg>`;if(r.set(n,m),r.size>20){const e=r.keys().next().value;void 0!==e&&r.delete(e)}return m}
package/lib/runtime.js ADDED
@@ -0,0 +1 @@
1
+ import r from"node:path";import{probeInfo as e,probeStatus as o,probeVersion as i}from"./probe.js";export async function readProbeCapability(){const r=o(),t=e(),a=r.available?await i():null;return{ok:r.available,available:r.available,binary:r.binary,platform:r.platform,arch:r.arch,directory:r.directory,disabled:t.disabled,error:r.error,version:a?.version||null,backends:a?{media:a.media,volume:a.volume,performance:a.performance,notifications:a.notifications,location:a.location}:null}}export function readNodeCapability(){const r=process.memoryUsage();return{ok:!0,version:process.version,v8:process.versions.v8||"",platform:process.platform,arch:process.arch,pid:process.pid,uptimeSeconds:Math.round(process.uptime()),memory:{rss:r.rss,heapUsed:r.heapUsed,heapTotal:r.heapTotal}}}export function readProfileName(){const e=String(process.env.DSH_PROFILE||"").trim();if(e)return e;const o=process.argv;for(let r=0;r<o.length;r+=1){const e=String(o[r]||""),i=/(?:^|[\\/])profiles[\\/]([^\\/]+)(?:[\\/]|$)/.exec(e);if(i?.[1])return i[1];if("--profile"===e||"-p"===e){const e=String(o[r+1]||"").trim();if(e)return e}if(e.startsWith("--profile=")){const r=e.slice(10).trim();if(r)return r}}const i=String(process.env.DSH_PROFILE_DIR||"").trim();if(i){const e=r.basename(i);if(e&&"."!==e&&e!==r.sep)return e}return""}
package/lib/service.js ADDED
@@ -0,0 +1 @@
1
+ import e from"node:fs/promises";import{createServer as t}from"node:http";import{createServer as n}from"node:https";import{networkInterfaces as o}from"node:os";import r from"node:path";import{fileURLToPath as i}from"node:url";import{createAccountService as a}from"./account.js";import{createAppMapping as s}from"./app-mapping.js";import{hostMessage as l}from"./i18n.js";import{createPerformanceService as c}from"./performance.js";import{createPlatformAdapters as u}from"./platform/index.js";import{probeInfo as d,runProbe as f}from"./probe.js";import{qrSvg as p}from"./qr.js";import{readNodeCapability as m,readProbeCapability as h,readProfileName as g}from"./runtime.js";import{collectSessions as y,createPendingTracker as w,parsePendingAnswer as v}from"./sessions.js";import{createSettingsStore as b,normalizeDshLocale as k,resolveLocale as E}from"./settings.js";import{INFO_PATH as S,QR_PATH as x,SETTINGS_PATH as N,VERSION as I,finiteNumber as $,safeString as A,serializeError as O}from"./util.js";import{createWeatherService as j}from"./weather.js";import{createWebSocketHub as P}from"./ws.js";import{isRecord as z}from"./types.js";const T=i(new URL("../web/",import.meta.url)),q="dsh_rc",L={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".svg":"image/svg+xml; charset=utf-8",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf",".otf":"font/otf",".png":"image/png",".txt":"text/plain; charset=utf-8",".mp3":"audio/mpeg",".m4a":"audio/mp4",".aac":"audio/aac",".wav":"audio/wav",".ogg":"audio/ogg",".oga":"audio/ogg",".flac":"audio/flac",".webm":"audio/webm",".webmanifest":"application/manifest+json; charset=utf-8"},R=/^[A-Za-z0-9._-]+$/;function C(e){let t;try{t=decodeURIComponent(e)}catch{return null}return!t||t.includes("\0")||t.includes("\\")?null:t.split("/").every(e=>e&&"."!==e&&".."!==e&&R.test(e))?t:null}function D(e,t){if(!e)return null;for(const n of String(e).split(";")){const e=n.indexOf("=");if(!(e<0)&&n.slice(0,e).trim()===t)return n.slice(e+1).trim()}return null}function J(e){const t=String(e).split(".");if(4!==t.length)return null;let n=0;for(const e of t){const t=Number(e);if(!Number.isInteger(t)||t<0||t>255)return null;n=n<<8|t}return n>>>0}function H(e,t){const n=J(e),o=J(t);if(null===n||null===o)return null;const r=function(e){const t=J(e);if(null===t)return null;let n=0;for(let e=31;e>=0&&!(1&~(t>>>e));e-=1)n+=1;return n}(t);return null===r?null:{network:n&o,mask:o,prefix:r,cidr:`${i=n&o,[24,16,8,0].map(e=>i>>>e&255).join(".")}/${r}`};var i}function U(e,t,n,o={}){const r=JSON.stringify(n);e.writeHead(t,{"content-type":"application/json; charset=utf-8","cache-control":"no-store","content-length":Buffer.byteLength(r),...o}),e.end(r)}function B(e,t,n,o={}){e.writeHead(t,{"content-type":"text/html; charset=utf-8","cache-control":"no-store",...o}),e.end(n)}function W(e,t=65536){return new Promise((n,o)=>{let r=0;const i=[];e.on("data",n=>{if(r+=n.length,r>t)return o(new Error("Request body is too large")),void e.destroy();i.push(n)}),e.on("end",()=>{const e=Buffer.concat(i).toString("utf8");if(!e)return n({});try{n(JSON.parse(e))}catch{o(new Error("Invalid JSON"))}}),e.on("error",o)})}export function createRemoteControlService(i,R={}){const F=R.log||(()=>{}),M=R.locale||"zh",Q=b(),G=s(Q.dir);let _=null;const K=w(i,{onChange:()=>{He(!0)},getLocale:xe}),Z=j(),V=c(),X=u({limit:8}),Y=a(i,{locale:M,getSettings:Q.get}),ee=function(e){const t=Number.parseInt(String(e),10);return Number.isInteger(t)&&t>=0&&t<=65535?t:38765}(process.env.DSH_REMOTE_CONTROL_PORT||38765),te=Date.now();let ne=null,oe=null,re=!1,ie="http",ae=!1,se=null,le=null,ce=[],ue="",de=!1,fe=null,pe=null;const me=new Map;let he=!1,ge=!1,ye=!1,we=!1,ve=!1,be=null,ke=0;function Ee(){return Q.get()}function Se(){const e=Ee();return"dsh"===e.theme?e.dshTheme:e.theme}function xe(){const e=Ee();return E(e.language,e.dshLocale)}function Ne(e=!1){const t=Ee().allowedInterfaces,n=[];for(const[r,i]of Object.entries(o()))for(const o of i??[]){if("IPv4"!==o.family||o.internal)continue;const i=H(o.address,o.netmask),a=null===t||t.includes(r);let s=5;o.address.startsWith("192.168.")?s=0:o.address.startsWith("10.")?s=1:/^172\.(1[6-9]|2\d|3[01])\./.test(o.address)&&(s=2),/wi-?fi|wlan|wireless|ethernet|en0|eth0/i.test(r)&&(s-=.5),/vethernet|virtual|vmware|vbox|tailscale|zerotier|wsl|docker|hyper-v/i.test(r)&&(s+=4),n.push({name:r,address:o.address,netmask:o.netmask,cidr:i?.cidr||null,selected:a,score:s,...e&&oe?{url:`${ie}://${o.address}:${oe}/`}:{}})}return n.sort((e,t)=>e.selected!==t.selected?e.selected?-1:1:e.score-t.score||e.name.localeCompare(t.name)||e.address.localeCompare(t.address)),n.map(({score:e,...t})=>t)}function Ie(e){const t=function(e){const t=String(e||"");return t.startsWith("::ffff:")?t.slice(7):t}(e);if(!t)return!1;if(function(e){return"::1"===e||"127.0.0.1"===e||e.startsWith("127.")}(t))return!0;const n=J(t);return null!==n&&Ne().filter(e=>e.selected).map(e=>H(e.address,e.netmask)).filter(e=>null!==e).some(e=>(n&e.mask)===(e.network&e.mask))}function $e(){if(!oe)return ce=[],void(ue="");ce=Ne(!0).filter(e=>e.selected).map(e=>({name:e.name,address:e.address,url:e.url})),ue=ce[0]?.url||`${ie}://127.0.0.1:${oe}/`}function Ae(){return ue?`${ue}${function(){const e=Ee();return e.tokenEnabled?`?token=${encodeURIComponent(e.token)}`:""}()}`:""}function Oe(){const e=Ee(),t=X.media?.status?.()||{},n=X.notifications?.status?.()||{};return{version:I,platform:X.platform,platformLabel:X.label,running:re,port:oe,protocol:ie,startedAt:te,error:le,tls:{configured:e.tls.enabled,active:ae,error:se},tokenRequired:e.tokenEnabled,primaryUrl:ue,urls:ce,usesLoopbackOnly:re&&0===ce.length,qrPath:`${x}?v=${te}${e.tokenEnabled?`&token=${encodeURIComponent(e.token)}`:""}`,routes:{info:S,settings:N,remote:Ae()},clients:_?.info?.()??[],capabilities:{notifications:n,media:t.media??{},volume:t.volume??{}}}}function je(){return{theme:Se(),locale:xe(),temperatureUnit:Ee().temperatureUnit,notificationLimit:Ee().notifications.limit,pushQuestions:Ee().notifications.pushQuestions,navPosition:Ee().navPosition,balance:{...Ee().balance},pomodoro:{...Ee().pomodoro},countdown:{enabled:Ee().countdown.enabled,presets:[...Ee().countdown.presets]},pageOrder:[...Ee().pageOrder],pageEnabled:{...Ee().pageEnabled}}}function Pe(e){const t=e.headers.origin;if(!t)return!0;const n=e.headers.host;return t===`http://${n}`||t===`https://${n}`}async function ze(){let e;try{e=await X.notifications.getNotifications()}catch(t){e={available:!1,reason:O(t),notifications:[],apps:[]}}return Array.isArray(e.apps)&&G.record(e.apps.map(e=>({key:e.key,count:e.count})),"notification"),function(e){const t=Ee(),n=new Set(t.notifications.enabledApps),o=G.list(),r=(Array.isArray(e?.notifications)?e.notifications:[]).filter(e=>0===n.size||n.has(e.appKey??"")).slice(0,t.notifications.limit).map(e=>{const t=G.displayName(e.appKey??""),n=e.title&&e.title!==e.appKey?e.title:t;return{...e,appName:t,title:n}});return{available:!1!==e?.available,reason:e?.reason||null,liveOnly:Boolean(e?.liveOnly),notifications:r,apps:o,enabledApps:[...t.notifications.enabledApps],updatedAt:e?.updatedAt||Date.now()}}(e)}async function Te(){const e=Ee();let t={};"manual"===e.weather.mode&&null!==e.weather.latitude&&null!==e.weather.longitude&&(t={latitude:e.weather.latitude,longitude:e.weather.longitude,name:e.weather.name});const n=await Z.getWeather(t);return n.forecastMode="daily"===e.weather.forecastMode?"daily":"hourly",fe=n,n}async function qe(){try{return function(e){const t=Ee().balance.lowThreshold,n=null===t||""===t?null:Number(t);return e?.signedIn&&z(e.balance)&&Number.isFinite(Number(e.balance.balance))&&null!==n&&Number.isFinite(n)&&n>=0?{...e,lowBalance:Number(e.balance.balance)<n,lowThreshold:n}:{...e,lowBalance:!1,lowThreshold:null}}(await Y.getAccount())}catch(e){return{available:!1,signedIn:!1,error:O(e),lowBalance:!1}}}async function Le(e=!1){const t=Date.now();if(!e&&be&&t-ke<1600)return be;try{return be=await V.sample(e),ke=t,be}catch(e){return{ok:!1,error:O(e),cpu:{usage:null,model:"",cores:null},memory:{total:null,used:null,percent:null},gpu:{ok:!1,error:O(e)},temperatures:[],sampledAt:t}}}function Re(){const e=y(i,K,xe);return Ee().notifications.pushQuestions?e:e.map(e=>e.pending?{...e,pending:{...e.pending,questions:[],answerable:!1}}:e)}async function Ce(){const e=Ee().pageEnabled,[t,n,o,r]=await Promise.allSettled([e.home?Te():Promise.resolve(null),e.home?ze():Promise.resolve(null),e.sessions?qe():Promise.resolve(null),e.performance?Le():Promise.resolve(null)]);let i=[],a=null;if(e.sessions)try{i=Re()}catch(e){a=O(e)}return{sessions:i,sessionsError:a,weather:"fulfilled"===t.status&&t.value?t.value:{ok:!1,error:O("reason"in t?t.reason:void 0)},notifications:"fulfilled"===n.status&&n.value?n.value:{available:!1,reason:O("reason"in n?n.reason:void 0),notifications:[],apps:[]},account:"fulfilled"===o.status&&o.value?o.value:{available:!1,signedIn:!1,error:O("reason"in o?o.reason:void 0),lowBalance:!1},performance:"fulfilled"===r.status&&r.value?r.value:null,settings:je()}}async function De(){try{const e=await X.media.getState(),t=e.media?.media??null;return t?.appId&&(G.record([{key:t.appId}],"music"),t.appName=G.displayName(t.appId)),e}catch(e){return{ok:!1,media:{ok:!1,available:!1,media:null,error:O(e)},volume:{ok:!1,volume:.5,muted:!1,error:O(e)}}}}function Je(e,t,n=!1){if(!_||0===_.size())return;let o;try{o=JSON.stringify(t)}catch{return}(n||me.get(e)!==o)&&(me.set(e,o),_.broadcast({type:e,data:t}))}async function He(e=!1){if(!_||0===_.size()||!Ee().pageEnabled.sessions)return;let t=[];try{t=Re()}catch{return}Je("sessions",t,e)}async function Ue(e=!1){if(_&&0!==_.size()&&!he&&Ee().pageEnabled.home){he=!0;try{Je("notifications",await ze(),e)}finally{he=!1}}}async function Be(e=!1){if(_&&0!==_.size()&&!ye&&Ee().pageEnabled.sessions){ye=!0;try{Je("account",await qe(),e)}finally{ye=!1}}}async function We(e=!1){if(_&&0!==_.size()&&!we&&Ee().pageEnabled.home){we=!0;try{Je("weather",await Te(),e)}finally{we=!1}}}async function Fe(e=!1){if(_&&0!==_.size()&&!ge&&Ee().pageEnabled.music){ge=!0;try{Je("media",await De(),e)}finally{ge=!1}}}async function Me(e=!1){if(_&&0!==_.size()&&!ve&&Ee().pageEnabled.performance){ve=!0;try{Je("performance",await Le(e),e)}finally{ve=!1}}}function Qe(e=!1){Je("settings",je(),e)}function Ge(){_&&Je("clients",_.info(),!0)}function _e(){if(pe){for(const e of pe)clearInterval(e);pe=null}}async function Ke(e){try{e.send({type:"welcome",settings:je()});const t=Ee().pageEnabled,[n,o,r]=await Promise.all([Ce(),t.music?De():Promise.resolve(null),t.performance?Le():Promise.resolve(null)]);e.send({type:"overview",data:n}),me.set("overview",JSON.stringify(n)),o&&(e.send({type:"media",data:o}),me.set("media",JSON.stringify(o))),r&&(e.send({type:"performance",data:r}),me.set("performance",JSON.stringify(r))),e.send({type:"clients",data:_?.info?.()??[]})}catch(t){e.send({type:"error",error:O(t)})}}function Ze(e,t){if(!Ie(e.socket.remoteAddress))return{ok:!1,status:403};const n=e.headers.origin;if(n){const t=e.headers.host;if(n!==`http://${t}`&&n!==`https://${t}`)return{ok:!1,status:403}}const o=Ee();if(!o.tokenEnabled)return{ok:!0};const r=D(e.headers.cookie,q),i=t.searchParams.get("token");return r===o.token||i===o.token?{ok:!0}:{ok:!1,status:401}}async function Ve(e,t){await new Promise((n,o)=>{const r=t=>{e.off("listening",i),o(t)},i=()=>{e.off("error",r),n()};e.once("error",r),e.once("listening",i),e.listen(t,"0.0.0.0")})}async function Xe(){if(ne||re||de)return Oe();try{const o=await async function(){const o=Ee();let i=null;if(se=null,ae=!1,o.tls.enabled)try{if(!o.tls.certPath||!o.tls.keyPath)throw new Error("Certificate and key paths are required");i={cert:await e.readFile(r.resolve(o.tls.certPath)),key:await e.readFile(r.resolve(o.tls.keyPath))},ae=!0}catch(e){se=O(e),i=null,ae=!1}ie=ae?"https":"http";const a=(t,n)=>{!async function(t,n){try{if(!Ie(t.socket.remoteAddress))return void B(n,403,"This device is not allowed to access the console.");const o=new URL(t.url||"/",`${ie}://${t.headers.host||"127.0.0.1"}`),i=o.pathname,a=function(e,t){if(!Ie(e.socket.remoteAddress))return!1;const n=Ee();return!n.tokenEnabled||t.searchParams.get("token")===n.token||D(e.headers.cookie,q)===n.token}(t,o);if("/"===i&&!a)return void B(n,401,function(e){const t=l(e,"pairingTitle");return`<!doctype html><html lang="${"zh"===e?"zh-CN":"en"}"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${t}</title>\n<style>html,body{height:100%;margin:0;background:#05070d;color:#f4f7ff;font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Noto Sans SC",sans-serif}main{height:100%;display:grid;place-items:center;padding:32px;box-sizing:border-box}.card{max-width:460px;padding:32px;border:1px solid #1b2436;border-radius:28px;background:#0c111c;box-shadow:0 30px 80px #0008}h1{font-size:24px;margin:0 0 12px}p{color:#93a0b8;margin:0 0 10px}.dot{display:inline-block;width:9px;height:9px;border-radius:50%;background:#4d6bff;margin-right:8px}</style>\n<main><div class="card"><h1><span class="dot"></span>${t}</h1><p>${l(e,"pairingBody")}</p></main></html>`}(xe()),{"x-content-type-options":"nosniff","referrer-policy":"no-referrer"});if(("/"===i||"/index.html"===i||"/manifest.webmanifest"===i||"/icon.svg"===i||i.startsWith("/assets/")||i.startsWith("/fonts/"))&&!a)return void B(n,401,"Pairing required");if("/api/pomodoro/sound"===i)return a?"GET"!==t.method&&"HEAD"!==t.method?void B(n,405,"Method not allowed"):void await async function(t,n){const o=Ee().pomodoro.soundPath.trim();if(!o)return void U(n,404,{ok:!1,error:"no pomodoro sound configured"});if(/^(https?:)?\/\//i.test(o)||/^(data|blob):/i.test(o))return void U(n,404,{ok:!1,error:"remote sound urls are played directly by the console"});const i=r.resolve(o);try{const o=await e.stat(i);if(!o.isFile())return void U(n,404,{ok:!1,error:"pomodoro sound is not a file"});if(o.size>67108864)return void U(n,413,{ok:!1,error:"pomodoro sound is too large"});const a=await e.readFile(i);n.writeHead(200,{"content-type":L[r.extname(i).toLowerCase()]||"application/octet-stream","content-length":a.length,"cache-control":"no-store","x-content-type-options":"nosniff","referrer-policy":"no-referrer"}),n.end("HEAD"===t.method?void 0:a)}catch(e){const t=z(e)?e.code:void 0;"ENOENT"===t||"ENOTDIR"===t?U(n,404,{ok:!1,error:"pomodoro sound file not found"}):U(n,500,{ok:!1,error:O(e)})}}(t,n):void B(n,401,"Pairing required");if(("GET"===t.method||"HEAD"===t.method)&&await async function(t,n,o,i={}){let a={"/":"index.html","/index.html":"index.html","/manifest.webmanifest":"manifest.webmanifest","/icon.svg":"icon.svg"}[o];if(!a&&o.startsWith("/assets/")){const e=C(o.slice(8));e&&(a=`assets/${e}`)}if(!a&&o.startsWith("/fonts/")){const e=C(o.slice(7));e&&(a=`fonts/${e}`)}if(!a)return!1;const s=r.resolve(T,a),l=r.resolve(T);if(!s.startsWith(`${l}${r.sep}`))return!1;try{const o=await e.readFile(s),l=/\.(woff2|woff|ttf|otf)$/i.test(a);return n.writeHead(200,{"content-type":L[r.extname(a)]||"application/octet-stream","cache-control":l?"public, max-age=31536000, immutable":"no-store","content-length":o.length,"x-content-type-options":"nosniff","referrer-policy":"no-referrer","content-security-policy":"default-src 'self'; img-src 'self' data: blob: https: http:; media-src 'self' data: blob: https: http:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src 'self' ws: wss: https:; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",...i}),n.end("HEAD"===t.method?void 0:o),!0}catch(e){const t=z(e)?e.code:void 0;return"ENOENT"===t||"ENOTDIR"===t?B(n,404,"Asset not found"):B(n,500,`Asset unavailable: ${O(e)}`),!0}}(t,n,i,function(e){const t=Ee();return t.tokenEnabled&&e.searchParams.get("token")===t.token?{"set-cookie":`${q}=${t.token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000${ae?"; Secure":""}`}:{}}(o)))return;if("/api/health"===i)return void U(n,200,{ok:!0,version:I,now:Date.now()});if("/favicon.ico"===i)return n.writeHead(204),void n.end();U(n,404,{ok:!1,error:"not found"})}catch(e){F(`request failed: ${O(e)}`),n.headersSent?n.end():U(n,500,{ok:!1,error:O(e)})}}(t,n)},s=i?n(i,a):t(a);return s.keepAliveTimeout=3e4,s.headersTimeout=35e3,s}();try{await Ve(o,ee)}catch(e){if("EADDRINUSE"!==(z(e)?e.code:void 0)||0===ee)throw e;await Ve(o,0)}ne=o;const i=ne.address();if(oe="object"==typeof i&&i?i.port:ee,re=!0,le=null,$e(),_=function(e){return P({server:e,path:"/ws",authorize:Ze,onConnection(e){!function(){if(!pe){pe=[setInterval(()=>{_&&0!==_.size()?Ee().pageEnabled.sessions&&He():_e()},1500),setInterval(()=>{_&&0!==_.size()&&Ee().pageEnabled.home&&Ue()},2500),setInterval(()=>{_&&0!==_.size()&&Ee().pageEnabled.music&&Fe()},2200),setInterval(()=>{_&&0!==_.size()&&Ee().pageEnabled.sessions&&Be()},3e4),setInterval(()=>{_&&0!==_.size()&&Ee().pageEnabled.home&&We()},3e5),setInterval(()=>{_&&0!==_.size()&&Ee().pageEnabled.performance&&Me()},2500)];for(const e of pe)e.unref?.()}}(),Ge(),Ke(e)},onMessage(e,t){!async function(e,t){if(z(t)){if("action"===t.type){const n=A(t.action,40).toLowerCase();if(!["playpause","next","previous","seek","like","volume","mute"].includes(n))return void e.send({type:"action-result",ok:!1,action:n,error:"Unsupported action"});try{const o="like"===n?!0===t.value||!1!==t.value&&null:$(t.value,null);await X.media.control(n,o),e.send({type:"action-result",ok:!0,action:n}),await Fe(!0)}catch(t){e.send({type:"action-result",ok:!1,action:n,error:O(t)})}return}if("refresh"!==t.type)if("settings"!==t.type){if("answer-question"===t.type){const n=A(t.sessionId,200),o=A(t.requestId,200);if(!Ee().notifications.pushQuestions)return void e.send({type:"answer-result",ok:!1,sessionId:n,requestId:o,error:l(xe(),"answerPushOff")});const r=v(t);if(!n||!o||!r)return void e.send({type:"answer-result",ok:!1,sessionId:n,requestId:o,error:l(xe(),"answerInvalid")});const i=await K.respond(n,o,r);return e.send({type:"answer-result",ok:i.ok,sessionId:n,requestId:o,error:i.error}),void(i.ok&&await He(!0))}"ping"===t.type&&e.send({type:"pong"})}else e.send({type:"settings",data:je()});else await Ke(e)}}(e,t)},onClose(){_&&0!==_.size()||_e(),Ge()}})}(ne),de)return _e(),_.dispose(),_=null,await Ye(),Oe();F(`listening on ${ue} (${ce.length} network${1===ce.length?"":"s"})`)}catch(e){le=O(e),re=!1,ne=null,_=null,F(`failed to start: ${le}`)}return Oe()}async function Ye(){_e();const e=_;_=null,e?.dispose?.();const t=ne;if(ne=null,re=!1,oe=null,ce=[],ue="",t)try{t.closeAllConnections?.(),await new Promise(e=>t.close(()=>e()))}catch{}}async function et(){return await Ye(),Xe()}async function tt(){const[e,t]=await Promise.all([ze(),h()]),n=X.media?.status?.()||{};return{ok:!0,settings:Q.public(),interfaces:Ne(!1),apps:G.list(),clients:_?.info?.()??[],info:Oe(),capabilities:{notifications:X.notifications?.status?.()||{},media:n.media||{},volume:n.volume||{},weather:{ok:fe?.ok??null,updatedAt:fe?.updatedAt??null},probe:t,node:m()},diagnostics:{platform:X.platform,platformLabel:X.label,version:I,running:re,protocol:ie,clientCount:_?.size?.()??0,notificationApps:e.apps.length,profile:g()}}}async function nt(e){const t=Q.get(),n=e&&"object"==typeof e?{...e}:{};delete n.token,delete n.tokenRotatedAt;const o=Q.update(n),r=JSON.stringify(t.tls)!==JSON.stringify(o.tls),i=t.tokenEnabled!==o.tokenEnabled,a=JSON.stringify(t.allowedInterfaces)!==JSON.stringify(o.allowedInterfaces);return(i||a)&&_?.closeAll("settings changed"),r?await et():$e(),Qe(!0),o.pageEnabled.home&&(JSON.stringify(t.notifications)===JSON.stringify(o.notifications)&&t.pageEnabled.home||Ue(!0),JSON.stringify(t.weather)===JSON.stringify(o.weather)&&t.pageEnabled.home||We(!0)),o.pageEnabled.sessions&&(JSON.stringify(t.balance)===JSON.stringify(o.balance)&&t.pageEnabled.sessions||(Be(!0),He(!0)),JSON.stringify(t.notifications)!==JSON.stringify(o.notifications)&&He(!0)),o.pageEnabled.music&&!t.pageEnabled.music&&Fe(!0),o.pageEnabled.performance&&!t.pageEnabled.performance&&Me(!0),o}return{start:Xe,stop:Ye,restart:et,getInfo:Oe,handleInfo:async function(e,t){U(t,200,Oe())},handleQr:async function(e,t){if(!ue)return void B(t,503,"Remote control server is not running");const n=p(Ae(),{cellSize:8,margin:4,label:"Remote control pairing QR code"});t.writeHead(200,{"content-type":"image/svg+xml; charset=utf-8","cache-control":"no-store","content-length":Buffer.byteLength(n),"x-content-type-options":"nosniff"}),t.end("HEAD"===e.method?void 0:n)},handleSettingsGet:async function(e,t){U(t,200,await tt())},handleSettingsPost:async function(e,t){if(Pe(e))try{const n=await W(e);await nt(n),U(t,200,await tt())}catch(e){U(t,400,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})},handleAppNames:async function(e,t){if(Pe(e))try{const n=await W(e),o=A(n?.key,240).trim(),r="string"==typeof n?.name?n.name:null;if(!o)return void U(t,400,{ok:!1,error:"application key is required"});if(!G.rename(o,r))return void U(t,404,{ok:!1,error:"application has not been recorded yet"});Ue(!0),Fe(!0),U(t,200,await tt())}catch(e){U(t,400,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})},handleLocation:async function(e,t){if(Pe(e))try{const n=await W(e),o=d();if(!o.available)return void U(t,503,{ok:!1,error:o.error||"native probe unavailable"});const r=["location","request"];!1!==n?.openSettings&&r.push("--open-settings");const i=await f(r,{timeout:2e4}),a=z(i)?i:{},s=!0===a.allowed||"allowed"===a.access;s&&(Z.resetLocation(),We(!0)),U(t,200,{ok:!0,allowed:s,access:A(a.access,40)||"unknown",lowIntegrity:!0===a.lowIntegrity,openedSettings:!0===a.openedSettings,reason:A(a.reason,60)||null,sampledAt:Date.now()})}catch(e){U(t,502,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})},handleGeocode:async function(e,t){if("GET"!==e.method)return void U(t,405,{ok:!1,error:"method not allowed"});const n=new URL(e.url||"/","http://127.0.0.1"),o=A(n.searchParams.get("q")||"",120).trim();if(o)try{U(t,200,{ok:!0,query:o,results:await Z.searchCities(o),updatedAt:Date.now()})}catch(e){F(`geocode "${o}" failed: ${O(e)}`),U(t,502,{ok:!1,error:O(e)})}else U(t,400,{ok:!1,error:"query is required"})},handleTheme:async function(e,t){if(Pe(e))try{const n=await W(e),o="light"===n?.theme?"light":"dark"===n?.theme?"dark":null;if(!o)return void U(t,400,{ok:!1,error:"invalid theme"});Q.setDshTheme(o),Qe(!0),U(t,200,{ok:!0,settings:Q.public()})}catch(e){U(t,400,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})},handleLocale:async function(e,t){if(Pe(e))try{const n=await W(e),o=A(n?.locale,35),r=k(o);if(!r)return void U(t,400,{ok:!1,error:"invalid locale"});Q.setDshLocale(r),Qe(!0),U(t,200,{ok:!0,dshLocale:r,locale:xe(),settings:Q.public()})}catch(e){U(t,400,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})},handleTokenRotate:async function(e,t){Pe(e)?(Q.rotateToken(),_?.closeAll("token rotated"),Qe(!0),U(t,200,await tt())):U(t,403,{ok:!1,error:"origin rejected"})},handleDisconnect:async function(e,t){return async function(e,t){if(Pe(e))try{const n=await W(e),o=A(n?.id,80);o&&_?.close(o,"disconnected by host"),U(t,200,{ok:!0,clients:_?.info?.()??[]})}catch(e){U(t,400,{ok:!1,error:O(e)})}else U(t,403,{ok:!1,error:"origin rejected"})}(e,t)},settingsStore:Q,applySettings:nt,dispose(){de=!0,K.dispose(),X.dispose(),G.dispose(),Ye()}}}
@@ -0,0 +1 @@
1
+ import{isRecord as e}from"./types.js";import{hostMessage as t}from"./i18n.js";import{basename as n,finiteNumber as r,serializeError as s}from"./util.js";function o(t){return e(t)}function i(e){return"string"==typeof e?e:""}function a(t){if(!t)return"";const n=e(t)?t:{},r=Array.isArray(n.content)?n.content:Array.isArray(t)?t:[],s=[];for(const t of r)"string"==typeof t?s.push(t):e(t)&&"text"===t.type&&"string"==typeof t.text&&s.push(t.text);return s.join(" ").replace(/\s+/g," ").trim()}function l(t){const n=e(t)?t:{},r=i(n.label).trim();return r?{label:r.slice(0,300),description:i(n.description).trim().slice(0,1e3)}:null}function u(t,n){if(!e(t))return null;const r=i(t.id).trim()||`q${n+1}`,s=i(t.question).trim();if(!s)return null;const o=Array.isArray(t.options)?t.options:[],a=[];for(const e of o.slice(0,30)){const t=l(e);t&&a.push(t)}return{id:r.slice(0,200),question:s.slice(0,3e3),header:i(t.header).trim().slice(0,200),detail:i(t.detail).slice(0,8e3),options:a,multiSelect:!0===t.multiSelect||!0===t.multi_select}}function c(e){const t=Array.isArray(e)?e:[],n=[];for(let e=0;e<t.length&&n.length<20;e+=1){const r=u(t[e],e);r&&n.push(r)}return n}function d(t){if("string"!=typeof t||0===t.length)return[];try{const n=JSON.parse(t);return e(n)?c(n.questions):[]}catch{return[]}}export function parsePendingAnswer(t){if(!e(t)||!Array.isArray(t.answers))return null;const n=[];for(const r of t.answers.slice(0,20)){if(!e(r))return null;const t=i(r.id).trim();if(!t)return null;const s=[];if(Array.isArray(r.selected))for(const e of r.selected.slice(0,30)){const t=i(e).trim();t&&s.push(t.slice(0,300))}const o=i(r.custom).trim(),a={id:t.slice(0,200),selected:s};o&&(a.custom=o.slice(0,4e3)),n.push(a)}return{answers:n}}function f(e,t=160){try{const n=Number(e.seq),r=Math.max(0,n-t);return e.snapshotEvents?.(r)??[]}catch{return[]}}function g(t){const n=e(t.data?.message)?t.data?.message:{},r=Array.isArray(n.content)?n.content:[];for(const t of r)if(e(t)&&"text"===t.type&&"string"==typeof t.text)try{const n=JSON.parse(t.text);if(e(n)&&!0===n.pending)return!0}catch{}const s=i((e(t.data?.error)?t.data?.error:{}).code);return"TOOL_OUTCOME_UNKNOWN"===s||"tool-outcome-unknown"===s}function p(n,s="zh"){const o=new Map,i=[];for(const t of n)if("tool/call"===t?.type&&"ask_user_question"===t.data?.name){const e=String(t.data?.callId??"");if(!e)continue;o.set(e,{event:t,result:null,requestId:e}),i.push(e)}else if("tool/result"===t?.type){const n=e(t.data)?t.data:{},r=e(n.message)?n.message:{},s=String(n.toolCallId??r.toolCallId??""),i=o.get(s);i&&(i.result=t)}else if("user/message"===t?.type){const n=e(t.data)?t.data:{},r=e(n.source)?n.source:{};if("user-question-reply"!==r.kind)continue;const s=String(r.callId??""),i=o.get(s);i&&(i.result=t)}const a=new Set;let l=null;for(const e of i){const n=o.get(e);if(!n)continue;const i=null!==n.result&&g(n.result);if(null!==n.result&&!i){a.add(e);continue}const u=d(n.event.data?.arguments),c=u[0]??null;l={kind:"question",question:c?.question||t(s,"answerWaiting"),header:c?.header||"",detail:c?.detail||"",count:u.length||1,since:r(n.event.time,Date.now())??Date.now(),requestId:e,questions:u,answerable:i}}return{pending:l,settledCallIds:a}}export function createPendingTracker(n,r={}){const o=()=>r.getLocale?.()??"zh",a=new Map,l=new Map,u=new Map,d=new Map;let f=0,g=null;function p(e,t){return`${e}\0${t}`}function h(){r.onChange&&!g&&(g=setTimeout(()=>{g=null;try{r.onChange?.()}catch{}},40),g.unref?.())}function m(e){for(const[t,n]of u)e-n>6e4&&u.delete(t);for(const[t,n]of d)e-n>6e3&&d.delete(t)}function w(e,t){const n=Date.now();m(n),d.set(e,n),t&&u.set(p(e,t),n)}function y(e,t){const n=Date.now();if(m(n),!e)return!1;if(t&&u.has(p(e,t)))return!0;const r=d.get(e);return void 0!==r&&n-r<=6e3}function v(e,t){if(!e)return null;const n=`${Date.now()}-${++f}`;let r=a.get(e);return r||(r=new Map,a.set(e,r)),r.set(n,t),h(),()=>{const t=a.get(e);t&&(t.delete(n),0===t.size&&a.delete(e),h())}}function I(e){const t=l.get(e);return t?(l.delete(e),t):null}const q=[];return"function"==typeof n.on&&(q.push(n.on("user-questions/request",async function(r,s){const a=n.get?.("agents"),u=e(r)?r:{},d=e(u.agent)?u.agent:null,g=(d&&"string"==typeof d.id?d.id:void 0)||this?.id||a?.currentInitiator?.()?.id||void 0,m=d||(g?a?.get?.(g):void 0),y=c(u.questions),I=y[0]??null,q=e(u.wait)?u.wait:{},S=i(q.callId),A=S||`question-${Date.now()}-${++f}`,k={kind:"question",question:I?.question||t(o(),"answerWaiting"),header:I?.header||"",detail:I?.detail||"",count:y.length||1,since:Date.now(),requestId:A,questions:y,answerable:!0},b=function(){let e;return{promise:new Promise(t=>{e=t}),resolve:e}}(),D={sessionId:g||"",requestId:A,callId:S,agent:m,questions:y,mode:"live",resolve:b.resolve,promise:b.promise};g&&(D.removeInfo=v(g,k)??void 0),l.set(p(g||"",A),D),h();const T=Promise.resolve().then(()=>s());T.catch(()=>{});let M=!1;try{const e=await Promise.race([b.promise.then(e=>({kind:"remote",answer:e})),T.then(e=>({kind:"local",outcome:e}))]);return M=!0,S&&w(g||"",S),"remote"===e.kind?e.answer:e.outcome}catch(t){if(!0===q.timed&&S&&function(t){return e(t)&&("ASK_TIMED_OUT"===t.code||"UserQuestionError"===t.name&&"ASK_TIMED_OUT"===t.code)}(t))throw D.mode="continued",D.resolve=void 0,h(),t;throw w(g||"",S),l.delete(p(g||"",A)),D.removeInfo?.(),h(),t}finally{M&&(w(g||"",S),l.delete(p(g||"",A)),D.removeInfo?.(),h())}},{prepend:!0})),q.push(n.on("approval/request",async function(r,s){const a=n.get?.("agents"),l=e(r)?r:{},u=e(l.agent)?l.agent:null,c=(u&&"string"==typeof u.id?u.id:void 0)||this?.id||a?.currentInitiator?.()?.id||void 0,d=e(l.displayReason)?l.displayReason:{},g=v(c,{kind:"approval",question:("en"===o()?i(d.en)||i(d.zh):i(d.zh)||i(d.en))||i(l.reason)||t(o(),"approvalPrompt",{tool:i(l.toolName)||t(o(),"approvalTool")}),header:i(l.toolName),detail:"",count:1,since:Date.now(),requestId:`approval-${Date.now()}-${++f}`,questions:[],answerable:!1});try{return await s()}finally{g?.()}},{prepend:!0}))),{snapshot:function(e){const t=a.get(e);return t?[...t.values()].sort((e,t)=>(t.since??0)-(e.since??0)):[]},respond:async function(r,a,u){const c=p(r,a),d=l.get(c);if(!d)return{ok:!1,error:t(o(),"answerExpired")};if("unavailable"===d.mode)return{ok:!1,error:t(o(),"answerNeedsDsh")};const f=function(e,n,r){if(n.answers.length!==e.length)return t(r,"answerCountMismatch",{expected:e.length,received:n.answers.length});const s=new Set;for(const o of n.answers){if(!o.id||s.has(o.id))return t(r,"answerDuplicate");s.add(o.id);const n=e.find(e=>e.id===o.id);if(!n)return t(r,"answerUnknownQuestion");if(!0!==n.multiSelect&&o.selected.length>1)return t(r,"answerSingleChoice");for(const e of o.selected)if(!n.options.some(t=>t.label===e))return t(r,"answerOptionGone")}for(const n of e)if(!s.has(n.id))return t(r,"answerMissingQuestion");return null}(d.questions,u,o());if(f)return{ok:!1,error:f};if("live"===d.mode&&d.resolve){const e=d.resolve;return d.resolve=void 0,w(r,d.callId),e(u),d.removeInfo?.(),I(c),h(),{ok:!0}}if(!d.callId)return I(c),d.removeInfo?.(),h(),{ok:!1,error:t(o(),"answerMissingId")};const g=n.get?.("userQuestions");if(!g?.answer)return{ok:!1,error:t(o(),"answerUnsupported")};try{return g.answer(d.agent,d.callId,u)?(w(r,d.callId),I(c),d.removeInfo?.(),h(),{ok:!0}):(w(r,d.callId),I(c),d.removeInfo?.(),h(),{ok:!1,error:t(o(),"answerNotWaiting")})}catch(t){const n=e(t)?i(t.code):"";return"CALLER_NOT_LIVE"!==n&&"DELEGATED_CALLER"!==n||(w(r,d.callId),I(c),d.removeInfo?.(),h()),{ok:!1,error:s(t)}}},adopt:function(e,t,n){const r=t.requestId||"";if(!r||!e)return;const s=p(e,r);l.has(s)||y(e,r)||l.set(s,{sessionId:e,requestId:r,callId:r,agent:n,questions:t.questions??[],mode:!0===t.answerable?"continued":"unavailable"})},pruneContinued:function(e,t){let n=!1;for(const[r,s]of[...l])s.sessionId===e&&"live"!==s.mode&&s.callId&&t.has(s.callId)&&(l.delete(r),s.removeInfo?.(),n=!0);n&&h()},isSettled:y,dispose(){for(const e of q)if("function"==typeof e)try{e()}catch{}q.length=0,g&&(clearTimeout(g),g=null),a.clear(),l.clear(),u.clear(),d.clear()}}}export function collectSessions(e,s,i){const l=e.get?.("sessions"),u=l?.list?.()??[],c=e.get?.("agents"),d=i?.()??"zh",g=c?.list?.()??[],h=new Map(g.map(e=>[String(e.id),e])),m=e.get?.("sessionTitle"),w=Date.now(),y=[];for(const e of u){const i=String(e.id),l=h.get(i)||c?.get?.(i),u=f(e),g=u.length>0?u[u.length-1]:null,v=r(g?.time,null)??r(e.header?.createdAt,w)??w,I=p(u,d);s?.pruneContinued(i,I.settledCallIds);const q=s?.snapshot(i)??[];let S=null;if(0===q.length&&"running"===l?.status&&I.pending){const e=I.pending.requestId||"";s?.isSettled(i,e)||(S=I.pending,s?.adopt(i,S,l))}const A=q.length>0?q:S?[S]:[];let k="";try{k=m?.get?.(e)?.title||""}catch{}let b="";for(let e=u.length-1;e>=0&&("assistant/message"!==u[e]?.type||(b=a(u[e].data?.message),!b));e-=1);if(!b)for(let e=u.length-1;e>=0&&("user/message"!==u[e]?.type||(b=a(u[e].data),!b));e-=1);if(!k)for(let e=u.length-1;e>=0&&("user/message"!==u[e]?.type||(k=a(u[e].data),!k));e-=1);const D=e.header?.cwd||"";k||(k=n(D)||t(d,"newSession"));let T=null,M=null;for(const e of u)"turn/start"===e?.type?T=e:"turn/end"===e?.type&&(M=e);let C=null;try{const t=c?.turnBoundaryOf?.(e),n=r(t?.openTurnStartSeq,null);null!==n&&(C=r(e.eventAt?.(n)?.time,null))}catch{}null===C&&"running"===l?.status&&T&&(C=r(T.time,null));const N=M?.data?.reason,O=M&&o(N)&&(!T||Number(M.seq)>Number(T.seq))&&["error","blocked"].includes(N.kind??"")?N:null;let x=0;try{x=r(c?.turnBoundaryOf?.(e)?.lastTurn,0)??0}catch{}let E="";try{const e=l?.model?.current?.();e?.provider&&e?.model&&(E=`${e.provider} / ${e.model}`)}catch{}const _=A.length>0?"question":"running"===l?.status?"running":O?"error":"done",$=A[0]||null,L="error"===O?.kind?O.error?.message||t(d,"turnFailed"):"blocked"===O?.kind?t(d,"turnBlocked"):"";y.push({id:i,title:String(k).slice(0,120),preview:String(b).slice(0,180),cwd:n(D)||"",cwdPath:D,createdAt:r(e.header?.createdAt,w)??w,lastActiveAt:v,turn:x,model:E,status:_,agentStatus:l?.status||"inactive",runningSince:C,error:L,pending:$?{kind:$.kind,question:String($.question||"").slice(0,220),header:String($.header||"").slice(0,80),detail:String($.detail||"").slice(0,260),since:r($.since,w)??w,count:r($.count,1)??1,requestId:String($.requestId||""),questions:$.questions??[],answerable:!0===$.answerable}:null,isSubagent:"subagent"===e.header?.origin,parentId:e.header?.parentSession?String(e.header.parentSession):""})}const v={question:0,running:1,error:2,done:3};return y.sort((e,t)=>{const n=(v[e.status]??3)-(v[t.status]??3);return 0!==n?n:t.lastActiveAt-e.lastActiveAt}),y}
@@ -0,0 +1 @@
1
+ import e from"node:crypto";import t from"node:fs";import o from"node:os";import n from"node:path";import{isRecord as a}from"./types.js";const r=["home","sessions","music","performance"];export const SUPPORTED_LOCALES=["zh","en"];const s=/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/u;export function resolveLocale(e,t){if("zh"===e||"en"===e)return e;const o=String(t||"").trim().toLowerCase();return SUPPORTED_LOCALES.find(e=>e===o)??"en"}export function normalizeDshLocale(e){if("string"!=typeof e)return"";const t=e.trim();return t.length>35||!s.test(t)?"":t.toLowerCase()}export const COUNTDOWN_PRESET_LIMIT=5;export const COUNTDOWN_MIN_SECONDS=1;export const COUNTDOWN_MAX_SECONDS=86400;export const POMODORO_MIN_MINUTES=.1;export const POMODORO_MAX_FOCUS_MINUTES=180;export const POMODORO_MAX_BREAK_MINUTES=120;export const DEFAULT_COUNTDOWN_PRESETS=[60,300,600];function l(){return e.randomBytes(24).toString("base64url")}function i(e,t){return"string"==typeof e&&t.includes(e)}export function defaultSettings(){const e=Date.now();return{version:1,token:l(),tokenRotatedAt:e,tokenEnabled:!0,allowedInterfaces:null,theme:"dsh",dshTheme:"dark",language:"dsh",dshLocale:"",temperatureUnit:"celsius",weather:{mode:"auto",name:"",latitude:null,longitude:null,forecastMode:"hourly"},balance:{spendWindow:"calendar",lowThreshold:null,showPeakValley:!0},notifications:{enabledApps:[],limit:5,pushQuestions:!1},tls:{enabled:!1,certPath:"",keyPath:""},navPosition:"left",pomodoro:{enabled:!1,focusMinutes:25,breakMinutes:5,soundPath:"",loopAlarm:!1},countdown:{enabled:!1,presets:[...DEFAULT_COUNTDOWN_PRESETS]},pageOrder:["home","sessions","music","performance"],pageEnabled:{home:!0,sessions:!0,music:!0,performance:!0},updatedAt:e}}function d(e){return Array.isArray(e)?[...new Set(e.filter(e=>"string"==typeof e&&e.length>0).slice(0,100))]:[]}function u(e){if(!Array.isArray(e))return[...DEFAULT_COUNTDOWN_PRESETS];const t=[];for(const o of e.slice(0,5)){if(null==o||""===o)continue;const e=Math.round(Number(o));Number.isFinite(e)&&t.push(Math.max(1,Math.min(86400,e)))}return t.length>0?t:[...DEFAULT_COUNTDOWN_PRESETS]}function c(e,t,o,n){const a=Number(e);return Number.isFinite(a)?Math.max(t,Math.min(o,Math.round(100*a)/100)):n}function h(e){const t=defaultSettings(),o=a(e)?e:{},n=a(o.weather)?o.weather:{},s=a(o.balance)?o.balance:{},l=a(o.notifications)?o.notifications:{},h=a(o.tls)?o.tls:{},m=a(o.pomodoro)?o.pomodoro:{},p=a(o.countdown)?o.countdown:{},f=n.latitude,b=n.longitude,g=null==f||""===f?NaN:Number(f),w=null==b||""===b?NaN:Number(b),O="manual"===n.mode?"manual":"auto",N=0===g&&0===w,S=Number.isFinite(g)&&g>=-90&&g<=90?g:null,A=Number.isFinite(w)&&w>=-180&&w<=180?w:null,E="auto"===O&&N?null:S,k="auto"===O&&N?null:A,y=Number(s.lowThreshold),T=o.theme,P=o.dshTheme,D=i(T,["dsh","light","dark"])?T:"dsh",M=i(P,["light","dark"])?P:"dark",_=o.language,L=i(_,["dsh","zh","en"])?_:"dsh",R=o.navPosition,U=i(R,["left","right","top","bottom"])?R:"left",F={enabled:!0===m.enabled,focusMinutes:c(m.focusMinutes,.1,180,25),breakMinutes:c(m.breakMinutes,.1,120,5),soundPath:"string"==typeof m.soundPath?m.soundPath.slice(0,2048):"",loopAlarm:!0===m.loopAlarm},v={enabled:!0===p.enabled,presets:u(p.presets)};F.enabled&&(v.enabled=!1);const I=Array.isArray(o.pageOrder)?o.pageOrder.filter(e=>"string"==typeof e&&r.includes(e)):[],x=[...new Set(I)];for(const e of r)x.includes(e)||x.push(e);const C=x.slice(0,r.length),W=a(o.pageEnabled)?o.pageEnabled:{},j=Object.fromEntries(r.map(e=>[e,!1!==W[e]]));return Object.values(j).some(Boolean)||(j.home=!0),{version:1,token:"string"==typeof o.token&&o.token.length>=16?o.token:t.token,tokenRotatedAt:Number.isFinite(Number(o.tokenRotatedAt))?Number(o.tokenRotatedAt):t.tokenRotatedAt,tokenEnabled:!1!==o.tokenEnabled,allowedInterfaces:null===o.allowedInterfaces?null:d(o.allowedInterfaces),theme:D,dshTheme:M,language:L,dshLocale:normalizeDshLocale(o.dshLocale),temperatureUnit:"fahrenheit"===o.temperatureUnit?"fahrenheit":"celsius",weather:{mode:O,name:"string"==typeof n.name?n.name.slice(0,80):"",latitude:E,longitude:k,forecastMode:"daily"===n.forecastMode?"daily":"hourly"},balance:{spendWindow:"rolling"===s.spendWindow?"rolling":"calendar",lowThreshold:null===s.lowThreshold||""===s.lowThreshold?null:Number.isFinite(y)&&y>=0?y:null,showPeakValley:!1!==s.showPeakValley},notifications:{enabledApps:d(l.enabledApps),limit:Math.max(1,Math.min(20,Number.isFinite(Number(l.limit))?Math.round(Number(l.limit)):5)),pushQuestions:!0===l.pushQuestions},tls:{enabled:!0===h.enabled,certPath:"string"==typeof h.certPath?h.certPath.slice(0,4096):"",keyPath:"string"==typeof h.keyPath?h.keyPath.slice(0,4096):""},navPosition:U,pomodoro:F,countdown:v,pageOrder:C,pageEnabled:j,updatedAt:Number.isFinite(Number(o.updatedAt))?Number(o.updatedAt):Date.now()}}export function createSettingsStore(){const e=process.env.DSH_PROFILE_DIR?n.join(process.env.DSH_PROFILE_DIR,"remote-control"):process.env.DSH_HOME?n.join(process.env.DSH_HOME,"remote-control"):n.join(o.homedir(),".dsh","remote-control"),r=n.join(e,"settings.json");let s;t.mkdirSync(e,{recursive:!0});try{s=h(JSON.parse(t.readFileSync(r,"utf8")))}catch{s=defaultSettings()}function i(e){s=h({...a(e)?e:{},updatedAt:Date.now()});const o=`${r}.tmp-${process.pid}`;return t.writeFileSync(o,JSON.stringify(s,null,2),"utf8"),t.renameSync(o,r),s}return t.writeFileSync(r,JSON.stringify(s,null,2),"utf8"),{dir:e,file:r,get:()=>s,public:function(){return{tokenEnabled:s.tokenEnabled,tokenRotatedAt:s.tokenRotatedAt,allowedInterfaces:s.allowedInterfaces,theme:s.theme,effectiveTheme:"dsh"===s.theme?s.dshTheme:s.theme,language:s.language,locale:resolveLocale(s.language,s.dshLocale),temperatureUnit:s.temperatureUnit,weather:{...s.weather},balance:{...s.balance},notifications:{...s.notifications,enabledApps:[...s.notifications.enabledApps]},tls:{...s.tls},navPosition:s.navPosition,pomodoro:{...s.pomodoro},countdown:{...s.countdown,presets:[...s.countdown.presets]},pageOrder:[...s.pageOrder],pageEnabled:{...s.pageEnabled},updatedAt:s.updatedAt}},update:function(e){const t=a(e)?e:{},o={...s,...t,weather:{...s.weather,...a(t.weather)?t.weather:{}},balance:{...s.balance,...a(t.balance)?t.balance:{}},notifications:{...s.notifications,...a(t.notifications)?t.notifications:{}},tls:{...s.tls,...a(t.tls)?t.tls:{}},pomodoro:{...s.pomodoro,...a(t.pomodoro)?t.pomodoro:{}},countdown:{...s.countdown,...a(t.countdown)?t.countdown:{}}},n=a(t.pomodoro)?t.pomodoro:{},r=a(t.countdown)?t.countdown:{};return!0===n.enabled&&!0!==r.enabled?o.countdown={...a(o.countdown)?o.countdown:{},enabled:!1}:!0===r.enabled&&!0!==n.enabled&&(o.pomodoro={...a(o.pomodoro)?o.pomodoro:{},enabled:!1}),i(o)},save:i,rotateToken:function(){return i({...s,token:l(),tokenRotatedAt:Date.now()})},setDshTheme:function(e){return"light"!==e&&"dark"!==e||s.dshTheme===e?s:i({...s,dshTheme:e})},setDshLocale:function(e){const t=normalizeDshLocale(e);return t&&s.dshLocale!==t?i({...s,dshLocale:t}):s}}}
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export function isRecord(r){return"object"==typeof r&&null!==r&&!Array.isArray(r)}
package/lib/util.js ADDED
@@ -0,0 +1 @@
1
+ import{isRecord as t}from"./types.js";export const VERSION="1.0.3";export const PANEL_ID="remote-control";export const ROUTE_PREFIX="/dsh-remote-control";export const INFO_PATH=`${ROUTE_PREFIX}/api/info`;export const QR_PATH=`${ROUTE_PREFIX}/qr.svg`;export const SETTINGS_PATH=`${ROUTE_PREFIX}/api/settings`;export const APP_NAMES_PATH=`${ROUTE_PREFIX}/api/apps`;export const LOCATION_PATH=`${ROUTE_PREFIX}/api/location`;export const GEOCODE_PATH=`${ROUTE_PREFIX}/api/geocode`;export const THEME_PATH=`${ROUTE_PREFIX}/api/theme`;export const LOCALE_PATH=`${ROUTE_PREFIX}/api/locale`;export const TOKEN_ROTATE_PATH=`${ROUTE_PREFIX}/api/token/rotate`;export const DISCONNECT_PATH=`${ROUTE_PREFIX}/api/disconnect`;export function clamp(t,e,r){return Math.min(r,Math.max(e,t))}export function finiteNumber(t,e=null){if(null==t)return e;if("string"==typeof t&&""===t.trim())return e;const r="number"==typeof t?t:Number(t);return Number.isFinite(r)?r:e}export function safeString(t,e=500){return"string"!=typeof t?"":t.length>e?t.slice(0,e):t}export function decodeXml(t){return String(t).replace(/&#x([0-9a-f]+);/gi,(t,e)=>String.fromCodePoint(parseInt(e,16))).replace(/&#([0-9]+);/g,(t,e)=>String.fromCodePoint(parseInt(e,10))).replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&amp;/g,"&")}export function escapeHtml(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export function basename(t){if("string"!=typeof t||0===t.length)return"";const e=t.replace(/[\\/]+$/,""),r=e.split(/[\\/]/);return r[r.length-1]||e}export function isObject(e){return t(e)}export function sniffImageMediaType(t){if(!t||t.length<12)return null;if(137===t[0]&&80===t[1]&&78===t[2]&&71===t[3])return"image/png";if(255===t[0]&&216===t[1]&&255===t[2])return"image/jpeg";const e=Buffer.from(t.buffer,t.byteOffset,Math.min(t.length,16)).toString("ascii");return e.startsWith("GIF8")?"image/gif":e.startsWith("RIFF")&&"WEBP"===e.slice(8,12)?"image/webp":e.slice(4,12).includes("ftyp")?"image/avif":null}export function startOfLocalDay(t=new Date){const e=new Date(t);return e.setHours(0,0,0,0),e}export function startOfLocalWeek(t=new Date){const e=startOfLocalDay(t),r=e.getDay(),n=0===r?6:r-1;return e.setDate(e.getDate()-n),e}export function localTimezoneOffsetSeconds(t=new Date){return 60*-t.getTimezoneOffset()}export function formatRelativeTime(t,e=Date.now()){if(!Number.isFinite(t)||t<=0)return"";const r=Math.max(0,Math.round((e-t)/1e3));return r<60?"刚刚":r<3600?`${Math.floor(r/60)} 分钟前`:r<86400?`${Math.floor(r/3600)} 小时前`:`${Math.floor(r/86400)} 天前`}export function serializeError(t){if(t instanceof Error)return t.message;try{return String(t)}catch{return"unknown error"}}
package/lib/weather.js ADDED
@@ -0,0 +1 @@
1
+ import{finiteNumber as e,safeString as t,serializeError as a}from"./util.js";import{probeInfo as r,runProbe as i}from"./probe.js";import{isRecord as n}from"./types.js";const o=/^[\u4e00-\u9fff]{2,}$/,u={"Asia/Shanghai":{name:"上海",latitude:31.2304,longitude:121.4737},"Asia/Chongqing":{name:"重庆",latitude:29.563,longitude:106.5516},"Asia/Harbin":{name:"哈尔滨",latitude:45.8038,longitude:126.535},"Asia/Hong_Kong":{name:"香港",latitude:22.3193,longitude:114.1694},"Asia/Macau":{name:"澳门",latitude:22.1987,longitude:113.5439},"Asia/Taipei":{name:"台北",latitude:25.033,longitude:121.5654},"Asia/Tokyo":{name:"东京",latitude:35.6762,longitude:139.6503},"Asia/Seoul":{name:"首尔",latitude:37.5665,longitude:126.978},"Asia/Singapore":{name:"新加坡",latitude:1.3521,longitude:103.8198},"America/Los_Angeles":{name:"洛杉矶",latitude:34.0522,longitude:-118.2437},"America/New_York":{name:"纽约",latitude:40.7128,longitude:-74.006},"Europe/London":{name:"伦敦",latitude:51.5072,longitude:-.1276},"Europe/Paris":{name:"巴黎",latitude:48.8566,longitude:2.3522}};function s(e){return u[e]}function l(e){return"string"==typeof e?e:null}async function c(e,t=9e3){const a=new AbortController,r=setTimeout(()=>a.abort(),t);try{const t=await fetch(e,{signal:a.signal,headers:{accept:"application/json","user-agent":"dsh-remote-control/1.0 (+local smart display)"}});if(!t.ok)throw new Error(`HTTP ${t.status}`);return await t.json()}finally{clearTimeout(r)}}async function m(a,r){const i=new URL("https://geocoding-api.open-meteo.com/v1/search");i.searchParams.set("name",a),i.searchParams.set("count",String(r)),i.searchParams.set("language","zh"),i.searchParams.set("format","json");const o=await c(i.toString(),8e3),u=n(o)?o:null,s=u&&Array.isArray(u.results)?u.results:[],l=[],m=new Set;for(const a of s){if(!n(a))continue;const r=t(a.name,120).trim(),i=e(a.latitude),o=e(a.longitude);if(!r||null===i||null===o)continue;const u=e(a.id,0),s=`${u||r}|${i.toFixed(4)}|${o.toFixed(4)}`;m.has(s)||(m.add(s),l.push({id:u,name:r,latitude:i,longitude:o,country:t(a.country,80).trim(),admin1:t(a.admin1,80).trim(),timezone:t(a.timezone,80).trim()}))}return l}let d=null,p=0;export function resetProbeLocationCache(){d=null,p=0}export function createWeatherService(){let u=null,y=0;const g=new Map,h=new Map;async function f(){const o=Date.now();if(u&&o-y<216e5)return u;if(u?.error&&o-y<3e5)return u;const l=await async function(){const o=Date.now();if(null!==d&&o-p<18e5)return d.ok?d:null;if(!r().available)return null;try{const a=await i(["location"],{timeout:12e3}),r=n(a)?a:null,u=e(r?.latitude,null),s=e(r?.longitude,null);if(r?.ok&&null!==u&&null!==s)return d={ok:!0,latitude:u,longitude:s,accuracy:e(r.accuracy,null),source:t(r.source,80)||"system"},p=o,d;const l=r&&"string"==typeof r.error&&r.error?r.error:"system location unavailable";return d={ok:!1,error:l},p=o,null}catch(e){return d={ok:!1,error:a(e)},p=o,null}}();if(l){const e=Intl.DateTimeFormat().resolvedOptions().timeZone||"",a=await async function(e,a){try{const r=new URL("https://api.bigdatacloud.net/data/reverse-geocode-client");r.searchParams.set("latitude",String(e)),r.searchParams.set("longitude",String(a)),r.searchParams.set("localityLanguage","zh");const i=await c(r.toString(),6e3);return n(i)?t(i.city||i.locality||i.principalSubdivision,80):""}catch{return""}}(l.latitude,l.longitude)||s(e)?.name||"当前位置";return u={name:a,latitude:l.latitude,longitude:l.longitude,timezone:e,source:l.source,accuracy:l.accuracy},y=o,u}try{const a=await c("https://ipwho.is/",6500),r=n(a)?a:null,i=e(r?.latitude),l=e(r?.longitude);if(r&&!1!==r.success&&null!==i&&null!==l){const e=n(r.timezone)?r.timezone:null,a=t(e?.id,80),c=s(a),m=t(r.city||r.region||c?.name||a,80);return u={name:m||"当前位置",latitude:i,longitude:l,timezone:a,source:"ip"},y=o,u}throw new Error("IP location response was incomplete")}catch(e){const t=Intl.DateTimeFormat().resolvedOptions().timeZone||"",r=s(t);return u=r?{...r,timezone:t,source:"timezone",error:a(e)}:{name:t||"当前位置",latitude:31.2304,longitude:121.4737,timezone:t,source:"fallback",error:a(e)},y=o,u}}return{getWeather:async function(r={}){const i=e(r.latitude),o=e(r.longitude),u=null!==i&&null!==o?{name:t(r.name,80)||"自定义位置",latitude:Math.max(-90,Math.min(90,i)),longitude:Math.max(-180,Math.min(180,o)),source:"manual"}:await f(),s=`${u.latitude.toFixed(3)},${u.longitude.toFixed(3)}`,m=Date.now(),d=g.get(s);if(d&&m-d.at<(d.error?45e3:6e5))return{...d.value,cached:!0};const p=new URL("https://api.open-meteo.com/v1/forecast");p.searchParams.set("latitude",String(u.latitude)),p.searchParams.set("longitude",String(u.longitude)),p.searchParams.set("current",["temperature_2m","relative_humidity_2m","apparent_temperature","is_day","precipitation","weather_code","wind_speed_10m"].join(",")),p.searchParams.set("daily",["weather_code","temperature_2m_max","temperature_2m_min","sunrise","sunset"].join(",")),p.searchParams.set("hourly",["temperature_2m","weather_code","precipitation_probability"].join(",")),p.searchParams.set("timezone","auto"),p.searchParams.set("forecast_days","7");try{const t=await c(p.toString(),1e4),a=n(t)?t:null,r=a&&n(a.current)?a.current:{},i=a&&n(a.daily)?a.daily:{},o=a&&n(a.hourly)?a.hourly:{},d=Array.isArray(i.time)?i.time:[],y=Array.isArray(i.weather_code)?i.weather_code:[],h=Array.isArray(i.temperature_2m_max)?i.temperature_2m_max:[],f=Array.isArray(i.temperature_2m_min)?i.temperature_2m_min:[],w=Array.isArray(i.sunrise)?i.sunrise:[],_=Array.isArray(i.sunset)?i.sunset:[],A=Array.isArray(o.time)?o.time:[],P=Array.isArray(o.temperature_2m)?o.temperature_2m:[],b=Array.isArray(o.weather_code)?o.weather_code:[],v=Array.isArray(o.precipitation_probability)?o.precipitation_probability:[],z="string"==typeof r.time?r.time:"";let S=0;if(z){const e=A.findIndex(e=>"string"==typeof e&&e>=z.slice(0,13)+":00");e>=0&&(S=e)}const x=A.slice(S,S+8).map((t,a)=>({time:l(t),temperature:e(P[S+a]),code:e(b[S+a]),precipitationProbability:e(v[S+a])})),k={ok:!0,location:{name:u.name,latitude:u.latitude,longitude:u.longitude,source:u.source},current:{time:l(r.time),temperature:e(r.temperature_2m),feelsLike:e(r.apparent_temperature),humidity:e(r.relative_humidity_2m),precipitation:e(r.precipitation,0)??0,code:e(r.weather_code,0)??0,isDay:1===r.is_day||!0===r.is_day,windSpeed:e(r.wind_speed_10m)},daily:{code:e(y[0],0)??0,high:e(h[0]),low:e(f[0]),sunrise:l(w[0]),sunset:l(_[0]),tomorrowHigh:e(h[1]),tomorrowLow:e(f[1])},dailyForecast:d.slice(0,7).map((t,a)=>({date:l(t),code:e(y[a],0)??0,high:e(h[a]),low:e(f[a]),sunrise:l(w[a]),sunset:l(_[a])})),hourly:x,timezone:l(a?.timezone),updatedAt:m};return g.set(s,{at:m,value:k}),k}catch(e){const t={ok:!1,location:{name:u.name,latitude:u.latitude,longitude:u.longitude,source:u.source},current:null,daily:null,hourly:[],dailyForecast:[],error:a(e),updatedAt:m};return g.set(s,{at:m,value:t,error:!0}),t}},resolveLocation:f,searchCities:async function(e,a=8){const r=t(e,80).trim();if(!r)return[];const i=Math.max(1,Math.min(20,Math.trunc(a)||8)),n=`${r.toLowerCase()}|${i}`,u=Date.now(),s=h.get(n);if(s&&u-s.at<6e5)return s.value;let l=await m(r,i);if(0===l.length&&o.test(r)&&(l=await m(`${r}市`,i)),h.set(n,{at:u,value:l}),h.size>64){const e=h.keys().next().value;void 0!==e&&h.delete(e)}return l},resetLocation:function(){resetProbeLocationCache(),u=null,y=0}}}
package/lib/ws.js ADDED
@@ -0,0 +1 @@
1
+ import e from"node:crypto";import{isRecord as t}from"./types.js";const r=262144;function n(e){const t=String(e||"");return t.startsWith("::ffff:")?t.slice(7):t}function o(e){const t=String(e||"");return/iPhone/i.test(t)?"iPhone":/iPad/i.test(t)?"iPad":/Android/i.test(t)?"Android":/Macintosh|Mac OS X/i.test(t)?"Mac":/Windows/i.test(t)?"Windows":/Linux/i.test(t)?"Linux":"浏览器"}function s(e,t=Buffer.alloc(0)){const r=t.length;let n;return r<126?(n=Buffer.alloc(2),n[1]=r):r<65536?(n=Buffer.alloc(4),n[1]=126,n.writeUInt16BE(r,2)):(n=Buffer.alloc(10),n[1]=127,n.writeBigUInt64BE(BigInt(r),2)),n[0]=128|e,Buffer.concat([n,t])}export function createWebSocketHub(c){const f=c.server,i=c.path||"/ws",a=c.authorize||(()=>({ok:!0})),u=c.onConnection||(()=>{}),l=c.onMessage||(()=>{}),d=c.onClose||(()=>{}),g=new Map;let b=!1;function h(e,t){try{!e.socket.destroyed&&e.socket.writable&&e.socket.write(t)}catch{e.close(1011,"write failed")}}function y(e,t){try{h(e,s(1,Buffer.from(JSON.stringify(t),"utf8")))}catch{}}function m(e,t=1e3,r=""){if(e&&!e.closed){e.closed=!0;try{e.socket.write(function(e=1e3,t=""){const r=Buffer.from(String(t),"utf8"),n=Buffer.alloc(2+r.length);return n.writeUInt16BE(e,0),r.copy(n,2),s(8,n)}(t,r))}catch{}try{e.socket.end()}catch{}setTimeout(()=>{try{e.socket.destroy()}catch{}},100).unref?.(),g.delete(e.id),d(e)}}function p(e,r,n){if(8===r)return void m(e,1e3,"bye");if(9===r)return void h(e,s(10,n));if(10===r)return void(e.lastPong=Date.now());if(1!==r)return void m(e,1003,"text frames only");let o;try{o=JSON.parse(n.toString("utf8"))}catch{return void y(e,{type:"error",error:"invalid message"})}e.lastSeen=Date.now();try{l(e,o)}catch(r){const n=t(r)&&r.message?r.message:r;y(e,{type:"error",error:String(n)})}}function w(e,t){if(e.buffer=Buffer.concat([e.buffer,t]),e.buffer.length>262158)m(e,1009,"message too large");else for(;e.buffer.length>=2;){const t=e.buffer[0],n=e.buffer[1],o=15&t,s=!!(128&n);let c=127&n,f=2;if(!(128&t))return void m(e,1003,"fragmented frames unsupported");if(126===c){if(e.buffer.length<4)return;c=e.buffer.readUInt16BE(2),f=4}else if(127===c){if(e.buffer.length<10)return;const t=e.buffer.readBigUInt64BE(2);if(t>BigInt(r))return void m(e,1009,"message too large");c=Number(t),f=10}if(!s)return void m(e,1002,"client frames must be masked");if(c>r)return void m(e,1009,"message too large");if(e.buffer.length<f+4+c)return;const i=e.buffer.subarray(f,f+4);f+=4;const a=Buffer.from(e.buffer.subarray(f,f+c));for(let e=0;e<a.length;e+=1)a[e]^=i[3&e];e.buffer=e.buffer.subarray(f+c),p(e,o,a)}}function v(r,s,c){if(b)return void s.destroy();let f;try{f=new URL(r.url||"/","http://localhost")}catch{return void s.destroy()}if(f.pathname!==i)return s.write("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"),void s.destroy();if("websocket"!==String(r.headers.upgrade||"").toLowerCase()||!r.headers["sec-websocket-key"])return s.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"),void s.destroy();const l=a(r,f);if(!l?.ok){const e=l?.status||403;return s.write(`HTTP/1.1 ${e} ${401===e?"Unauthorized":"Forbidden"}\r\nConnection: close\r\n\r\n`),void s.destroy()}const d=e.createHash("sha1").update(String(r.headers["sec-websocket-key"])+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");s.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${d}\r\n\r\n`),s.setNoDelay?.(!0);const h={id:e.randomUUID(),socket:s,ip:n(r.socket.remoteAddress),userAgent:String(r.headers["user-agent"]||""),label:o(r.headers["user-agent"]),connectedAt:Date.now(),lastSeen:Date.now(),lastPong:Date.now(),buffer:Buffer.alloc(0),closed:!1,send:e=>y(h,e),close:(e,t)=>m(h,e,t)};g.set(h.id,h),s.on("data",e=>w(h,e)),s.on("error",()=>m(h,1011,"socket error")),s.on("close",()=>m(h,1e3,"closed")),c?.length&&w(h,c);try{u(h)}catch(e){const r=t(e)&&e.message?e.message:e;y(h,{type:"error",error:String(r)}),m(h,1011,"connection failed")}}f.on("upgrade",v);const B=setInterval(()=>{const e=Date.now();for(const t of g.values())e-t.lastPong>7e4?m(t,1001,"stale"):h(t,s(9,Buffer.alloc(0)))},25e3);return B.unref?.(),{broadcast:function(e,t=null){const r=Buffer.from(JSON.stringify(e),"utf8");for(const e of g.values())t&&!t(e)||h(e,s(1,r))},closeAll(e="server restart"){for(const t of[...g.values()])m(t,1001,e)},close(e,t="closed by host"){const r=g.get(e);r&&m(r,1001,t)},info:function(){return[...g.values()].map(e=>({id:e.id,label:e.label,ip:e.ip,connectedAt:e.connectedAt,lastSeen:e.lastSeen})).sort((e,t)=>t.connectedAt-e.connectedAt)},size:()=>g.size,send:y,dispose:function(){b=!0,clearInterval(B),f.off("upgrade",v);for(const e of[...g.values()])m(e,1001,"server restart");g.clear()}}}
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Remote control",
4
+ "description": "Open a 1280×800 smart-display console from a phone browser on your LAN: clock and weather, system notifications, DSH sessions and balance, media and volume, and system performance. Configure interfaces, HTTPS, theme, page order, and notification filters."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "远程控制台",
4
+ "description": "在局域网用手机浏览器打开 1280×800 智能中控台:时钟天气、系统通知、DSH 会话与余额、媒体与音量、系统性能;支持网卡访问控制、HTTPS 证书、主题、页面顺序和通知筛选。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,120 @@
1
1
  {
2
2
  "name": "@ohmykreee/dsh-remote-control",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.3",
4
+ "description": "A touch-first LAN console for DeepSeek Harness built with Svelte 5, shadcn-svelte, and Lucide icons.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./client": "./client.js",
11
+ "./locale/*.json": "./locale/*.json",
12
+ "./icon": "./icon.svg"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/ohmykreee/dsh-remote-control.git"
17
+ },
18
+ "homepage": "https://github.com/ohmykreee/dsh-remote-control#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/ohmykreee/dsh-remote-control/issues"
21
+ },
22
+ "keywords": [
23
+ "dsh",
24
+ "dsh-bundle",
25
+ "dsh-plugin",
26
+ "deepseek-harness",
27
+ "remote-control",
28
+ "lan",
29
+ "svelte",
30
+ "touch"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true,
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "scripts": {
38
+ "dev": "vite",
39
+ "build": "node scripts/build-dsh.mjs && node scripts/build-web.mjs",
40
+ "build:dsh": "node scripts/build-dsh.mjs",
41
+ "build:web": "node scripts/build-web.mjs",
42
+ "build:rust": "node scripts/build-rust.mjs",
43
+ "preview": "vite preview",
44
+ "shot": "node scripts/browser-shot.mjs",
45
+ "check": "tsc -p tsconfig.dsh.json --noEmit && tsc -p tsconfig.dsh-client.json --noEmit && svelte-check --tsconfig ./tsconfig.json",
46
+ "test": "node test/smoke.mjs && node test/questions.mjs && node test/settings.mjs && node test/media.mjs && node test/notifications.mjs",
47
+ "pack:check": "node scripts/check-npm-package.mjs"
48
+ },
49
+ "files": [
50
+ "index.js",
51
+ "client.js",
52
+ "cordis.patch.yml",
53
+ "locale/*.json",
54
+ "icon.svg",
55
+ "lib/**/*.js",
56
+ "web/**",
57
+ "src/dsh/**",
58
+ "src/svelte/**",
59
+ "src/rust/Cargo.toml",
60
+ "src/rust/Cargo.lock",
61
+ "src/rust/src/**",
62
+ "src/rust/tests/**",
63
+ "static/**",
64
+ "vite.config.js",
65
+ "svelte.config.js",
66
+ "components.json",
67
+ "tsconfig.json",
68
+ "tsconfig.dsh.json",
69
+ "tsconfig.dsh-client.json",
70
+ "scripts/**/*.mjs",
71
+ "scripts/**/*.py",
72
+ "scripts/**/*.sh",
73
+ "README.md",
74
+ "INSTALLATION.md",
75
+ "AGENTS.md",
76
+ "docs/**",
77
+ "LICENSE"
78
+ ],
79
+ "engines": {
80
+ "node": ">=22"
81
+ },
82
+ "dsh": {
83
+ "bundle": {
84
+ "patch": "./cordis.patch.yml"
85
+ },
86
+ "client": {
87
+ "platform": "web",
88
+ "immediately": true,
89
+ "inject": [
90
+ "@deepseek-ai/dsh-client-locale"
91
+ ]
92
+ }
93
+ },
94
+ "dependencies": {
95
+ "qrcode-generator": "^2.0.4"
96
+ },
97
+ "devDependencies": {
98
+ "@fontsource-variable/inter": "^5.3.0",
99
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
100
+ "@fontsource-variable/noto-sans-sc": "^5.3.1",
101
+ "@internationalized/date": "^3.12.0",
102
+ "@lucide/svelte": "^1.72.0",
103
+ "@sveltejs/vite-plugin-svelte": "^7.3.1",
104
+ "@tailwindcss/vite": "^4.3.5",
105
+ "@types/node": "^26.6.6",
106
+ "@types/react": "^19.3.0",
107
+ "bits-ui": "^2.20.0",
108
+ "cn": "^0.5.2",
109
+ "playwright-core": "^1.65.0",
110
+ "shadcn-svelte": "^1.8.1",
111
+ "svelte": "^5.57.3",
112
+ "svelte-check": "^4.7.8",
113
+ "tailwind-variants": "^3.3.0",
114
+ "tailwindcss": "^4.3.5",
115
+ "terser": "^5.51.2",
116
+ "tw-animate-css": "^1.4.0",
117
+ "typescript": "~6.0.3",
118
+ "vite": "^8.3.8"
119
+ }
120
+ }
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Dev-only screenshot helper for the phone console.
4
+ *
5
+ * Starts the deterministic mock preview (`scripts/mock-preview.py`), opens it
6
+ * in a locally installed Edge/Chrome through `playwright-core`, and writes a
7
+ * PNG. No Playwright browser download is required: the script uses the system
8
+ * browser, and `DSH_BROWSER_PATH` can point at a custom executable.
9
+ *
10
+ * Examples:
11
+ * node scripts/browser-shot.mjs --timer countdown
12
+ * node scripts/browser-shot.mjs --timer countdown --locale en --out .mock-web/countdown-en.png
13
+ * node scripts/browser-shot.mjs --timer countdown --click '.countdown-main-row button' --settle 2500
14
+ * node scripts/browser-shot.mjs --url http://127.0.0.1:4173/?page=home --out shot.png
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import http from 'node:http';
20
+ import net from 'node:net';
21
+ import { spawn } from 'node:child_process';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { chromium } from 'playwright-core';
24
+
25
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
26
+
27
+ function parseArgs(argv) {
28
+ const args = {
29
+ timer: 'pomodoro',
30
+ locale: 'zh',
31
+ page: 'home',
32
+ width: 1280,
33
+ height: 800,
34
+ wait: 900,
35
+ settle: 0,
36
+ click: '',
37
+ out: '',
38
+ url: '',
39
+ port: 0,
40
+ web: path.join(ROOT, 'web'),
41
+ browser: '',
42
+ };
43
+ for (let index = 0; index < argv.length; index += 1) {
44
+ const name = argv[index];
45
+ const next = (label) => {
46
+ const value = argv[index + 1];
47
+ if (!value || value.startsWith('--')) throw new Error(`${label} needs a value`);
48
+ index += 1;
49
+ return value;
50
+ };
51
+ switch (name) {
52
+ case '--timer': args.timer = next(name); break;
53
+ case '--locale': args.locale = next(name); break;
54
+ case '--page': args.page = next(name); break;
55
+ case '--width': args.width = Number(next(name)); break;
56
+ case '--height': args.height = Number(next(name)); break;
57
+ case '--wait': args.wait = Number(next(name)); break;
58
+ case '--settle': args.settle = Number(next(name)); break;
59
+ case '--click': args.click = next(name); break;
60
+ case '--out': args.out = next(name); break;
61
+ case '--url': args.url = next(name); break;
62
+ case '--port': args.port = Number(next(name)); break;
63
+ case '--web': args.web = path.resolve(ROOT, next(name)); break;
64
+ case '--browser': args.browser = next(name); break;
65
+ case '--help': case '-h':
66
+ console.log(fs.readFileSync(fileURLToPath(import.meta.url), 'utf8').split('\n').slice(1, 17).join('\n'));
67
+ process.exit(0);
68
+ break;
69
+ default:
70
+ throw new Error(`unknown option: ${name}`);
71
+ }
72
+ }
73
+ if (!['pomodoro', 'countdown'].includes(args.timer)) throw new Error(`unknown timer: ${args.timer}`);
74
+ if (!['zh', 'en'].includes(args.locale)) throw new Error(`unknown locale: ${args.locale}`);
75
+ if (!args.out) {
76
+ args.out = path.join(ROOT, '.mock-web', `${args.page}-${args.timer}-${args.locale}.png`);
77
+ } else {
78
+ args.out = path.resolve(ROOT, args.out);
79
+ }
80
+ return args;
81
+ }
82
+
83
+ /** First existing browser executable; Playwright channels are the fallback. */
84
+ function browserExecutable(explicit) {
85
+ const candidates = [
86
+ explicit,
87
+ process.env.DSH_BROWSER_PATH,
88
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
89
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
90
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
91
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
92
+ '/usr/bin/google-chrome',
93
+ '/usr/bin/google-chrome-stable',
94
+ '/usr/bin/chromium',
95
+ '/usr/bin/chromium-browser',
96
+ '/usr/bin/microsoft-edge',
97
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
98
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
99
+ ].filter(Boolean);
100
+ return candidates.find((candidate) => fs.existsSync(candidate)) || '';
101
+ }
102
+
103
+ function freePort() {
104
+ return new Promise((resolve, reject) => {
105
+ const server = net.createServer();
106
+ server.unref();
107
+ server.on('error', reject);
108
+ server.listen(0, '127.0.0.1', () => {
109
+ const address = server.address();
110
+ const port = typeof address === 'object' && address ? address.port : 0;
111
+ server.close(() => resolve(port));
112
+ });
113
+ });
114
+ }
115
+
116
+ function waitForHttp(url, timeoutMs = 20_000) {
117
+ const deadline = Date.now() + timeoutMs;
118
+ return new Promise((resolve, reject) => {
119
+ const attempt = () => {
120
+ const request = http.get(url, (response) => {
121
+ response.resume();
122
+ if (response.statusCode && response.statusCode < 500) resolve();
123
+ else retry();
124
+ });
125
+ request.on('error', retry);
126
+ request.setTimeout(1500, () => {
127
+ request.destroy();
128
+ retry();
129
+ });
130
+ };
131
+ const retry = () => {
132
+ if (Date.now() > deadline) {
133
+ reject(new Error(`mock preview did not start: ${url}`));
134
+ return;
135
+ }
136
+ setTimeout(attempt, 200);
137
+ };
138
+ attempt();
139
+ });
140
+ }
141
+
142
+ async function launchBrowser(explicit) {
143
+ const executablePath = browserExecutable(explicit);
144
+ if (executablePath) {
145
+ return chromium.launch({ headless: true, executablePath });
146
+ }
147
+ const channels = process.platform === 'win32' ? ['msedge', 'chrome'] : ['chrome', 'chromium', 'msedge'];
148
+ let lastError;
149
+ for (const channel of channels) {
150
+ try {
151
+ return await chromium.launch({ headless: true, channel });
152
+ } catch (error) {
153
+ lastError = error;
154
+ }
155
+ }
156
+ throw new Error(
157
+ `no local Chrome/Edge found; set DSH_BROWSER_PATH (last error: ${lastError instanceof Error ? lastError.message : lastError})`,
158
+ );
159
+ }
160
+
161
+ function stopProcess(child) {
162
+ if (!child || child.exitCode !== null) return;
163
+ try {
164
+ child.kill('SIGTERM');
165
+ } catch {
166
+ /* already gone */
167
+ }
168
+ }
169
+
170
+ async function main() {
171
+ const args = parseArgs(process.argv.slice(2));
172
+ const python = process.env.PYTHON || process.env.PYTHON3 || 'python';
173
+ let server;
174
+ let browser;
175
+ let url = args.url;
176
+ try {
177
+ if (!url) {
178
+ const port = args.port || await freePort();
179
+ const previewDir = path.join(ROOT, '.mock-web', `shot-${port}`);
180
+ server = spawn(python, [
181
+ path.join(ROOT, 'scripts', 'mock-preview.py'),
182
+ '--web', args.web,
183
+ '--out', previewDir,
184
+ '--port', String(port),
185
+ '--locale', args.locale,
186
+ '--timer', args.timer,
187
+ ], { cwd: ROOT, stdio: 'ignore', windowsHide: true });
188
+ url = `http://127.0.0.1:${port}/?page=${encodeURIComponent(args.page)}`;
189
+ await waitForHttp(url);
190
+ }
191
+
192
+ browser = await launchBrowser(args.browser);
193
+ const context = await browser.newContext({
194
+ viewport: { width: args.width, height: args.height },
195
+ deviceScaleFactor: 1,
196
+ });
197
+ const page = await context.newPage();
198
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 30_000 });
199
+ await page.evaluate(() => document.fonts.ready);
200
+ await page.waitForTimeout(args.wait);
201
+ if (args.click) {
202
+ await page.locator(args.click).first().click();
203
+ if (args.settle > 0) await page.waitForTimeout(args.settle);
204
+ }
205
+ fs.mkdirSync(path.dirname(args.out), { recursive: true });
206
+ await page.screenshot({ path: args.out });
207
+ console.log(args.out);
208
+ } finally {
209
+ if (browser) await browser.close().catch(() => {});
210
+ stopProcess(server);
211
+ }
212
+ }
213
+
214
+ main().catch((error) => {
215
+ console.error(error instanceof Error ? error.message : error);
216
+ process.exitCode = 1;
217
+ });