@hienlh/ppm 0.23.5 → 0.23.7

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 (714) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/CLAUDE.md +21 -1
  3. package/Dockerfile.test +8 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +30 -1
  7. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js +1 -0
  8. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.br +0 -0
  9. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.gz +0 -0
  10. package/dist/web/assets/{ai-resource-editor-DKcsVDle.js → ai-resource-editor-CY4n_Ccv.js} +1 -1
  11. package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.br +0 -0
  12. package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.gz +0 -0
  13. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js +1 -0
  14. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.br +0 -0
  15. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.gz +0 -0
  16. package/dist/web/assets/ai-settings-section-DDs11XV4.js +1 -0
  17. package/dist/web/assets/api-client-CJPnCOG-.js +1 -0
  18. package/dist/web/assets/{api-client-c8YOpTlT.js → api-client-D-SMbdiH.js} +1 -1
  19. package/dist/web/assets/api-client-D-SMbdiH.js.br +0 -0
  20. package/dist/web/assets/api-client-D-SMbdiH.js.gz +0 -0
  21. package/dist/web/assets/api-settings-BDkkhFMq.js +1 -0
  22. package/dist/web/assets/{api-settings-BmvNoCbn.js → api-settings-Bv9mLCty.js} +1 -1
  23. package/dist/web/assets/api-settings-Bv9mLCty.js.br +0 -0
  24. package/dist/web/assets/api-settings-Bv9mLCty.js.gz +0 -0
  25. package/dist/web/assets/{api-themes-6fOQJgM9.js → api-themes-3wW-zLzU.js} +1 -1
  26. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js +1 -0
  27. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.br +0 -0
  28. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.gz +0 -0
  29. package/dist/web/assets/apps-panel-DXAFsveZ.js +1 -0
  30. package/dist/web/assets/apps-panel-DXAFsveZ.js.br +0 -0
  31. package/dist/web/assets/apps-panel-DXAFsveZ.js.gz +0 -0
  32. package/dist/web/assets/{audio-preview-vaG2cB-k.js → audio-preview-BfES4Cx5.js} +1 -1
  33. package/dist/web/assets/audio-preview-BfES4Cx5.js.br +0 -0
  34. package/dist/web/assets/audio-preview-BfES4Cx5.js.gz +0 -0
  35. package/dist/web/assets/branch-review-tab-CA8hPSX9.js +1 -0
  36. package/dist/web/assets/branch-review-tab-CA8hPSX9.js.br +0 -0
  37. package/dist/web/assets/branch-review-tab-CA8hPSX9.js.gz +0 -0
  38. package/dist/web/assets/{button-Ci9EUgqd.js → button-DeAyLH9U.js} +1 -1
  39. package/dist/web/assets/button-DeAyLH9U.js.br +0 -0
  40. package/dist/web/assets/button-DeAyLH9U.js.gz +0 -0
  41. package/dist/web/assets/{chat-preparation-cache-BSIQGiWx.js → chat-preparation-cache-CcCd2g7A.js} +1 -1
  42. package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.br +0 -0
  43. package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.gz +0 -0
  44. package/dist/web/assets/chat-tab-BWi_bUzA.js +1 -0
  45. package/dist/web/assets/chat-tab-BWi_bUzA.js.br +0 -0
  46. package/dist/web/assets/chat-tab-BWi_bUzA.js.gz +0 -0
  47. package/dist/web/assets/chat-tab-BvJhIaVE.js +15 -0
  48. package/dist/web/assets/chat-tab-BvJhIaVE.js.br +0 -0
  49. package/dist/web/assets/chat-tab-BvJhIaVE.js.gz +0 -0
  50. package/dist/web/assets/code-editor-OJUdLVnn.js +7 -0
  51. package/dist/web/assets/code-editor-OJUdLVnn.js.br +0 -0
  52. package/dist/web/assets/code-editor-OJUdLVnn.js.gz +0 -0
  53. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js +1 -0
  54. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.br +0 -0
  55. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.gz +0 -0
  56. package/dist/web/assets/{conflict-editor-qMGJH0Bx.js → conflict-editor-BpKW_vbL.js} +1 -1
  57. package/dist/web/assets/conflict-editor-BpKW_vbL.js.br +0 -0
  58. package/dist/web/assets/conflict-editor-BpKW_vbL.js.gz +0 -0
  59. package/dist/web/assets/context-menu-7OX80HDB.js +1 -0
  60. package/dist/web/assets/context-menu-7OX80HDB.js.br +0 -0
  61. package/dist/web/assets/context-menu-7OX80HDB.js.gz +0 -0
  62. package/dist/web/assets/{csv-preview-D2I742dl.js → csv-preview-COYdTABa.js} +1 -1
  63. package/dist/web/assets/csv-preview-COYdTABa.js.br +0 -0
  64. package/dist/web/assets/csv-preview-COYdTABa.js.gz +0 -0
  65. package/dist/web/assets/{data-grid-overlay-editor-CQRVKr7y.js → data-grid-overlay-editor-CeZoduke.js} +1 -1
  66. package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.br +0 -0
  67. package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.gz +0 -0
  68. package/dist/web/assets/{database-viewer-Bz4LjgIa.js → database-viewer-BQcBqCKm.js} +1 -1
  69. package/dist/web/assets/database-viewer-BQcBqCKm.js.br +0 -0
  70. package/dist/web/assets/database-viewer-BQcBqCKm.js.gz +0 -0
  71. package/dist/web/assets/{design-export-client-ChMcMoqW.js → design-export-client-OrnGCSwu.js} +1 -1
  72. package/dist/web/assets/design-export-client-OrnGCSwu.js.br +0 -0
  73. package/dist/web/assets/design-export-client-OrnGCSwu.js.gz +0 -0
  74. package/dist/web/assets/{design-responsive-dialog-DNBhJJLf.js → design-responsive-dialog-vJjQrC4n.js} +1 -1
  75. package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.br +0 -0
  76. package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.gz +0 -0
  77. package/dist/web/assets/design-tab-BbmFk38H.js +33 -0
  78. package/dist/web/assets/design-tab-BbmFk38H.js.br +0 -0
  79. package/dist/web/assets/design-tab-BbmFk38H.js.gz +0 -0
  80. package/dist/web/assets/{designs-sidebar-panel-CapP1yXq.js → designs-sidebar-panel-D2kXwB8B.js} +1 -1
  81. package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.br +0 -0
  82. package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.gz +0 -0
  83. package/dist/web/assets/dialog-Bocc0Fgb.js +1 -0
  84. package/dist/web/assets/dialog-Bocc0Fgb.js.br +0 -0
  85. package/dist/web/assets/dialog-Bocc0Fgb.js.gz +0 -0
  86. package/dist/web/assets/{diff-viewer-CdXi7cZN.js → diff-viewer-Bj4DT6Mj.js} +1 -1
  87. package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.br +0 -0
  88. package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.gz +0 -0
  89. package/dist/web/assets/{docx-preview-C9XdKEj1.js → docx-preview-C2QqNGRE.js} +1 -1
  90. package/dist/web/assets/docx-preview-C2QqNGRE.js.br +0 -0
  91. package/dist/web/assets/docx-preview-C2QqNGRE.js.gz +0 -0
  92. package/dist/web/assets/editor-language-service-COqB2tuv.js +1 -0
  93. package/dist/web/assets/editor-language-service-COqB2tuv.js.br +3 -0
  94. package/dist/web/assets/editor-language-service-COqB2tuv.js.gz +0 -0
  95. package/dist/web/assets/explorer-body-DGrYajDN.js +2 -0
  96. package/dist/web/assets/explorer-body-DGrYajDN.js.br +0 -0
  97. package/dist/web/assets/explorer-body-DGrYajDN.js.gz +0 -0
  98. package/dist/web/assets/explorer-window-content-DLzkODTP.js +1 -0
  99. package/dist/web/assets/explorer-window-content-DLzkODTP.js.br +0 -0
  100. package/dist/web/assets/explorer-window-content-DLzkODTP.js.gz +0 -0
  101. package/dist/web/assets/{extension-manager-section-B2nZzJ2b.js → extension-manager-section-Bu0qcB2i.js} +1 -1
  102. package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.br +0 -0
  103. package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.gz +0 -0
  104. package/dist/web/assets/{extension-webview-BGoDxkdF.js → extension-webview-G_WJ0N52.js} +1 -1
  105. package/dist/web/assets/extension-webview-G_WJ0N52.js.br +0 -0
  106. package/dist/web/assets/extension-webview-G_WJ0N52.js.gz +0 -0
  107. package/dist/web/assets/{file-icons-gx_FBOXY.js → file-icons-l7fNOmdS.js} +1 -1
  108. package/dist/web/assets/file-icons-l7fNOmdS.js.br +0 -0
  109. package/dist/web/assets/file-icons-l7fNOmdS.js.gz +0 -0
  110. package/dist/web/assets/{file-store-D9KE0kQC.js → file-store-B0e7sLag.js} +1 -1
  111. package/dist/web/assets/file-store-B0e7sLag.js.br +0 -0
  112. package/dist/web/assets/file-store-B0e7sLag.js.gz +0 -0
  113. package/dist/web/assets/{files-settings-section-DC3OzzOl.js → files-settings-section-ChZ_CynX.js} +1 -1
  114. package/dist/web/assets/files-settings-section-ChZ_CynX.js.br +0 -0
  115. package/dist/web/assets/files-settings-section-ChZ_CynX.js.gz +0 -0
  116. package/dist/web/assets/format-bytes-__396eCN.js +1 -0
  117. package/dist/web/assets/{format-file-meta-mQwA6SuY.js → format-file-meta-o1XBuoKS.js} +1 -1
  118. package/dist/web/assets/format-file-meta-o1XBuoKS.js.br +0 -0
  119. package/dist/web/assets/format-file-meta-o1XBuoKS.js.gz +0 -0
  120. package/dist/web/assets/{fs-api-DL36slbd.js → fs-api-CeHKwx95.js} +1 -1
  121. package/dist/web/assets/fs-api-CeHKwx95.js.br +0 -0
  122. package/dist/web/assets/fs-api-CeHKwx95.js.gz +0 -0
  123. package/dist/web/assets/{general-settings-section-i41W43ER.js → general-settings-section-MSvDEbFa.js} +1 -1
  124. package/dist/web/assets/general-settings-section-MSvDEbFa.js.br +0 -0
  125. package/dist/web/assets/general-settings-section-MSvDEbFa.js.gz +0 -0
  126. package/dist/web/assets/{git-log-panel-DHzPIkJg.js → git-log-panel-gw5_a6j3.js} +1 -1
  127. package/dist/web/assets/git-log-panel-gw5_a6j3.js.br +0 -0
  128. package/dist/web/assets/git-log-panel-gw5_a6j3.js.gz +0 -0
  129. package/dist/web/assets/{git-repo-store-DSypNj19.js → git-repo-store-BpPnah5l.js} +1 -1
  130. package/dist/web/assets/git-repo-store-BpPnah5l.js.br +0 -0
  131. package/dist/web/assets/git-repo-store-BpPnah5l.js.gz +0 -0
  132. package/dist/web/assets/{glide-data-grid-Dj4vrDvj.js → glide-data-grid-Db2Cl26B.js} +5 -5
  133. package/dist/web/assets/glide-data-grid-Db2Cl26B.js.br +0 -0
  134. package/dist/web/assets/glide-data-grid-Db2Cl26B.js.gz +0 -0
  135. package/dist/web/assets/group-chat-tab-B4etmYTR.js +1 -0
  136. package/dist/web/assets/group-chat-tab-B4etmYTR.js.br +0 -0
  137. package/dist/web/assets/group-chat-tab-B4etmYTR.js.gz +0 -0
  138. package/dist/web/assets/{image-preview-CGdZrmLo.js → image-preview-CjNhpfWw.js} +1 -1
  139. package/dist/web/assets/index-CtAvmybi.js +52 -0
  140. package/dist/web/assets/index-CtAvmybi.js.br +0 -0
  141. package/dist/web/assets/index-CtAvmybi.js.gz +0 -0
  142. package/dist/web/assets/index-DeCJasyy.css +1 -0
  143. package/dist/web/assets/index-DeCJasyy.css.br +0 -0
  144. package/dist/web/assets/index-DeCJasyy.css.gz +0 -0
  145. package/dist/web/assets/{jira-watcher-section-BJ9edGEP.js → jira-watcher-section-CKSI1Alo.js} +1 -1
  146. package/dist/web/assets/keybindings-store-C4_yyU4u.js +1 -0
  147. package/dist/web/assets/{keyboard-shortcuts-section-CxdGSNGr.js → keyboard-shortcuts-section-CEaVUc9a.js} +1 -1
  148. package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.br +0 -0
  149. package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.gz +0 -0
  150. package/dist/web/assets/language-servers-section-ePtEpnDc.js +2 -0
  151. package/dist/web/assets/language-servers-section-ePtEpnDc.js.br +0 -0
  152. package/dist/web/assets/language-servers-section-ePtEpnDc.js.gz +0 -0
  153. package/dist/web/assets/lsp-client-XzmR3Brr.js +1 -0
  154. package/dist/web/assets/lsp-client-XzmR3Brr.js.br +0 -0
  155. package/dist/web/assets/lsp-client-XzmR3Brr.js.gz +0 -0
  156. package/dist/web/assets/markdown-renderer-CTtDap-l.js +1 -0
  157. package/dist/web/assets/{markdown-renderer-ok96IgnS.js → markdown-renderer-D05fAr57.js} +2 -2
  158. package/dist/web/assets/markdown-renderer-D05fAr57.js.br +0 -0
  159. package/dist/web/assets/markdown-renderer-D05fAr57.js.gz +0 -0
  160. package/dist/web/assets/{media-url-CzUJrREv.js → media-url-C6NumuN5.js} +1 -1
  161. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js +1 -0
  162. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.br +0 -0
  163. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.gz +0 -0
  164. package/dist/web/assets/{mobile-bottom-sheet-CuNi2FRv.js → mobile-bottom-sheet-D4UyOatB.js} +1 -1
  165. package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.br +0 -0
  166. package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.gz +0 -0
  167. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js +1 -0
  168. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.br +0 -0
  169. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.gz +0 -0
  170. package/dist/web/assets/{new-design-dialog-Drkjb_LI.js → new-design-dialog-BgmQopJM.js} +1 -1
  171. package/dist/web/assets/new-design-dialog-BgmQopJM.js.br +0 -0
  172. package/dist/web/assets/new-design-dialog-BgmQopJM.js.gz +0 -0
  173. package/dist/web/assets/notification-store-q5QBklKA.js +1 -0
  174. package/dist/web/assets/{notifications-settings-section-C05WYqcE.js → notifications-settings-section-BBJ0r9OS.js} +1 -1
  175. package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.br +0 -0
  176. package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.gz +0 -0
  177. package/dist/web/assets/{onboarding-entry-CmUqv1kl.js → onboarding-entry-BYOYswsi.js} +1 -1
  178. package/dist/web/assets/onboarding-entry-BYOYswsi.js.br +0 -0
  179. package/dist/web/assets/onboarding-entry-BYOYswsi.js.gz +0 -0
  180. package/dist/web/assets/{onboarding-quick-orientation-DIhWv1CN.js → onboarding-quick-orientation-DPHYFMbA.js} +1 -1
  181. package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.br +0 -0
  182. package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.gz +0 -0
  183. package/dist/web/assets/overview-panel-pkPs44Tv.js +1 -0
  184. package/dist/web/assets/overview-panel-pkPs44Tv.js.br +0 -0
  185. package/dist/web/assets/overview-panel-pkPs44Tv.js.gz +0 -0
  186. package/dist/web/assets/{pdf-preview-qdXmMkOR.js → pdf-preview-c-L_kDAs.js} +1 -1
  187. package/dist/web/assets/pdf-preview-c-L_kDAs.js.br +0 -0
  188. package/dist/web/assets/pdf-preview-c-L_kDAs.js.gz +0 -0
  189. package/dist/web/assets/performance-panel-yPrq0DJV.js +3 -0
  190. package/dist/web/assets/performance-panel-yPrq0DJV.js.br +0 -0
  191. package/dist/web/assets/performance-panel-yPrq0DJV.js.gz +0 -0
  192. package/dist/web/assets/{postgres-viewer-Xvlun_n5.js → postgres-viewer-CnwDCMNd.js} +1 -1
  193. package/dist/web/assets/postgres-viewer-CnwDCMNd.js.br +0 -0
  194. package/dist/web/assets/postgres-viewer-CnwDCMNd.js.gz +0 -0
  195. package/dist/web/assets/{ppmbot-settings-section-C8PFlvaq.js → ppmbot-settings-section-BDXoSIDp.js} +1 -1
  196. package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.br +0 -0
  197. package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.gz +0 -0
  198. package/dist/web/assets/{pptx-export-CG6MRNok.js → pptx-export-D0RDY48h.js} +4 -4
  199. package/dist/web/assets/pptx-export-D0RDY48h.js.br +0 -0
  200. package/dist/web/assets/pptx-export-D0RDY48h.js.gz +0 -0
  201. package/dist/web/assets/{problems-panel-CFYO0Ro3.js → problems-panel-BgY8oyo9.js} +1 -1
  202. package/dist/web/assets/problems-panel-BgY8oyo9.js.br +0 -0
  203. package/dist/web/assets/problems-panel-BgY8oyo9.js.gz +0 -0
  204. package/dist/web/assets/process-row-format-CaaMcHzn.js +1 -0
  205. package/dist/web/assets/process-table-BOwDtXuG.js +1 -0
  206. package/dist/web/assets/process-table-BOwDtXuG.js.br +0 -0
  207. package/dist/web/assets/process-table-BOwDtXuG.js.gz +0 -0
  208. package/dist/web/assets/{project-framework-store-XxFXcUpb.js → project-framework-store-UDEMu28U.js} +1 -1
  209. package/dist/web/assets/{project-store-C5gJXkvb.js → project-store-DGa31y5B.js} +1 -1
  210. package/dist/web/assets/project-store-DGa31y5B.js.br +0 -0
  211. package/dist/web/assets/project-store-DGa31y5B.js.gz +0 -0
  212. package/dist/web/assets/{proxy-settings-section-BcHIEANE.js → proxy-settings-section-Db6Kc6xG.js} +1 -1
  213. package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.br +0 -0
  214. package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.gz +0 -0
  215. package/dist/web/assets/{query-audit-section-UKG_smyt.js → query-audit-section-C0PAXhfT.js} +1 -1
  216. package/dist/web/assets/query-audit-section-C0PAXhfT.js.br +0 -0
  217. package/dist/web/assets/query-audit-section-C0PAXhfT.js.gz +0 -0
  218. package/dist/web/assets/{remote-desktop-clipboard-notice-BGsp0edA.js → remote-desktop-clipboard-notice-DOVtobj_.js} +1 -1
  219. package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.br +0 -0
  220. package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.gz +0 -0
  221. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js +2 -0
  222. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.br +0 -0
  223. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.gz +0 -0
  224. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js +1 -0
  225. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.br +0 -0
  226. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.gz +0 -0
  227. package/dist/web/assets/{remote-desktop-readiness-gate-CsZCCRJI.js → remote-desktop-readiness-gate-BsfoJzdc.js} +1 -1
  228. package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.br +0 -0
  229. package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.gz +0 -0
  230. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js +1 -0
  231. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.br +0 -0
  232. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.gz +0 -0
  233. package/dist/web/assets/{schedules-settings-section-Ci0O2Ng6.js → schedules-settings-section-CvbjQM8z.js} +1 -1
  234. package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.br +0 -0
  235. package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.gz +0 -0
  236. package/dist/web/assets/{scroll-area-XonvWYn_.js → scroll-area-sjOeAfoD.js} +1 -1
  237. package/dist/web/assets/scroll-area-sjOeAfoD.js.br +0 -0
  238. package/dist/web/assets/scroll-area-sjOeAfoD.js.gz +0 -0
  239. package/dist/web/assets/{separator-sQj04Ari.js → separator-9A3PCG1t.js} +1 -1
  240. package/dist/web/assets/services-panel-CimW47P8.js +1 -0
  241. package/dist/web/assets/services-panel-CimW47P8.js.br +0 -0
  242. package/dist/web/assets/services-panel-CimW47P8.js.gz +0 -0
  243. package/dist/web/assets/settings-body-BTXNtpWL.js +2 -0
  244. package/dist/web/assets/settings-body-BTXNtpWL.js.br +0 -0
  245. package/dist/web/assets/settings-body-BTXNtpWL.js.gz +0 -0
  246. package/dist/web/assets/settings-store-DvftLqzy.js +2 -0
  247. package/dist/web/assets/settings-store-DvftLqzy.js.br +0 -0
  248. package/dist/web/assets/settings-store-DvftLqzy.js.gz +0 -0
  249. package/dist/web/assets/settings-tab-CGhaRXjs.js +1 -0
  250. package/dist/web/assets/settings-window-content--Uyg_Psr.js +1 -0
  251. package/dist/web/assets/sortable-header-DUW9T1Hz.js +1 -0
  252. package/dist/web/assets/sortable-header-DUW9T1Hz.js.br +0 -0
  253. package/dist/web/assets/sortable-header-DUW9T1Hz.js.gz +0 -0
  254. package/dist/web/assets/{sql-query-editor-4iCGE4h2.js → sql-query-editor-Dn-cgDrW.js} +1 -1
  255. package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.br +0 -0
  256. package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.gz +0 -0
  257. package/dist/web/assets/{sqlite-viewer-CK6DulYk.js → sqlite-viewer-B0NfaMjq.js} +1 -1
  258. package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.br +0 -0
  259. package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.gz +0 -0
  260. package/dist/web/assets/system-monitor-body-CxCfRQFb.js +2 -0
  261. package/dist/web/assets/system-monitor-body-CxCfRQFb.js.br +0 -0
  262. package/dist/web/assets/system-monitor-body-CxCfRQFb.js.gz +0 -0
  263. package/dist/web/assets/system-monitor-tab-Cb3EZUdp.js +1 -0
  264. package/dist/web/assets/system-monitor-window-content-51CA-2PH.js +1 -0
  265. package/dist/web/assets/{tab-host-window-content-CWBaWKjA.js → tab-host-window-content-CSrcmKNG.js} +1 -1
  266. package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.br +0 -0
  267. package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.gz +0 -0
  268. package/dist/web/assets/{team-member-sheet-Br6mrxrw.js → team-member-sheet-D4X6nh-x.js} +2 -2
  269. package/dist/web/assets/team-member-sheet-D4X6nh-x.js.br +0 -0
  270. package/dist/web/assets/team-member-sheet-D4X6nh-x.js.gz +0 -0
  271. package/dist/web/assets/{team-member-window-content-CwCy7ZWf.js → team-member-window-content-BxkbGVIL.js} +1 -1
  272. package/dist/web/assets/team-member-window-content-BxkbGVIL.js.br +0 -0
  273. package/dist/web/assets/team-member-window-content-BxkbGVIL.js.gz +0 -0
  274. package/dist/web/assets/{terminal-tab-BW14RpCb.js → terminal-tab-BYo34IqU.js} +3 -3
  275. package/dist/web/assets/terminal-tab-BYo34IqU.js.br +0 -0
  276. package/dist/web/assets/terminal-tab-BYo34IqU.js.gz +0 -0
  277. package/dist/web/assets/{tool-cards-CgOBEUnb.js → tool-cards-DvXjOped.js} +4 -4
  278. package/dist/web/assets/tool-cards-DvXjOped.js.br +0 -0
  279. package/dist/web/assets/tool-cards-DvXjOped.js.gz +0 -0
  280. package/dist/web/assets/{use-blob-url-CmavyEP8.js → use-blob-url-CISBWKG5.js} +1 -1
  281. package/dist/web/assets/use-inline-blame-Br6mheS4.js +16 -0
  282. package/dist/web/assets/use-inline-blame-Br6mheS4.js.br +0 -0
  283. package/dist/web/assets/use-inline-blame-Br6mheS4.js.gz +0 -0
  284. package/dist/web/assets/use-lsp-BpJmvQ0n.js +6 -0
  285. package/dist/web/assets/use-lsp-BpJmvQ0n.js.br +0 -0
  286. package/dist/web/assets/use-lsp-BpJmvQ0n.js.gz +0 -0
  287. package/dist/web/assets/{use-monaco-theme-rH6epJYA.js → use-monaco-theme-UGOzyIhv.js} +1 -1
  288. package/dist/web/assets/use-process-kill-CyiiUD1E.js +1 -0
  289. package/dist/web/assets/use-process-kill-CyiiUD1E.js.br +0 -0
  290. package/dist/web/assets/use-process-kill-CyiiUD1E.js.gz +0 -0
  291. package/dist/web/assets/{use-websocket-QwURkjpV.js → use-websocket-Dpl21cHG.js} +1 -1
  292. package/dist/web/assets/{vendor-ui-BHh12_0v.js → vendor-ui-P9Ym7hCv.js} +1 -1
  293. package/dist/web/assets/vendor-ui-P9Ym7hCv.js.br +0 -0
  294. package/dist/web/assets/vendor-ui-P9Ym7hCv.js.gz +0 -0
  295. package/dist/web/assets/video-preview-D6Jw-IZK.js +1 -0
  296. package/dist/web/assets/video-preview-D6Jw-IZK.js.br +0 -0
  297. package/dist/web/assets/video-preview-D6Jw-IZK.js.gz +0 -0
  298. package/dist/web/assets/voice-settings-section-CcHQDYzN.js +1 -0
  299. package/dist/web/assets/voice-settings-section-CcHQDYzN.js.br +0 -0
  300. package/dist/web/assets/voice-settings-section-CcHQDYzN.js.gz +0 -0
  301. package/dist/web/assets/ws-auth-CqI8-aoA.js +1 -0
  302. package/dist/web/index.html +20 -20
  303. package/dist/web/index.html.br +0 -0
  304. package/dist/web/index.html.gz +0 -0
  305. package/dist/web/sw.js +1 -1
  306. package/dist/web/sw.js.br +0 -0
  307. package/dist/web/sw.js.gz +0 -0
  308. package/docs/deployment-guide.md +23 -0
  309. package/package.json +1 -1
  310. package/release-from-worktree.sh +224 -0
  311. package/src/cli/commands/config-cmd.ts +12 -0
  312. package/src/providers/claude-agent-sdk.ts +65 -2
  313. package/src/providers/codex-app-server/codex-jsonrpc-client.ts +16 -1
  314. package/src/providers/codex-app-server/codex-provider.ts +39 -7
  315. package/src/providers/codex-app-server/codex-reset-credit.ts +49 -0
  316. package/src/providers/provider-probe.ts +63 -0
  317. package/src/providers/registry.ts +109 -29
  318. package/src/server/index.ts +9 -0
  319. package/src/server/middleware/auth.ts +5 -1
  320. package/src/server/routes/codex-accounts.ts +14 -0
  321. package/src/server/routes/cross-origin-guard.ts +20 -0
  322. package/src/server/routes/git.ts +40 -4
  323. package/src/server/routes/lsp.ts +97 -4
  324. package/src/server/routes/resources.ts +72 -9
  325. package/src/server/routes/settings.ts +19 -1
  326. package/src/server/routes/speech.ts +66 -0
  327. package/src/server/routes/system-services.ts +76 -0
  328. package/src/server/routes/tunnel.ts +33 -1
  329. package/src/server/ws/chat.ts +74 -3
  330. package/src/services/codex-account-login.ts +26 -2
  331. package/src/services/codex-reset-credit.service.ts +83 -0
  332. package/src/services/compaction-savings.ts +127 -0
  333. package/src/services/db.service.ts +123 -8
  334. package/src/services/edge-forwarder.ts +191 -191
  335. package/src/services/fs-ops/fs-ops-trash.service.ts +22 -9
  336. package/src/services/git-refs/for-each-ref.ts +118 -0
  337. package/src/services/git.service.ts +25 -2
  338. package/src/services/host-info/spawn-runner.ts +48 -4
  339. package/src/services/jsonl-transcript-parser.ts +7 -42
  340. package/src/services/lsp/lsp-install.ts +289 -0
  341. package/src/services/lsp/lsp-manager.ts +160 -29
  342. package/src/services/lsp/server-registry.ts +120 -4
  343. package/src/services/named-tunnel/named-tunnel-config.ts +8 -2
  344. package/src/services/named-tunnel/named-tunnel-setup.service.ts +4 -0
  345. package/src/services/read-lines.ts +54 -0
  346. package/src/services/speech-to-text/whisper-catalog.ts +142 -0
  347. package/src/services/speech-to-text/whisper-download.ts +113 -0
  348. package/src/services/speech-to-text/whisper-hallucinations.ts +51 -0
  349. package/src/services/speech-to-text/whisper-install.service.ts +247 -0
  350. package/src/services/speech-to-text/whisper-paths.ts +62 -0
  351. package/src/services/speech-to-text/whisper-transcribe.service.ts +117 -0
  352. package/src/services/supervisor.ts +94 -11
  353. package/src/services/system-metrics/cpu-details-linux.ts +252 -0
  354. package/src/services/system-metrics/cpu-memory-collector.ts +140 -18
  355. package/src/services/system-metrics/device-collector-linux.ts +31 -0
  356. package/src/services/system-metrics/device-collector-types.ts +40 -0
  357. package/src/services/system-metrics/disk-devices-linux.ts +156 -0
  358. package/src/services/system-metrics/disk-inventory-linux.ts +150 -0
  359. package/src/services/system-metrics/fans-linux.ts +51 -0
  360. package/src/services/system-metrics/gpu-api-versions.ts +120 -0
  361. package/src/services/system-metrics/gpu-devices-linux.ts +370 -0
  362. package/src/services/system-metrics/gpu-fdinfo-linux.ts +253 -0
  363. package/src/services/system-metrics/hardware-inventory.ts +50 -0
  364. package/src/services/system-metrics/linux-fs.ts +43 -0
  365. package/src/services/system-metrics/memory-darwin.ts +91 -0
  366. package/src/services/system-metrics/memory-linux.ts +156 -0
  367. package/src/services/system-metrics/net-devices-linux.ts +114 -0
  368. package/src/services/system-metrics/net-inventory-linux.ts +100 -0
  369. package/src/services/system-metrics/partitions-linux.ts +158 -0
  370. package/src/services/system-metrics/process-collector-linux.ts +58 -6
  371. package/src/services/system-metrics/process-collector-types.ts +8 -1
  372. package/src/services/system-metrics/process-details-darwin.ts +166 -0
  373. package/src/services/system-metrics/process-details-linux.ts +150 -0
  374. package/src/services/system-metrics/process-grouping.ts +2 -1
  375. package/src/services/system-metrics/process-rows-builder.ts +2 -0
  376. package/src/services/system-metrics/process-swap-linux.ts +37 -0
  377. package/src/services/system-metrics/signal-executor.ts +84 -0
  378. package/src/services/system-metrics/signal-request-handler.ts +92 -0
  379. package/src/services/system-metrics/system-metrics-platform.ts +30 -4
  380. package/src/services/system-metrics/system-metrics-tick.ts +73 -4
  381. package/src/services/system-metrics/system-metrics.service.ts +87 -2
  382. package/src/services/system-metrics/udev-db.ts +40 -0
  383. package/src/services/system-services/app-cgroup-linux.ts +115 -0
  384. package/src/services/system-services/app-icon-service.ts +31 -0
  385. package/src/services/system-services/app-icons-linux.ts +124 -0
  386. package/src/services/system-services/apps-linux.ts +174 -0
  387. package/src/services/system-services/desktop-entry.ts +103 -0
  388. package/src/services/system-services/service-guard.ts +124 -0
  389. package/src/services/system-services/systemd-collector.ts +203 -0
  390. package/src/services/system-services/systemd-parse.ts +162 -0
  391. package/src/services/windows-process-tree.ts +351 -351
  392. package/src/shared/prompt-cache-idle.ts +197 -0
  393. package/src/shared/turn-usage.ts +85 -1
  394. package/src/shared/usage-extra.ts +19 -0
  395. package/src/types/chat.ts +29 -0
  396. package/src/types/config.ts +14 -0
  397. package/src/types/git.ts +41 -0
  398. package/src/types/system-hardware.ts +137 -0
  399. package/src/types/system-metrics.ts +231 -2
  400. package/src/types/system-services.ts +73 -0
  401. package/src/web/components/branch-review/branch-review-tab.tsx +20 -20
  402. package/src/web/components/branch-review/branch-review-tree-row.tsx +1 -13
  403. package/src/web/components/chat/chat-tab.tsx +3 -0
  404. package/src/web/components/chat/codex-usage-panel.tsx +4 -0
  405. package/src/web/components/chat/compaction-divider.tsx +48 -0
  406. package/src/web/components/chat/idle-cache-notice.tsx +67 -0
  407. package/src/web/components/chat/message-input.tsx +48 -13
  408. package/src/web/components/chat/message-list.tsx +15 -0
  409. package/src/web/components/chat/prompt-cache-chip.tsx +74 -0
  410. package/src/web/components/editor/code-editor.tsx +1 -0
  411. package/src/web/components/editor/editor-language-service.tsx +11 -3
  412. package/src/web/components/editor/lsp-status.tsx +115 -13
  413. package/src/web/components/git/branch-picker.tsx +419 -0
  414. package/src/web/components/git/branch-select.tsx +34 -0
  415. package/src/web/components/git/create-worktree-dialog.tsx +17 -16
  416. package/src/web/components/git/git-repo-picker.tsx +28 -40
  417. package/src/web/components/layout/status-bar.tsx +42 -19
  418. package/src/web/components/settings/accounts/account-card.tsx +10 -2
  419. package/src/web/components/settings/accounts/account-usage-extras.tsx +3 -2
  420. package/src/web/components/settings/accounts/codex-accounts-section.tsx +3 -1
  421. package/src/web/components/settings/accounts/codex-reset-credit-button.tsx +120 -0
  422. package/src/web/components/settings/ai-settings-section.tsx +67 -3
  423. package/src/web/components/settings/appearance-settings-section.tsx +2 -28
  424. package/src/web/components/settings/language-servers-section.tsx +362 -0
  425. package/src/web/components/settings/settings-categories.ts +8 -3
  426. package/src/web/components/settings/settings-section-content.tsx +2 -0
  427. package/src/web/components/settings/voice-settings-section.tsx +283 -0
  428. package/src/web/components/system/apps/app-row.tsx +110 -0
  429. package/src/web/components/system/apps/app-rows.ts +140 -0
  430. package/src/web/components/system/apps/apps-panel.tsx +129 -0
  431. package/src/web/components/system/metric-chart-canvas.tsx +79 -3
  432. package/src/web/components/system/overview-cards/card-shell.tsx +54 -0
  433. package/src/web/components/system/overview-cards/cpu-card.tsx +11 -6
  434. package/src/web/components/system/overview-cards/disk-card.tsx +11 -6
  435. package/src/web/components/system/overview-cards/gpu-card.tsx +16 -6
  436. package/src/web/components/system/overview-cards/mem-card.tsx +11 -6
  437. package/src/web/components/system/overview-cards/net-card.tsx +11 -6
  438. package/src/web/components/system/overview-device-target.ts +69 -0
  439. package/src/web/components/system/overview-panel.tsx +15 -1
  440. package/src/web/components/system/performance/cpu-core-grid.tsx +74 -0
  441. package/src/web/components/system/performance/cpu-detail.tsx +220 -0
  442. package/src/web/components/system/performance/detail-parts.tsx +149 -0
  443. package/src/web/components/system/performance/device-list.ts +139 -0
  444. package/src/web/components/system/performance/disk-detail.tsx +64 -0
  445. package/src/web/components/system/performance/fans-detail.tsx +35 -0
  446. package/src/web/components/system/performance/gpu-detail.tsx +116 -0
  447. package/src/web/components/system/performance/memory-detail.tsx +109 -0
  448. package/src/web/components/system/performance/nic-detail.tsx +55 -0
  449. package/src/web/components/system/performance/partition-list.tsx +79 -0
  450. package/src/web/components/system/performance/performance-panel.tsx +206 -0
  451. package/src/web/components/system/performance/use-hardware-inventory.ts +48 -0
  452. package/src/web/components/system/process-columns-grid.ts +5 -3
  453. package/src/web/components/system/process-details-dialog.tsx +116 -0
  454. package/src/web/components/system/process-group-row.tsx +6 -1
  455. package/src/web/components/system/process-row-format.ts +6 -0
  456. package/src/web/components/system/process-row-menu.tsx +72 -0
  457. package/src/web/components/system/process-row.tsx +7 -1
  458. package/src/web/components/system/process-table-model.ts +1 -1
  459. package/src/web/components/system/process-table-sort.ts +8 -4
  460. package/src/web/components/system/process-table-toolbar.tsx +10 -2
  461. package/src/web/components/system/process-table-totals.ts +3 -0
  462. package/src/web/components/system/process-table.tsx +25 -2
  463. package/src/web/components/system/services/service-action-confirm.tsx +100 -0
  464. package/src/web/components/system/services/service-columns.ts +103 -0
  465. package/src/web/components/system/services/service-details-sheet.tsx +97 -0
  466. package/src/web/components/system/services/service-resources.ts +105 -0
  467. package/src/web/components/system/services/service-row.tsx +146 -0
  468. package/src/web/components/system/services/service-rows.ts +92 -0
  469. package/src/web/components/system/services/service-sort.ts +90 -0
  470. package/src/web/components/system/services/services-header.tsx +51 -0
  471. package/src/web/components/system/services/services-panel.tsx +207 -0
  472. package/src/web/components/system/services/use-services.ts +92 -0
  473. package/src/web/components/system/sortable-header.tsx +13 -8
  474. package/src/web/components/system/sysmon-preferences.tsx +85 -0
  475. package/src/web/components/system/system-monitor-body.tsx +101 -43
  476. package/src/web/components/system/system-monitor-window-content.tsx +2 -3
  477. package/src/web/components/system/use-process-signal.ts +87 -0
  478. package/src/web/components/tunnels/public-tunnel-switch.tsx +82 -0
  479. package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
  480. package/src/web/components/ui/dialog.tsx +14 -1
  481. package/src/web/components/ui/search-select.tsx +279 -0
  482. package/src/web/components/ui/start-ellipsis.tsx +18 -0
  483. package/src/web/hooks/use-chat.ts +28 -0
  484. package/src/web/hooks/use-global-keybindings.ts +7 -5
  485. package/src/web/hooks/use-lsp.ts +23 -2
  486. package/src/web/hooks/use-minute-clock.ts +20 -0
  487. package/src/web/hooks/use-whisper-voice-input.ts +113 -0
  488. package/src/web/lib/api-tunnels.ts +19 -0
  489. package/src/web/lib/branch-select-items.ts +29 -0
  490. package/src/web/lib/cpu-graph-mode.ts +90 -0
  491. package/src/web/lib/format-bytes.ts +14 -0
  492. package/src/web/lib/git-ref-picker.ts +120 -0
  493. package/src/web/lib/lsp/lsp-client.ts +32 -0
  494. package/src/web/lib/search-select-rows.ts +55 -0
  495. package/src/web/lib/temperature.ts +27 -0
  496. package/src/web/lib/voice-wav.ts +69 -0
  497. package/src/web/stores/git-status-store.ts +31 -17
  498. package/src/web/stores/keybindings-store.ts +14 -1
  499. package/src/web/stores/settings-store.ts +65 -0
  500. package/src/web/styles/globals.css +11 -0
  501. package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js +0 -1
  502. package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.br +0 -0
  503. package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.gz +0 -0
  504. package/dist/web/assets/ai-resource-editor-DKcsVDle.js.br +0 -0
  505. package/dist/web/assets/ai-resource-editor-DKcsVDle.js.gz +0 -0
  506. package/dist/web/assets/ai-settings-section-D7zJ-hCO.js +0 -1
  507. package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.br +0 -0
  508. package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.gz +0 -0
  509. package/dist/web/assets/ai-settings-section-OPqC2bYG.js +0 -1
  510. package/dist/web/assets/api-client-BgedDTvo.js +0 -1
  511. package/dist/web/assets/api-client-c8YOpTlT.js.br +0 -0
  512. package/dist/web/assets/api-client-c8YOpTlT.js.gz +0 -0
  513. package/dist/web/assets/api-settings-BmvNoCbn.js.br +0 -0
  514. package/dist/web/assets/api-settings-BmvNoCbn.js.gz +0 -0
  515. package/dist/web/assets/api-settings-Bti1-Zn3.js +0 -1
  516. package/dist/web/assets/appearance-settings-section-DL466x83.js +0 -1
  517. package/dist/web/assets/appearance-settings-section-DL466x83.js.br +0 -0
  518. package/dist/web/assets/appearance-settings-section-DL466x83.js.gz +0 -0
  519. package/dist/web/assets/audio-preview-vaG2cB-k.js.br +0 -0
  520. package/dist/web/assets/audio-preview-vaG2cB-k.js.gz +0 -0
  521. package/dist/web/assets/branch-review-tab-Dag86n_R.js +0 -1
  522. package/dist/web/assets/branch-review-tab-Dag86n_R.js.br +0 -0
  523. package/dist/web/assets/branch-review-tab-Dag86n_R.js.gz +0 -0
  524. package/dist/web/assets/button-Ci9EUgqd.js.br +0 -0
  525. package/dist/web/assets/button-Ci9EUgqd.js.gz +0 -0
  526. package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.br +0 -0
  527. package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.gz +0 -0
  528. package/dist/web/assets/chat-tab-CcYZLXls.js +0 -15
  529. package/dist/web/assets/chat-tab-CcYZLXls.js.br +0 -0
  530. package/dist/web/assets/chat-tab-CcYZLXls.js.gz +0 -0
  531. package/dist/web/assets/chat-tab-QFDhzzBZ.js +0 -1
  532. package/dist/web/assets/chat-tab-QFDhzzBZ.js.br +0 -0
  533. package/dist/web/assets/chat-tab-QFDhzzBZ.js.gz +0 -0
  534. package/dist/web/assets/code-editor-K-IN8VwJ.js +0 -7
  535. package/dist/web/assets/code-editor-K-IN8VwJ.js.br +0 -0
  536. package/dist/web/assets/code-editor-K-IN8VwJ.js.gz +0 -0
  537. package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js +0 -1
  538. package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.br +0 -0
  539. package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.gz +0 -0
  540. package/dist/web/assets/conflict-editor-qMGJH0Bx.js.br +0 -0
  541. package/dist/web/assets/conflict-editor-qMGJH0Bx.js.gz +0 -0
  542. package/dist/web/assets/context-menu-CjYTq8DI.js +0 -1
  543. package/dist/web/assets/context-menu-CjYTq8DI.js.br +0 -0
  544. package/dist/web/assets/context-menu-CjYTq8DI.js.gz +0 -0
  545. package/dist/web/assets/csv-preview-D2I742dl.js.br +0 -0
  546. package/dist/web/assets/csv-preview-D2I742dl.js.gz +0 -0
  547. package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.br +0 -0
  548. package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.gz +0 -0
  549. package/dist/web/assets/database-viewer-Bz4LjgIa.js.br +0 -0
  550. package/dist/web/assets/database-viewer-Bz4LjgIa.js.gz +0 -0
  551. package/dist/web/assets/design-export-client-ChMcMoqW.js.br +0 -0
  552. package/dist/web/assets/design-export-client-ChMcMoqW.js.gz +0 -0
  553. package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.br +0 -0
  554. package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.gz +0 -0
  555. package/dist/web/assets/design-tab-C987MtMR.js +0 -33
  556. package/dist/web/assets/design-tab-C987MtMR.js.br +0 -0
  557. package/dist/web/assets/design-tab-C987MtMR.js.gz +0 -0
  558. package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.br +0 -0
  559. package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.gz +0 -0
  560. package/dist/web/assets/dialog-B8clVkyi.js +0 -1
  561. package/dist/web/assets/dialog-B8clVkyi.js.br +0 -0
  562. package/dist/web/assets/dialog-B8clVkyi.js.gz +0 -0
  563. package/dist/web/assets/diff-viewer-CdXi7cZN.js.br +0 -0
  564. package/dist/web/assets/diff-viewer-CdXi7cZN.js.gz +0 -0
  565. package/dist/web/assets/docx-preview-C9XdKEj1.js.br +0 -0
  566. package/dist/web/assets/docx-preview-C9XdKEj1.js.gz +0 -0
  567. package/dist/web/assets/editor-language-service-Zle_PZV8.js +0 -1
  568. package/dist/web/assets/editor-language-service-Zle_PZV8.js.br +0 -0
  569. package/dist/web/assets/editor-language-service-Zle_PZV8.js.gz +0 -0
  570. package/dist/web/assets/explorer-body-DHMQR1Rk.js +0 -2
  571. package/dist/web/assets/explorer-body-DHMQR1Rk.js.br +0 -0
  572. package/dist/web/assets/explorer-body-DHMQR1Rk.js.gz +0 -0
  573. package/dist/web/assets/explorer-window-content-Df40nQBk.js +0 -1
  574. package/dist/web/assets/explorer-window-content-Df40nQBk.js.br +0 -0
  575. package/dist/web/assets/explorer-window-content-Df40nQBk.js.gz +0 -0
  576. package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.br +0 -0
  577. package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.gz +0 -0
  578. package/dist/web/assets/extension-webview-BGoDxkdF.js.br +0 -0
  579. package/dist/web/assets/extension-webview-BGoDxkdF.js.gz +0 -0
  580. package/dist/web/assets/file-icons-gx_FBOXY.js.br +0 -0
  581. package/dist/web/assets/file-icons-gx_FBOXY.js.gz +0 -0
  582. package/dist/web/assets/file-store-D9KE0kQC.js.br +0 -0
  583. package/dist/web/assets/file-store-D9KE0kQC.js.gz +0 -0
  584. package/dist/web/assets/files-settings-section-DC3OzzOl.js.br +0 -0
  585. package/dist/web/assets/files-settings-section-DC3OzzOl.js.gz +0 -0
  586. package/dist/web/assets/format-bytes-DmSHAKFe.js +0 -1
  587. package/dist/web/assets/format-file-meta-mQwA6SuY.js.br +0 -0
  588. package/dist/web/assets/format-file-meta-mQwA6SuY.js.gz +0 -0
  589. package/dist/web/assets/fs-api-DL36slbd.js.br +0 -0
  590. package/dist/web/assets/fs-api-DL36slbd.js.gz +0 -0
  591. package/dist/web/assets/general-settings-section-i41W43ER.js.br +0 -0
  592. package/dist/web/assets/general-settings-section-i41W43ER.js.gz +0 -0
  593. package/dist/web/assets/git-log-panel-DHzPIkJg.js.br +0 -0
  594. package/dist/web/assets/git-log-panel-DHzPIkJg.js.gz +0 -0
  595. package/dist/web/assets/git-repo-store-DSypNj19.js.br +0 -0
  596. package/dist/web/assets/git-repo-store-DSypNj19.js.gz +0 -0
  597. package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.br +0 -0
  598. package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.gz +0 -0
  599. package/dist/web/assets/group-chat-tab-DfwZb-fI.js +0 -1
  600. package/dist/web/assets/group-chat-tab-DfwZb-fI.js.br +0 -0
  601. package/dist/web/assets/group-chat-tab-DfwZb-fI.js.gz +0 -0
  602. package/dist/web/assets/index-DMA-So69.css +0 -1
  603. package/dist/web/assets/index-DMA-So69.css.br +0 -0
  604. package/dist/web/assets/index-DMA-So69.css.gz +0 -0
  605. package/dist/web/assets/index-jG9mLs-J.js +0 -52
  606. package/dist/web/assets/index-jG9mLs-J.js.br +0 -0
  607. package/dist/web/assets/index-jG9mLs-J.js.gz +0 -0
  608. package/dist/web/assets/keybindings-store-BlapkC6O.js +0 -1
  609. package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.br +0 -0
  610. package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.gz +0 -0
  611. package/dist/web/assets/markdown-renderer-DJ9EVU1B.js +0 -1
  612. package/dist/web/assets/markdown-renderer-ok96IgnS.js.br +0 -0
  613. package/dist/web/assets/markdown-renderer-ok96IgnS.js.gz +0 -0
  614. package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.br +0 -0
  615. package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.gz +0 -0
  616. package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js +0 -1
  617. package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.br +0 -0
  618. package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.gz +0 -0
  619. package/dist/web/assets/new-design-dialog-Drkjb_LI.js.br +0 -0
  620. package/dist/web/assets/new-design-dialog-Drkjb_LI.js.gz +0 -0
  621. package/dist/web/assets/notification-store-DCyxho0O.js +0 -1
  622. package/dist/web/assets/notifications-settings-section-C05WYqcE.js.br +0 -0
  623. package/dist/web/assets/notifications-settings-section-C05WYqcE.js.gz +0 -0
  624. package/dist/web/assets/onboarding-entry-CmUqv1kl.js.br +0 -0
  625. package/dist/web/assets/onboarding-entry-CmUqv1kl.js.gz +0 -0
  626. package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.br +0 -0
  627. package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.gz +0 -0
  628. package/dist/web/assets/overview-panel-BZAW_nag.js +0 -1
  629. package/dist/web/assets/overview-panel-BZAW_nag.js.br +0 -0
  630. package/dist/web/assets/overview-panel-BZAW_nag.js.gz +0 -0
  631. package/dist/web/assets/pdf-preview-qdXmMkOR.js.br +0 -0
  632. package/dist/web/assets/pdf-preview-qdXmMkOR.js.gz +0 -0
  633. package/dist/web/assets/postgres-viewer-Xvlun_n5.js.br +0 -0
  634. package/dist/web/assets/postgres-viewer-Xvlun_n5.js.gz +0 -0
  635. package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.br +0 -0
  636. package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.gz +0 -0
  637. package/dist/web/assets/pptx-export-CG6MRNok.js.br +0 -0
  638. package/dist/web/assets/pptx-export-CG6MRNok.js.gz +0 -0
  639. package/dist/web/assets/problems-panel-CFYO0Ro3.js.br +0 -0
  640. package/dist/web/assets/problems-panel-CFYO0Ro3.js.gz +0 -0
  641. package/dist/web/assets/process-table-BVeRguFg.js +0 -1
  642. package/dist/web/assets/process-table-BVeRguFg.js.br +0 -0
  643. package/dist/web/assets/process-table-BVeRguFg.js.gz +0 -0
  644. package/dist/web/assets/project-store-C5gJXkvb.js.br +0 -0
  645. package/dist/web/assets/project-store-C5gJXkvb.js.gz +0 -0
  646. package/dist/web/assets/proxy-settings-section-BcHIEANE.js.br +0 -0
  647. package/dist/web/assets/proxy-settings-section-BcHIEANE.js.gz +0 -0
  648. package/dist/web/assets/query-audit-section-UKG_smyt.js.br +0 -0
  649. package/dist/web/assets/query-audit-section-UKG_smyt.js.gz +0 -0
  650. package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.br +0 -0
  651. package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.gz +0 -0
  652. package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js +0 -2
  653. package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.br +0 -0
  654. package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.gz +0 -0
  655. package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js +0 -1
  656. package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.br +0 -0
  657. package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.gz +0 -0
  658. package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.br +0 -0
  659. package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.gz +0 -0
  660. package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js +0 -1
  661. package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.br +0 -0
  662. package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.gz +0 -0
  663. package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.br +0 -0
  664. package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.gz +0 -0
  665. package/dist/web/assets/scroll-area-XonvWYn_.js.br +0 -0
  666. package/dist/web/assets/scroll-area-XonvWYn_.js.gz +0 -0
  667. package/dist/web/assets/settings-body-B6JGOIYb.js +0 -2
  668. package/dist/web/assets/settings-body-B6JGOIYb.js.br +0 -0
  669. package/dist/web/assets/settings-body-B6JGOIYb.js.gz +0 -0
  670. package/dist/web/assets/settings-store-Br5c1efC.js +0 -2
  671. package/dist/web/assets/settings-store-Br5c1efC.js.br +0 -0
  672. package/dist/web/assets/settings-store-Br5c1efC.js.gz +0 -0
  673. package/dist/web/assets/settings-tab-CIQM4fNE.js +0 -1
  674. package/dist/web/assets/settings-window-content-BGNNz-E5.js +0 -1
  675. package/dist/web/assets/sql-query-editor-4iCGE4h2.js.br +0 -3
  676. package/dist/web/assets/sql-query-editor-4iCGE4h2.js.gz +0 -0
  677. package/dist/web/assets/sqlite-viewer-CK6DulYk.js.br +0 -0
  678. package/dist/web/assets/sqlite-viewer-CK6DulYk.js.gz +0 -0
  679. package/dist/web/assets/system-monitor-body-J9JtvAWV.js +0 -2
  680. package/dist/web/assets/system-monitor-body-J9JtvAWV.js.br +0 -0
  681. package/dist/web/assets/system-monitor-body-J9JtvAWV.js.gz +0 -0
  682. package/dist/web/assets/system-monitor-tab-DjBeRE81.js +0 -1
  683. package/dist/web/assets/system-monitor-window-content-BT7-ugFx.js +0 -1
  684. package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.br +0 -0
  685. package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.gz +0 -0
  686. package/dist/web/assets/team-member-sheet-Br6mrxrw.js.br +0 -0
  687. package/dist/web/assets/team-member-sheet-Br6mrxrw.js.gz +0 -0
  688. package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.br +0 -0
  689. package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.gz +0 -0
  690. package/dist/web/assets/terminal-tab-BW14RpCb.js.br +0 -0
  691. package/dist/web/assets/terminal-tab-BW14RpCb.js.gz +0 -0
  692. package/dist/web/assets/tool-cards-CgOBEUnb.js.br +0 -0
  693. package/dist/web/assets/tool-cards-CgOBEUnb.js.gz +0 -0
  694. package/dist/web/assets/use-inline-blame-Bzjfru4n.js +0 -16
  695. package/dist/web/assets/use-inline-blame-Bzjfru4n.js.br +0 -0
  696. package/dist/web/assets/use-inline-blame-Bzjfru4n.js.gz +0 -0
  697. package/dist/web/assets/use-lsp-hGdjL2hV.js +0 -6
  698. package/dist/web/assets/use-lsp-hGdjL2hV.js.br +0 -0
  699. package/dist/web/assets/use-lsp-hGdjL2hV.js.gz +0 -0
  700. package/dist/web/assets/vendor-ui-BHh12_0v.js.br +0 -0
  701. package/dist/web/assets/vendor-ui-BHh12_0v.js.gz +0 -0
  702. package/dist/web/assets/video-preview-P9T-ZVdN.js +0 -1
  703. package/dist/web/assets/video-preview-P9T-ZVdN.js.br +0 -0
  704. package/dist/web/assets/video-preview-P9T-ZVdN.js.gz +0 -0
  705. package/dist/web/assets/ws-auth-kXgsX2Js.js +0 -1
  706. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  707. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  708. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  709. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  710. /package/dist/web/assets/{__vite-browser-external-BpSQWxcq.js → __vite-browser-external-BILfYaAs.js} +0 -0
  711. /package/dist/web/assets/{run-in-terminal-HIdNgYim.js → run-in-terminal-2pSiup8e.js} +0 -0
  712. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js → vendor-xterm-DF1JTfsy.js} +0 -0
  713. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.br → vendor-xterm-DF1JTfsy.js.br} +0 -0
  714. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.gz → vendor-xterm-DF1JTfsy.js.gz} +0 -0
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Whole-machine CPU + memory from `node:os`. Runs in both tiers, spawns nothing.
2
+ * Whole-machine CPU + memory. Runs in both tiers.
3
+ *
4
+ * Reads files on Linux and spawns nothing there. On darwin it spawns exactly one
5
+ * `sysctl` per tick for swap, which `node:os` does not carry — measured at
6
+ * 1.33 ms, see `memory-darwin.ts`.
3
7
  */
4
8
  import os from "node:os";
5
9
  import { readFileSync } from "node:fs";
6
10
  import type { CpuMetrics, MemoryMetrics } from "../../types/system-metrics.ts";
11
+ import { enrichMemory, readZramTotals, type ZramTotals } from "./memory-linux.ts";
12
+ import { parseSwapUsage, readSwapUsage } from "./memory-darwin.ts";
7
13
 
8
14
  export interface CoreTimes {
9
15
  user: number;
@@ -11,8 +17,16 @@ export interface CoreTimes {
11
17
  sys: number;
12
18
  idle: number;
13
19
  irq: number;
20
+ /** Blocked on I/O: neither busy nor idle. `os.cpus()` has no field for it and
21
+ * libuv reads the column into a throwaway, so these three are 0 on any host
22
+ * whose sample came from there — see `sampleCpuTimes`. */
23
+ iowait: number;
24
+ softirq: number;
25
+ steal: number;
14
26
  }
15
27
 
28
+ const NO_EXTRA = { iowait: 0, softirq: 0, steal: 0 } as const;
29
+
16
30
  export interface CpuTimesSample {
17
31
  times: CoreTimes[];
18
32
  /** Wall clock of the sample, epoch ms. */
@@ -20,38 +34,110 @@ export interface CpuTimesSample {
20
34
  model: string;
21
35
  }
22
36
 
37
+ /** Read `/proc/stat` on Linux, null elsewhere or on failure. Injectable for tests. */
38
+ export function readProcStat(): string | null {
39
+ if (process.platform !== "linux") return null;
40
+ try {
41
+ return readFileSync("/proc/stat", "utf-8");
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
23
47
  /**
24
- * Read `os.cpus()` and copy the numbers out immediately. Bun 1.3.10 on Windows
25
- * returns the SAME `times` on a later call while the array from the previous
26
- * call is still referenced — the delta then reads 0 % forever, with no error
27
- * anywhere. Keeping only plain copies between ticks avoids it.
48
+ * Per-core times from a `/proc/stat` dump, in the file's own jiffies. Null when
49
+ * no `cpuN` line is present, so the caller can fall back.
50
+ *
51
+ * `guest`/`guest_nice` are deliberately NOT read: the kernel already counts them
52
+ * inside `user`/`nice`, so adding them would inflate the total and under-report
53
+ * every busy percentage on a host running VMs.
28
54
  */
29
- export function sampleCpuTimes(now: number = Date.now()): CpuTimesSample {
55
+ export function parseProcStatCores(stat: string): CoreTimes[] | null {
56
+ const cores: CoreTimes[] = [];
57
+ for (const line of stat.split("\n")) {
58
+ const m = /^cpu(\d+)\s+(.*)$/.exec(line);
59
+ if (!m) continue;
60
+ const n = m[2]!.trim().split(/\s+/).map(Number);
61
+ if (n.length < 8 || n.some((v) => !Number.isFinite(v))) return null;
62
+ cores[Number(m[1])] = {
63
+ user: n[0]!, nice: n[1]!, sys: n[2]!, idle: n[3]!,
64
+ iowait: n[4]!, irq: n[5]!, softirq: n[6]!, steal: n[7]!,
65
+ };
66
+ }
67
+ // A hole would mean a core index we never saw, which no kernel produces — but
68
+ // a sparse array here would crash the delta, so treat it as unusable.
69
+ if (cores.length === 0 || cores.some((c) => c === undefined)) return null;
70
+ return cores;
71
+ }
72
+
73
+ /**
74
+ * Prefer `/proc/stat`; fall back to `os.cpus()`.
75
+ *
76
+ * `os.cpus()` cannot express a core that is blocked on I/O. libuv's Linux
77
+ * `uv_cpu_info` reads the `iowait`, `softirq` and `steal` columns into a
78
+ * throwaway variable, so a core parked in iowait shows a *tiny* total delta with
79
+ * no idle in it — measured on this host, 6 jiffies of which 0 were idle while
80
+ * the kernel reported 195 of 201 in iowait, i.e. the Overview card drew one core
81
+ * pinned at a red **100%** while it was 3% busy and waiting on a disk write.
82
+ *
83
+ * The fallback still copies the numbers out immediately: Bun 1.3.10 on Windows
84
+ * returns the SAME `times` object on a later call while the previous array is
85
+ * still referenced, and the delta then reads 0 % forever with no error anywhere.
86
+ */
87
+ export function sampleCpuTimes(
88
+ now: number = Date.now(),
89
+ stat: () => string | null = readProcStat,
90
+ ): CpuTimesSample {
30
91
  const cpus = os.cpus();
31
- const times = cpus.map((c) => ({ ...c.times }));
32
- return { times, at: now, model: cpus[0]?.model?.trim() ?? "" };
92
+ const model = cpus[0]?.model?.trim() ?? "";
93
+ const dump = stat();
94
+ const fromProc = dump ? parseProcStatCores(dump) : null;
95
+ if (fromProc && fromProc.length === cpus.length) return { times: fromProc, at: now, model };
96
+ return { times: cpus.map((c) => ({ ...c.times, ...NO_EXTRA })), at: now, model };
33
97
  }
34
98
 
35
99
  const round1 = (n: number): number => Math.round(n * 10) / 10;
36
100
 
37
- /** Busy % total + per core between two samples. Pure. First sample → all zeros. */
101
+ /**
102
+ * Busy % total + per core between two samples. Pure. First sample → all zeros.
103
+ *
104
+ * The kernel share is `sys + irq` over the same interval — Mission Center draws
105
+ * it as a second, darker line under the total, so it is a SUBSET of `total` and
106
+ * must never be added to it.
107
+ */
38
108
  export function computeCpuFromSamples(prev: CpuTimesSample | null, next: CpuTimesSample): CpuMetrics {
39
- const zeros: CpuMetrics = { total: 0, cores: next.times.map(() => 0), model: next.model };
109
+ const zeros: CpuMetrics = {
110
+ total: 0, cores: next.times.map(() => 0), model: next.model,
111
+ kernelPercent: 0, coreKernel: next.times.map(() => 0),
112
+ };
40
113
  if (!prev || prev.times.length !== next.times.length) return zeros;
41
114
 
42
115
  let busySum = 0;
116
+ let kernelSum = 0;
43
117
  let totalSum = 0;
118
+ const coreKernel: number[] = [];
44
119
  const cores = next.times.map((n, i) => {
45
120
  const p = prev.times[i]!;
46
- const idle = n.idle - p.idle;
47
- const total = (n.user - p.user) + (n.nice - p.nice) + (n.sys - p.sys) + idle + (n.irq - p.irq);
48
- if (!(total > 0)) return 0;
49
- busySum += total - idle;
121
+ // Waiting on I/O is not work. Folding it into `idle` is what `top` and
122
+ // Mission Center both do, and leaving it out of `total` altogether is the
123
+ // bug that drew an idle core at 100% — see `sampleCpuTimes`.
124
+ const notBusy = (n.idle - p.idle) + (n.iowait - p.iowait);
125
+ const kernel = (n.sys - p.sys) + (n.irq - p.irq) + (n.softirq - p.softirq);
126
+ const total = (n.user - p.user) + (n.nice - p.nice) + kernel
127
+ + notBusy + (n.steal - p.steal);
128
+ if (!(total > 0)) {
129
+ coreKernel.push(0);
130
+ return 0;
131
+ }
132
+ busySum += total - notBusy;
133
+ kernelSum += kernel;
50
134
  totalSum += total;
51
- return clampPercent((total - idle) / total * 100);
135
+ coreKernel.push(clampPercent(kernel / total * 100));
136
+ return clampPercent((total - notBusy) / total * 100);
52
137
  });
53
138
  const total = totalSum > 0 ? clampPercent(busySum / totalSum * 100) : 0;
54
- return { total, cores, model: next.model };
139
+ const kernelPercent = totalSum > 0 ? clampPercent(kernelSum / totalSum * 100) : 0;
140
+ return { total, cores, model: next.model, kernelPercent, coreKernel };
55
141
  }
56
142
 
57
143
  function clampPercent(v: number): number {
@@ -59,6 +145,20 @@ function clampPercent(v: number): number {
59
145
  return round1(Math.min(100, Math.max(0, v)));
60
146
  }
61
147
 
148
+ /**
149
+ * Seconds since boot, on all three platforms — `os.uptime()` reads
150
+ * `/proc/uptime`, `kern.boottime` and `GetTickCount64` respectively.
151
+ *
152
+ * Linux has a finer figure of its own and `collectDevices` merges it OVER this
153
+ * one; the reason this exists is that the per-device collector is null off
154
+ * Linux, so "Up time" was an em dash — the claim that the host could not be
155
+ * asked how long it had been up — on macOS and Windows both.
156
+ */
157
+ export function hostUptimeSec(): number | undefined {
158
+ const seconds = os.uptime();
159
+ return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds) : undefined;
160
+ }
161
+
62
162
  /** Read `/proc/meminfo` on Linux, null elsewhere or on failure. Injectable for tests. */
63
163
  export function readMeminfo(): string | null {
64
164
  if (process.platform !== "linux") return null;
@@ -82,8 +182,19 @@ export function parseMemAvailableBytes(meminfo: string): number | null {
82
182
  * and `os.freemem()` is within 0.2 GiB of CIM's `FreePhysicalMemory`. On Linux
83
183
  * `freemem()` is `MemFree`, which makes a warm page cache look like a nearly
84
184
  * full machine — `MemAvailable` is what `free` and Task-Manager-like tools show.
185
+ * On **darwin** `freemem()` is already right and must be left alone: it agrees
186
+ * with what `top` calls unused (75 MB against this figure's 124 MB on the same
187
+ * host, sampled seconds apart), which is the number Activity Monitor shows.
188
+ *
189
+ * Swap is the one field macOS publishes and `os` does not, so it is fetched
190
+ * separately — see `memory-darwin.ts` for why that costs a spawn and why the
191
+ * page counts from `vm_stat` are deliberately not used.
85
192
  */
86
- export function collectMemory(meminfo: () => string | null = readMeminfo): MemoryMetrics {
193
+ export function collectMemory(
194
+ meminfo: () => string | null = readMeminfo,
195
+ zram: () => ZramTotals | undefined = defaultZram,
196
+ swapusage: () => string | null = readSwapUsage,
197
+ ): MemoryMetrics {
87
198
  const totalBytes = os.totalmem();
88
199
  let availableBytes = os.freemem();
89
200
  const info = meminfo();
@@ -97,5 +208,16 @@ export function collectMemory(meminfo: () => string | null = readMeminfo): Memor
97
208
  const availableMB = round1(availableBytes / MB);
98
209
  const usedMB = round1(Math.max(totalMB - availableMB, 0));
99
210
  const percent = totalMB > 0 ? round1(usedMB / totalMB * 100) : 0;
100
- return { totalMB, usedMB, availableMB, percent };
211
+ const base: MemoryMetrics = { totalMB, usedMB, availableMB, percent };
212
+ // `info` is null off Linux, so the composition fields simply stay absent there
213
+ // — but swap is not one of those: macOS answers for it, and leaving it absent
214
+ // rendered an em dash on a host with 8 GB of swap in use.
215
+ if (info) return enrichMemory(base, info, zram());
216
+ return { ...base, ...parseSwapUsage(swapusage()) };
217
+ }
218
+
219
+ /** Two `/sys` reads on a host that has zram and one cheap directory listing on
220
+ * one that does not — small enough to run per tick, unlike the DIMM layout. */
221
+ function defaultZram(): ZramTotals | undefined {
222
+ return readZramTotals();
101
223
  }
@@ -0,0 +1,31 @@
1
+ /** Every per-device Linux collector as one tick-shaped call. */
2
+ import { collectLinuxDiskDevices } from "./disk-devices-linux.ts";
3
+ import { collectLinuxNicDevices } from "./net-devices-linux.ts";
4
+ import { collectLinuxFans } from "./fans-linux.ts";
5
+ import { collectCpuLive } from "./cpu-details-linux.ts";
6
+ import { collectLinuxGpus } from "./gpu-devices-linux.ts";
7
+ import type { DrmGpuCollector } from "./gpu-fdinfo-linux.ts";
8
+ import { realLinuxFs, type LinuxFs } from "./linux-fs.ts";
9
+ import type { DeviceCollection, DeviceSampleState } from "./device-collector-types.ts";
10
+
11
+ export function collectLinuxDevices(
12
+ prev: DeviceSampleState,
13
+ drm: DrmGpuCollector,
14
+ fs: LinuxFs = realLinuxFs,
15
+ now: () => number = Date.now,
16
+ ): DeviceCollection {
17
+ const disks = collectLinuxDiskDevices(prev.disks, fs, now);
18
+ const nics = collectLinuxNicDevices(prev.nics, fs, now);
19
+ const cpu = collectCpuLive(prev.rapl, fs, now);
20
+ // The same walk the process rows are built from: asking here first (or second)
21
+ // costs nothing, because the collector memoises one result per tick.
22
+ const gpus = collectLinuxGpus(prev.gpus, drm.usage(), fs, now);
23
+ return {
24
+ disks: disks.disks,
25
+ nics: nics.nics,
26
+ fans: collectLinuxFans(fs),
27
+ cpu: cpu.extras,
28
+ gpus: gpus.gpus,
29
+ next: { disks: disks.next, nics: nics.next, rapl: cpu.rapl, gpus: gpus.next },
30
+ };
31
+ }
@@ -0,0 +1,40 @@
1
+ /** Shape of the per-device (drive / interface) half of a tick, platform-neutral so
2
+ * the tick assembler does not import a Linux module to name its own state. */
3
+ import type { CpuMetrics, DiskMetrics, FanMetrics, GpuMetrics, NicMetrics } from "../../types/system-metrics.ts";
4
+ import type { DiskSampleState } from "./disk-devices-linux.ts";
5
+ import type { NicSampleState } from "./net-devices-linux.ts";
6
+ import type { RaplSample } from "./cpu-details-linux.ts";
7
+ import type { GpuSampleState } from "./gpu-devices-linux.ts";
8
+
9
+ /** Previous counters, per device id. Each device is measured against its own
10
+ * baseline, so one appearing mid-session costs one unavailable tick and never a
11
+ * spike measured from zero. */
12
+ export interface DeviceSampleState {
13
+ disks: DiskSampleState;
14
+ nics: NicSampleState;
15
+ /** Cumulative RAPL energy, the baseline package power is measured against. */
16
+ rapl: RaplSample | null;
17
+ /** Per-card rc6 / uncore counters. */
18
+ gpus: GpuSampleState;
19
+ }
20
+
21
+ export const EMPTY_DEVICE_STATE: DeviceSampleState = {
22
+ disks: new Map(), nics: new Map(), rapl: null, gpus: new Map(),
23
+ };
24
+
25
+ export interface DeviceCollection {
26
+ disks: DiskMetrics[];
27
+ nics: NicMetrics[];
28
+ /** Empty where the host has no fan tachometer. */
29
+ fans: FanMetrics[];
30
+ /** Merged over the cross-platform `CpuMetrics` by the tick, never replacing it. */
31
+ cpu: Partial<CpuMetrics>;
32
+ /** Cards this platform reads from sysfs. Appended to whatever the vendor-tool
33
+ * collector (nvidia-smi) found, which covers the drivers sysfs cannot. */
34
+ gpus: GpuMetrics[];
35
+ next: DeviceSampleState;
36
+ }
37
+
38
+ /** Synchronous by design: every source is a small sysfs read, so there is no
39
+ * subprocess to await and no reason for the tick to yield. */
40
+ export type DeviceCollector = (prev: DeviceSampleState) => DeviceCollection;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Per-disk figures for the Performance page's drive entries, from
3
+ * `/sys/block/<dev>/stat`. The arithmetic is Mission Center's
4
+ * (`platform-linux/src/disks/stats.rs`): busy% is iostat's %util, response time
5
+ * is weighted ticks over completed requests, and throughput is sectors x 512.
6
+ *
7
+ * Every device is sampled against its OWN previous sample, so a drive that
8
+ * appears mid-session reports `available:false` for one tick instead of a spike
9
+ * measured from zero.
10
+ */
11
+ import type { DiskMetrics } from "../../types/system-metrics.ts";
12
+ import { readNumber, realLinuxFs, type LinuxFs } from "./linux-fs.ts";
13
+
14
+ export const SYS_BLOCK = "/sys/block";
15
+ const SECTOR_BYTES = 512;
16
+
17
+ /** Mission Center's list, matched on a 2-4 character prefix: virtual devices
18
+ * whose counters double-count the real drive underneath, or are not a drive. */
19
+ const IGNORED_PREFIXES = ["loop", "ram", "zram", "fd", "md", "dm", "zd"];
20
+
21
+ /** `/sys/block/<dev>/stat` field indices. The line has 17 fields since 5.5; the
22
+ * discard group (12-16) and flush group (17-18) are absent on older kernels,
23
+ * which reads as 0 here rather than as an error. */
24
+ const IDX = {
25
+ readIos: 0, readSectors: 2, readTicks: 3,
26
+ writeIos: 4, writeSectors: 6, writeTicks: 7,
27
+ ioTicks: 9,
28
+ discardIos: 11, discardTicks: 14,
29
+ flushIos: 15, flushTicks: 16,
30
+ } as const;
31
+
32
+ export interface DiskSample {
33
+ /** Wall clock in seconds — the sysfs stat line carries no timestamp. */
34
+ atSec: number;
35
+ readIos: number; readSectors: number; readTicks: number;
36
+ writeIos: number; writeSectors: number; writeTicks: number;
37
+ ioTicks: number;
38
+ discardIos: number; discardTicks: number;
39
+ flushIos: number; flushTicks: number;
40
+ }
41
+
42
+ /** Previous sample per device id. */
43
+ export type DiskSampleState = Map<string, DiskSample>;
44
+
45
+ export function isIgnoredDisk(id: string): boolean {
46
+ return IGNORED_PREFIXES.some((p) => id.startsWith(p));
47
+ }
48
+
49
+ /** Whole devices udev would call disks: skips the virtual prefixes and anything
50
+ * reporting a zero size (an empty card reader, a detached loop device). */
51
+ export function listDiskDevices(fs: Pick<LinuxFs, "list" | "read"> = realLinuxFs): string[] {
52
+ const names = fs.list(SYS_BLOCK);
53
+ if (!names) return [];
54
+ return names
55
+ .filter((n) => !isIgnoredDisk(n))
56
+ .filter((n) => (readNumber(fs, `${SYS_BLOCK}/${n}/size`) ?? 0) > 0)
57
+ .sort();
58
+ }
59
+
60
+ export function parseDiskStat(text: string, atSec: number): DiskSample | null {
61
+ const f = text.trim().split(/\s+/).map(Number);
62
+ if (f.length < 11 || f.some((v) => !Number.isFinite(v))) return null;
63
+ const at = (i: number) => f[i] ?? 0;
64
+ return {
65
+ atSec,
66
+ readIos: at(IDX.readIos), readSectors: at(IDX.readSectors), readTicks: at(IDX.readTicks),
67
+ writeIos: at(IDX.writeIos), writeSectors: at(IDX.writeSectors), writeTicks: at(IDX.writeTicks),
68
+ ioTicks: at(IDX.ioTicks),
69
+ discardIos: at(IDX.discardIos), discardTicks: at(IDX.discardTicks),
70
+ flushIos: at(IDX.flushIos), flushTicks: at(IDX.flushTicks),
71
+ };
72
+ }
73
+
74
+ /** NVMe exposes a composite sensor at `device/hwmon<N>/temp1_input`; a SATA drive
75
+ * exposes the same path only once `drivetemp` is loaded, which it usually is not
76
+ * — hence undefined rather than 0. Millidegrees, one decimal kept. */
77
+ export function readDiskTempC(id: string, fs: Pick<LinuxFs, "list" | "read"> = realLinuxFs): number | undefined {
78
+ const dir = `${SYS_BLOCK}/${id}/device`;
79
+ const hwmon = fs.list(dir)?.find((n) => n.startsWith("hwmon"));
80
+ if (!hwmon) return undefined;
81
+ const milli = readNumber(fs, `${dir}/${hwmon}/temp1_input`);
82
+ return milli === undefined ? undefined : Math.round(milli / 100) / 10;
83
+ }
84
+
85
+ export interface DiskDeviceCollection {
86
+ disks: DiskMetrics[];
87
+ next: DiskSampleState;
88
+ }
89
+
90
+ /** One tick's worth of per-device figures. Devices absent from `prev` report
91
+ * `available:false` — totals are still real, they are absolutes. */
92
+ export function collectLinuxDiskDevices(
93
+ prev: DiskSampleState,
94
+ fs: Pick<LinuxFs, "list" | "read"> = realLinuxFs,
95
+ now: () => number = Date.now,
96
+ ): DiskDeviceCollection {
97
+ const atSec = now() / 1000;
98
+ const next: DiskSampleState = new Map();
99
+ const disks: DiskMetrics[] = [];
100
+
101
+ for (const id of listDiskDevices(fs)) {
102
+ const text = fs.read(`${SYS_BLOCK}/${id}/stat`);
103
+ const sample = text ? parseDiskStat(text, atSec) : null;
104
+ if (!sample) continue;
105
+ next.set(id, sample);
106
+ disks.push(toDiskMetrics(id, prev.get(id) ?? null, sample, readDiskTempC(id, fs)));
107
+ }
108
+ return { disks, next };
109
+ }
110
+
111
+ export function toDiskMetrics(
112
+ id: string,
113
+ prev: DiskSample | null,
114
+ next: DiskSample,
115
+ tempC: number | undefined,
116
+ ): DiskMetrics {
117
+ const base: DiskMetrics = {
118
+ id,
119
+ available: false,
120
+ busyPercent: 0,
121
+ responseMs: 0,
122
+ readBps: 0,
123
+ writeBps: 0,
124
+ readTotal: next.readSectors * SECTOR_BYTES,
125
+ writeTotal: next.writeSectors * SECTOR_BYTES,
126
+ ...(tempC === undefined ? {} : { tempC }),
127
+ };
128
+
129
+ const dt = prev ? next.atSec - prev.atSec : 0;
130
+ // A device reset (hot-replug reusing the name) walks the counters backwards;
131
+ // one unavailable tick is the honest answer, not a negative rate.
132
+ if (!prev || !Number.isFinite(dt) || dt <= 0 || next.ioTicks < prev.ioTicks) return base;
133
+
134
+ const ticks = delta(next.readTicks, prev.readTicks) + delta(next.writeTicks, prev.writeTicks)
135
+ + delta(next.discardTicks, prev.discardTicks) + delta(next.flushTicks, prev.flushTicks);
136
+ const ios = delta(next.readIos, prev.readIos) + delta(next.writeIos, prev.writeIos)
137
+ + delta(next.discardIos, prev.discardIos) + delta(next.flushIos, prev.flushIos);
138
+
139
+ return {
140
+ ...base,
141
+ available: true,
142
+ busyPercent: round1(Math.min(100, delta(next.ioTicks, prev.ioTicks) / (dt * 10))),
143
+ responseMs: ios > 0 ? round2(ticks / ios) : 0,
144
+ readBps: Math.round(delta(next.readSectors, prev.readSectors) * SECTOR_BYTES / dt),
145
+ writeBps: Math.round(delta(next.writeSectors, prev.writeSectors) * SECTOR_BYTES / dt),
146
+ };
147
+ }
148
+
149
+ /** Saturating, as Mission Center's `saturating_sub`: these counters only ever
150
+ * grow, so a negative delta is a reset and contributes nothing. */
151
+ function delta(next: number, prev: number): number {
152
+ return next > prev ? next - prev : 0;
153
+ }
154
+
155
+ const round1 = (n: number) => Math.round(n * 10) / 10;
156
+ const round2 = (n: number) => Math.round(n * 100) / 100;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Static facts about each whole disk: model, kind, capacity, serial, and which
3
+ * one carries the root filesystem. Read once per inventory request, not per tick.
4
+ *
5
+ * Mission Center gets all of this from udisks2 over D-Bus and SKIPS any drive
6
+ * udisks2 has no object for, which on a headless or container host is every
7
+ * drive. PPM reads sysfs plus udev's own database instead, so the list is the
8
+ * same one the kernel has and needs neither a system bus nor root.
9
+ */
10
+ import type { DiskInfo, DiskKind } from "../../types/system-hardware.ts";
11
+ import { readAttr, readNumber, realLinuxFs, type LinuxFs } from "./linux-fs.ts";
12
+ import { readUdevProperties, udevValue } from "./udev-db.ts";
13
+ import { listDiskDevices, SYS_BLOCK } from "./disk-devices-linux.ts";
14
+ import { parseMountInfo, readPartitions } from "./partitions-linux.ts";
15
+
16
+ const SECTOR_BYTES = 512;
17
+ /** How far to follow `slaves` links from the root filesystem's device down to a
18
+ * real drive (partition -> LUKS -> LVM -> RAID is already four). */
19
+ const MAX_SLAVE_DEPTH = 8;
20
+
21
+ /** The kernel fills SCSI's vendor field with the transport name for a SATA drive,
22
+ * so "ATA" is never a manufacturer. Mission Center prints it; PPM drops it. */
23
+ const GENERIC_VENDORS = new Set(["ATA", "SATA", "NVME", "USB", "SCSI"]);
24
+
25
+ export function readDiskInventory(fs: LinuxFs = realLinuxFs): DiskInfo[] {
26
+ const systemDisk = findSystemDisk(fs);
27
+ // Parsed once for the whole inventory rather than per disk: mountinfo is one
28
+ // file describing every mount on the host, and re-reading it per drive is the
29
+ // same work times however many drives there are.
30
+ const mounts = parseMountInfo(fs.read("/proc/self/mountinfo") ?? "");
31
+ return listDiskDevices(fs).map((id) => readDiskInfo(id, fs, systemDisk === id, mounts));
32
+ }
33
+
34
+ export function readDiskInfo(
35
+ id: string,
36
+ fs: LinuxFs,
37
+ systemDisk: boolean,
38
+ mounts = parseMountInfo(fs.read("/proc/self/mountinfo") ?? ""),
39
+ ): DiskInfo {
40
+ const udev = readUdevProperties(blockDeviceKey(id, fs), fs.read);
41
+ const sectors = readNumber(fs, `${SYS_BLOCK}/${id}/size`) ?? 0;
42
+ const rpm = Number(udevValue(udev, "ID_ATA_ROTATION_RATE_RPM"));
43
+
44
+ return {
45
+ id,
46
+ ...optional("model", diskModel(id, fs, udev)),
47
+ kind: diskKind(id, fs, udev),
48
+ capacityBytes: sectors * SECTOR_BYTES,
49
+ systemDisk,
50
+ removable: readAttr(fs, `${SYS_BLOCK}/${id}/removable`) === "1",
51
+ ...optional("serial", readAttr(fs, `${SYS_BLOCK}/${id}/device/serial`) ?? udevValue(udev, "ID_SERIAL_SHORT")),
52
+ ...optional("wwn", diskWwn(id, fs, udev)),
53
+ ...(Number.isFinite(rpm) && rpm > 0 ? { rotationRpm: rpm } : {}),
54
+ partitions: readPartitions(id, fs, mounts),
55
+ };
56
+ }
57
+
58
+ /** udev keys a block device by `b<major>:<minor>`, which sysfs reports in `dev`. */
59
+ export function blockDeviceKey(id: string, fs: Pick<LinuxFs, "read">): string {
60
+ return `b${readAttr(fs, `${SYS_BLOCK}/${id}/dev`) ?? ""}`;
61
+ }
62
+
63
+ /** "vendor model", with the vendor dropped when it is generic or already the
64
+ * start of the model — Mission Center's rule plus the ATA case. */
65
+ export function diskModel(id: string, fs: Pick<LinuxFs, "read">, udev: ReadonlyMap<string, string>): string | undefined {
66
+ const model = readAttr(fs, `${SYS_BLOCK}/${id}/device/model`) ?? udevValue(udev, "ID_MODEL")?.replace(/_/g, " ");
67
+ const vendor = readAttr(fs, `${SYS_BLOCK}/${id}/device/vendor`);
68
+ if (!vendor || GENERIC_VENDORS.has(vendor.toUpperCase())) return model;
69
+ if (!model) return vendor;
70
+ return model.startsWith(vendor) ? model : `${vendor} ${model}`;
71
+ }
72
+
73
+ export function diskKind(id: string, fs: Pick<LinuxFs, "read" | "list">, udev: ReadonlyMap<string, string>): DiskKind {
74
+ if (id.startsWith("nvme")) return "nvme";
75
+ if (id.startsWith("mmcblk")) return mmcKind(id, fs);
76
+ if (id.startsWith("sr")) return "optical";
77
+ const removable = readAttr(fs, `${SYS_BLOCK}/${id}/removable`) === "1";
78
+ // udisks2 answers MediaCompatibility::Thumb for these; the same drives are the
79
+ // removable ones on the USB bus, which udev already knows.
80
+ if (removable && udevValue(udev, "ID_BUS") === "usb") return "thumb";
81
+ const rotational = readAttr(fs, `${SYS_BLOCK}/${id}/queue/rotational`);
82
+ if (rotational === "0") return "ssd";
83
+ if (rotational === "1") return "hdd";
84
+ return "unknown";
85
+ }
86
+
87
+ /** The mmc host's `type` attribute is "SD" or "MMC". */
88
+ function mmcKind(id: string, fs: Pick<LinuxFs, "read" | "list">): DiskKind {
89
+ const index = id.slice("mmcblk".length);
90
+ const host = `/sys/class/mmc_host/mmc${index}`;
91
+ for (const entry of fs.list(host) ?? []) {
92
+ if (!entry.startsWith(`mmc${index}`)) continue;
93
+ const type = readAttr(fs, `${host}/${entry}/type`);
94
+ if (type === "SD") return "sd";
95
+ if (type === "MMC") return "emmc";
96
+ }
97
+ return "unknown";
98
+ }
99
+
100
+ /** NVMe writes `wwid` on the namespace, SATA on the device; udev's `ID_WWN` is
101
+ * cleaner than either and is preferred when present. A T10 identifier is built
102
+ * by concatenating fixed-width ATA fields, so it arrives padded — the runs are
103
+ * collapsed, since the value is shown to a person, not compared byte for byte. */
104
+ function diskWwn(id: string, fs: Pick<LinuxFs, "read">, udev: ReadonlyMap<string, string>): string | undefined {
105
+ const raw = udevValue(udev, "ID_WWN")
106
+ ?? readAttr(fs, `${SYS_BLOCK}/${id}/wwid`)
107
+ ?? readAttr(fs, `${SYS_BLOCK}/${id}/device/wwid`);
108
+ return raw?.replace(/\s+/g, " ");
109
+ }
110
+
111
+ /** The drive behind `/`. mountinfo names the SOURCE path (`/dev/nvme0n1p2`), not
112
+ * a disk, so it is resolved through sysfs: a partition's parent directory is its
113
+ * disk, and a mapper device (LUKS, LVM, RAID) is followed through `slaves`. */
114
+ export function findSystemDisk(fs: LinuxFs): string | undefined {
115
+ const source = rootMountSource(fs.read("/proc/self/mountinfo") ?? "");
116
+ if (!source) return undefined;
117
+ const node = fs.realpath(source) ?? source;
118
+ return wholeDiskOf(basename(node), fs, 0);
119
+ }
120
+
121
+ /** The `/` line's source device, or undefined when `/` is not a block mount. */
122
+ export function rootMountSource(mountinfo: string): string | undefined {
123
+ for (const line of mountinfo.split("\n")) {
124
+ const fields = line.split(" ");
125
+ if (fields[4] !== "/") continue;
126
+ const sep = fields.indexOf("-", 6);
127
+ const source = sep >= 0 ? fields[sep + 2] : undefined;
128
+ if (source?.startsWith("/dev/")) return source;
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ function wholeDiskOf(name: string, fs: LinuxFs, depth: number): string | undefined {
134
+ if (!name || depth > MAX_SLAVE_DEPTH) return undefined;
135
+ const real = fs.realpath(`/sys/class/block/${name}`);
136
+ if (!real) return undefined;
137
+ if (fs.exists(`${real}/partition`)) return wholeDiskOf(basename(dirname(real)), fs, depth + 1);
138
+ for (const slave of fs.list(`${real}/slaves`) ?? []) {
139
+ const disk = wholeDiskOf(slave, fs, depth + 1);
140
+ if (disk) return disk;
141
+ }
142
+ return fs.exists(`${SYS_BLOCK}/${name}`) ? name : undefined;
143
+ }
144
+
145
+ const basename = (p: string) => p.slice(p.lastIndexOf("/") + 1);
146
+ const dirname = (p: string) => p.slice(0, p.lastIndexOf("/"));
147
+
148
+ function optional<K extends string>(key: K, value: string | undefined): Partial<Record<K, string>> {
149
+ return value === undefined ? {} : ({ [key]: value } as Record<K, string>);
150
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Mission Center's Fan page, from `/sys/class/hwmon`. A chip exposes `fanN_input`
3
+ * in RPM, an optional `pwmN` (0-255 duty) and often a `tempN_input` with the same
4
+ * index — Mission Center pairs them by index, which is what the board's own
5
+ * labelling means.
6
+ *
7
+ * Fans reading 0 are kept: a header with nothing plugged into it is information,
8
+ * and hiding it would make the list change length as a fan spins down.
9
+ */
10
+ import type { FanMetrics } from "../../types/system-metrics.ts";
11
+ import { readAttr, readNumber, realLinuxFs, type LinuxFs } from "./linux-fs.ts";
12
+
13
+ export const HWMON_DIR = "/sys/class/hwmon";
14
+ const PWM_MAX = 255;
15
+
16
+ export function collectLinuxFans(fs: Pick<LinuxFs, "list" | "read"> = realLinuxFs): FanMetrics[] {
17
+ const fans: FanMetrics[] = [];
18
+ for (const chip of (fs.list(HWMON_DIR) ?? []).sort()) {
19
+ const dir = `${HWMON_DIR}/${chip}`;
20
+ const name = readAttr(fs, `${dir}/name`) ?? chip;
21
+ for (const index of fanIndices(fs, dir)) {
22
+ const rpm = readNumber(fs, `${dir}/fan${index}_input`);
23
+ if (rpm === undefined) continue;
24
+ const pwm = readNumber(fs, `${dir}/pwm${index}`);
25
+ const milliC = readNumber(fs, `${dir}/temp${index}_input`);
26
+ fans.push({
27
+ id: `${name}/fan${index}`,
28
+ label: readAttr(fs, `${dir}/fan${index}_label`) ?? `Fan ${index}`,
29
+ rpm: Math.max(0, Math.round(rpm)),
30
+ ...(pwm === undefined ? {} : { pwmPercent: Math.round(pwm / PWM_MAX * 1000) / 10 }),
31
+ ...(milliC === undefined ? {} : { tempC: Math.round(milliC / 100) / 10 }),
32
+ ...defined("tempName", readAttr(fs, `${dir}/temp${index}_label`)),
33
+ });
34
+ }
35
+ }
36
+ return fans;
37
+ }
38
+
39
+ /** The `N` of every `fanN_input` the chip publishes, ascending. */
40
+ export function fanIndices(fs: Pick<LinuxFs, "list">, dir: string): number[] {
41
+ const indices: number[] = [];
42
+ for (const entry of fs.list(dir) ?? []) {
43
+ const m = /^fan(\d+)_input$/.exec(entry);
44
+ if (m) indices.push(Number(m[1]));
45
+ }
46
+ return indices.sort((a, b) => a - b);
47
+ }
48
+
49
+ function defined<K extends string, V>(key: K, value: V | undefined): Partial<Record<K, V>> {
50
+ return value === undefined ? {} : ({ [key]: value } as Record<K, V>);
51
+ }