@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.
- package/CHANGELOG.md +89 -0
- package/CLAUDE.md +21 -1
- package/Dockerfile.test +8 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +30 -1
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js +1 -0
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-DKcsVDle.js → ai-resource-editor-CY4n_Ccv.js} +1 -1
- package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js +1 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-DDs11XV4.js +1 -0
- package/dist/web/assets/api-client-CJPnCOG-.js +1 -0
- package/dist/web/assets/{api-client-c8YOpTlT.js → api-client-D-SMbdiH.js} +1 -1
- package/dist/web/assets/api-client-D-SMbdiH.js.br +0 -0
- package/dist/web/assets/api-client-D-SMbdiH.js.gz +0 -0
- package/dist/web/assets/api-settings-BDkkhFMq.js +1 -0
- package/dist/web/assets/{api-settings-BmvNoCbn.js → api-settings-Bv9mLCty.js} +1 -1
- package/dist/web/assets/api-settings-Bv9mLCty.js.br +0 -0
- package/dist/web/assets/api-settings-Bv9mLCty.js.gz +0 -0
- package/dist/web/assets/{api-themes-6fOQJgM9.js → api-themes-3wW-zLzU.js} +1 -1
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js +1 -0
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.gz +0 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js +1 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js.br +0 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js.gz +0 -0
- package/dist/web/assets/{audio-preview-vaG2cB-k.js → audio-preview-BfES4Cx5.js} +1 -1
- package/dist/web/assets/audio-preview-BfES4Cx5.js.br +0 -0
- package/dist/web/assets/audio-preview-BfES4Cx5.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js +1 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js.br +0 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js.gz +0 -0
- package/dist/web/assets/{button-Ci9EUgqd.js → button-DeAyLH9U.js} +1 -1
- package/dist/web/assets/button-DeAyLH9U.js.br +0 -0
- package/dist/web/assets/button-DeAyLH9U.js.gz +0 -0
- package/dist/web/assets/{chat-preparation-cache-BSIQGiWx.js → chat-preparation-cache-CcCd2g7A.js} +1 -1
- package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.br +0 -0
- package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.gz +0 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js +1 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js.br +0 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js.gz +0 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js +15 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js.br +0 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js.gz +0 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js +7 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js.br +0 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js +1 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-qMGJH0Bx.js → conflict-editor-BpKW_vbL.js} +1 -1
- package/dist/web/assets/conflict-editor-BpKW_vbL.js.br +0 -0
- package/dist/web/assets/conflict-editor-BpKW_vbL.js.gz +0 -0
- package/dist/web/assets/context-menu-7OX80HDB.js +1 -0
- package/dist/web/assets/context-menu-7OX80HDB.js.br +0 -0
- package/dist/web/assets/context-menu-7OX80HDB.js.gz +0 -0
- package/dist/web/assets/{csv-preview-D2I742dl.js → csv-preview-COYdTABa.js} +1 -1
- package/dist/web/assets/csv-preview-COYdTABa.js.br +0 -0
- package/dist/web/assets/csv-preview-COYdTABa.js.gz +0 -0
- package/dist/web/assets/{data-grid-overlay-editor-CQRVKr7y.js → data-grid-overlay-editor-CeZoduke.js} +1 -1
- package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.br +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.gz +0 -0
- package/dist/web/assets/{database-viewer-Bz4LjgIa.js → database-viewer-BQcBqCKm.js} +1 -1
- package/dist/web/assets/database-viewer-BQcBqCKm.js.br +0 -0
- package/dist/web/assets/database-viewer-BQcBqCKm.js.gz +0 -0
- package/dist/web/assets/{design-export-client-ChMcMoqW.js → design-export-client-OrnGCSwu.js} +1 -1
- package/dist/web/assets/design-export-client-OrnGCSwu.js.br +0 -0
- package/dist/web/assets/design-export-client-OrnGCSwu.js.gz +0 -0
- package/dist/web/assets/{design-responsive-dialog-DNBhJJLf.js → design-responsive-dialog-vJjQrC4n.js} +1 -1
- package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.br +0 -0
- package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.gz +0 -0
- package/dist/web/assets/design-tab-BbmFk38H.js +33 -0
- package/dist/web/assets/design-tab-BbmFk38H.js.br +0 -0
- package/dist/web/assets/design-tab-BbmFk38H.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-CapP1yXq.js → designs-sidebar-panel-D2kXwB8B.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.gz +0 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js +1 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js.br +0 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-CdXi7cZN.js → diff-viewer-Bj4DT6Mj.js} +1 -1
- package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.br +0 -0
- package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.gz +0 -0
- package/dist/web/assets/{docx-preview-C9XdKEj1.js → docx-preview-C2QqNGRE.js} +1 -1
- package/dist/web/assets/docx-preview-C2QqNGRE.js.br +0 -0
- package/dist/web/assets/docx-preview-C2QqNGRE.js.gz +0 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js +1 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js.br +3 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js.gz +0 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js +2 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js.br +0 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js +1 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js.br +0 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-B2nZzJ2b.js → extension-manager-section-Bu0qcB2i.js} +1 -1
- package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.br +0 -0
- package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.gz +0 -0
- package/dist/web/assets/{extension-webview-BGoDxkdF.js → extension-webview-G_WJ0N52.js} +1 -1
- package/dist/web/assets/extension-webview-G_WJ0N52.js.br +0 -0
- package/dist/web/assets/extension-webview-G_WJ0N52.js.gz +0 -0
- package/dist/web/assets/{file-icons-gx_FBOXY.js → file-icons-l7fNOmdS.js} +1 -1
- package/dist/web/assets/file-icons-l7fNOmdS.js.br +0 -0
- package/dist/web/assets/file-icons-l7fNOmdS.js.gz +0 -0
- package/dist/web/assets/{file-store-D9KE0kQC.js → file-store-B0e7sLag.js} +1 -1
- package/dist/web/assets/file-store-B0e7sLag.js.br +0 -0
- package/dist/web/assets/file-store-B0e7sLag.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-DC3OzzOl.js → files-settings-section-ChZ_CynX.js} +1 -1
- package/dist/web/assets/files-settings-section-ChZ_CynX.js.br +0 -0
- package/dist/web/assets/files-settings-section-ChZ_CynX.js.gz +0 -0
- package/dist/web/assets/format-bytes-__396eCN.js +1 -0
- package/dist/web/assets/{format-file-meta-mQwA6SuY.js → format-file-meta-o1XBuoKS.js} +1 -1
- package/dist/web/assets/format-file-meta-o1XBuoKS.js.br +0 -0
- package/dist/web/assets/format-file-meta-o1XBuoKS.js.gz +0 -0
- package/dist/web/assets/{fs-api-DL36slbd.js → fs-api-CeHKwx95.js} +1 -1
- package/dist/web/assets/fs-api-CeHKwx95.js.br +0 -0
- package/dist/web/assets/fs-api-CeHKwx95.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-i41W43ER.js → general-settings-section-MSvDEbFa.js} +1 -1
- package/dist/web/assets/general-settings-section-MSvDEbFa.js.br +0 -0
- package/dist/web/assets/general-settings-section-MSvDEbFa.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-DHzPIkJg.js → git-log-panel-gw5_a6j3.js} +1 -1
- package/dist/web/assets/git-log-panel-gw5_a6j3.js.br +0 -0
- package/dist/web/assets/git-log-panel-gw5_a6j3.js.gz +0 -0
- package/dist/web/assets/{git-repo-store-DSypNj19.js → git-repo-store-BpPnah5l.js} +1 -1
- package/dist/web/assets/git-repo-store-BpPnah5l.js.br +0 -0
- package/dist/web/assets/git-repo-store-BpPnah5l.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-Dj4vrDvj.js → glide-data-grid-Db2Cl26B.js} +5 -5
- package/dist/web/assets/glide-data-grid-Db2Cl26B.js.br +0 -0
- package/dist/web/assets/glide-data-grid-Db2Cl26B.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js +1 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js.br +0 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js.gz +0 -0
- package/dist/web/assets/{image-preview-CGdZrmLo.js → image-preview-CjNhpfWw.js} +1 -1
- package/dist/web/assets/index-CtAvmybi.js +52 -0
- package/dist/web/assets/index-CtAvmybi.js.br +0 -0
- package/dist/web/assets/index-CtAvmybi.js.gz +0 -0
- package/dist/web/assets/index-DeCJasyy.css +1 -0
- package/dist/web/assets/index-DeCJasyy.css.br +0 -0
- package/dist/web/assets/index-DeCJasyy.css.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-BJ9edGEP.js → jira-watcher-section-CKSI1Alo.js} +1 -1
- package/dist/web/assets/keybindings-store-C4_yyU4u.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-CxdGSNGr.js → keyboard-shortcuts-section-CEaVUc9a.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.gz +0 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js +2 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js.br +0 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js.gz +0 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js +1 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js.br +0 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-CTtDap-l.js +1 -0
- package/dist/web/assets/{markdown-renderer-ok96IgnS.js → markdown-renderer-D05fAr57.js} +2 -2
- package/dist/web/assets/markdown-renderer-D05fAr57.js.br +0 -0
- package/dist/web/assets/markdown-renderer-D05fAr57.js.gz +0 -0
- package/dist/web/assets/{media-url-CzUJrREv.js → media-url-C6NumuN5.js} +1 -1
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js +1 -0
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.br +0 -0
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.gz +0 -0
- package/dist/web/assets/{mobile-bottom-sheet-CuNi2FRv.js → mobile-bottom-sheet-D4UyOatB.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.br +0 -0
- package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-Drkjb_LI.js → new-design-dialog-BgmQopJM.js} +1 -1
- package/dist/web/assets/new-design-dialog-BgmQopJM.js.br +0 -0
- package/dist/web/assets/new-design-dialog-BgmQopJM.js.gz +0 -0
- package/dist/web/assets/notification-store-q5QBklKA.js +1 -0
- package/dist/web/assets/{notifications-settings-section-C05WYqcE.js → notifications-settings-section-BBJ0r9OS.js} +1 -1
- package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.gz +0 -0
- package/dist/web/assets/{onboarding-entry-CmUqv1kl.js → onboarding-entry-BYOYswsi.js} +1 -1
- package/dist/web/assets/onboarding-entry-BYOYswsi.js.br +0 -0
- package/dist/web/assets/onboarding-entry-BYOYswsi.js.gz +0 -0
- package/dist/web/assets/{onboarding-quick-orientation-DIhWv1CN.js → onboarding-quick-orientation-DPHYFMbA.js} +1 -1
- package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.br +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.gz +0 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js +1 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js.br +0 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js.gz +0 -0
- package/dist/web/assets/{pdf-preview-qdXmMkOR.js → pdf-preview-c-L_kDAs.js} +1 -1
- package/dist/web/assets/pdf-preview-c-L_kDAs.js.br +0 -0
- package/dist/web/assets/pdf-preview-c-L_kDAs.js.gz +0 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js +3 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js.br +0 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js.gz +0 -0
- package/dist/web/assets/{postgres-viewer-Xvlun_n5.js → postgres-viewer-CnwDCMNd.js} +1 -1
- package/dist/web/assets/postgres-viewer-CnwDCMNd.js.br +0 -0
- package/dist/web/assets/postgres-viewer-CnwDCMNd.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-C8PFlvaq.js → ppmbot-settings-section-BDXoSIDp.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.gz +0 -0
- package/dist/web/assets/{pptx-export-CG6MRNok.js → pptx-export-D0RDY48h.js} +4 -4
- package/dist/web/assets/pptx-export-D0RDY48h.js.br +0 -0
- package/dist/web/assets/pptx-export-D0RDY48h.js.gz +0 -0
- package/dist/web/assets/{problems-panel-CFYO0Ro3.js → problems-panel-BgY8oyo9.js} +1 -1
- package/dist/web/assets/problems-panel-BgY8oyo9.js.br +0 -0
- package/dist/web/assets/problems-panel-BgY8oyo9.js.gz +0 -0
- package/dist/web/assets/process-row-format-CaaMcHzn.js +1 -0
- package/dist/web/assets/process-table-BOwDtXuG.js +1 -0
- package/dist/web/assets/process-table-BOwDtXuG.js.br +0 -0
- package/dist/web/assets/process-table-BOwDtXuG.js.gz +0 -0
- package/dist/web/assets/{project-framework-store-XxFXcUpb.js → project-framework-store-UDEMu28U.js} +1 -1
- package/dist/web/assets/{project-store-C5gJXkvb.js → project-store-DGa31y5B.js} +1 -1
- package/dist/web/assets/project-store-DGa31y5B.js.br +0 -0
- package/dist/web/assets/project-store-DGa31y5B.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-BcHIEANE.js → proxy-settings-section-Db6Kc6xG.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.gz +0 -0
- package/dist/web/assets/{query-audit-section-UKG_smyt.js → query-audit-section-C0PAXhfT.js} +1 -1
- package/dist/web/assets/query-audit-section-C0PAXhfT.js.br +0 -0
- package/dist/web/assets/query-audit-section-C0PAXhfT.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-BGsp0edA.js → remote-desktop-clipboard-notice-DOVtobj_.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-CsZCCRJI.js → remote-desktop-readiness-gate-BsfoJzdc.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.gz +0 -0
- package/dist/web/assets/{schedules-settings-section-Ci0O2Ng6.js → schedules-settings-section-CvbjQM8z.js} +1 -1
- package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.gz +0 -0
- package/dist/web/assets/{scroll-area-XonvWYn_.js → scroll-area-sjOeAfoD.js} +1 -1
- package/dist/web/assets/scroll-area-sjOeAfoD.js.br +0 -0
- package/dist/web/assets/scroll-area-sjOeAfoD.js.gz +0 -0
- package/dist/web/assets/{separator-sQj04Ari.js → separator-9A3PCG1t.js} +1 -1
- package/dist/web/assets/services-panel-CimW47P8.js +1 -0
- package/dist/web/assets/services-panel-CimW47P8.js.br +0 -0
- package/dist/web/assets/services-panel-CimW47P8.js.gz +0 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js +2 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js.br +0 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js.gz +0 -0
- package/dist/web/assets/settings-store-DvftLqzy.js +2 -0
- package/dist/web/assets/settings-store-DvftLqzy.js.br +0 -0
- package/dist/web/assets/settings-store-DvftLqzy.js.gz +0 -0
- package/dist/web/assets/settings-tab-CGhaRXjs.js +1 -0
- package/dist/web/assets/settings-window-content--Uyg_Psr.js +1 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js +1 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js.br +0 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js.gz +0 -0
- package/dist/web/assets/{sql-query-editor-4iCGE4h2.js → sql-query-editor-Dn-cgDrW.js} +1 -1
- package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.br +0 -0
- package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-CK6DulYk.js → sqlite-viewer-B0NfaMjq.js} +1 -1
- package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js +2 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js.br +0 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-Cb3EZUdp.js +1 -0
- package/dist/web/assets/system-monitor-window-content-51CA-2PH.js +1 -0
- package/dist/web/assets/{tab-host-window-content-CWBaWKjA.js → tab-host-window-content-CSrcmKNG.js} +1 -1
- package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.gz +0 -0
- package/dist/web/assets/{team-member-sheet-Br6mrxrw.js → team-member-sheet-D4X6nh-x.js} +2 -2
- package/dist/web/assets/team-member-sheet-D4X6nh-x.js.br +0 -0
- package/dist/web/assets/team-member-sheet-D4X6nh-x.js.gz +0 -0
- package/dist/web/assets/{team-member-window-content-CwCy7ZWf.js → team-member-window-content-BxkbGVIL.js} +1 -1
- package/dist/web/assets/team-member-window-content-BxkbGVIL.js.br +0 -0
- package/dist/web/assets/team-member-window-content-BxkbGVIL.js.gz +0 -0
- package/dist/web/assets/{terminal-tab-BW14RpCb.js → terminal-tab-BYo34IqU.js} +3 -3
- package/dist/web/assets/terminal-tab-BYo34IqU.js.br +0 -0
- package/dist/web/assets/terminal-tab-BYo34IqU.js.gz +0 -0
- package/dist/web/assets/{tool-cards-CgOBEUnb.js → tool-cards-DvXjOped.js} +4 -4
- package/dist/web/assets/tool-cards-DvXjOped.js.br +0 -0
- package/dist/web/assets/tool-cards-DvXjOped.js.gz +0 -0
- package/dist/web/assets/{use-blob-url-CmavyEP8.js → use-blob-url-CISBWKG5.js} +1 -1
- package/dist/web/assets/use-inline-blame-Br6mheS4.js +16 -0
- package/dist/web/assets/use-inline-blame-Br6mheS4.js.br +0 -0
- package/dist/web/assets/use-inline-blame-Br6mheS4.js.gz +0 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js +6 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js.br +0 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-rH6epJYA.js → use-monaco-theme-UGOzyIhv.js} +1 -1
- package/dist/web/assets/use-process-kill-CyiiUD1E.js +1 -0
- package/dist/web/assets/use-process-kill-CyiiUD1E.js.br +0 -0
- package/dist/web/assets/use-process-kill-CyiiUD1E.js.gz +0 -0
- package/dist/web/assets/{use-websocket-QwURkjpV.js → use-websocket-Dpl21cHG.js} +1 -1
- package/dist/web/assets/{vendor-ui-BHh12_0v.js → vendor-ui-P9Ym7hCv.js} +1 -1
- package/dist/web/assets/vendor-ui-P9Ym7hCv.js.br +0 -0
- package/dist/web/assets/vendor-ui-P9Ym7hCv.js.gz +0 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js +1 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js.br +0 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js.gz +0 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js +1 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js.br +0 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js.gz +0 -0
- package/dist/web/assets/ws-auth-CqI8-aoA.js +1 -0
- package/dist/web/index.html +20 -20
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/deployment-guide.md +23 -0
- package/package.json +1 -1
- package/release-from-worktree.sh +224 -0
- package/src/cli/commands/config-cmd.ts +12 -0
- package/src/providers/claude-agent-sdk.ts +65 -2
- package/src/providers/codex-app-server/codex-jsonrpc-client.ts +16 -1
- package/src/providers/codex-app-server/codex-provider.ts +39 -7
- package/src/providers/codex-app-server/codex-reset-credit.ts +49 -0
- package/src/providers/provider-probe.ts +63 -0
- package/src/providers/registry.ts +109 -29
- package/src/server/index.ts +9 -0
- package/src/server/middleware/auth.ts +5 -1
- package/src/server/routes/codex-accounts.ts +14 -0
- package/src/server/routes/cross-origin-guard.ts +20 -0
- package/src/server/routes/git.ts +40 -4
- package/src/server/routes/lsp.ts +97 -4
- package/src/server/routes/resources.ts +72 -9
- package/src/server/routes/settings.ts +19 -1
- package/src/server/routes/speech.ts +66 -0
- package/src/server/routes/system-services.ts +76 -0
- package/src/server/routes/tunnel.ts +33 -1
- package/src/server/ws/chat.ts +74 -3
- package/src/services/codex-account-login.ts +26 -2
- package/src/services/codex-reset-credit.service.ts +83 -0
- package/src/services/compaction-savings.ts +127 -0
- package/src/services/db.service.ts +123 -8
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-ops/fs-ops-trash.service.ts +22 -9
- package/src/services/git-refs/for-each-ref.ts +118 -0
- package/src/services/git.service.ts +25 -2
- package/src/services/host-info/spawn-runner.ts +48 -4
- package/src/services/jsonl-transcript-parser.ts +7 -42
- package/src/services/lsp/lsp-install.ts +289 -0
- package/src/services/lsp/lsp-manager.ts +160 -29
- package/src/services/lsp/server-registry.ts +120 -4
- package/src/services/named-tunnel/named-tunnel-config.ts +8 -2
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +4 -0
- package/src/services/read-lines.ts +54 -0
- package/src/services/speech-to-text/whisper-catalog.ts +142 -0
- package/src/services/speech-to-text/whisper-download.ts +113 -0
- package/src/services/speech-to-text/whisper-hallucinations.ts +51 -0
- package/src/services/speech-to-text/whisper-install.service.ts +247 -0
- package/src/services/speech-to-text/whisper-paths.ts +62 -0
- package/src/services/speech-to-text/whisper-transcribe.service.ts +117 -0
- package/src/services/supervisor.ts +94 -11
- package/src/services/system-metrics/cpu-details-linux.ts +252 -0
- package/src/services/system-metrics/cpu-memory-collector.ts +140 -18
- package/src/services/system-metrics/device-collector-linux.ts +31 -0
- package/src/services/system-metrics/device-collector-types.ts +40 -0
- package/src/services/system-metrics/disk-devices-linux.ts +156 -0
- package/src/services/system-metrics/disk-inventory-linux.ts +150 -0
- package/src/services/system-metrics/fans-linux.ts +51 -0
- package/src/services/system-metrics/gpu-api-versions.ts +120 -0
- package/src/services/system-metrics/gpu-devices-linux.ts +370 -0
- package/src/services/system-metrics/gpu-fdinfo-linux.ts +253 -0
- package/src/services/system-metrics/hardware-inventory.ts +50 -0
- package/src/services/system-metrics/linux-fs.ts +43 -0
- package/src/services/system-metrics/memory-darwin.ts +91 -0
- package/src/services/system-metrics/memory-linux.ts +156 -0
- package/src/services/system-metrics/net-devices-linux.ts +114 -0
- package/src/services/system-metrics/net-inventory-linux.ts +100 -0
- package/src/services/system-metrics/partitions-linux.ts +158 -0
- package/src/services/system-metrics/process-collector-linux.ts +58 -6
- package/src/services/system-metrics/process-collector-types.ts +8 -1
- package/src/services/system-metrics/process-details-darwin.ts +166 -0
- package/src/services/system-metrics/process-details-linux.ts +150 -0
- package/src/services/system-metrics/process-grouping.ts +2 -1
- package/src/services/system-metrics/process-rows-builder.ts +2 -0
- package/src/services/system-metrics/process-swap-linux.ts +37 -0
- package/src/services/system-metrics/signal-executor.ts +84 -0
- package/src/services/system-metrics/signal-request-handler.ts +92 -0
- package/src/services/system-metrics/system-metrics-platform.ts +30 -4
- package/src/services/system-metrics/system-metrics-tick.ts +73 -4
- package/src/services/system-metrics/system-metrics.service.ts +87 -2
- package/src/services/system-metrics/udev-db.ts +40 -0
- package/src/services/system-services/app-cgroup-linux.ts +115 -0
- package/src/services/system-services/app-icon-service.ts +31 -0
- package/src/services/system-services/app-icons-linux.ts +124 -0
- package/src/services/system-services/apps-linux.ts +174 -0
- package/src/services/system-services/desktop-entry.ts +103 -0
- package/src/services/system-services/service-guard.ts +124 -0
- package/src/services/system-services/systemd-collector.ts +203 -0
- package/src/services/system-services/systemd-parse.ts +162 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/shared/prompt-cache-idle.ts +197 -0
- package/src/shared/turn-usage.ts +85 -1
- package/src/shared/usage-extra.ts +19 -0
- package/src/types/chat.ts +29 -0
- package/src/types/config.ts +14 -0
- package/src/types/git.ts +41 -0
- package/src/types/system-hardware.ts +137 -0
- package/src/types/system-metrics.ts +231 -2
- package/src/types/system-services.ts +73 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +20 -20
- package/src/web/components/branch-review/branch-review-tree-row.tsx +1 -13
- package/src/web/components/chat/chat-tab.tsx +3 -0
- package/src/web/components/chat/codex-usage-panel.tsx +4 -0
- package/src/web/components/chat/compaction-divider.tsx +48 -0
- package/src/web/components/chat/idle-cache-notice.tsx +67 -0
- package/src/web/components/chat/message-input.tsx +48 -13
- package/src/web/components/chat/message-list.tsx +15 -0
- package/src/web/components/chat/prompt-cache-chip.tsx +74 -0
- package/src/web/components/editor/code-editor.tsx +1 -0
- package/src/web/components/editor/editor-language-service.tsx +11 -3
- package/src/web/components/editor/lsp-status.tsx +115 -13
- package/src/web/components/git/branch-picker.tsx +419 -0
- package/src/web/components/git/branch-select.tsx +34 -0
- package/src/web/components/git/create-worktree-dialog.tsx +17 -16
- package/src/web/components/git/git-repo-picker.tsx +28 -40
- package/src/web/components/layout/status-bar.tsx +42 -19
- package/src/web/components/settings/accounts/account-card.tsx +10 -2
- package/src/web/components/settings/accounts/account-usage-extras.tsx +3 -2
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +3 -1
- package/src/web/components/settings/accounts/codex-reset-credit-button.tsx +120 -0
- package/src/web/components/settings/ai-settings-section.tsx +67 -3
- package/src/web/components/settings/appearance-settings-section.tsx +2 -28
- package/src/web/components/settings/language-servers-section.tsx +362 -0
- package/src/web/components/settings/settings-categories.ts +8 -3
- package/src/web/components/settings/settings-section-content.tsx +2 -0
- package/src/web/components/settings/voice-settings-section.tsx +283 -0
- package/src/web/components/system/apps/app-row.tsx +110 -0
- package/src/web/components/system/apps/app-rows.ts +140 -0
- package/src/web/components/system/apps/apps-panel.tsx +129 -0
- package/src/web/components/system/metric-chart-canvas.tsx +79 -3
- package/src/web/components/system/overview-cards/card-shell.tsx +54 -0
- package/src/web/components/system/overview-cards/cpu-card.tsx +11 -6
- package/src/web/components/system/overview-cards/disk-card.tsx +11 -6
- package/src/web/components/system/overview-cards/gpu-card.tsx +16 -6
- package/src/web/components/system/overview-cards/mem-card.tsx +11 -6
- package/src/web/components/system/overview-cards/net-card.tsx +11 -6
- package/src/web/components/system/overview-device-target.ts +69 -0
- package/src/web/components/system/overview-panel.tsx +15 -1
- package/src/web/components/system/performance/cpu-core-grid.tsx +74 -0
- package/src/web/components/system/performance/cpu-detail.tsx +220 -0
- package/src/web/components/system/performance/detail-parts.tsx +149 -0
- package/src/web/components/system/performance/device-list.ts +139 -0
- package/src/web/components/system/performance/disk-detail.tsx +64 -0
- package/src/web/components/system/performance/fans-detail.tsx +35 -0
- package/src/web/components/system/performance/gpu-detail.tsx +116 -0
- package/src/web/components/system/performance/memory-detail.tsx +109 -0
- package/src/web/components/system/performance/nic-detail.tsx +55 -0
- package/src/web/components/system/performance/partition-list.tsx +79 -0
- package/src/web/components/system/performance/performance-panel.tsx +206 -0
- package/src/web/components/system/performance/use-hardware-inventory.ts +48 -0
- package/src/web/components/system/process-columns-grid.ts +5 -3
- package/src/web/components/system/process-details-dialog.tsx +116 -0
- package/src/web/components/system/process-group-row.tsx +6 -1
- package/src/web/components/system/process-row-format.ts +6 -0
- package/src/web/components/system/process-row-menu.tsx +72 -0
- package/src/web/components/system/process-row.tsx +7 -1
- package/src/web/components/system/process-table-model.ts +1 -1
- package/src/web/components/system/process-table-sort.ts +8 -4
- package/src/web/components/system/process-table-toolbar.tsx +10 -2
- package/src/web/components/system/process-table-totals.ts +3 -0
- package/src/web/components/system/process-table.tsx +25 -2
- package/src/web/components/system/services/service-action-confirm.tsx +100 -0
- package/src/web/components/system/services/service-columns.ts +103 -0
- package/src/web/components/system/services/service-details-sheet.tsx +97 -0
- package/src/web/components/system/services/service-resources.ts +105 -0
- package/src/web/components/system/services/service-row.tsx +146 -0
- package/src/web/components/system/services/service-rows.ts +92 -0
- package/src/web/components/system/services/service-sort.ts +90 -0
- package/src/web/components/system/services/services-header.tsx +51 -0
- package/src/web/components/system/services/services-panel.tsx +207 -0
- package/src/web/components/system/services/use-services.ts +92 -0
- package/src/web/components/system/sortable-header.tsx +13 -8
- package/src/web/components/system/sysmon-preferences.tsx +85 -0
- package/src/web/components/system/system-monitor-body.tsx +101 -43
- package/src/web/components/system/system-monitor-window-content.tsx +2 -3
- package/src/web/components/system/use-process-signal.ts +87 -0
- package/src/web/components/tunnels/public-tunnel-switch.tsx +82 -0
- package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
- package/src/web/components/ui/dialog.tsx +14 -1
- package/src/web/components/ui/search-select.tsx +279 -0
- package/src/web/components/ui/start-ellipsis.tsx +18 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-global-keybindings.ts +7 -5
- package/src/web/hooks/use-lsp.ts +23 -2
- package/src/web/hooks/use-minute-clock.ts +20 -0
- package/src/web/hooks/use-whisper-voice-input.ts +113 -0
- package/src/web/lib/api-tunnels.ts +19 -0
- package/src/web/lib/branch-select-items.ts +29 -0
- package/src/web/lib/cpu-graph-mode.ts +90 -0
- package/src/web/lib/format-bytes.ts +14 -0
- package/src/web/lib/git-ref-picker.ts +120 -0
- package/src/web/lib/lsp/lsp-client.ts +32 -0
- package/src/web/lib/search-select-rows.ts +55 -0
- package/src/web/lib/temperature.ts +27 -0
- package/src/web/lib/voice-wav.ts +69 -0
- package/src/web/stores/git-status-store.ts +31 -17
- package/src/web/stores/keybindings-store.ts +14 -1
- package/src/web/stores/settings-store.ts +65 -0
- package/src/web/styles/globals.css +11 -0
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js +0 -1
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-DKcsVDle.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-DKcsVDle.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js +0 -1
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.br +0 -0
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-OPqC2bYG.js +0 -1
- package/dist/web/assets/api-client-BgedDTvo.js +0 -1
- package/dist/web/assets/api-client-c8YOpTlT.js.br +0 -0
- package/dist/web/assets/api-client-c8YOpTlT.js.gz +0 -0
- package/dist/web/assets/api-settings-BmvNoCbn.js.br +0 -0
- package/dist/web/assets/api-settings-BmvNoCbn.js.gz +0 -0
- package/dist/web/assets/api-settings-Bti1-Zn3.js +0 -1
- package/dist/web/assets/appearance-settings-section-DL466x83.js +0 -1
- package/dist/web/assets/appearance-settings-section-DL466x83.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DL466x83.js.gz +0 -0
- package/dist/web/assets/audio-preview-vaG2cB-k.js.br +0 -0
- package/dist/web/assets/audio-preview-vaG2cB-k.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-Dag86n_R.js +0 -1
- package/dist/web/assets/branch-review-tab-Dag86n_R.js.br +0 -0
- package/dist/web/assets/branch-review-tab-Dag86n_R.js.gz +0 -0
- package/dist/web/assets/button-Ci9EUgqd.js.br +0 -0
- package/dist/web/assets/button-Ci9EUgqd.js.gz +0 -0
- package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.br +0 -0
- package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.gz +0 -0
- package/dist/web/assets/chat-tab-CcYZLXls.js +0 -15
- package/dist/web/assets/chat-tab-CcYZLXls.js.br +0 -0
- package/dist/web/assets/chat-tab-CcYZLXls.js.gz +0 -0
- package/dist/web/assets/chat-tab-QFDhzzBZ.js +0 -1
- package/dist/web/assets/chat-tab-QFDhzzBZ.js.br +0 -0
- package/dist/web/assets/chat-tab-QFDhzzBZ.js.gz +0 -0
- package/dist/web/assets/code-editor-K-IN8VwJ.js +0 -7
- package/dist/web/assets/code-editor-K-IN8VwJ.js.br +0 -0
- package/dist/web/assets/code-editor-K-IN8VwJ.js.gz +0 -0
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js +0 -1
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.br +0 -0
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.gz +0 -0
- package/dist/web/assets/conflict-editor-qMGJH0Bx.js.br +0 -0
- package/dist/web/assets/conflict-editor-qMGJH0Bx.js.gz +0 -0
- package/dist/web/assets/context-menu-CjYTq8DI.js +0 -1
- package/dist/web/assets/context-menu-CjYTq8DI.js.br +0 -0
- package/dist/web/assets/context-menu-CjYTq8DI.js.gz +0 -0
- package/dist/web/assets/csv-preview-D2I742dl.js.br +0 -0
- package/dist/web/assets/csv-preview-D2I742dl.js.gz +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.br +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.gz +0 -0
- package/dist/web/assets/database-viewer-Bz4LjgIa.js.br +0 -0
- package/dist/web/assets/database-viewer-Bz4LjgIa.js.gz +0 -0
- package/dist/web/assets/design-export-client-ChMcMoqW.js.br +0 -0
- package/dist/web/assets/design-export-client-ChMcMoqW.js.gz +0 -0
- package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.br +0 -0
- package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.gz +0 -0
- package/dist/web/assets/design-tab-C987MtMR.js +0 -33
- package/dist/web/assets/design-tab-C987MtMR.js.br +0 -0
- package/dist/web/assets/design-tab-C987MtMR.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.gz +0 -0
- package/dist/web/assets/dialog-B8clVkyi.js +0 -1
- package/dist/web/assets/dialog-B8clVkyi.js.br +0 -0
- package/dist/web/assets/dialog-B8clVkyi.js.gz +0 -0
- package/dist/web/assets/diff-viewer-CdXi7cZN.js.br +0 -0
- package/dist/web/assets/diff-viewer-CdXi7cZN.js.gz +0 -0
- package/dist/web/assets/docx-preview-C9XdKEj1.js.br +0 -0
- package/dist/web/assets/docx-preview-C9XdKEj1.js.gz +0 -0
- package/dist/web/assets/editor-language-service-Zle_PZV8.js +0 -1
- package/dist/web/assets/editor-language-service-Zle_PZV8.js.br +0 -0
- package/dist/web/assets/editor-language-service-Zle_PZV8.js.gz +0 -0
- package/dist/web/assets/explorer-body-DHMQR1Rk.js +0 -2
- package/dist/web/assets/explorer-body-DHMQR1Rk.js.br +0 -0
- package/dist/web/assets/explorer-body-DHMQR1Rk.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-Df40nQBk.js +0 -1
- package/dist/web/assets/explorer-window-content-Df40nQBk.js.br +0 -0
- package/dist/web/assets/explorer-window-content-Df40nQBk.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.br +0 -0
- package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.gz +0 -0
- package/dist/web/assets/extension-webview-BGoDxkdF.js.br +0 -0
- package/dist/web/assets/extension-webview-BGoDxkdF.js.gz +0 -0
- package/dist/web/assets/file-icons-gx_FBOXY.js.br +0 -0
- package/dist/web/assets/file-icons-gx_FBOXY.js.gz +0 -0
- package/dist/web/assets/file-store-D9KE0kQC.js.br +0 -0
- package/dist/web/assets/file-store-D9KE0kQC.js.gz +0 -0
- package/dist/web/assets/files-settings-section-DC3OzzOl.js.br +0 -0
- package/dist/web/assets/files-settings-section-DC3OzzOl.js.gz +0 -0
- package/dist/web/assets/format-bytes-DmSHAKFe.js +0 -1
- package/dist/web/assets/format-file-meta-mQwA6SuY.js.br +0 -0
- package/dist/web/assets/format-file-meta-mQwA6SuY.js.gz +0 -0
- package/dist/web/assets/fs-api-DL36slbd.js.br +0 -0
- package/dist/web/assets/fs-api-DL36slbd.js.gz +0 -0
- package/dist/web/assets/general-settings-section-i41W43ER.js.br +0 -0
- package/dist/web/assets/general-settings-section-i41W43ER.js.gz +0 -0
- package/dist/web/assets/git-log-panel-DHzPIkJg.js.br +0 -0
- package/dist/web/assets/git-log-panel-DHzPIkJg.js.gz +0 -0
- package/dist/web/assets/git-repo-store-DSypNj19.js.br +0 -0
- package/dist/web/assets/git-repo-store-DSypNj19.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.br +0 -0
- package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js +0 -1
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js.gz +0 -0
- package/dist/web/assets/index-DMA-So69.css +0 -1
- package/dist/web/assets/index-DMA-So69.css.br +0 -0
- package/dist/web/assets/index-DMA-So69.css.gz +0 -0
- package/dist/web/assets/index-jG9mLs-J.js +0 -52
- package/dist/web/assets/index-jG9mLs-J.js.br +0 -0
- package/dist/web/assets/index-jG9mLs-J.js.gz +0 -0
- package/dist/web/assets/keybindings-store-BlapkC6O.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-DJ9EVU1B.js +0 -1
- package/dist/web/assets/markdown-renderer-ok96IgnS.js.br +0 -0
- package/dist/web/assets/markdown-renderer-ok96IgnS.js.gz +0 -0
- package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.br +0 -0
- package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-Drkjb_LI.js.br +0 -0
- package/dist/web/assets/new-design-dialog-Drkjb_LI.js.gz +0 -0
- package/dist/web/assets/notification-store-DCyxho0O.js +0 -1
- package/dist/web/assets/notifications-settings-section-C05WYqcE.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-C05WYqcE.js.gz +0 -0
- package/dist/web/assets/onboarding-entry-CmUqv1kl.js.br +0 -0
- package/dist/web/assets/onboarding-entry-CmUqv1kl.js.gz +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.br +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.gz +0 -0
- package/dist/web/assets/overview-panel-BZAW_nag.js +0 -1
- package/dist/web/assets/overview-panel-BZAW_nag.js.br +0 -0
- package/dist/web/assets/overview-panel-BZAW_nag.js.gz +0 -0
- package/dist/web/assets/pdf-preview-qdXmMkOR.js.br +0 -0
- package/dist/web/assets/pdf-preview-qdXmMkOR.js.gz +0 -0
- package/dist/web/assets/postgres-viewer-Xvlun_n5.js.br +0 -0
- package/dist/web/assets/postgres-viewer-Xvlun_n5.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.gz +0 -0
- package/dist/web/assets/pptx-export-CG6MRNok.js.br +0 -0
- package/dist/web/assets/pptx-export-CG6MRNok.js.gz +0 -0
- package/dist/web/assets/problems-panel-CFYO0Ro3.js.br +0 -0
- package/dist/web/assets/problems-panel-CFYO0Ro3.js.gz +0 -0
- package/dist/web/assets/process-table-BVeRguFg.js +0 -1
- package/dist/web/assets/process-table-BVeRguFg.js.br +0 -0
- package/dist/web/assets/process-table-BVeRguFg.js.gz +0 -0
- package/dist/web/assets/project-store-C5gJXkvb.js.br +0 -0
- package/dist/web/assets/project-store-C5gJXkvb.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-BcHIEANE.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-BcHIEANE.js.gz +0 -0
- package/dist/web/assets/query-audit-section-UKG_smyt.js.br +0 -0
- package/dist/web/assets/query-audit-section-UKG_smyt.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.gz +0 -0
- package/dist/web/assets/scroll-area-XonvWYn_.js.br +0 -0
- package/dist/web/assets/scroll-area-XonvWYn_.js.gz +0 -0
- package/dist/web/assets/settings-body-B6JGOIYb.js +0 -2
- package/dist/web/assets/settings-body-B6JGOIYb.js.br +0 -0
- package/dist/web/assets/settings-body-B6JGOIYb.js.gz +0 -0
- package/dist/web/assets/settings-store-Br5c1efC.js +0 -2
- package/dist/web/assets/settings-store-Br5c1efC.js.br +0 -0
- package/dist/web/assets/settings-store-Br5c1efC.js.gz +0 -0
- package/dist/web/assets/settings-tab-CIQM4fNE.js +0 -1
- package/dist/web/assets/settings-window-content-BGNNz-E5.js +0 -1
- package/dist/web/assets/sql-query-editor-4iCGE4h2.js.br +0 -3
- package/dist/web/assets/sql-query-editor-4iCGE4h2.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-CK6DulYk.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-CK6DulYk.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js +0 -2
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js.br +0 -0
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-DjBeRE81.js +0 -1
- package/dist/web/assets/system-monitor-window-content-BT7-ugFx.js +0 -1
- package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.gz +0 -0
- package/dist/web/assets/team-member-sheet-Br6mrxrw.js.br +0 -0
- package/dist/web/assets/team-member-sheet-Br6mrxrw.js.gz +0 -0
- package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.br +0 -0
- package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.gz +0 -0
- package/dist/web/assets/terminal-tab-BW14RpCb.js.br +0 -0
- package/dist/web/assets/terminal-tab-BW14RpCb.js.gz +0 -0
- package/dist/web/assets/tool-cards-CgOBEUnb.js.br +0 -0
- package/dist/web/assets/tool-cards-CgOBEUnb.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js +0 -16
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js.br +0 -0
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js.gz +0 -0
- package/dist/web/assets/use-lsp-hGdjL2hV.js +0 -6
- package/dist/web/assets/use-lsp-hGdjL2hV.js.br +0 -0
- package/dist/web/assets/use-lsp-hGdjL2hV.js.gz +0 -0
- package/dist/web/assets/vendor-ui-BHh12_0v.js.br +0 -0
- package/dist/web/assets/vendor-ui-BHh12_0v.js.gz +0 -0
- package/dist/web/assets/video-preview-P9T-ZVdN.js +0 -1
- package/dist/web/assets/video-preview-P9T-ZVdN.js.br +0 -0
- package/dist/web/assets/video-preview-P9T-ZVdN.js.gz +0 -0
- package/dist/web/assets/ws-auth-kXgsX2Js.js +0 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- /package/dist/web/assets/{__vite-browser-external-BpSQWxcq.js → __vite-browser-external-BILfYaAs.js} +0 -0
- /package/dist/web/assets/{run-in-terminal-HIdNgYim.js → run-in-terminal-2pSiup8e.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DLDtzLib.js → vendor-xterm-DF1JTfsy.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.br → vendor-xterm-DF1JTfsy.js.br} +0 -0
- /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
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
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
|
|
32
|
-
|
|
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
|
-
/**
|
|
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 = {
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
+
}
|